# 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