Initial scaffold: spec, architecture playbook, and M0 skeleton

This commit is contained in:
2026-06-04 22:14:33 +03:00
commit 056644450c
51 changed files with 13754 additions and 0 deletions
@@ -0,0 +1,2 @@
- [vega-editor reference clone](vega-editor-reference-clone.md) — local vega/editor at /Users/oleh/code/reference/vega-editor for Monaco/render/validation techniques
- [Stack: React + Zustand](stack-moving-to-react.md) — final UI stack is React + Zustand (migrated off Preact + signals at M0); doc sweep done
@@ -0,0 +1,29 @@
---
name: stack-moving-to-react
description: Astrolabe's UI stack is React + Zustand (migrated from Preact + signals at M0)
metadata:
type: project
---
The UI stack is **React + Zustand** (user decision, 2026-06-03, executed at M0 before feature
work). It started as Preact + `@preact/signals`; a brief intermediate step migrated to React +
`@preact/signals-react`, but the final call is **React + Zustand** — signals were dropped.
**Why React:** the Preact pain was React-ecosystem friction (real-React-only libraries not
cooperating with `preact/compat`) — *not* the signals model. Real React removes that whole
class of problem and makes borrowing from the reference [[vega-editor-reference-clone]] (a
React app) port directly.
**Why Zustand over keeping signals:** signals were never the problem, but switching framework
was the one cheap moment (M0, ~nothing implemented) to also pick the lowest-future-migration-risk
state lib. Zustand is idiomatic React, has first-class outside-React access
(`getState`/`setState`/`subscribe`) that fits the "logic lives in core/services, not components"
architecture, and carries no build-time transform. The signals→Zustand cost was only rewriting
unimplemented docs.
**How to apply:** Stores are `create<State>()` modules exporting a `useXStore` hook (state +
actions in one object); components read via `useXStore(selector)` (+ `useShallow` for object
selections); non-component code uses `getState()/setState()/subscribe()`; derive in selectors,
never store derived fields. See `docs/architecture/01-state-and-stores.md`. The repo-wide
Preact/signals→React/Zustand doc sweep is **done** — no stale "Preact"/"signals" wording should
remain except where it describes the sibling project Syto, Vega's own signals, or plain English.
@@ -0,0 +1,27 @@
---
name: vega-editor-reference-clone
description: Location and nature of the local vega/editor clone used as a technique reference for Astrolabe's Monaco/vega-embed/validation work
metadata:
type: reference
---
The canonical Vega-Lite editor (vega/editor) is cloned locally at
`/Users/oleh/code/reference/vega-editor` (shallow clone of `main`, HEAD 4fdbb59). It is the
reference for the "editor + renderer" wiring Astrolabe's M1/M2 call out ("mine vega-editor
for how it wires the schema"). Re-clone with `git clone --depth 1 https://github.com/vega/editor`.
Key divergences to remember when borrowing — it is **React + Redux/context**, uses
`@monaco-editor/react` + `@monaco-editor/loader` (CDN-loaded Monaco, no explicit worker
config), and — surprisingly — **does NOT use vega-embed for its live preview** (it hand-rolls
`vegaLite.compile``vega.parse``new vega.View().runAsync()`; vega-embed is imported only
for types + the exported standalone HTML). Astrolabe is React (see [[stack-moving-to-react]]) +
Zustand stores + raw `monaco-editor` + `vegaEmbed()`. Since both are now React, vega/editor's
component lifecycle patterns port fairly directly; the friction is state (their flat Redux →
our Zustand stores) and Monaco worker wiring (their CDN loader → our explicit Vite workers).
The Monaco choice (self-hosted from npm + raw API, not the CDN loader / `@monaco-editor/react`
route) is a recorded decision — see `docs/architecture/08` § Decision · Monaco integration.
The highest-value files: `src/utils/monaco.ts` (schema wiring), `src/utils/validate.ts` (ajv),
`src/utils/jsonc-parser.ts`, `src/utils/logger.ts` (LocalLogger), `src/components/renderer/renderer.tsx`
(View lifecycle), `src/components/app.tsx:188-365` (parse→validate→compile→render flow),
`src/constants/default-state.ts` (state shape). Relevant to [[]] M1/M2 of the implementation plan.
+185
View File
@@ -0,0 +1,185 @@
---
name: alignment
description: Review staged or uncommitted code to ensure quality, test coverage, and alignment with project specifications
disable-model-invocation: true
---
# Code Alignment
Review staged or uncommitted code to ensure quality, test coverage, and alignment with the
project's spec (`docs/spec/`) and architecture playbook (`docs/architecture/`).
## Scope
Determine the review scope using `git diff` (unstaged) and `git diff --staged` (staged).
Review all changes in scope. If changes span multiple patterns below, apply all relevant sections.
## General Instructions
### Process
1. **Git**: **NEVER** stage (`git add`) or commit (`git commit`) — that is the USER's
responsibility. If the reviewed changes span multiple independent concerns (a feature + an
unrelated fix, a refactor + a new capability), suggest splitting them into separate commits
and mention the logical boundaries.
2. **Verification**: After changes, run `npm run typecheck` and `npm test`; run `npm run build`
if the change could affect the build. If tests fail, fix the issue if straightforward; ask
the user only if non-trivial or ambiguous.
3. **Fix directly; don't ask first.** When you find an issue covered by these instructions,
fix it in place rather than reporting it and waiting. Ask the user only when the fix is
genuinely ambiguous or several valid approaches exist with real trade-offs. When guidelines
conflict, prefer in this order: **SOUL.md philosophy > `docs/spec/` behavioral contract >
`docs/architecture/` patterns > local cleanup**. These instructions are not strictly
prohibitive — if a guideline has a valid reason to be bypassed, mention it in the summary.
### Code Quality
4. **Code Cleanup**: Remove leftover code, unnecessary defensive programming, and
over-engineering from iterative development — dead code, try/catch around internal calls
that can't throw, abstraction layers wrapping a single implementation. Proceed with caution;
ask if unsure.
5. **Styles and UI**: When altering CSS or layout, follow or generalize existing patterns
(CSS Modules + design tokens in `styles/tokens.css`) rather than writing from scratch. Don't
fix whitespace/formatting (trailing newlines etc.) — Prettier owns that.
6. **Code Comments**: Comments should not duplicate what the code already says. Remove
parroting comments. Ensure comments capture non-obvious *why* — design decisions,
constraints, gotchas. Flag missing comments where a reader would reasonably ask "why is this
done this way?"
7. **Workarounds**: Flag code that works around a problem rather than solving it (`// HACK`,
silent catch-and-ignore, feature detection for internal bugs). A justified workaround
(upstream bug, browser quirk) needs a comment explaining why and a tracking reference; an
unjustified one should be replaced with a proper fix.
8. **Pre-existing & out-of-scope issues — leave a breadcrumb.** For anything you notice but
don't fix (pre-existing patterns the new code follows; observations the change exposes but
that are out of scope), mark it with a `// TODO:` at the relevant code site explaining
*what* could be improved and *why* (13 lines). **If an observation is important enough to
mention in the summary, it is important enough to deserve a `// TODO:` at the code location**
— otherwise the next reader has no way to recover the context.
### Architecture & Project-Specific Checks
9. **Portable core boundary**: `src/core/` must stay pure — no browser APIs (`window`,
`document`, `indexedDB`, `localStorage`), no React, no Monaco, no `vega-embed`. Flag any such
import. Pure spec logic (detection, profiling, reference resolution, fit transforms,
validation, import normalization) belongs in `src/core/` and must be unit-tested. See
`docs/architecture/00-overview.md` for the layering.
10. **Infrastructure-adapter boundary**: Only `src/app/infrastructure/` touches `indexedDB`,
`localStorage`, or `window.location`. Flag direct access elsewhere — route it through an
adapter (`docs/architecture/02-persistence.md`, `04-routing-and-events.md`).
11. **Rendering safety** (`docs/architecture/05-rendering-theming-preview.md`): the
reference-resolution/fit-mode transform must run on a **copy** of the spec — never mutate the
stored spec; a previous `vega-embed` view must be `.finalize()`d before re-render (no leaks);
user-derived field names must be escaped before going into `field:`; an invalid/unrenderable
spec must fail safe (readable error, no crash), and a blank spec renders nothing.
12. **Persistence safety**: records that may need migration carry a `version` field; reads
apply migrations; destructive actions (delete, revert, reset) confirm; storage failures
warn rather than silently lose data.
13. **Self-containment**: documentation and comments must not add pointers that require an
external repository to follow. Knowledge gets captured locally (`docs/spec/`,
`docs/architecture/`), not linked out.
14. **User-facing copy**: keep user-visible strings centralized and written for users (sentence
case, active voice, no "please", no exclamation marks in errors). If/when an i18n layer
exists, route strings through it instead of hardcoding.
### Output
15. **Summary**: respond with a summary of changes — choices made due to these instructions,
choices where multiple approaches existed, and non-obvious architectural assumptions the
user should know but might not spot in the diff. If the summary mentions an observation you
chose not to fix (rule #8), confirm a `// TODO:` breadcrumb was placed at the code site.
---
## Pattern A: New Functionality
### Testing
- Unit tests for new `src/core/` logic (test the core hardest).
- Lighter component/interaction tests for new UI.
- Tests pass before proceeding.
### Documentation
Update relevant docs if the feature is significant:
- **`docs/spec/`** — if product behavior changed (this is a contract; change deliberately).
- **`docs/architecture/`** — if a new pattern, navigation map, or decision rule emerged.
- **`docs/IMPLEMENTATION-PLAN.md`** — mark milestone progress.
Use the `/doc-update` skill for session-discovered gaps. The list is not exclusive.
### Dependencies
If `package.json` changed:
- Flag each new dependency; explain what it does and why it's needed.
- Could a small custom implementation avoid it? Note the trade-off.
- Prefer dependencies that solve genuinely hard problems (parsing, rendering) over those that
save boilerplate.
### Alignment Check
- **SOUL.md** — philosophy (must not violate without good reason).
- **`docs/spec/`** — behavioral contract.
- **`docs/architecture/`** — the relevant pattern doc.
---
## Pattern B: Bug Fixes
### Testing
- Add a regression test that reproduces the bug and verifies the fix.
- Interaction test if the bug affected UI behavior.
### Documentation
Usually not required unless the bug revealed incorrect docs, or the fix changes documented
(spec) behavior.
### Alignment Check
- **SOUL.md** philosophy; **`docs/spec/`** behavioral contract; **`docs/architecture/`** patterns.
---
## Pattern C: Refactoring
### Impact Analysis
1. **Search for usages** of modified functions/types across the codebase (Grep).
2. **Identify call sites** (components, stores, services, infrastructure, tests).
3. **Check exports** used by other modules.
4. **Review dependencies** — what the code depends on and what depends on it.
### Testing
- Update existing tests to the new structure; verify all call sites.
- Run `npm test` and `npm run typecheck`.
### Documentation
Update `docs/architecture/` if a pattern, module responsibility, or navigation map changed.
Update JSDoc/inline comments if signatures or behavior changed.
### Alignment Check
- **SOUL.md** (simplicity, no parallel systems); **`docs/architecture/`** (consistent with the
documented patterns); **`docs/spec/`** (behavior unchanged unless intended).
### Common Refactoring Checks
- Function signatures → all call sites updated.
- Type definitions → search type usages.
- Imports → correct after file moves.
- Stores → all consumers verified.
- Component props → all usages checked.
- Constants/enums → all references updated.
---
## Reference Documents
| Document | Purpose |
| --- | --- |
| [SOUL.md](../../../SOUL.md) | Project philosophy and core values |
| [AGENTS.md](../../../AGENTS.md) | AI onboarding and project context |
| [docs/spec/](../../../docs/spec/) | Behavioral contract — *what* the app does |
| [docs/architecture/](../../../docs/architecture/00-overview.md) | Architecture playbook — *how* it's built |
| [docs/IMPLEMENTATION-PLAN.md](../../../docs/IMPLEMENTATION-PLAN.md) | Milestone sequence and scope |
+111
View File
@@ -0,0 +1,111 @@
---
name: doc-update
description: Update project documentation based on knowledge gaps discovered during the current session
disable-model-invocation: false
---
# Documentation Update from Session Context
Review the current session to identify knowledge gaps that caused suboptimal codebase
navigation, then update the relevant documentation.
## The quality bar
Every addition must pass this test: **"Would this save a future session at least 5 minutes
of exploration?"**
Documentation serves two purposes — know **where to look** and know **what to do**. Both
are valuable, but at different levels of detail:
- **Navigation map** (good): "Preview flow: `LivePreview.tsx``prepareSpecForRender()` (core) → `vega-embed`" — lists the files and their roles so you don't read a dozen files to find the right four.
- **Decision rule** (good): "The fit-mode/reference-resolution transform runs on a *copy* of the spec — never mutate the stored spec" — captures a non-obvious convention.
- **Code walkthrough** (bad): "SnippetStore.updateDraft sets draftSpec, which a startup subscriber watches, debounces, then calls snippetStore.put… " — restates the code, goes stale on any rename.
**Navigation maps** use file/module names (stable) to show flow direction. **Decision
rules** capture "when/why" constraints. **Code walkthroughs** restate implementation
details — that's what reading the code is for.
For documentation organization, see **[CLAUDE.md](../../../CLAUDE.md)** and the doc index in
**[AGENTS.md](../../../AGENTS.md)**.
## The three documentation layers (know which one a gap belongs to)
- **`docs/spec/`** — the *what*: behavioral contract (what the app does, acceptance points).
This is a **contract**. Only change it when product behavior genuinely changes, and do so
deliberately — never as a casual "fill a doc gap" edit. A how-detail does NOT belong here.
- **`docs/architecture/`** — the *how*: the patterns behind each layer (state, persistence,
modals, routing, rendering, inference, relationships). Most navigation maps and decision
rules land here.
- **`docs/IMPLEMENTATION-PLAN.md`** — the *when*: milestone sequence and scope.
## Process
### 1. Analyze the session
Look back through the conversation and identify:
- **Missing navigation maps**: Where did you read many files to discover which 34 files a
flow actually involves? A one-line map of file roles would have saved that.
- **Missing rules**: What conventions or constraints were discovered that a new session
would violate or re-discover?
- **Non-obvious "when/why" knowledge**: What decisions require understanding intent, not
just implementation?
Produce a brief list of gaps before proceeding. For each, state what's needed in one
sentence — a navigation map ("X flow: file → file → file") or a decision rule ("X must/must
not do Y"). If you can't state it concisely, it may be too implementation-specific to document.
### 2. Categorize and target
Map each gap to the right document:
| Gap type | Target document |
| --- | --- |
| Product behavior, capabilities, acceptance points | `docs/spec/` (the relevant 0010 section) — **contract; change deliberately** |
| State / Zustand stores | `docs/architecture/01-state-and-stores.md` |
| Persistence, IndexedDB, localStorage, migrations | `docs/architecture/02-persistence.md` |
| Modals, dialog lifecycle | `docs/architecture/03-modal-system.md` |
| URL routing, keyboard/events | `docs/architecture/04-routing-and-events.md` |
| Rendering, theming, vega-embed, preview | `docs/architecture/05-rendering-theming-preview.md` |
| Type inference, dataset profiling | `docs/architecture/06-type-inference.md` |
| Names, snippet↔dataset links, rename propagation | `docs/architecture/07-naming-and-relationships.md` |
| Milestone scope, build order | `docs/IMPLEMENTATION-PLAN.md` |
| Project philosophy / identity | `SOUL.md` |
| Onboarding, conventions, stack | `AGENTS.md` / `CLAUDE.md` |
If a gap fits no existing document, consider a new section in the closest one; prefer
extending over creating. A brand-new architecture topic can become `docs/architecture/08-*.md`
(add it to `docs/architecture/00-overview.md`).
### 3. Read, locate, and check for bloat
For each target document:
- Confirm the gap isn't already covered (if partially covered, extend rather than duplicate).
- Find the right insertion point.
- **Check section length**: if a section is already long (>50 lines), tighten or consolidate
before adding. Documentation that only grows becomes noise.
### 4. Apply updates
- **Rules and constraints over descriptions**: "X must do Y because Z" beats "X works by A, B, C".
- **Stability over specifics**: no line numbers, no file counts, no volatile details.
- **Proportional**: a missing sentence doesn't need a new section; a missing concept does.
- **Match existing style**: follow surrounding formatting, heading levels, tone.
- **Self-contained**: never add a pointer that requires an external repository to follow.
- **Consolidate while adding**: net size increase should be minimal.
### 5. Update index (if needed)
Only update `CLAUDE.md` (or `docs/architecture/00-overview.md`) if a new document was created
or a major new section was added that should be discoverable. Not for minor additions.
## What NOT to document
- **Code walkthroughs**: prose that restates the code; goes stale on any rename. (Navigation
maps that list file roles are fine.)
- **Obvious-from-code patterns**: if reading the file makes it clear, don't add docs.
- **Session-specific context**: current task details, debugging steps taken.
- **Speculative patterns**: only document conventions confirmed across multiple instances.
- **Implementation details that change with refactoring**: if renaming a variable would
invalidate the doc, it's too specific.
+94
View File
@@ -0,0 +1,94 @@
---
name: release
description: Bump the app version, update the changelog, and prepare a git tag for release
disable-model-invocation: false
---
# Release
Bump the app version, update the changelog, and prepare a git tag.
## Process
### 1. Determine what changed since the last version
Run `git log` from the last version tag (or all history if no tags exist) and review the
changes. Categorize:
- **Features**: new user-facing capabilities
- **Fixes**: bug fixes
- **Improvements**: performance, UX polish, refactoring that affects behavior
- **Internal**: refactoring, docs, tests, build (don't list individually — summarize if substantial)
### 2. Determine bump type
Read the current version from `package.json`. The project uses **simplified semver during
pre-1.0**:
| Bump | When | Example |
| --- | --- | --- |
| **Minor** (`0.x.0`) | New features, UI changes, behavior changes | `0.1.0``0.2.0` |
| **Patch** (`0.x.y`) | Bug fixes, polish, performance, internal | `0.1.0``0.1.1` |
| **Major** (`1.0.0`) | Only when declaring public stability (user decision) | — |
Present the categorized changes and your recommended bump type to the user **for confirmation
before proceeding**.
### 3. Update version
Update the `version` field in `package.json`. This is the **single source of truth** — Vite
injects it as `__APP_VERSION__` at build time (shown in the header badge, and in Settings /
the export envelope once those exist).
### 4. Update the changelog
Maintain `docs/CHANGELOG.md`. If it doesn't exist yet, create it with a top-level `# Changelog`
heading. Add a new entry under a month heading:
```markdown
## June 2026
### v0.2.0
- **New feature** — description…
### v0.1.1
- **Bug fix** — description…
```
- Group by feature/change, not by commit.
- Lead with the name in bold, then a dash and description.
- Most important changes first; summarize related commits into coherent items.
### 5. Cross-check user-facing docs
Scan hand-maintained user-facing surfaces against the changes landing in this release and fix
drift **before** the version-bump commit (the CHANGELOG entry is not the place to quietly slip
in doc fixes):
- `README.md` — the status line and any feature claims still accurate?
- Any in-app help / about / onboarding content that exists at release time.
(When narrative content pages are added later, list them here so this net catches accumulated
drift across many changes.)
### 6. Suggest the git tag
After the user stages and commits the version bump, suggest:
```bash
git tag v{version}
git push --tags
```
If/when a deploy pipeline is wired, note here what publishing the tag triggers.
## Rules
- **Never bump the version without user confirmation** on the bump type.
- **Never stage or commit** — the user handles git operations.
- The export-format `version` (the import/export envelope) is **independent** of the app
version — only bump it when the export schema actually changes.
- Keep commit subjects single-line; no `Co-Authored-By` trailers (the user handles the commit
regardless).