Found by following an unexplained number rather than by a sweep: while
re-verifying #3141 the resolver reported `intake: "todo"` where
`BUILTIN_CODING_WORKFLOW_IR` resolves `intake: "triage"`. That
divergence is correct and intentional inside core — and wrong here.
## The defect
`dashboard.ts` resolved a task's columns as `def?.ir ??
BUILTIN_CODING_WORKFLOW_IR`, and its card-chip fields the same way. That
constant is the **legacy** monolithic IR (`builtin:legacy-coding`); the
catalog's actual default is `resolveDefaultWorkflowIr()`. Post-U11 they
differ **by a whole column**:
```
default todo, in-progress, in-review, done, archived (planning merged into todo)
legacy triage, todo, in-progress, in-review, done, archived
```
So a task with **no workflow selection row** was rendered against a
six-column board including `triage` — a lane the real default no longer
declares.
## The same drift is already documented as fixed elsewhere
`builtin-workflows.ts` records it:
> `prepareWorkflowMovePolicyPreflightImpl` resolved the default through
the catalog while `resolveTaskWorkflowIrForMove` used the raw constant,
so a task with NO selection row produced two different workflow
signatures and every flag-ON move threw *"workflow move policy preflight
is stale"*. Both sides (and the sync resolver) now call this helper so
the default cannot drift again.
This surface was missed, and it is the **last non-test consumer of the
legacy constant outside core**.
## Test scope, stated because it is narrow
Driving the TUI end-to-end needs a rendered terminal and a live store.
That harness does not exist here, and building one to assert a fallback
would be testing the harness. So the test pins the two facts that make
the bug possible and the fix meaningful:
1. **the two IRs genuinely disagree, about `triage` specifically** — if
a future change re-merges them, this reports it rather than leaving the
fix silently pointless;
2. **the source no longer reaches for the legacy constant.**
(2) is a source assertion, weaker than driving the code. It is used for
the same reason as the `FloatingWindow` aria-label scan: the defect is a
**value at a call site**, there is no single render that reaches both
sites, and a per-site render test would pin the one someone bothered to
write. Both assertions are anti-vacuity guarded — the IR comparison
fails if either side stops resolving to a v2 column set.
## Verification
| | result |
|---|---|
| cli `tsc` | **0 errors** |
| new test | **2 passed** |
| mutation — restore `?? BUILTIN_CODING_WORKFLOW_IR` | **1 failed / 2**
|
| census `--strict`, `check-fnxc-future-dates` | exit 0 (this class is
invisible to the census — an argument, not a comparison) |
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>