# 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.