Estimate: 60m · Depends on: 6.12.1, 6.12.4, 6.12.5, 6.12.6
Why this exists (planning gap, found running 6.12.10): 6.12.5 shipped the public submit-to-triage + duplicate-detection BACKEND only (the POST /api/public/projects/[projectId]/requests create + the GET …/requests/duplicates pre-check + their service/tests) — but NO public-facing FORM was built. The 6.12.4 PublicSubmitRequestButton is still a sign-in-to-act STUB (its own comment: "The submit FORM is 6.12.5 and isn’t built here"). So a public viewer cannot actually submit a request, and the e2e (6.12.10) has no real UI to drive. This card builds the missing UI half of 6.12.5, per the 6.12.1 Panel 3 design (the submit form + the duplicate-detection "upvote this instead" state).
PublicSubmitRequestButton into the real composer — a kind toggle (bug | feature; "feature" maps to the backend task kind the route accepts — see the 6.12.5 route grammar), a title, and a descriptionMd body — that POSTs to /api/public/projects/[projectId]/requests (the GLOBAL project id, not the PROD identifier — ADR §2.2; the public page already has the id server-side, thread it in). On 201 show the design’s "thanks, we got it" confirmation. A LOGGED-OUT viewer keeps the existing sign-in-to-act prompt (reading is open; posting needs an account) — do NOT remove that branch.design/triage/design-notes.md). This form is the signed-in external-intake channel: NO name/email capture, NO honeypot, NO per-form anonymous-token route. A work item is created only by a signed-in account (the submission is attributed to it, per the Panel 4 "Submitted as {name} ({org})" hint), so a logged-out viewer NEVER sees the form — only the sign-in-to-act prompt. Do NOT build an anonymous public form.GET /api/public/projects/[projectId]/requests/duplicates?title=… and surface the returned matching existing public request(s) with an "Upvote this instead" affordance. Choosing it UPVOTES the existing request (reuse the 6.12.6 POST /api/public-requests/[id]/upvote the roadmap’s PublicRoadmapVote already calls) and creates NO new item; "Submit as new" runs the create path. Render the empty / loading / rate-limited (typed-error, not a raw 500) states per the design.components/ui/* + --el-* / [data-display-style] tokens (no Tier-0 --color-*, no raw rounded-*/p-*/h-*).--el-* + [data-display-style] tokens + shipped components/ui/*; matches the 6.12.1 Panel 3 design; no new service/route (pure wiring over 6.12.5/6.12.6).design/public-projects/.design/triage/design-notes.md (Yue, 2026-06-14) — the sign-in-to-act intake model: the unauthenticated public portal form is DROPPED; the 6.12 public-project "Submit a request" is the SIGNED-IN external-intake channel into the 6.11 triage queue.app/api/public/projects/[projectId]/requests/route.ts (POST create, { kind: bug|task, title, descriptionMd? } → 201) + …/requests/duplicates/route.ts (GET ?title= → { candidates: [{ id, kind, identifier, title, status, voteCount }] }).app/api/public-requests/[id]/upvote/route.ts (POST → { voted, voteCount }) — the "upvote this instead" target.app/(public)/_components/PublicSubmitRequestButton.tsx (the stub to replace) + PublicRoadmapVote.tsx (the shipped upvote-control pattern to reuse).motir-core/CLAUDE.md § 4-layer + § colour/shape tokens.