Files
astrolabe/AGENTS.md
T

7.8 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 spec-driven rebuild on an architecture adapted from its sibling project Syto. The authoritative behavioral contract is docs/spec/ (sections 0010). Implement to the spec; 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.

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

src/
├── main.tsx         # App entry (font wiring, startup, render)
├── 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/            # Authoritative behavioral specification (0010) — the WHAT
├── architecture/    # Architecture playbook (0010) — the HOW (self-contained)
│   └── visual-specimen.html  # Standalone token sandbox + reusable-primitive catalog
├── 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 is the contract — when in doubt, read docs/spec/. If the spec is wrong or silent, raise it; change the spec deliberately rather than drifting from it.
  • Core-first — for each feature, build the pure src/core/ logic with tests before UI.

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.

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.

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.