diff --git a/docs/spec-implementation-audit.md b/docs/spec-implementation-audit.md new file mode 100644 index 0000000..39c378b --- /dev/null +++ b/docs/spec-implementation-audit.md @@ -0,0 +1,175 @@ +# Spec ⇄ Implementation Audit + +> Generated 2026-06-07 against `main` @ `807d3c8` (version 0.1.0). +> Method: one read-only auditor per spec section (00–10) classified every discrete +> requirement, then an adversarial verify pass re-checked each claimed gap to refute +> false negatives. Baseline at audit time: **`typecheck` clean, 470 tests passing (40 files)**. + +## Verdict + +Astrolabe is **substantially complete against its own spec**. Across the 11 spec sections, +**320 of 344 discrete requirements are fully implemented** (93%). Of the remaining 24: + +- **16 are deferred-by-plan** to M6 (the milestone in progress) — planned, not forgotten. +- **3 are genuine gaps** the plan does _not_ account for (one missing feature, one + divergence, one partial animation). +- **3 are partials** that are really "core done, UI deferred to M6." +- **2 are documented/intentional divergences** (Settings architecture) — these are + **spec drift to reconcile**, not implementation bugs. + +The MVP loop (M1) and everything through M5 is solid. The only work that escapes the M6 +net is small and listed under **Verified gaps** below. + +## Scorecard + +| § | Section | State | impl | gaps (non-impl) | +| --- | ------------------------ | ----------- | ---- | ------------------------------------- | +| 00 | Product Overview | ✅ complete | 28 | — | +| 01 | Application Shell & Nav | 🟢 mostly | 27 | 3 partial · 1 deferred · 1 diverges | +| 02 | Snippet Library | 🟡 partial | 28 | 11 deferred (search/sort/storage-mon) | +| 03 | Editor & Draft/Published | 🟢 mostly | 38 | **1 missing (auto-format)** | +| 04 | Live Preview | 🟢 mostly | 31 | 1 deferred (busy indicator) | +| 05 | Datasets | ✅ complete | 31 | **1 diverges (Save not disabled)** | +| 06 | Chart Builder | ✅ complete | 25 | — | +| 07 | Settings | ✅ complete | 27 | — | +| 08 | Import & Export | 🟢 mostly | 33 | 1 deferred (atomic rollback on quota) | +| 09 | Data Model & Persistence | 🟡 partial | 18 | 1 deferred (sort preference §09D) | +| 10 | Non-Functional | 🟡 partial | 34 | 2 partial · 1 deferred | + +`partial` totals are concentrated in §10 because they reflect "infrastructure built, M6 UI +pending" rather than absent capability. + +--- + +## Verified gaps (NOT covered by the M6 plan) + +These survived the adversarial verify pass and have **no deferral note** in +`IMPLEMENTATION-PLAN.md`. Worth a decision now. + +### 1. §03A — Automatic JSON reformatting on type/paste · **MISSING** + +Spec §03A requires that pasting and typing trigger automatic reformatting so JSON stays +consistently indented. **No document-formatting provider is registered anywhere in `src/`.** +The editor buffers raw text as-typed; only programmatic rewrites (Extract) emit pretty JSON +via `JSON.stringify(…, null, 2)`. + +- M2 is marked **complete**, and `architecture/08` explicitly lists the formatter in the M2 + borrow list (`json-stringify-pretty-compact` format action) — so this was scoped for M2 + and dropped, not deferred. +- Evidence: `SpecEditor.tsx:264-267` (raw `getValue()`, no format); `monaco-schema.ts` + registers diagnostics only; `grep registerDocumentFormattingEditProvider` → no hits. +- **Decision needed:** implement the formatter (small, self-contained), or amend §03A / + `architecture/08` to drop the requirement if auto-reformat-on-type is no longer wanted. + +### 2. §05 — Dataset Save button not disabled until valid · **DIVERGES** + +Spec §05 says Save is disabled until a name and valid data/URL are present. Implementation +keeps the button always clickable and validates **on submit** (`save()` returns `false`, +sets `formError`). Functionally safe, but the spec'd affordance (disabled state) never +appears. No deferral note exists. + +- Evidence: `DatasetsModal.tsx:436` (no `disabled`); contrast `ChartBuilderModal.tsx:483` + which already does `disabled={!valid}` — the precedent/pattern exists in-repo. +- **Decision needed:** cheap to align (derive a `canSave` from the existing validation), or + accept submit-time validation and note it in §05. + +### 3. §01F — Toast fade-**out** missing · **PARTIAL** + +Spec §01F: toasts "appear/disappear with a brief fade." Toasts fade **in** (`@keyframes +toast-in`, reduced-motion respected) but on dismiss are removed from the store immediately — +React unmounts with no fade-**out**. + +- Evidence: `Toaster.module.css:62-71` (in only); `NotificationStore.ts:77` (immediate + filter-out); `Toaster.tsx:57` (timeout → immediate `dismiss`). +- This is M6 polish territory but isn't itemized in the M6 list. Add an exit animation + (delay-before-removal or `AnimatePresence`-style pattern). + +--- + +## Spec drift to reconcile (not a code bug) + +### §01B/§01C vs §07 — Settings: modal or popover? + +`§01B` (header entry points) and `§01C` (modal set) still describe **Settings as a header +button opening a modal**. `§07` and `architecture/10` deliberately redesigned Settings into +**distributed, live-applied per-pane disclosure popovers** (no modal, no header button), and +the implementation correctly follows §07: + +- `modal-registry.ts:14-16` — "Settings is deliberately NOT a modal" +- `EventRouter.ts:91-98` — `Cmd/Ctrl+,` opens the editor settings **popover**, not a modal +- `App.tsx:67-102` — header has Datasets/Import/Export but **no Settings button** + +The code is right; **the spec is internally inconsistent.** Action: update §01B/§01C to +match the §07 distributed-settings decision (per AGENTS.md "fix spec/code drift +immediately"). Same note applies to the §01C modal set still listing Settings. + +--- + +## Deferred to M6 (planned — informational) + +All of the following are explicitly deferred in `IMPLEMENTATION-PLAN.md`. They are the real +remaining M6 surface area: + +| Area | Spec | State | +| --------------------------------------- | --------- | ------------------------------------------------------------------- | +| **Search** (filter, clear, scope) | §02 | not started; 5 sub-reqs | +| **Sort** controls + persistence | §02, §09D | hardcoded modified/desc (= spec default); no UI / no `ux-prefs` key | +| Two distinct empty-state messages | §02 | single message; tied to search | +| **Storage Monitor** UI | §02, §10 | **core + adapter done & tested** (`storage-estimate`); no component | +| Quota-failure save warning (UI) | §02, §10 | error→toast path exists; no monitor UI | +| Render **busy indicator** (>~1s) | §04, §10 | non-blocking render done; overlay + `aria-busy` deferred (TODO) | +| Atomic rollback on quota during import | §08 | warns on overage; no hard-fail rollback (TODO `transfer.ts:135`) | +| **About & Privacy** + **Donate** modals | §01 | typed in `ModalName`, no components/registry entries | +| Offline/PWA manual verification | §10 | configured (`registerType:'prompt'`); not yet manually verified | + +Note the Storage Monitor and sort are "groundwork landed, UI pending" — the core logic ships +and is tested (e.g. `storage-estimate.test.ts`), so M6 is wiring, not building from zero. + +--- + +## Section detail + +- **§00 Product Overview — complete.** Every overview claim (local-first, no account, JSON + editing, live preview, reusable datasets, draft/published, import/export, three-pane + + modals) maps to shipped code. +- **§01 Shell — mostly complete.** Three-pane layout, resize handles, toggle strip (a11y), + keyboard router, URL routing w/ back-forward, toasts, modal system all present. Gaps: + About/Donate modals (deferred), toast fade-out (partial), Settings-modal drift. +- **§02 Snippet Library — partial.** List, list-item (status + linked datasets), metadata + panel (rename/comment/timestamps), create/duplicate/delete, naming, first-run seed all + done. Deferred: search, sort UI+persistence, dual empty states, storage monitor. +- **§03 Editor — mostly complete.** Draft/Published, auto-save, debounced auto-render, + publish/revert + confirm, inline errors, Extract-to-Dataset all working. Gap: + auto-reformat-on-type (**missing**, see above). +- **§04 Live Preview — mostly complete.** Rendering contract (ref resolution + fit-mode, + recursive, tested), empty/error handling, tailored dataset-not-found message, responsive + resize. Deferred: >1s busy indicator. +- **§05 Datasets — complete.** Two-pane modal, profile inference, ref resolution, + bidirectional links, extract flow. Minor divergence: Save not disabled-until-valid. +- **§06 Chart Builder — complete.** Five marks, four channels, Tier-B smart defaults + + guards, channel transforms (aggregate/bin/timeUnit), sort/stacking, pre-population, + validation, live preview, snippet creation + linking. Matches research doc intent. +- **§07 Settings — complete.** Three clusters as distributed disclosure popovers, live + apply, theme header toggle, `Cmd/Ctrl+,`, localStorage load-with-fallback, hydrate before + paint. +- **§08 Import/Export — mostly complete.** Envelope build, shape detection, normalization, + merge/collision/auto-suffix, feedback messaging — all tested. Deferred: atomic rollback on + quota. +- **§09 Data Model — partial.** Snippet/Dataset/UserSettings schemas with versioned + read-time migrations; storage tiers separated; `datasetRefs` relationships. Deferred: sort + preference (§09D) — only panel layout (widths/visibility) persisted today. +- **§10 Non-Functional — partial.** Keyboard routing, focus management, modal behavior, + reduced-motion, debouncing, error→toast safety all comprehensive. Partial/deferred: + storage-monitor warnings, >1s busy indicator, offline manual verification. + +--- + +## Suggested next actions + +1. **Reconcile §01 spec drift** with §07 (settings-as-popovers) — pure doc fix, do now. +2. **Decide §03A auto-format** — implement (small) or amend the spec; it's the only true + "marked-complete-but-missing" item. +3. **Align §05 Save-disabled** using the existing `ChartBuilderModal` pattern (cheap). +4. **Proceed with M6** for the rest — search, sort, storage-monitor UI, About/Donate, + toast fade-out, busy indicator, atomic rollback, offline verification. Most have core + groundwork already landed.