MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-413

6.12.5 Public submit-to-triage for any account + DUPLICATE DETECTION (surface the existing request to upvote)

Done
Description

Estimate: 60m · Depends on: 6.12.3, 6.11.4

The cross-account public submission path, REUSING 6.11.4’s intake (no second submissions table), plus the duplicate-detection pre-check (Canny’s core behaviour). Per 6.12.2:

  • Cross-org-account submit: a service + route taking { kind (bug|feature), title, descriptionMd } that creates a triage work_item through workItemsService (the SAME 6.11.4 creation path), attributed to the SUBMITTING cross-org account (a real authenticated submittedByUserId, not an external/anonymous submitter), scoped to the public project. Gated by 6.12.3’s canSubmitToTriage (true for any authed account on a public project) — NOT canEdit. Rate-limited + abuse-guarded per the 6.11.4 precedent (per-account throttle, size cap), since this is an internet-facing write.
  • Duplicate detection (BEFORE create): a service method that, given a draft title/text, finds matching EXISTING public requests for the project (a deterministic title/text match — e.g. normalized token / trigram similarity, reusing the 6.1.1 search where it fits; NOT an AI call — AI dedupe is an Epic-7 enhancement) and returns the candidates so the UI can offer "upvote this instead". If the user chooses an existing request, NO new item is created — the flow hands off to the 6.12.6 upvote. If they choose "submit as new", the create path runs. The match read must respect the public projection (it only searches public-facing requests) and the triage queue (a duplicate of a still-in-triage request is still surfaceable).

Stay 4-layer: routes parse + call one service method; the service owns the transaction + the throttle; creation goes through workItemsService.

Acceptance criteria

  • A cross-org authenticated account submits a request on a public project → a triage work_item is created via workItemsService, attributed to that account, invisible to the normal tree (it shows in the project’s triage queue); gated by canSubmitToTriage, NOT canEdit; rate-limited (rapid repeats throttle with a typed error, not a 500).
  • The duplicate-detection method returns matching existing public requests for a draft title BEFORE creation; choosing an existing one creates NO new item and routes to upvote (6.12.6); choosing "submit as new" creates the item.
  • The match is deterministic (no AI call) and searches only public-facing requests; no raw Prisma in the route; creation reuses 6.11.4’s path (no second submissions table).

Context refs

  • 6.12.3 — canSubmitToTriage (the grant this checks) + the public projection the match respects.
  • scripts/plan-seed/data/story-6.11.ts § 6.11.4 (the intake creation path REUSED) + § 6.11.3 (the triage queue the item lands in).
  • motir-core/lib/services/workItemsService.ts — the create authority; the 6.1.1 FilterAST search (shipped) — the match read reuses it where it fits.
  • Canny duplicate detection (https://canny.io/use-cases/feature-request-management) — the "upvote the existing request instead of creating a dupe" behaviour.
  • motir-core/CLAUDE.md § 4-layer; the 6.11.4 rate-limit precedent.