feat(board): triage column — distinct surface + freshness sort + arrival flash (B7) #423

Merged
code-lead merged 4 commits from dev/415 into main 2026-04-27 05:39:32 +00:00
Collaborator

Turns the Unassigned column into a Linear-style triage inbox: warm bg-triage surface, Triage (N) header with rounded-pill count badge (inverts when empty), cards sorted by created_at desc, 🎉 Inbox zero empty state, and a 1 s border-accent ring flash on SSE-driven card arrivals.

Test plan

  • Header reads Triage (N) where N matches live card count; badge inverts colour at N = 0.
  • Column surface is visually warmer than agent columns (bg-triage token).
  • Cards render newest-first (create date, not update date).
  • Empty column shows 🎉 Inbox zero placeholder.
  • New card appearing via SSE re-render flashes ch-arrival-flash ring for ~1 s then stops.
  • B1 drop-to-unassign, B4 keyboard nav, and B5 card face all continue to work inside the triage column.
  • just qa clean; 453 web tests + 2010 server tests pass.

Closes #415

Turns the Unassigned column into a Linear-style triage inbox: warm `bg-triage` surface, `Triage (N)` header with `rounded-pill` count badge (inverts when empty), cards sorted by `created_at` desc, `🎉 Inbox zero` empty state, and a 1 s `border-accent` ring flash on SSE-driven card arrivals. ## Test plan - [ ] Header reads `Triage (N)` where N matches live card count; badge inverts colour at N = 0. - [ ] Column surface is visually warmer than agent columns (bg-triage token). - [ ] Cards render newest-first (create date, not update date). - [ ] Empty column shows `🎉 Inbox zero` placeholder. - [ ] New card appearing via SSE re-render flashes `ch-arrival-flash` ring for ~1 s then stops. - [ ] B1 drop-to-unassign, B4 keyboard nav, and B5 card face all continue to work inside the triage column. - [ ] `just qa` clean; 453 web tests + 2010 server tests pass. Closes #415
feat(board): triage column — distinct surface + freshness sort + arrival flash
All checks were successful
qa / qa (pull_request) Successful in 6m13s
qa / dockerfile (pull_request) Successful in 9s
5f21bebcfb
Implements B7 (#415): the Unassigned synthetic column is treated as a
Linear-style triage inbox — warm `bg-triage` surface, `Triage (N)` header
with a `rounded-pill` count badge that inverts when empty, cards sorted by
`created_at` desc (newest first), `🎉 Inbox zero` empty state, and a 1 s
`border-accent` arrival flash on SSE-driven new card additions.

- `packages/shared`: adds `created_at: string` to `BoardCard`
- `apps/server`: populates `created_at` from `issue.createdAt` for all card
  types; sorts triage column by `created_at` desc; label → "Triage"
- `apps/web/styles`: adds `--ch-color-triage` token (dark + light), exposes
  via `@theme`, defines `@keyframes ch-arrival` / `.ch-arrival-flash`
- `board-column.tsx`: full triage column treatment via `isTriage` branch;
  arrival detection via `useRef` + `useState` (skips initial mount)
- `board.test.tsx`: 4 new B7 tests (header, sort, arrival flash, inbox zero)
  plus `created_at` added to all test `card()` fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-27 01:03:38 +00:00
reviewer requested changes 2026-04-27 01:06:46 +00:00
Dismissed
reviewer left a comment
  • behavior: apps/web/src/components/board/board-column.tsx — arrival flash useEffect cleanup calls clearTimeout(timer) but never removes the batch-1 keys from flashingKeys. If a second SSE event arrives within 1 s of the first (e.g. two issues land in quick succession), the effect re-runs, cancels batch-1's timer, and batch-1's keys stay in flashingKeys permanently — the ring flashes indefinitely rather than once. The spec AC says "flashes a 1 s ring once."

    Fix: include the key removal in the cleanup function so that when a new effect run pre-empts the old timer, the stale flash keys are cleared before the new batch is applied:

    return () => {
      clearTimeout(timer);
      setFlashingKeys((prev) => {
        const next = new Set(prev);
        for (const k of newKeys) next.delete(k);
        return next;
      });
    };
    

Everything else is correct: token definitions (--ch-color-triage in both themes), bg-triage surface application, rounded-pill badge with invert-on-zero, 🎉 Inbox zero empty state, created_at desc sort (both server-side in buildBoard and client-side in sortedTriageCards), ch-arrival keyframe animation, all four B7 test scenarios covered. CI green.

- **behavior**: `apps/web/src/components/board/board-column.tsx` — arrival flash `useEffect` cleanup calls `clearTimeout(timer)` but never removes the batch-1 keys from `flashingKeys`. If a second SSE event arrives within 1 s of the first (e.g. two issues land in quick succession), the effect re-runs, cancels batch-1's timer, and batch-1's keys stay in `flashingKeys` permanently — the ring flashes indefinitely rather than once. The spec AC says "flashes a 1 s ring **once**." Fix: include the key removal in the cleanup function so that when a new effect run pre-empts the old timer, the stale flash keys are cleared before the new batch is applied: ```ts return () => { clearTimeout(timer); setFlashingKeys((prev) => { const next = new Set(prev); for (const k of newKeys) next.delete(k); return next; }); }; ``` Everything else is correct: token definitions (`--ch-color-triage` in both themes), `bg-triage` surface application, `rounded-pill` badge with invert-on-zero, `🎉 Inbox zero` empty state, `created_at` desc sort (both server-side in `buildBoard` and client-side in `sortedTriageCards`), `ch-arrival` keyframe animation, all four B7 test scenarios covered. CI green.
fix(board): clear stale flash keys in arrival-flash effect cleanup
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
66706420b1
When a second SSE event arrived within 1 s of the first, the cleanup
only cancelled the timer but left the previous batch's keys in
flashingKeys, causing a permanent ring. Remove the pre-empted batch's
keys inside the cleanup function so each flash is exactly 1 s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Collaborator

Fixed in 6670642. Cleanup now removes pre-empted batch keys from flashingKeys before the new batch is applied, so rapid SSE arrivals don't leave stale rings.

Fixed in 6670642. Cleanup now removes pre-empted batch keys from `flashingKeys` before the new batch is applied, so rapid SSE arrivals don't leave stale rings.
dev force-pushed dev/415 from 66706420b1
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to 4cb4d9b933
Some checks failed
qa / qa (pull_request) Failing after 3m19s
qa / dockerfile (pull_request) Successful in 8s
2026-04-27 01:37:58 +00:00
Compare
dev force-pushed dev/415 from 4cb4d9b933
Some checks failed
qa / qa (pull_request) Failing after 3m19s
qa / dockerfile (pull_request) Successful in 8s
to 25a4e0f26b
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
2026-04-27 02:07:45 +00:00
Compare
dev force-pushed dev/415 from 25a4e0f26b
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to ccd98cd691
All checks were successful
qa / qa (pull_request) Successful in 5m53s
qa / dockerfile (pull_request) Successful in 9s
2026-04-27 02:15:23 +00:00
Compare
dev requested review from reviewer 2026-04-27 02:45:55 +00:00
reviewer approved these changes 2026-04-27 02:50:45 +00:00
reviewer left a comment

Cleanup fix applied correctly — setFlashingKeys key removal is now present alongside clearTimeout in the effect cleanup, eliminating the indefinite-flash regression on rapid SSE bursts. CI green.

Cleanup fix applied correctly — `setFlashingKeys` key removal is now present alongside `clearTimeout` in the effect cleanup, eliminating the indefinite-flash regression on rapid SSE bursts. CI green.
dev force-pushed dev/415 from ccd98cd691
All checks were successful
qa / qa (pull_request) Successful in 5m53s
qa / dockerfile (pull_request) Successful in 9s
to 2e13a5fde6
Some checks failed
qa / qa (pull_request) Failing after 5m36s
qa / dockerfile (pull_request) Successful in 9s
2026-04-27 02:55:02 +00:00
Compare
fix(board): restore WIP badge in AgentHeader; narrow triage sort test selector
All checks were successful
qa / qa (pull_request) Successful in 6m12s
qa / dockerfile (pull_request) Successful in 8s
bce1adf4ce
WIP badge was dropped when the inline header was extracted to AgentHeader
(conflict resolution during rebase). Pass wipTotal/wipExceeded/wipTooltip
as props so the badge renders correctly on agent columns.

Triage sort test used /^board-card-/ which started matching nested
board-card-stage-icon elements after main added card-face indicators
(B5 / #413). Narrowed to /^board-card-.*#/ to match only outer wrappers.
charles force-pushed dev/415 from bce1adf4ce
All checks were successful
qa / qa (pull_request) Successful in 6m12s
qa / dockerfile (pull_request) Successful in 8s
to ba029ddfec
All checks were successful
qa / qa (pull_request) Successful in 5m59s
qa / dockerfile (pull_request) Successful in 9s
2026-04-27 05:32:59 +00:00
Compare
code-lead deleted branch dev/415 2026-04-27 05:39:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!423
No description provided.