The public project SUBJECT gets a route — app/api/public/p/[identifier], the one endpoint the page's own subject has never had
Opened by Zhu Yue ·
Type: code · Executor: coding_agent · Repo: motir-core · ONE PR.
The page's own subject has no public endpoint. Every LIST a public project page renders has one — app/api/public/p/[identifier]/{tree,items,roadmap,changelog} — and the project itself does not: there is no app/api/public/p/[identifier]/route.ts on origin/main. A second renderer can fetch a project's work items and never learn its name.
This is the single hard gap the 2026-08-29 re-measurement found (MOTIR-3877 carries it in full), and it is one route.
What it returns
The subject of /p/<identifier> as the page renders it today: the display name, the public overview / README markdown, the tags, the owning workspace's public identity, and whatever the hero reads. Read app/(public)/p/[identifier]/page.tsx for the field set rather than inventing one — the contract is "what the page needs", and the page is the specification.
Conventions
- Anonymous, like its four sibling READS. Those take an optional
actorUserId = session?.user.id ?? nullfor viewer-awareness and are explicitly "NOT session-gated on READ"; this route follows them exactly. It is not a new access posture. - A framework boundary calls a service.
publicProjectsService.getOverviewalready exists and is what the page uses; this route is a thin caller, not a second read path (the 4-layer rule). - The project-access gate is the service's, unchanged. A non-public identifier answers exactly as the page does today — do not invent a second notion of "public".
Boundary
- It renders nothing. The consumer is MOTIR-3877, later and elsewhere.
- It does not version anything. Promoting
/api/public/*into a contract a second repository may depend on is the sibling card,blocked_bythis one. - It adds no capability. The data is already public and already served to this repository's own pages; this exposes the same read over HTTP.
Acceptance criteria
GET /api/public/p/<identifier>returns the subject fieldsapp/(public)/p/[identifier]/page.tsxrenders, for a public project, with no session.- A non-public or unknown identifier answers with the same status and shape the existing sibling routes use for that case — asserted, so the two cannot drift.
- The route calls
publicProjectsService; no Prisma client is imported intoapp/api/public/, asserted in the shape the repository already uses for framework-boundary rules. - The response field set is derived from the page's own read — a test fails if the page starts rendering a subject field this route does not return.
- ≥90% coverage on the files this PR touches.
Context refs
motir-core/app/api/public/p/[identifier]/— the four sibling reads whose shape and access posture this followsmotir-core/app/(public)/p/[identifier]/page.tsx— the specification for the field setmotir-core/lib/services/publicProjectsService.ts—getOverview, the service this calls- MOTIR-3877 — the consumer, and the card carrying the measurement that found this gap
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.