MOTIR-414Done
6.12.6 Upvoting + comments on public requests — the vote model (the demand signal the triage queue sorts by)
Estimate: 60m · Depends on: 6.12.3, 6.11.3
The two remaining public-viewer writes — UPVOTE and COMMENT — over the 6.12.3 PublicRequestVote model + the 6.11.3 queue. Per 6.12.2:
- Upvote (one per account per item, server-enforced): a service + route that records a
PublicRequestVote(workItemId, userId)for the signed-in account, gated by 6.12.3’scanUpvotePublicRequest(any authed account on a public project) — NOTcanEdit. The unique(workItemId, userId)makes a second upvote a no-op / toggle (never a double count); the vote COUNT becomes a sort key the 6.11.3 triage queue reads (so the project admin sees the highest-demand requests first — the demand signal). Lock-before-read-derived-update where a concurrent vote could race the count. - Comment on a public request: a service + route that adds a comment to the public request, attributed to the signed-in cross-org account, gated by
canCommentPublicRequest— NOTcanEdit. These PUBLIC-REQUEST comments are visible on the public surface (distinct from the work item’s INTERNAL comments, which the 6.12.4 projection hides — 6.12.2 fixes that line). Reuse the existing comment model/service where the request is awork_itemwith a comment thread; mark the public-request comments as public-visible.
Stay 4-layer: routes parse + call one service method; the vote/comment writes own their transaction; the count sort threads into the 6.11.3 queue read.
Acceptance criteria
- A signed-in cross-org account upvotes a public request → one vote recorded; a second upvote from the same account is a no-op / toggle (the unique
(workItemId, userId)holds, no double count); gated bycanUpvotePublicRequest, NOTcanEdit. - The vote count is a sort key the 6.11.3 triage queue reads (highest-demand-first); a concurrent vote serializes via the row lock (no lost update).
- A signed-in cross-org account comments on a public request → the comment is attributed + public-visible; gated by
canCommentPublicRequest, NOTcanEdit; the work item’s internal comments remain hidden by the 6.12.4 projection. - 4-layer respected (vote/comment writes through a service → repository /
workItemsService; no raw Prisma in routes).
Context refs
- 6.12.3 — the
PublicRequestVotemodel +canUpvotePublicRequest/canCommentPublicRequestgrants. scripts/plan-seed/data/story-6.11.ts§ 6.11.3 — the triage queue the vote-count sort feeds.motir-core/lib/services/workItemsService.ts+ the existing comment model/service — the comment thread reused.- Canny feature voting (https://canny.io/blog/feature-voting-best-practices/) — the upvote + comment-on-the-request behaviour.
motir-core/CLAUDE.md§ 4-layer + the lock-before-read-derived-update rule.