Collect and report failed declarations, failed tag modifier expressions, and circular dependencies instead of swallowing them. Diagnostics are deduped, logged with key/expression context, cleared on re-init, and exposed through useJournalCompletions and the VariableView warning banner. A bad declaration no longer aborts the rest of a cascade. Reject malformed numeric literals in the tokenizer so inputs like "1.2.3" throw instead of being truncated by parseFloat.
6.3 KiB
6.3 KiB
Variable store — outstanding fixes
Notes from an audit of the client-side variable / tag reactivity system:
src/components/journal/var-reactivity.ts— engine (dependency graph, base/mod separation, threshold-gated tag activation, cascade).src/components/journal/variable-expression.ts— expression evaluator.src/components/journal/command-dispatcher.ts—/set,/rolltag, cascade.src/components/journal/completions.ts— init + seed.src/components/stores/journalStream.ts— replay + hydration rebuild.src/components/journal/types/var.tsx—varmessage + reducer.
Already fixed (context)
Commit a26d53c (fix(journal): keep variable bases in the stream):
- Hydration double-counted active tag modifiers because the stream carried only
combined values.
varmessages now carrybasealongsidevalue, the store keepsvariableBases, andrebuildReactivityFromStorerestores exact bases. - Seeding ran at import against an empty store (clobbering persisted values and
appending duplicate
varmessages on every load). It now runs after hydration and only for keys absent from the store.
The items below are still open.
Open issues
1. Modifier and declaration errors are swallowed — ✅ fixed
- Where:
computeInitialValues(catch {}, "skip failed evaluations at init time") andapplyTagMapActivations(catch {}, "skip failed modifier") invar-reactivity.ts;initReactivitythrows are reduced toconsole.warnincompletions.ts. - Symptom: a typo'd declaration or modifier expression silently yields a
wrong value (often
0) with no author-facing feedback. - Fixed: the engine now collects
ReactivityDiagnosticentries for failed declarations (computeInitialValues,reevaluateDependents), failed modifier expressions (applyTagMapActivations), and circular declarations (initReactivity). They are deduped, logged viaconsole.warnwith key/expression/tag context, cleared on eachinitReactivity, and bridged into a signal exposed asuseJournalCompletions().diagnostics().VariableViewrenders them as a warning banner. A bad declaration no longer aborts the rest of a cascade.
2. var del does not reach the engine — medium
- Where: reducer in
types/var.tsxsupportsaction: "del"; the parser (command-parser.ts) has no/del;var-reactivity.tsnever clearsbaseValuesandrebuildReactivityFromStorenever removes stale keys. - Symptom: a deleted variable still resolves locally via
getCombined, because it readsbaseValuesfirst. - Fix: either wire a
/delcommand and clear engine state (base + mods) on delete, or dropdelfrom the schema/reducer if it is intentionally unused.
3. Reactivity is initialized once, at import — medium
- Where:
bootstrapReactivityincompletions.tsrunsinitReactivityonce; nothing re-runs it when declare blocks change or a session/document is switched. - Also:
DispatchContext.declarations/tagModifiers(command-dispatcher.ts) are declared but never read — a vestige of a per-dispatch init that was never wired up. - Symptom: editing
role=declarecontent requires a full reload; stale graph/expressions persist. - Fix: expose a re-init entry point keyed to content/session changes, and either use or remove the unused context fields.
4. lookup contract disagrees with its docs — low
- Where:
EvalContext.lookupdocuments "Resolve$var", butparseFactorcallsctx.lookup(varName.slice(1))(bare name), and every caller re-adds$. - Symptom: a caller following the doc would key variables with
$and silently get0s. - Fix: update the doc comment to state the argument is the bare name (no
$), or stop stripping inparseFactorand update callers.
5. Undefined variables evaluate to 0 — low
- Where:
parseFactor(variable-expression.ts) returns a numeric0for an unresolved$var. - Symptom:
/set $hp $cn*5(typo) silently sets0instead of failing. - Fix: consider erroring on unknown variables in
/setcontext while keeping lenient behavior for display/roll contexts, or warn.
6. Tokenizer accepts malformed numbers — ✅ fixed
- Where: the number branch of
tokenizeconsumes[0-9.]greedily, so1.2.3becomes one "number" andparseFloattruncates it. - Fixed: the tokenizer now validates the scanned chunk against
^\d+(\.\d+)?$and throwsInvalid number literal: "…"instead of pushing a silently-truncated value. The error propagates through the diagnostics channel (#1) for declarations/modifiers and throughdispatchErrorfor/setand/roll.
7. topoSortAffected re-evaluates untouched declarations — low
- Where: in
var-reactivity.ts, the visit conditionaffected.has(dep) || declExprs.has(dep)pulls declared dependencies that are not inaffectedinto the result. - Symptom:
reevaluateDependentsre-evaluates and re-publishes variables that did not change. Mostly harmless, but noisy. - Fix: only visit/topologically order keys in
affected.
8. Failed initReactivity leaves partial state — low
- Where:
initReactivitypopulatesdepGraph/declExprs/tagModMapbeforecheckCircular()throws;inFlightis never cleared on re-init. - Fix: build into locals and commit on success, or roll back on failure.
Clear
inFlightininitReactivity.
9. Residual base / tagmap-target edges — low
- Base fallback:
rebuildReactivityFromStorefalls back to the combinedvaluewhen a message has nobase, which double-counts mods. In-repo emitters always sendbase; only hand-crafted/externalvarmessages hit this. - Tagmap targets: rebuild derives activations from each source's base tagmap (this fixed a feedback loop where a tagmap-valued target re-triggered its own modifier on every rebuild). If a variable's effective count crosses a threshold purely via mods, that activation is not reconstructed on hydration. Revisit if tagmap-valued modifier targets come into use.
Suggested order
- #3 (re-init) and #2 (
del) — correctness of engine lifecycle. - #1 (diagnostics) — authoring feedback.
- #4–#8 — cleanup and hardening.
- #9 — only if the tagmap-target pattern is adopted.