## How this was found
By generalizing the memo-dependency defect in the blocker fan-out
(#2993) into a sweep for memoized hooks that read a lane value absent
from their dependency list — rather than treating that one as a one-off.
**13 raw hits, 12 benign** (refs, or values reached through a covered
object). This is the one that's a live defect.
## The defect
`wantsLiveTimeIndicator` decides whether a card subscribes to the shared
time ticker. It reads `isWipColumn`, `isReviewColumn` and
`taskColumnFlags` — all derived from the `taskColumnFlags` **prop** —
while its dependency array listed only `task.*` fields.
Those flags arrive **after first paint**: the board resolves workflow
traits asynchronously. So:
1. first computation runs with flags `undefined`;
2. role helpers fall back to legacy ids — `isWipColumnRole(undefined,
"building")` is **false**;
3. the card declines the ticker;
4. flags arrive, but `task.column` hasn't changed, so nothing in the dep
array changed;
5. the memo never recomputes. **No live elapsed time, for the life of
the mount.**
## Why it survived
On a legacy board the fallback already answers `true` on the very first
paint (`column === "in-progress"`), so the memo's initial value is
correct and the stale list costs nothing. The defect is
**renamed-board-only**.
This repo also has no `react-hooks/exhaustive-deps` rule, so the entire
class is invisible to lint — and a disable directive for that rule fails
CI, so these lists are maintained by hand.
## Measured
The test was written **first** and was red for the right reason before
any fix — the control and the negative passed, and only the renamed case
failed:
| stage | result |
|---|---|
| before the fix | `expected false to be true` (renamed case only) |
| after | 3 passed |
| `TaskCard.test` + `cli-states` + `oversight` + new suite | **456 tests
green** |
| gates | census + FNXC green; lint and `tsc` clean |
The assertion is on `useLiveTimeTicker(enabled)` — `enabled` *is*
`wantsLiveTimeIndicator`, so it observes the subscription itself rather
than a proxy for it.
## The negative case is the one that matters
Recomputing must not degrade into "every card subscribes". A card in the
renamed **complete** lane must stay off the shared ticker, or the fix
trades one stalled indicator for sixty cards waking a backgrounded tab —
the exact cost the shared-ticker refactor documented at this site (it
replaced 60 per-card `setInterval`s precisely because mobile browsers
discard a page that never goes idle).
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>