Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-292Done

5.4.2 `labelsService` — type-to-create folksonomy (no-spaces, case-insensitive find-or-create, delete-on-last-use), bounded autocomplete, revision diffs

Estimate: 26m · Depends on: 5.4.1

The folksonomy mechanics, server-side. lib/services/labelsService.ts + typed errors + DTOs + HTTP-only routes.

setLabels(workItemId, names[], ctx) / addLabel / removeLabel — edit-gated (admin/member with view; viewer 403; cross-workspace 404 per finding #44). Validation per the verified Jira rules: no spaces (typed 422 naming the hyphen convention), trimmed, length-capped (a constant, e.g. 60 chars), per-issue cap 20 (the recorded sanity guard). Find-or-create case-insensitively: match on nameLower within the project; create with first-typed display casing on miss — in the SAME transaction as the join write. Delete-on-last-use: removing a label from an issue checks countUsesByLabel inside the tx and deletes the label row at zero (the FOR-UPDATE/lock discipline for the read-derived delete — the lock-before-read-derived-update rule). Revision diff per change: { labels: { added: [name], removed: [name] } } (the links-diff precedent).

searchLabels(projectId, q, ctx) — the autocomplete read: case-insensitive prefix match, bounded take: 20, view-gated, returning display names. getLabelsForWorkItem rides the detail read (slotted into getIssueDetail's parallel fetch, the 5.3.3 pattern — one bounded query, no N+1).

Acceptance criteria

  • Add/remove/set round-trip with find-or-create (case-insensitive — 'PERF-Q3' matches 'perf-q3', original casing returned), no-spaces + length + per-issue-cap 422s, and the delete-on-last-use rule (verified under concurrent removal — the locked count); revision diffs written per change.
  • searchLabels is bounded (20), prefix, case-insensitive; getIssueDetail carries the issue's labels without an extra round-trip.
  • Permission matrix enforced (member edits, viewer 403, cross-workspace 404); one service method = one transaction; routes HTTP-only; coverage gate ≥90%.

Context refs

  • 5.4.1 repos; the verified folksonomy rules in the Story 5.4 description
  • lib/services/workItemsService.ts getIssueDetail (the parallel-fetch slot) + the links revision-diff precedent ({ links: { added/removed } })
  • The lock-before-read-derived-update rule (the delete-at-zero count)
  • 5.1.2 / 5.3.3 — the sibling service conventions