Files
astrolabe/docs/spec-implementation-audit.md
T

177 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec ⇄ Implementation Audit
> Generated 2026-06-07 against `main` @ `807d3c8` (version 0.1.0).
> Method: one read-only auditor per spec section (0010) 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 onboarding
canvas (welcome + example gallery, replacing the old placeholder seed), and the list's
no-search-matches empty state all done. Deferred: search, sort UI+persistence, 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.