mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Implement M1 authoring loop: library, editor, live preview, persistence
M1 MVP from docs/IMPLEMENTATION-PLAN.md, with the /alignment pass applied. - core: Snippet model + factory; prepareSpecForRender (copy-not-mutate); per-theme Vega chart config - state/orchestration: SnippetStore with debounced auto-save; IndexedDB adapter + read-time migration; startup hydration + write-through persistence - ui: SnippetLibrary, SpecEditor (Monaco edcore.main — full editor features, JSON-only languages), LivePreview - build: Monaco/Vega manual chunks; raised PWA precache ceiling - alignment: flush a valid draft on snippet switch (+regression tests); TODO breadcrumbs for the preview render race and the window.confirm delete - housekeeping: gitignore .claude/projects/
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Rendering contract — pure spec preparation (spec §04 → Rendering Contract).
|
||||
*
|
||||
* Portable core: no browser APIs, no React, no vega-embed. `prepareSpecForRender`
|
||||
* is the single transform that sits between "parsed spec the user authored" and
|
||||
* "spec the preview actually embeds" (see docs/architecture/05). It performs two
|
||||
* deterministic steps, in order, **on a deep copy** so the user's stored spec is
|
||||
* never mutated by rendering:
|
||||
*
|
||||
* 1. Dataset reference resolution — arrives in M3 (no-op here).
|
||||
* 2. Fit-mode sizing — arrives in M2 (no-op here).
|
||||
*
|
||||
* In M1 it is an identity transform over a copy: it establishes the
|
||||
* copy-not-mutate invariant and the call site the renderer depends on, so M2/M3
|
||||
* can fill in the steps without the preview pipeline changing shape.
|
||||
*/
|
||||
|
||||
/** Preview sizing modes (spec §04 → Fit / Sizing Modes). `default` = Original. */
|
||||
export type FitMode = 'default' | 'width' | 'height' | 'full';
|
||||
|
||||
export interface PrepareOptions {
|
||||
/** Active fit mode. Applied in M2; ignored in M1. */
|
||||
fitMode?: FitMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape `.`/`[`/`]` so Vega-Lite treats a string as a literal field name rather
|
||||
* than a nested-property accessor (docs/architecture/05 §4). Used wherever
|
||||
* Astrolabe *constructs* a `field:` from a data-derived column name (chart
|
||||
* builder, M4); hand-authored specs are the user's responsibility.
|
||||
*/
|
||||
export function escapeVegaField(name: string): string {
|
||||
return name.replace(/([.[\]])/g, '\\$1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the authored spec into the spec to embed. Operates on a deep copy
|
||||
* and returns it; the input is never mutated.
|
||||
*/
|
||||
export function prepareSpecForRender<T>(spec: T, _options: PrepareOptions = {}): T {
|
||||
const copy = structuredClone(spec);
|
||||
|
||||
// M3: resolveDatasetRefs(copy, datasets)
|
||||
// M2: applyFitMode(copy, options.fitMode)
|
||||
|
||||
return copy;
|
||||
}
|
||||
Reference in New Issue
Block a user