Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-3945Done

The public project SUBJECT gets a route — app/api/public/p/[identifier], the one endpoint the page's own subject has never had

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 ?? null for 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.getOverview already 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_by this 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 fields app/(public)/p/[identifier]/page.tsx renders, 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 into app/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 follows
  • motir-core/app/(public)/p/[identifier]/page.tsx — the specification for the field set
  • motir-core/lib/services/publicProjectsService.tsgetOverview, the service this calls
  • MOTIR-3877 — the consumer, and the card carrying the measurement that found this gap