mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add dataset library, extract-to-dataset, and render-time reference resolution
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { createSnippet, type CreateSnippetOptions, type Snippet } from '@core/snippet';
|
||||
import { recomputeDatasetRefs, renameDatasetInSpec } from '@core/spec-refs';
|
||||
|
||||
/** Which version of the active snippet the editor is showing (spec §03D). */
|
||||
export type EditorView = 'draft' | 'published';
|
||||
@@ -48,6 +49,13 @@ export interface SnippetState {
|
||||
removeSnippet: (id: string) => void;
|
||||
/** Update the draft buffer only (no persistence; debounced commit follows). */
|
||||
updateDraft: (text: string) => void;
|
||||
/**
|
||||
* Replace the active snippet's draft spec with new text and reload the editor
|
||||
* on the draft view (bumps `bufferEpoch`). Used by programmatic rewrites such
|
||||
* as Extract-to-Dataset (spec §03F), which substitutes inline data for a
|
||||
* by-name reference. No-op when no snippet is active. `now` injectable.
|
||||
*/
|
||||
replaceActiveDraft: (text: string, now?: Date) => void;
|
||||
/**
|
||||
* Persist the editor buffer into the active snippet's **draft**, if it parses
|
||||
* as JSON. Returns whether it committed (a half-typed, unparseable buffer is
|
||||
@@ -57,6 +65,14 @@ export interface SnippetState {
|
||||
commitDraft: (now?: Date) => boolean;
|
||||
/** Switch the editor between the draft and published views (spec §03D). */
|
||||
setEditorView: (view: EditorView) => void;
|
||||
/**
|
||||
* Propagate a dataset rename across every referencing snippet: rewrite the
|
||||
* named-data references in both `spec` and `draftSpec`, recompute `datasetRefs`,
|
||||
* and refresh the live editor buffer if the active snippet was rewritten — so a
|
||||
* user mid-edit doesn't see their draft silently break (docs/architecture/07
|
||||
* §6). Returns the number of snippets changed. `now` injectable.
|
||||
*/
|
||||
renameDatasetRefs: (oldName: string, newName: string, now?: Date) => number;
|
||||
/**
|
||||
* Promote the active snippet's current draft to its published version (spec
|
||||
* §03D → Publish). Flushes the live buffer first, then makes `spec` identical
|
||||
@@ -151,6 +167,20 @@ export const useSnippetStore = create<SnippetState>((set, get) => ({
|
||||
|
||||
updateDraft: (draftText) => set({ draftText }),
|
||||
|
||||
replaceActiveDraft: (text, now) => {
|
||||
const { activeSnippetId } = get();
|
||||
if (!activeSnippetId) return;
|
||||
const modified = (now ?? new Date()).toISOString();
|
||||
set((s) => ({
|
||||
snippets: s.snippets.map((x) =>
|
||||
x.id === activeSnippetId ? { ...x, draftSpec: text, modified } : x,
|
||||
),
|
||||
draftText: text,
|
||||
editorView: 'draft',
|
||||
bufferEpoch: s.bufferEpoch + 1,
|
||||
}));
|
||||
},
|
||||
|
||||
commitDraft: (now) => {
|
||||
const { activeSnippetId, draftText, snippets } = get();
|
||||
if (!activeSnippetId) return false;
|
||||
@@ -178,6 +208,43 @@ export const useSnippetStore = create<SnippetState>((set, get) => ({
|
||||
|
||||
setEditorView: (editorView) => set({ editorView }),
|
||||
|
||||
renameDatasetRefs: (oldName, newName, now) => {
|
||||
if (oldName === newName) return 0;
|
||||
const lower = oldName.toLowerCase();
|
||||
const { snippets, activeSnippetId, editorView } = get();
|
||||
let updated = 0;
|
||||
let activeDraftAfter: string | null = null;
|
||||
|
||||
const next = snippets.map((s) => {
|
||||
if (!s.datasetRefs.some((r) => r.toLowerCase() === lower)) return s;
|
||||
updated++;
|
||||
const spec = renameDatasetInSpec(s.spec, oldName, newName);
|
||||
const draftSpec = renameDatasetInSpec(s.draftSpec, oldName, newName);
|
||||
if (s.id === activeSnippetId) activeDraftAfter = draftSpec;
|
||||
return {
|
||||
...s,
|
||||
spec,
|
||||
draftSpec,
|
||||
datasetRefs: recomputeDatasetRefs(spec),
|
||||
modified: (now ?? new Date()).toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
if (updated === 0) return 0;
|
||||
set((st) => ({
|
||||
snippets: next,
|
||||
// If the active snippet's draft was rewritten, reload the editor buffer so
|
||||
// the live (draft-view) buffer reflects the new name and the next auto-save
|
||||
// doesn't clobber the rewrite with the stale old name.
|
||||
...(activeDraftAfter !== null && editorView === 'draft'
|
||||
? { draftText: activeDraftAfter, bufferEpoch: st.bufferEpoch + 1 }
|
||||
: activeDraftAfter !== null
|
||||
? { bufferEpoch: st.bufferEpoch + 1 }
|
||||
: {}),
|
||||
}));
|
||||
return updated;
|
||||
},
|
||||
|
||||
publish: (now) => {
|
||||
// Flush the live buffer into the draft first, so Publish promotes exactly
|
||||
// what the user sees (an invalid buffer leaves the last valid draft in place).
|
||||
@@ -189,8 +256,15 @@ export const useSnippetStore = create<SnippetState>((set, get) => ({
|
||||
set({
|
||||
snippets: snippets.map((s) =>
|
||||
s.id === activeSnippetId
|
||||
? // M3: recompute datasetRefs from the now-published spec (spec §03D).
|
||||
{ ...s, spec: s.draftSpec, modified }
|
||||
? // Promote the draft and recompute datasetRefs from the now-published
|
||||
// spec, so the bidirectional snippet↔dataset link mirrors reality
|
||||
// (spec §03D, docs/architecture/07 §3).
|
||||
{
|
||||
...s,
|
||||
spec: s.draftSpec,
|
||||
datasetRefs: recomputeDatasetRefs(s.draftSpec),
|
||||
modified,
|
||||
}
|
||||
: s,
|
||||
),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user