Labels, filters, and saved views #
All four phases of the plan are implemented and green. Branch radial/impl-731b515f5ac9, commit 79ec72f.
What landed #
Phase 0 — lexicons. com.disnetdev.radial.labelGoal and com.disnetdev.radial.savedView, shapes exactly as the plan specified, plus regenerated records.ts. COLLECTIONS auto-extends radialCollections, so the poller and Jetstream ingest both with no ingest change (verified against the built client).
Phase 1 — core fold. labelsByGoal and savedViewsByAuthorAndName pools; GoalView.labels and index.savedViews. goldenScenario() gained two competing label writes tied on the instant, a clearing labels: [], a dangling label, a superseded view, a deleted tombstone, a same-name view from a second author, and an off-space view. The existing 200-permutation shuffle covers all of it.
Phase 2 — sidecar. goal label --set, view save, view delete, with tests for shape, normalization, clearing, editing and deletion.
Phase 3 — UI. labels.ts, filters.ts/filters.svelte.ts, views.ts with vitest coverage; LabelChip, LabelEditor (mounted-component tests), FilterBar; chips on GoalRow, the editor on the goal page, the bar on the project page, a Views section in the rail.
Phase 4 — bundle and docs. TurnBundle.goal.labels and a Labels: line in bundle.md; design §4/§7/§15, operators.md, and the lexicons/sidecar/ui READMEs.
Where I departed from the plan, and why #
- The validator gap (the plan's top risk) does not exist.
validation.tsalready enforces arraymaxLengthand per-item schemas. No fix was needed — only the test that pins it, since no record exercised a bounded string array before. - Normalization lives in
@radial/core, notpackages/ui/src/lib/labels.ts. The plan said "shared expectation with the sidecar" without saying how; two copies of a case-folding rule is exactly whatnormalizeArtifactTitlesits in core to avoid. The UI module keeps the UI-only parts (argv, vocabulary, hue). - The fold does not normalize, and the plan's "no digest change" is right for the wrong reason.
recordsis assembled from surfaced views, not from every trusted record, socloseGoal,setAutoReviewandeditProjectare already absent from it and the two new collections are too. I left that alone — widening digest coverage is a separate change — and documented the consequence inoperators.mdalongside the mixed-version note the plan asked for. - Added
/goals. An unpinned saved view had nowhere to land: the smart lists fold over work and the Logbook over endings, so neither is "the goals matching this". Without it the rail's Views section points at nothing for any label-only view. - Two things the plan did not mention. The UI's OAuth scope list needed both collections (its own guard test caught this) — so a session predating this change must re-authorize before labelling, which
write.tsalready detects and explains. And the fixture space now carries labels and two of Tim's views, so the comp shows the feature.
One implementation bug was caught by a test I wrote for it: labelsOf counted a goal twice when it carried two cases of one label. Fixed to count goals, not label occurrences.
Verification #
pnpm lint (typecheck + generate.mjs --check), pnpm test (616 UI tests across 42 files, plus every other package at 0 failures), and pnpm build all pass. Beyond the suites I folded the fixture space and confirmed both saved views resolve to the right goals, and served the built bundle to confirm /goals, the chips and their CSS ship.
Remaining risks #
- Lost concurrent label edits under whole-set latest-wins — accepted by the design, documented on
GoalView.labels, in the CLI help and in the sidecar README. - Vocabulary drift with no registry — held down by writer normalization plus autocomplete over labels in use; two spellings can still coexist if two members race, which the UI groups in presentation and the fold deliberately does not.
- Not manually convergence-tested across two live browser profiles and a daemon — no live space was available here. The permutation tests cover the fold's half of that; the ingestion half is unexercised outside them.