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-414

6.12.6 Upvoting + comments on public requests — the vote model (the demand signal the triage queue sorts by)

Done
Description

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’s canUpvotePublicRequest (any authed account on a public project) — NOT canEdit. 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 — NOT canEdit. 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 a work_item with 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 by canUpvotePublicRequest, NOT canEdit.
  • 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, NOT canEdit; 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 PublicRequestVote model + canUpvotePublicRequest / canCommentPublicRequest grants.
  • 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.