From a2d9605f4b453d38fd7b9b60b3421b2c08a013f9 Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 13 Jul 2026 01:07:13 +0800 Subject: [PATCH] feat(journal): seed declared variables on initialization Implement `computeInitialValues` to calculate the starting state of declared variables and seed them into the journal stream store during the completions initialization process. --- .../journal/ReactiveVariableManager.tsx | 2 +- src/components/journal/completions.ts | 24 +++++++++-- src/components/journal/index.ts | 2 +- src/components/journal/var-reactivity.ts | 43 +++++++++++++++++++ src/components/journal/variable-expression.ts | 2 +- 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/components/journal/ReactiveVariableManager.tsx b/src/components/journal/ReactiveVariableManager.tsx index 5f61e7a..833c736 100644 --- a/src/components/journal/ReactiveVariableManager.tsx +++ b/src/components/journal/ReactiveVariableManager.tsx @@ -151,4 +151,4 @@ function escapeAttr(s: string): string { .replace(/>/g, ">"); } -export default ReactiveVariableManager; \ No newline at end of file +export default ReactiveVariableManager; diff --git a/src/components/journal/completions.ts b/src/components/journal/completions.ts index db7c28a..781457f 100644 --- a/src/components/journal/completions.ts +++ b/src/components/journal/completions.ts @@ -25,7 +25,8 @@ import { } from "../../cli/completions/directive-scanner"; import { parseDeclareCsv } from "./declare-parser"; import type { VarDeclaration, TagModifier } from "./declare-parser"; -import { initReactivity } from "./var-reactivity"; +import { initReactivity, computeInitialValues } from "./var-reactivity"; +import { sendMessage, journalStreamState } from "../stores/journalStream"; export type { VarDeclaration, TagModifier }; @@ -171,7 +172,10 @@ const _initPromise: Promise = (async () => { const serverData = await tryServer(); if (serverData) { setCompletionsState({ status: "loaded", data: serverData }); - try { initReactivity({ declarations: serverData.declarations, tagModifiers: serverData.tagModifiers }); } catch (e) { console.warn("[completions] reactivity init error:", e); } + try { + initReactivity({ declarations: serverData.declarations, tagModifiers: serverData.tagModifiers }); + seedDeclaredVariables(); + } catch (e) { console.warn("[completions] reactivity init error:", e); } return; } @@ -180,7 +184,10 @@ const _initPromise: Promise = (async () => { const data = await scanClientSide(); if (data.dice.length > 0 || data.links.length > 0) { setCompletionsState({ status: "loaded", data }); - try { initReactivity({ declarations: data.declarations, tagModifiers: data.tagModifiers }); } catch (e) { console.warn("[completions] reactivity init error:", e); } + try { + initReactivity({ declarations: data.declarations, tagModifiers: data.tagModifiers }); + seedDeclaredVariables(); + } catch (e) { console.warn("[completions] reactivity init error:", e); } } else { setCompletionsState({ status: "empty" }); } @@ -230,4 +237,15 @@ export function useJournalCompletions(): { tagModifiers: [], }, }; +} + +// --------------------------------------------------------------------------- +// Seed declared variables into the store on load +// --------------------------------------------------------------------------- + +function seedDeclaredVariables(): void { + const initial = computeInitialValues(journalStreamState.variables); + for (const { key, value } of initial) { + sendMessage("var", { action: "set", key, value }); + } } \ No newline at end of file diff --git a/src/components/journal/index.ts b/src/components/journal/index.ts index a375eed..44c94af 100644 --- a/src/components/journal/index.ts +++ b/src/components/journal/index.ts @@ -60,7 +60,7 @@ export { parseDeclareCsv } from "./declare-parser"; export type { VarDeclaration, TagModifier } from "./declare-parser"; export { evaluateExpression, expressionIsTag } from "./variable-expression"; export type { EvalContext, EvalResult } from "./variable-expression"; -export { initReactivity, computeCascade, computeActiveTags, extractDependencies } from "./var-reactivity"; +export { initReactivity, computeCascade, computeActiveTags, extractDependencies, computeInitialValues } from "./var-reactivity"; export type { VarReactivityState, VariableStore } from "./var-reactivity"; export { JournalContext, useJournalContext } from "./JournalContext"; export type { JournalContextValue } from "./JournalContext"; diff --git a/src/components/journal/var-reactivity.ts b/src/components/journal/var-reactivity.ts index 9d1f723..88013a7 100644 --- a/src/components/journal/var-reactivity.ts +++ b/src/components/journal/var-reactivity.ts @@ -174,6 +174,49 @@ export function computeCascade( return results; } +/** + * Compute initial values for all declared variables. + * Called once after initReactivity() to seed the store. + * Unset dependencies default to 0. + */ +export function computeInitialValues( + currentVars: VariableStore, +): Array<{ key: string; value: string }> { + if (!declExprs) return []; + + const allKeys = [...declExprs.keys()]; + const sorted = topoSortAffected(new Set(allKeys)); + + const results: Array<{ key: string; value: string }> = []; + const localVars = { ...currentVars }; + + for (const key of sorted) { + const expr = declExprs.get(key); + if (!expr) continue; + + try { + const result = evaluateExpression(expr, { + lookup: (name: string) => { + const k = "$" + name; + return localVars[k] ?? undefined; + }, + }); + + const newValue = String(result.value); + const finalValue = applyTagModifiersTo(key, newValue, localVars); + + if (finalValue !== (localVars[key] ?? "")) { + localVars[key] = finalValue; + results.push({ key, value: finalValue }); + } + } catch { + // skip failed evaluations at init time + } + } + + return results; +} + /** * Compute which tags are active given the current variable store. * A tag is active if any variable has that tag as its value. diff --git a/src/components/journal/variable-expression.ts b/src/components/journal/variable-expression.ts index ba19f8a..7713afd 100644 --- a/src/components/journal/variable-expression.ts +++ b/src/components/journal/variable-expression.ts @@ -273,7 +273,7 @@ function parseFactor( const varName = tok.value; // includes $ prefix const resolved = ctx.lookup(varName.slice(1)); // strip $ for lookup if (resolved === undefined) { - throw new Error(`Unknown variable: ${varName}`); + return { value: 0, next: pos + 1 }; } // Tag values cannot be used in arithmetic if (resolved.startsWith("#")) {