fix(journal): re-init reactivity and drop var del

Make the reactivity engine re-initialize when the loaded declarations
change instead of only at import. ensureReactivity() reads the live
completions signal, compares a signature, and rebuilds from the store
(with exact bases) only when needed; dispatchCommand calls it before each
dispatch. Remove the unused DispatchContext.declarations/tagModifiers
fields, which were mount-time snapshots and could not be trusted.

Build initReactivity's maps into locals and commit only after the
circular check passes, so a failed re-init leaves the previous engine
intact; clear inFlight on commit.

Drop the unreachable var "del" action from the schema, render, and
reducer.
This commit is contained in:
hyper
2026-09-12 22:29:25 +08:00
parent 9f372fa2b0
commit 02442a1100
9 changed files with 131 additions and 70 deletions
+31 -20
View File
@@ -216,44 +216,46 @@ export function onReactivityDiagnostics(
* Initialize (or re-initialize) the reactivity engine from declarations
* and tag modifiers parsed from role=declare blocks.
*
* The new state is built into locals and only committed once validation
* passes, so a throw (e.g. a circular dependency) leaves the previously
* initialized engine intact rather than half-populated.
*
* Throws if a circular dependency is detected.
*/
export function initReactivity(state: VarReactivityState): void {
depGraph = new Map();
declExprs = new Map();
tagModMap = new Map();
baseValues.clear();
numericMods.clear();
tagMapMods.clear();
sourceActivations.clear();
clearReactivityDiagnostics();
const nextTagModMap = new Map<
string,
Array<{ target: string; expression: string; threshold: number }>
>();
const nextDeclExprs = new Map<string, string>();
const nextDepGraph = new Map<string, Set<string>>();
// Index tag modifiers
for (const tm of state.tagModifiers) {
let list = tagModMap.get(tm.tag);
let list = nextTagModMap.get(tm.tag);
if (!list) {
list = [];
tagModMap.set(tm.tag, list);
nextTagModMap.set(tm.tag, list);
}
list.push({ target: tm.target, expression: tm.expression, threshold: tm.threshold });
}
// Index declarations and build dependency graph
for (const decl of state.declarations) {
declExprs.set(decl.key, decl.expression);
nextDeclExprs.set(decl.key, decl.expression);
const deps = extractDependencies(decl.expression);
for (const dep of deps) {
let dependents = depGraph.get(dep);
let dependents = nextDepGraph.get(dep);
if (!dependents) {
dependents = new Set();
depGraph.set(dep, dependents);
nextDepGraph.set(dep, dependents);
}
dependents.add(decl.key);
}
}
try {
checkCircular();
checkCircular(nextDeclExprs);
} catch (e) {
reportDiagnostic({
kind: "init",
@@ -261,6 +263,17 @@ export function initReactivity(state: VarReactivityState): void {
});
throw e;
}
// Validation passed — commit the new configuration and reset runtime state.
depGraph = nextDepGraph;
declExprs = nextDeclExprs;
tagModMap = nextTagModMap;
baseValues.clear();
numericMods.clear();
tagMapMods.clear();
sourceActivations.clear();
inFlight.clear();
clearReactivityDiagnostics();
}
/** Extract $var names from an expression string. */
@@ -789,10 +802,8 @@ function topoSortAffected(affected: Set<string>): string[] {
return result;
}
function checkCircular(): void {
if (!declExprs) return;
const allKeys = [...declExprs.keys()];
function checkCircular(exprs: Map<string, string>): void {
const allKeys = [...exprs.keys()];
const state = new Map<string, "unvisited" | "visiting" | "visited">();
for (const k of allKeys) state.set(k, "unvisited");
@@ -812,11 +823,11 @@ function checkCircular(): void {
state.set(key, "visiting");
path.push(key);
const expr = declExprs!.get(key);
const expr = exprs.get(key);
if (expr) {
const deps = extractDependencies(expr);
for (const dep of deps) {
if (declExprs!.has(dep)) {
if (exprs.has(dep)) {
dfs(dep);
}
}