Files
astrolabe/AGENTS.md
T

10 KiB
Raw Blame History

Astrolabe Project

Purpose: Onboarding document for AI agents (and humans) working on Astrolabe.


Project Overview

Astrolabe is a browser-based snippet manager for Vega-Lite visualizations. A user keeps a local library of snippets (saved Vega-Lite specs), edits each as JSON with live validation and a live chart preview, and reuses datasets across many snippets. Fully local, offline-capable, no account.

It is a rebuild on an architecture adapted from its sibling project Syto. docs/spec/ (sections 0010) is the behavioral record: the code leads and the spec is kept rewritten to match — a user-facing change isn't done until its spec section describes it. Do not port legacy code.

Technical Stack

Layer Technology
Build Vite, TypeScript, Vitest (happy-dom)
UI React, Zustand, CSS Modules
Editor Monaco (JSON + Vega-Lite schema service)
Charts Vega-Lite + vega-embed
Storage IndexedDB (snippets, datasets), localStorage (settings/prefs), URL hash (view state)
Offline vite-plugin-pwa (Workbox), registerType: 'prompt'

Architecture (non-negotiable)

  • src/core/ is portable — no browser APIs, no React, no Monaco. All spec operations live here and are tested hardest.
  • src/app/ — React + Zustand UI. State in Zustand stores; browser specifics in src/app/infrastructure/ adapters (IndexedDB / localStorage / URL hash). The rest of the app never touches window/indexedDB directly.
  • Modals go through a registry + coordinator + shell, not ad-hoc rendering.
  • CSS Modules + design tokens (styles/tokens.css); themes flip [data-theme].
  • No shared library with Syto — patterns are adapted, never imported.
  • The app is served at /app/; / is a standalone marketing landing (src/landing/). Multi-page Vite build — index.html → the landing, app/index.html → the app (hash view-state routing is unchanged by the base path). The landing reuses src/core and the chart-renderer service only — never stores, orchestration, modals, or components — and lazy-loads Vega, so / stays light. The PWA service worker and manifest are scoped to /app/, leaving the landing uncontrolled and always-fresh. /learn/ is a second such entry (src/learn/) — the markdown-authored deep-dive section, under the same rules (see architecture 11). The landing depicts only shipped behavior: live demos run the real core, and staged visuals (the editor still) mirror the app's actual strings — CodeLens labels, validation messages — never invented UI.

See docs/architecture/ for the patterns behind each layer (state, persistence, modals, routing, rendering, inference, relationships) and docs/IMPLEMENTATION-PLAN.md for the milestone sequence. Both are self-contained — no external repo is needed to work from them.


Directory Structure

index.html           # Landing entry (served at /)
app/index.html       # App entry (served at /app/)
learn/index.html     # Learning-section entry (served at /learn/)
src/
├── main.tsx         # App bootstrap (font wiring, startup, render)
├── landing/         # Marketing landing at / — standalone page; reuses core + chart-renderer
├── learn/           # /learn/ deep-dive — markdown lessons + the SpecProgression engine (arch 11)
├── core/            # Portable spec engine (no browser/React/Monaco)
├── app/
│   ├── components/  # React UI (CSS Modules co-located)
│   ├── hooks/       # Reusable React hooks (e.g. useFocusTrap — shared by overlays)
│   ├── stores/      # Zustand stores (incl. ConfirmStore — in-app confirm dialogs)
│   ├── services/    # Business logic
│   ├── orchestration/   # Startup wiring: store↔adapter subscribers (persistence)
│   └── infrastructure/  # IndexedDB, localStorage, Monaco, settings adapters
styles/              # Global CSS (tokens, base)
docs/
├── spec/            # Behavioral specification (0010) — the WHAT (record; code leads)
├── architecture/    # Architecture playbook (0011) — the HOW (self-contained)
│   └── visual-specimen.html  # Standalone token sandbox + reusable-primitive catalog
├── exploration/     # Point-in-time records (research, reviews, scope memos) — not maintained
├── IMPLEMENTATION-PLAN.md    # Milestone sequence (M0M6)
└── WHY-A-SEPARATE-REBUILD.md

Development

npm run dev        # Dev server
npm run build      # Typecheck + production build (+ PWA)
npm run typecheck  # tsc --noEmit
npm test           # Vitest (run once)
npm run test:watch # Vitest watch
npm run format     # Prettier

