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.
This commit is contained in:
hypercross 2026-07-13 01:07:13 +08:00
parent c9c977bee1
commit a2d9605f4b
5 changed files with 67 additions and 6 deletions

View File

@ -25,7 +25,8 @@ import {
} from "../../cli/completions/directive-scanner"; } from "../../cli/completions/directive-scanner";
import { parseDeclareCsv } from "./declare-parser"; import { parseDeclareCsv } from "./declare-parser";
import type { VarDeclaration, TagModifier } 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 }; export type { VarDeclaration, TagModifier };
@ -171,7 +172,10 @@ const _initPromise: Promise<void> = (async () => {
const serverData = await tryServer(); const serverData = await tryServer();
if (serverData) { if (serverData) {
setCompletionsState({ status: "loaded", data: 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; return;
} }
@ -180,7 +184,10 @@ const _initPromise: Promise<void> = (async () => {
const data = await scanClientSide(); const data = await scanClientSide();
if (data.dice.length > 0 || data.links.length > 0) { if (data.dice.length > 0 || data.links.length > 0) {
setCompletionsState({ status: "loaded", data }); 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 { } else {
setCompletionsState({ status: "empty" }); setCompletionsState({ status: "empty" });
} }
@ -231,3 +238,14 @@ export function useJournalCompletions(): {
}, },
}; };
} }
// ---------------------------------------------------------------------------
// 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 });
}
}

View File

@ -60,7 +60,7 @@ export { parseDeclareCsv } from "./declare-parser";
export type { VarDeclaration, TagModifier } from "./declare-parser"; export type { VarDeclaration, TagModifier } from "./declare-parser";
export { evaluateExpression, expressionIsTag } from "./variable-expression"; export { evaluateExpression, expressionIsTag } from "./variable-expression";
export type { EvalContext, EvalResult } 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 type { VarReactivityState, VariableStore } from "./var-reactivity";
export { JournalContext, useJournalContext } from "./JournalContext"; export { JournalContext, useJournalContext } from "./JournalContext";
export type { JournalContextValue } from "./JournalContext"; export type { JournalContextValue } from "./JournalContext";

View File

@ -174,6 +174,49 @@ export function computeCascade(
return results; 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. * Compute which tags are active given the current variable store.
* A tag is active if any variable has that tag as its value. * A tag is active if any variable has that tag as its value.

View File

@ -273,7 +273,7 @@ function parseFactor(
const varName = tok.value; // includes $ prefix const varName = tok.value; // includes $ prefix
const resolved = ctx.lookup(varName.slice(1)); // strip $ for lookup const resolved = ctx.lookup(varName.slice(1)); // strip $ for lookup
if (resolved === undefined) { if (resolved === undefined) {
throw new Error(`Unknown variable: ${varName}`); return { value: 0, next: pos + 1 };
} }
// Tag values cannot be used in arithmetic // Tag values cannot be used in arithmetic
if (resolved.startsWith("#")) { if (resolved.startsWith("#")) {