Feature #1547
closedFeature #1511: Replace ticket view with Kanban board for CTO and assigned developers
Frontend: board polling, manual refresh and card-move animation
0%
docs/superpowers/specs/2026-08-03-kanban-board-design.md
Description
**Surface:** frontend (`src/`)
**Depends on:** "Frontend: Kanban board replaces the ticketing list view"
Decision **D3**: "real-time" for this epic means **polling only**. This narrows the epic's original AC ("tickets move instantly") to *"tickets move within one poll interval, with an animated transition, plus an on-demand manual refresh"*.
An SSE or websocket channel is **explicitly out of scope** and becomes a follow-up ticket. There is precedent for both approaches in the repo — SSE in `src/features/chat/useAssistantStream.ts`, polling in `src/features/projects/useProject.ts:26` and `src/features/usage/useHostMetrics.ts:24` — and we are deliberately taking the cheaper one for v1.
Reuse the existing `refetchInterval` convention rather than inventing a new one; the interval lives in **one named constant**. **Proposed: 30s** — confirm with the PO.
## Acceptance criteria
- [ ] The board query polls on an interval defined in one named constant and refetches on window focus; a fake-timer test asserts exactly one refetch per interval.
- [ ] Polling **pauses when the tab is hidden** and resumes on focus: a test dispatching `visibilitychange` asserts zero fetches while hidden and exactly one refetch on focus.
- [ ] No duplicate intervals after remount: a mount/unmount/mount cycle followed by one tick issues exactly one refetch.
- [ ] **In-flight drag protection, concrete test:** start a transition mutation and leave it pending; force a refetch that returns the server state with the card still in its origin column → the card stays at its optimistic position while the mutation is pending, and reconciles to server truth only after it settles. Both phases asserted.
- [ ] A card whose status changed server-side moves to its new column on the next poll with the animated transition from the design section.
- [ ] `prefers-reduced-motion: reduce` disables or reduces the animation — asserted under a mocked `matchMedia`.
- [ ] A manual refresh control triggers an immediate refetch, shows the designed loading state, and is disabled while a fetch is in flight — clicking it mid-fetch issues no second request.
- [ ] No `EventSource`, WebSocket or SSE endpoint is introduced (assertion in test or lint).
- [ ] Refresh-related strings exist in **both** `en.ts` and `fr.ts` with identical ICU placeholders.
- [ ] Gates green: `npx vitest run && npx tsc --noEmit && npx eslint . && npm run build`.