AI Developer Protocol

  • No git on your own initiative — don't add/commit/push unless explicitly invited.
  • Verify — run npm run typecheck and npm test after changes. A green build and a smaller bundle prove nothing about behavior: when a change touches what the user sees or does, confirm it by exercising the feature, not by the compiler alone.
  • Trim content, not capability — when narrowing a third-party import or build to cut size, remove optional content, never the library's features. The smallest/lowest-level entry point is seldom the right one — it often drops capabilities you meant to keep. Prefer the entry that excludes the unwanted content while retaining behavior, and validate the behavior survived. (This bit us once: importing Monaco's editor.api to drop unused languages also stripped every editor feature — see docs/architecture/08.)
    • The line isn't "content vs. capability" by category — it's "does any real user path depend on this?" Safe to drop: data no user path exercises (a date formatter's unused locale tables, an icon set you never render, themes you don't ship). Not safe, even though it looks like "content": human-language coverage — font script subsets, translatable strings — which is capability the moment the app is meant to be usable in that language. Treat dropping it like dropping a feature. (This bit us a second time: trimming IBM Plex to the latin subsets dropped Cyrillic — capability for an internationally-usable app. We ship every script subset and precache them for offline; unicode-range means the browser only downloads what a glyph needs anyway.)
  • Spec follows codedocs/spec/ is the descriptive record of behavior; the code leads. When in doubt about existing behavior, read the spec. When you ship user-facing behavior, update the matching spec section in the same session; if spec and app disagree, the spec is stale — rewrite it deliberately, never drift silently.
  • Core-first — for each feature, build the pure src/core/ logic with tests before UI.
  • Session wrap-up — when the user signals the session is wrapping, run the review pass before any commit: /doc-update in-session first (flush unrecorded rationale), then /alignment — and /eng-council review when the diff is structural — each as a clean-context subagent that reports back. Full protocol in CLAUDE.md.

Project skills

Invoke with /<name> (defined in .claude/skills/):

  • /alignment — review uncommitted/staged changes for quality, test coverage, and alignment with docs/spec/ + docs/architecture/. Fixes issues directly and leaves // TODO: breadcrumbs at code sites for out-of-scope observations.
  • /doc-update — capture session-discovered knowledge gaps into the right doc layer (docs/spec/ for behavior, docs/architecture/ for patterns).
  • /council — consult the design council (Carbon, GOV.UK, WAI-ARIA APG, Nielsen Norman, cloned under reference/) before a user-facing interaction/content/a11y decision. Auto-fires on error/empty-state copy and new interactive-widget keyboard/focus work; on demand otherwise. It advises; docs/architecture/09+10 decide.
  • /eng-council — convene the engineering council: an evidence-grounded review of codebase structure, consistency, layering altitude, and subtraction (what to delete). Modes: whole-codebase sweep, refactor review, new-functionality review, and a pre-build consult (only the consult auto-fires — before building a new instance of a kind). Recurring findings become docs/architecture/ rules and new /alignment checks.
  • /release — bump version, update the changelog, prepare a git tag.

Deployment

Push to main auto-deploys to astrolabe-viz.com (Cloudflare Pages, Git-connected). Hosting, analytics posture, and distribution facts: docs/deployment.md.

Versioning

Simplified semver 0.x.y (pre-1.0): minor for features/behavior, patch for fixes. Single source of truth is version in package.json, injected as __APP_VERSION__.

Testing Philosophy

High coverage on src/core/ (parsing, detection, profiling, reference resolution, fit transforms, import normalization). Lighter on components. Extract testable logic out of components into core/stores where practical.

Don't test static presentational components — copy, markup, and links with no logic behind them. Content assertions ("renders the heading X", "this link exists") are change-detectors: they break on intentional copy edits and catch no real bug. Test the logic a component carries — platform branches, state transitions, config-path writes, render serialization — not the strings it renders; if that logic is worth guarding, lift it into core/stores and test it there.

Component tests (happy-dom) share a harness shape: createRoot + act with IS_REACT_ACT_ENVIRONMENT = true set at module level, stores reset in beforeEach, and vi.mock('../services/chart-renderer', …) for anything that embeds a chart (vega-embed is integration-heavy; a resolved no-op handle suffices) — see any components/*.test.tsx. Infrastructure tests that touch IndexedDB run against fake-indexeddb.