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-241Done

4.4.8 Bug — start-sprint isn't atomic: the dialog PATCHes the goal then starts (two writes) because `startSprint` takes no `goal` (finding #68)

Estimate: 14m · Depends on: 4.4.2, 4.4.5

Type: bug (tech-debt seam) · Parent: Story 4.4 (sprint lifecycle) · Code surface owned by: Story 4.4.2 (sprintsService.startSprint + StartSprintInput) crossed with Story 4.4.5 (the StartSprintDialog that has to compose around the gap) · Status: open · Reported by: the planner during the 4.4.5 build · Source: PRODECT_FINDINGS #68.

The start-sprint modal (4.4.5) draws an editable Sprint goal (design panel 1), and the mirror product (Jira) edits the goal as part of Start. But the shipped StartSprintInput (4.4.2) is { name?, startDate?, endDate? }no goal — and POST /api/sprints/[id]/start silently drops any extra body field. So 4.4.5 persists an edited goal with a separate PATCH /api/sprints/[id] { goal } BEFORE the start POST (allowed while the sprint is still planned). That is two writes, not one — and a small non-atomic window: if the start 409s (another sprint went active in between) after the goal PATCH already landed, the goal edit persists on the still-planned sprint (harmless + retryable, but untidy). name is editable on start; goal should be too.

Fix. Add goal?: string | null to StartSprintInput (lib/dto/sprints.ts) and have sprintsService.startSprint stamp it inside its existing $transaction (the start route already reads an arbitrary JSON body — extend its parse + forward goal, same shape as name). Then the StartSprintDialog (4.4.5) drops the pre-start PATCH and sends { name, goal, startDate, endDate } to /start in ONE call. One service method = one transaction (CLAUDE.md); the whole start (window + scope-lock baseline + scrum-board ensure + name + goal) becomes a single atomic write. Keep the updateSprint PATCH for plain goal/name/window edits on an already-planned sprint (4.1.3) — unchanged.

Acceptance criteria

  • StartSprintInput carries goal?: string | null; startSprint writes it inside the same transaction that flips the sprint active (no separate update), and POST /api/sprints/[id]/start parses + forwards goal (a non-string goal is a 400, mirroring the existing name guard).
  • The StartSprintDialog (4.4.5) sends goal in the single /start call and no longer issues a pre-start PATCH — verified by the existing 4.4.5 component test (the "persists an edited goal" case is rewritten to assert ONE POST carrying the goal, zero PATCH).
  • The committed goal is what the started sprint shows (the sprint detail / report reads it); an empty goal clears it (sends null).
  • pnpm test:coverage keeps the changed sprintsService file ≥90% branch/fn/line (the coverage gate); a service test asserts startSprint stamps the goal in-transaction.

Context refs

  • lib/dto/sprints.ts StartSprintInput + lib/services/sprintsService.ts startSprint (Story 4.4.2) — where goal is added + stamped in-transaction
  • app/api/sprints/[id]/start/route.ts (4.4.2) — the body parse to extend (forward goal like name)
  • app/(authed)/backlog/_components/StartSprintDialog.tsx (4.4.5) — drops the pre-start PATCH; its component test asserts the single-call shape
  • lib/services/sprintsService.ts updateSprint (4.1.3) — the plain-edit PATCH, left as-is; PRODECT_FINDINGS #68; motir-core/CLAUDE.md (one service method = one transaction)