# Astrolabe Architecture
**A lightweight, browser-based snippet manager for Vega-Lite visualizations**
> **Technical reference for developers**
> For project overview, see [../README.md](../README.md)
> For development guidelines, see [../CLAUDE.md](../CLAUDE.md)
> For feature inventory, see [features-list.md](features-list.md)
## Project Vision
Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visualization specifications. It emphasizes efficient snippet management with a clean, resizable three-panel interface: snippet library, Monaco editor with schema-aware autocomplete, and live preview.
## Design Principles
- **Local-first**: All data stored in browser (localStorage for snippets, IndexedDB for datasets)
- **Offline-capable**: Progressive Web App with service worker for full offline functionality
- **Minimal dependencies**: Vanilla JavaScript, no build tools, direct CDN imports
- **Developer-friendly**: Full JSON schema support, syntax validation, and intellisense
- **Version-aware**: Draft/published workflow for safe experimentation
- **Dataset management**: Separate storage for datasets with reference system
- **Extensible**: Clean architecture for future cloud sync and authentication
---
## Technical Stack
- **Frontend**: Vanilla JavaScript (ES6+), HTML5, CSS3
- **Reactivity**: Alpine.js v3.x (7KB, lightweight reactive framework)
- **Editor**: Monaco Editor v0.47.0 (via CDN)
- **Visualization**: Vega-Embed v6 (includes Vega v5 & Vega-Lite v5)
- **Storage**: localStorage (snippets) + IndexedDB (datasets)
- **Offline**: Service Worker API with Cache API for PWA functionality
- **Architecture**: Modular script organization with logical file separation
- **Backend**: None (frontend-only application)
---
## Alpine.js Integration
Astrolabe uses Alpine.js for reactive UI management while maintaining vanilla JavaScript for business logic. This hybrid approach provides automatic DOM updates without complex state management overhead.
### Architecture Pattern
```
┌─────────────────────┐
│ Alpine.js (7KB) │ ← Reactivity + UI bindings
└──────────┬──────────┘
│ calls
▼
┌─────────────────────┐
│ Storage Layer │ ← All business logic
│ - SnippetStorage │ (filtering, sorting, CRUD)
│ - DatasetStorage │
└─────────────────────┘
```
**Clean separation:**
- **Alpine**: Handles reactivity, DOM updates, user interactions
- **Storage**: Single source of truth for data logic
### Alpine Stores
Global reactive state managed through Alpine stores:
**`Alpine.store('snippets')`**
- `currentSnippetId` - Currently selected snippet
- `viewMode` - 'draft' or 'published' view toggle
**`Alpine.store('datasets')`**
- `currentDatasetId` - Currently selected dataset
- `currentDatasetData` - Currently loaded dataset data
**`Alpine.store('panels')`**
- `snippetVisible` - Snippet panel visibility
- `editorVisible` - Editor panel visibility
- `previewVisible` - Preview panel visibility
**`Alpine.store('toasts')`**
- `items` - Toast notification queue
- `add(message, type)` - Add toast
- `remove(id)` - Dismiss toast
**`Alpine.store('preview')`**
- `fitMode` - Preview fit mode ('default' | 'width' | 'full')
### Alpine Components
**`snippetList()`** - Snippet panel management
- `searchQuery` - Reactive search filter
- `sortBy`, `sortOrder` - Sort state
- `snippetName`, `snippetComment` - Meta field values
- `filteredSnippets` - Computed property calling SnippetStorage
- Auto-save with debouncing for meta fields
**`datasetList()`** - Dataset list rendering
- `datasets` - Dataset array from IndexedDB
- Helper methods for formatting and usage counts
**`settingsPanel()`** - Settings modal form
- All form field values with `x-model` binding
- `isDirty` - Computed property for Apply button state
- Form validation and persistence
**`chartBuilder()`** - Chart Builder modal
- Full reactive state for mark types, encodings, dimensions
- `spec` - Computed Vega-Lite spec from current state
- `isValid` - Computed validation for required encodings
- Debounced preview rendering
### Key Patterns
**Two-way binding with x-model:**
```html
```
**Conditional rendering with x-show:**
```html
Unsaved changes
```
**List rendering with x-for:**
```html
...
```
**Dynamic classes with :class:**
```html