motir.co serves the two detail pages and the feature-request intake — /items/[key], /requests/[requestKey] and the submit form
The two detail pages under /p/[identifier], and the feature-request intake that hangs off them:
/p/<identifier>/items/[key]— one work item as the public surface shows it, against the new work-item detail read (the detail routes). It is the deep link a changelog entry, a roadmap card and an items row all point at./p/<identifier>/requests/[requestKey]— one feature request: its body, its public comment thread as the read returns it, and its vote count. Rendered, not yet actionable — the upvote button and the comment box are the act affordances card's, and this card renders the surfaces they attach to.The submit-request intake — the form, and the duplicate-suggestion step … Both endpoints are anonymous today (— ⚠️ CONFIRMED BY READING, AS THE CARD ASKED, AND IT IS FALSE (2026-09-02). OngetSessionis not called in either), so this flow works for a logged-out visitor with no cross-origin session question at allorigin/mainbothPOST …/requestsandGET …/requests/duplicatescallrequireCompliantSession(); the submit's own header says "A LOGGED-OUT caller is rejected 401 (sign-in-to-act)". This is the third card carrying the same measurement error (MOTIR-3877's body and MOTIR-4108's table were the first two) — filed as MOTIR-4166.
WHAT SHIPPED: /p/<id>/requests/new is a HAND-OFF DOORWAY, per public-surface-hosts.md AMENDMENT 4 row 6 — it states what is about to happen and links to app.motir.co with the return trip carried, and it holds no field a visitor can fill in. Not a reduced form but the honest one: a partial form would take a title, return no candidates (401), take a body, and lose the draft at sign-in. Canny — the mirror row 6 follows — identifies the visitor first for exactly this reason. noindex, because a doorway must not outrank the roadmap it hands off from.
Read findDuplicateRequests and submitPublicRequest before writing the form. The duplicate step is a real two-phase interaction, not a nicety, and its shape is the service's; the form must send what the route validates, including the projectId the request routes are keyed by rather than the identifier the page's URL carries.
Empty, loading and error states per screen, from the design — including the request thread with no comments, and the duplicate check finding nothing.
Rate limiting is the producer's and it is visible here. enforcePublicWriteRateLimit answers a 429 on the submit path; the form renders that as a state rather than as a failure, because on a public form it is a normal outcome.
Scope boundary: no upvote, no comment posting, no follow. This card ships the two READ screens and the anonymous SUBMIT flow.
Acceptance criteria
/p/<id>/items/<key>renders a public work item for a logged-out visitor, and 404s for one that does not exist or that the epic-privacy exclusion hides — distinguishably from an API outage./p/<id>/requests/<key>renders a feature request with its public comment thread and vote count as the read returns them; the vote control and comment box are present as surfaces and perform nothing in this card.A logged-out visitor can submit a feature request end to end, including the duplicate-suggestion step, and the request appears on the project afterwards.⚠️ UNBUILDABLE — WITHDRAWN. Both endpoints are session-gated (see the banner). Replaced by: the intake is a hand-off that carriesintent=request, the subject and a validated return path, and asks for nothing it cannot submit — asserted in the browser.The form renders the validation errors, the duplicate-candidate state, the empty-duplicates state and the 429 rate-limit state, each as its own state rather than a generic failure.⚠️ WITHDRAWN with the form. There is no form on this host, so there are no submit-side states here: validation, duplicates and the 429 all belong to the application's own intake, which owns them already. Nothing is lost and nothing is deferred.- Every read and write goes through
lib/publicProject.ts(or a sibling module beside it) rather than afetchinline in a component; no database client appears. - Every deep link into these pages resolves from the tabs — an items row, a roadmap card and a changelog entry each link to the work-item detail — with no dead link introduced.
- Canonicals and
og:urlnameSITE_ORIGINand the page's own path. - No file outside
motir-marketingis touched.
Context refs
motir-core/app/api/public/projects/[projectId]/requests/route.ts·requests/duplicates/route.ts— the anonymous intake and its duplicate checkmotir-core/lib/services/publicProjectsService.ts—getWorkItemDetail(:993),getRequestDetail(:899),findDuplicateRequests(:1094),submitPublicRequest(:1127)motir-core/lib/rateLimit/publicWriteGuard.ts— the 429 this form must rendermotir-core/lib/dto/publicRequests.tsmotir-core/app/(public)/p/[identifier]/{items/[key],requests/[requestKey]}/page.tsx@95a2d4468^— prior art onlymotir-marketing/design/public-projects/— the two detail boards and the intake board