Follow-up to #81, which specified the adapter rendering only loosely ("desktop toast/flash, maybe a per-note marker") and shipped the toast. In practice, on a vault with more than one anomaly in a pass, that toast is close to useless.
The gap
anomalyNotice() (ui/src/reconcile.ts:64-82) flattens every collision/restamp in a ProjectReport into one run-on string, joined with spaces — no line breaks between anomalies. That string is handed to flash() (ui/src/main.ts:204-212), which paints it as plain textContent in a toast and auto-clears it on a fixed 4.5s timer (ui/src/main.ts:1463-1489, 2359-2362) regardless of how much text it's carrying.
A real example, reindexing a ~1100-note vault with three unrelated duplicate-b2id collisions:
Indexed 1122 note(s) — 0 embedded, 0 stamped. ⚠ Duplicate b2id: AlteredCraft/Workshops/AGENTS.md not indexed — AlteredCraft/Workshops/CLAUDE.md kept it. Delete the copy, or remove its b2id line for a fresh identity. Duplicate b2id: __DailyNotes/2026.07.09.md, __DailyNotes/2026.07.10.md not indexed — __Templates/Daily note template.md kept it. Delete the copy, or remove its b2id line for a fresh identity. Duplicate b2id: AlteredCraft/Product Development/B2/B2 Steward Trace Harness - one generator for eval tasks and training data.md not indexed — AlteredCraft/Product Development/B2/B2 Steward Eval - measuring the vault-steward model.md kept it. Delete the copy, or remove its b2id line for a fresh identity.
Three distinct, unrelated anomalies, each naming two files, run together into one paragraph. Concretely, this toast:
- gives no way to jump to (or even copy) any of the named paths — they're inert text, not tree/open-note affordances;
- gives no way to act on one anomaly independently of the others (dismiss, defer, mark seen);
- disappears on the same 4.5s clock whether it's carrying one anomaly or ten, so a multi-anomaly pass is very likely gone before it's been read in full; and
- has no "where did that go" recall — closing/missing the toast loses it until the next projection pass happens to re-surface the same anomaly.
The underlying data is already fully typed and structured on both sides (B2idCollision, RestampedNote in crates/b2-core/src/ingest.rs:101-148 and mirrored in ui/src/types.ts:307-360) and the CLI already exposes it faithfully via reindex --dry-run --json. The desktop toast is the one place this collapses to an unstructured sentence.
Proposed shape
Exploratory — not committing to one of these:
- Keep the toast as the "something needs attention" ping (one line: "⚠ 3 anomalies from this reindex"), but make it click-through to a lightweight review surface — a panel/modal listing each collision/restamp as its own row: the shadowed path(s), the path that kept the identity, why, and the suggested fix — each path wired to the existing reveal-in-tree/open-note affordance (
expandAncestors, ui/src/main.ts:218) instead of being inert text.
- Whatever the surface, it must stay consistent with index-engine.md §8 / S2: "nothing anomaly-shaped is ever stored" — every notice re-derives from vault + index each pass. So this is a richer rendering of the
ProjectReport already returned by project/reindex, not a persisted notification log or dismissal-state table. A panel reopened after restart shows nothing until the next pass re-surfaces the same anomaly, same as today.
- Open question: does a non-time-boxed but user-dismissible banner cover this, or does it need a full multi-row panel? Probably depends on how often multi-anomaly passes actually occur in practice — worth a look at real vaults before committing to UI weight.
Non-goals
Sources
Follow-up to #81, which specified the adapter rendering only loosely ("desktop toast/flash, maybe a per-note marker") and shipped the toast. In practice, on a vault with more than one anomaly in a pass, that toast is close to useless.
The gap
anomalyNotice()(ui/src/reconcile.ts:64-82) flattens every collision/restamp in aProjectReportinto one run-on string, joined with spaces — no line breaks between anomalies. That string is handed toflash()(ui/src/main.ts:204-212), which paints it as plaintextContentin a toast and auto-clears it on a fixed 4.5s timer (ui/src/main.ts:1463-1489,2359-2362) regardless of how much text it's carrying.A real example, reindexing a ~1100-note vault with three unrelated duplicate-
b2idcollisions:Three distinct, unrelated anomalies, each naming two files, run together into one paragraph. Concretely, this toast:
The underlying data is already fully typed and structured on both sides (
B2idCollision,RestampedNoteincrates/b2-core/src/ingest.rs:101-148and mirrored inui/src/types.ts:307-360) and the CLI already exposes it faithfully viareindex --dry-run --json. The desktop toast is the one place this collapses to an unstructured sentence.Proposed shape
Exploratory — not committing to one of these:
expandAncestors,ui/src/main.ts:218) instead of being inert text.ProjectReportalready returned byproject/reindex, not a persisted notification log or dismissal-state table. A panel reopened after restart shows nothing until the next pass re-surfaces the same anomaly, same as today.Non-goals
b2idcollisions (and identity restamps) instead of silently shadowing #81.ProjectReport.Sources
b2idcollisions (and identity restamps) instead of silently shadowing #81 (the anomaly diagnostic this extends)ui/src/reconcile.ts:52-82(IndexAnomalies,anomalyNotice)ui/src/main.ts:204-212(flash),:1463-1489,:2359-2362(reindex/pulse call sites)crates/b2-core/src/ingest.rs:101-148(B2idCollision,RestampedNote),ui/src/types.ts:307-360(TS mirror)docs/design/index-engine.md§8 (surfacing-only design intent, S2)