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 · Roadmap

MOTIR-4109Done

The Board tab and the Roadmap's first page get public read routes — /api/public/p/{identifier}/board and …/roadmap with no cursor

Opened by Zhu Yue ·

Two of the five tabs a public project page renders have no public endpoint that returns their first page, so a renderer outside this repository cannot draw them at all.

  • Board. publicProjectsService.getBoard(identifier, actorUserId) exists and is tested, and nothing under app/api/public/ calls it. It was reached directly by the deleted app/(public)/p/[identifier]/board/page.tsx.
  • Roadmap. app/api/public/p/[identifier]/roadmap/route.ts exists but serves only the per-column pagination — it requires bucket and cursor and answers 400 MISSING_ROADMAP_CURSOR without them. The tab's initial four columns come from getRoadmap, which likewise has no route.

This card adds the two reads. MOTIR-3951's own service sweep names both as "the public project READ/WRITE contract that MOTIR-3877's motir-marketing /p/* pages will re-expose through new API routes" — this is that card, for the read half of the tab payloads.

Follow the shape of the route beside them exactly (app/api/public/p/[identifier]/route.ts, MOTIR-3945): the capability gate FIRST (publicSurfaceUnavailable() — before the rate limit and before any session read), then getSession() for an optional actorUserId ?? null, then one service call, then map ProjectNotFoundError → 404. Anonymous on READ, viewer-aware only for personalisation. HTTP layer only — no logic outside the service (the 4-layer rule, motir-core/CLAUDE.md).

The roadmap route already exists, so extend it rather than adding a second one: with no bucket/cursor it returns the whole PublicRoadmapDto; with both it keeps today's per-column page and today's two 400s. That keeps one path per resource and leaves the shipped pagination contract untouched.

Every new operation owes its contract entry. tests/api/public/contract-coverage.test.ts fails on a route with no declared operation, so lib/api/public/openapi/operations.ts gains the board operation and the roadmap operation's response shape gains its no-cursor arm. tests/api/public/cloud-gate-totality.test.ts and anonymous-posture.test.ts enumerate the surface too — a new route that is not in them is a hole in the gate, not a passing test.

Acceptance criteria

  • GET /api/public/p/{identifier}/board answers 200 with PublicBoardDto for an anonymous caller on a public project, and 404 { code } for an unknown or non-public one, with no existence leak.
  • GET /api/public/p/{identifier}/roadmap with no bucket and no cursor answers 200 with the full PublicRoadmapDto; with both it behaves exactly as it does on origin/main today, including INVALID_ROADMAP_BUCKET and INVALID_ROADMAP_CURSOR → 400. A regression test pins the existing arm.
  • Both routes call publicSurfaceUnavailable() before any session read, and both answer the gate's own response when MOTIR_CLOUD is unset.
  • lib/api/public/openapi/operations.ts declares the board operation and the roadmap's no-cursor response; tests/api/public/contract-coverage.test.ts, contract-drift.test.ts, cloud-gate-totality.test.ts and anonymous-posture.test.ts are green without an exemption entry being added for either route.
  • The routes contain no business logic: each is parse → gate → one publicProjectsService call → error mapping.
  • No file outside motir-core is touched.

Context refs

  • motir-core/app/api/public/p/[identifier]/route.ts — the shape to copy, and its posture comment
  • motir-core/app/api/public/p/[identifier]/roadmap/route.ts — the route being extended
  • motir-core/lib/services/publicProjectsService.tsgetBoard (:474), getRoadmap (:814), getRoadmapColumn (:844)
  • motir-core/lib/dto/publicProjects.tsPublicBoardDto, PublicRoadmapDto, PUBLIC_ROADMAP_BUCKET_KEYS
  • motir-core/lib/publicProjects/cloudGate.tspublicSurfaceUnavailable()
  • motir-core/lib/api/public/openapi/operations.ts — the twelve declared operations
  • motir-core/tests/api/public/contract-coverage, contract-drift, cloud-gate-totality, anonymous-posture
  • motir-core/docs/decisions/public-surface-hosts.md AMENDMENT 1 — the contract this surface is published under
  • the five tabs on motir.co — the consumer these two reads exist for

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment