@llui/dom 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +5 -0
- package/dist/binding.js.map +1 -1
- package/dist/devtools.d.ts +109 -0
- package/dist/devtools.d.ts.map +1 -1
- package/dist/devtools.js +401 -1
- package/dist/devtools.js.map +1 -1
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +2 -0
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +18 -2
- package/dist/mount.js.map +1 -1
- package/dist/primitives/branch.d.ts.map +1 -1
- package/dist/primitives/branch.js +10 -1
- package/dist/primitives/branch.js.map +1 -1
- package/dist/primitives/child.d.ts.map +1 -1
- package/dist/primitives/child.js +25 -5
- package/dist/primitives/child.js.map +1 -1
- package/dist/primitives/context.d.ts +30 -6
- package/dist/primitives/context.d.ts.map +1 -1
- package/dist/primitives/context.js +30 -6
- package/dist/primitives/context.js.map +1 -1
- package/dist/primitives/each.d.ts.map +1 -1
- package/dist/primitives/each.js +79 -6
- package/dist/primitives/each.js.map +1 -1
- package/dist/primitives/foreign.d.ts.map +1 -1
- package/dist/primitives/foreign.js +1 -0
- package/dist/primitives/foreign.js.map +1 -1
- package/dist/primitives/lazy.d.ts.map +1 -1
- package/dist/primitives/lazy.js +1 -0
- package/dist/primitives/lazy.js.map +1 -1
- package/dist/primitives/portal.d.ts.map +1 -1
- package/dist/primitives/portal.js +1 -0
- package/dist/primitives/portal.js.map +1 -1
- package/dist/primitives/show.d.ts.map +1 -1
- package/dist/primitives/show.js +4 -0
- package/dist/primitives/show.js.map +1 -1
- package/dist/primitives/virtual-each.d.ts.map +1 -1
- package/dist/primitives/virtual-each.js +3 -0
- package/dist/primitives/virtual-each.js.map +1 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js.map +1 -1
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +68 -1
- package/dist/scope.js.map +1 -1
- package/dist/ssr.d.ts.map +1 -1
- package/dist/ssr.js +5 -1
- package/dist/ssr.js.map +1 -1
- package/dist/tracking/coverage.d.ts +27 -0
- package/dist/tracking/coverage.d.ts.map +1 -0
- package/dist/tracking/coverage.js +39 -0
- package/dist/tracking/coverage.js.map +1 -0
- package/dist/tracking/disposer-log.d.ts +22 -0
- package/dist/tracking/disposer-log.d.ts.map +1 -0
- package/dist/tracking/disposer-log.js +3 -0
- package/dist/tracking/disposer-log.js.map +1 -0
- package/dist/tracking/each-diff.d.ts +62 -0
- package/dist/tracking/each-diff.d.ts.map +1 -0
- package/dist/tracking/each-diff.js +26 -0
- package/dist/tracking/each-diff.js.map +1 -0
- package/dist/tracking/effect-timeline.d.ts +73 -0
- package/dist/tracking/effect-timeline.d.ts.map +1 -0
- package/dist/tracking/effect-timeline.js +89 -0
- package/dist/tracking/effect-timeline.js.map +1 -0
- package/dist/types.d.ts +18 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-loop.d.ts.map +1 -1
- package/dist/update-loop.js +78 -0
- package/dist/update-loop.js.map +1 -1
- package/package.json +1 -1
package/dist/binding.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;IACnC,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,IAAI,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;CACjB;AAID,wBAAgB,eAAe,IAAI,OAAO,EAAE,GAAG,IAAI,CAElD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAiB5E;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACvD,KAAK,EAAE,OAAO,GACb,IAAI,
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;IACnC,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,IAAI,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;CACjB;AAID,wBAAgB,eAAe,IAAI,OAAO,EAAE,GAAG,IAAI,CAElD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAiB5E;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACvD,KAAK,EAAE,OAAO,GACb,IAAI,CA+DN"}
|
package/dist/binding.js
CHANGED
|
@@ -39,6 +39,11 @@ export function applyBinding(target, value) {
|
|
|
39
39
|
`would have been serialized into the DOM otherwise. Offender: ${value.toString().slice(0, 120)}`);
|
|
40
40
|
}
|
|
41
41
|
switch (target.kind) {
|
|
42
|
+
case 'effect':
|
|
43
|
+
// Side-effect-only binding — the accessor already ran for its
|
|
44
|
+
// side effects in Phase 2. We keep `applyBinding` callable for
|
|
45
|
+
// type-uniform call sites, but there is no DOM write to perform.
|
|
46
|
+
break;
|
|
42
47
|
case 'text':
|
|
43
48
|
target.node.nodeValue = String(value);
|
|
44
49
|
break;
|
package/dist/binding.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAWvC,IAAI,YAAY,GAAqB,IAAI,CAAA;AAEzC,MAAM,UAAU,eAAe;IAC7B,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAqB;IACnD,YAAY,GAAG,GAAG,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAY,EAAE,IAAuB;IACjE,MAAM,OAAO,GAAY;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAuC;QACtD,SAAS,EAAE,SAAS;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,UAAU,EAAE,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,KAAK;KACZ,CAAA;IAED,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC1B,IAAI,YAAY;QAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAE5C,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAuD,EACvD,KAAc;IAEd,iEAAiE;IACjE,mEAAmE;IACnE,qEAAqE;IACrE,qEAAqE;IACrE,oEAAoE;IACpE,kEAAkE;IAClE,oBAAoB;IACpB,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,uBAAuB,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,aAAa;YACrF,wEAAwE;YACxE,4EAA4E;YAC5E,2EAA2E;YAC3E,gEAAgE,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CACnG,CAAA;IACH,CAAC;IAED,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,MAAM;YACT,MAAM,CAAC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;YACrC,MAAK;QAEP,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,GAAG,MAAM,CAAC,IAAmB,CAEpC;YAAC,EAAU,CAAC,MAAM,CAAC,GAAI,CAAC,GAAG,KAAK,CAAA;YACjC,MAAK;QACP,CAAC;QAED,KAAK,MAAM;YACT,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,CAAC;gBAAC,MAAM,CAAC,IAAgB,CAAC,eAAe,CAAC,MAAM,CAAC,GAAI,CAAC,CAAA;YACxD,CAAC;iBAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;gBAC3B,4DAA4D;gBAC5D,IAAI,MAAM,CAAC,GAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpC,CAAC;oBAAC,MAAM,CAAC,IAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,GAAI,EAAE,OAAO,CAAC,CAAA;gBAC9D,CAAC;qBAAM,CAAC;oBACN,CAAC;oBAAC,MAAM,CAAC,IAAgB,CAAC,eAAe,CAAC,MAAM,CAAC,GAAI,CAAC,CAAA;gBACxD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,CAAC;gBAAC,MAAM,CAAC,IAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,GAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YACpE,CAAC;YACD,MAAK;QAEP,KAAK,OAAO;YACV,CAAC;YAAC,MAAM,CAAC,IAAgB,CAAC,YAAY,CAAC,OAAO,EAAE,KAAe,CAAC,CAAA;YAChE,MAAK;QAEP,KAAK,OAAO;YACV,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,CAAC;gBAAC,MAAM,CAAC,IAAoB,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GAAI,CAAC,CAAA;YACjE,CAAC;iBAAM,CAAC;gBACN,CAAC;gBAAC,MAAM,CAAC,IAAoB,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,GAAI,EAAE,KAAe,CAAC,CAAA;YAC/E,CAAC;YACD,MAAK;IACT,CAAC;AACH,CAAC","sourcesContent":["import type { Scope, Binding, BindingKind } from './types.js'\nimport { addBinding } from './scope.js'\n\nexport interface CreateBindingOpts {\n mask: number\n accessor: (state: never) => unknown\n kind: BindingKind\n node: Node\n key?: string\n perItem: boolean\n}\n\nlet flatBindings: Binding[] | null = null\n\nexport function getFlatBindings(): Binding[] | null {\n return flatBindings\n}\n\nexport function setFlatBindings(arr: Binding[] | null): void {\n flatBindings = arr\n}\n\nexport function createBinding(scope: Scope, opts: CreateBindingOpts): Binding {\n const binding: Binding = {\n mask: opts.mask,\n accessor: opts.accessor as (state: unknown) => unknown,\n lastValue: undefined,\n kind: opts.kind,\n node: opts.node,\n key: opts.key,\n ownerScope: scope,\n perItem: opts.perItem,\n dead: false,\n }\n\n addBinding(scope, binding)\n if (flatBindings) flatBindings.push(binding)\n\n return binding\n}\n\nexport function applyBinding(\n target: { kind: BindingKind; node: Node; key?: string },\n value: unknown,\n): void {\n // Defensive guard: if a reactive accessor leaks through as a raw\n // function value, emitting its `.toString()` into the DOM (e.g. as\n // an attribute) would be a silent correctness bug that only surfaces\n // on server-rendered pages. Throw loudly so the callsite is obvious.\n // Event handlers (onXxx → 'prop' kind) are NOT handled here; events\n // are registered via addEventListener in the element helpers, not\n // via applyBinding.\n if (typeof value === 'function') {\n throw new TypeError(\n `[LLui] applyBinding(${target.kind}${target.key ? `, '${target.key}'` : ''}) received ` +\n `a function as its value. This means an accessor wasn't invoked before ` +\n `reaching the binding layer — usually a bug in a compiled binding tuple or ` +\n `in a helper that forwards props without calling them. The arrow's source ` +\n `would have been serialized into the DOM otherwise. Offender: ${value.toString().slice(0, 120)}`,\n )\n }\n\n switch (target.kind) {\n case 'text':\n target.node.nodeValue = String(value)\n break\n\n case 'prop': {\n const el = target.node as HTMLElement\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(el as any)[target.key!] = value\n break\n }\n\n case 'attr':\n if (value == null) {\n ;(target.node as Element).removeAttribute(target.key!)\n } else if (value === false) {\n // ARIA attributes need explicit \"false\"; others are removed\n if (target.key!.startsWith('aria-')) {\n ;(target.node as Element).setAttribute(target.key!, 'false')\n } else {\n ;(target.node as Element).removeAttribute(target.key!)\n }\n } else {\n ;(target.node as Element).setAttribute(target.key!, String(value))\n }\n break\n\n case 'class':\n ;(target.node as Element).setAttribute('class', value as string)\n break\n\n case 'style':\n if (value == null) {\n ;(target.node as HTMLElement).style.removeProperty(target.key!)\n } else {\n ;(target.node as HTMLElement).style.setProperty(target.key!, value as string)\n }\n break\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Scope, Binding, BindingKind } from './types.js'\nimport { addBinding } from './scope.js'\n\nexport interface CreateBindingOpts {\n mask: number\n accessor: (state: never) => unknown\n kind: BindingKind\n node: Node\n key?: string\n perItem: boolean\n}\n\nlet flatBindings: Binding[] | null = null\n\nexport function getFlatBindings(): Binding[] | null {\n return flatBindings\n}\n\nexport function setFlatBindings(arr: Binding[] | null): void {\n flatBindings = arr\n}\n\nexport function createBinding(scope: Scope, opts: CreateBindingOpts): Binding {\n const binding: Binding = {\n mask: opts.mask,\n accessor: opts.accessor as (state: unknown) => unknown,\n lastValue: undefined,\n kind: opts.kind,\n node: opts.node,\n key: opts.key,\n ownerScope: scope,\n perItem: opts.perItem,\n dead: false,\n }\n\n addBinding(scope, binding)\n if (flatBindings) flatBindings.push(binding)\n\n return binding\n}\n\nexport function applyBinding(\n target: { kind: BindingKind; node: Node; key?: string },\n value: unknown,\n): void {\n // Defensive guard: if a reactive accessor leaks through as a raw\n // function value, emitting its `.toString()` into the DOM (e.g. as\n // an attribute) would be a silent correctness bug that only surfaces\n // on server-rendered pages. Throw loudly so the callsite is obvious.\n // Event handlers (onXxx → 'prop' kind) are NOT handled here; events\n // are registered via addEventListener in the element helpers, not\n // via applyBinding.\n if (typeof value === 'function') {\n throw new TypeError(\n `[LLui] applyBinding(${target.kind}${target.key ? `, '${target.key}'` : ''}) received ` +\n `a function as its value. This means an accessor wasn't invoked before ` +\n `reaching the binding layer — usually a bug in a compiled binding tuple or ` +\n `in a helper that forwards props without calling them. The arrow's source ` +\n `would have been serialized into the DOM otherwise. Offender: ${value.toString().slice(0, 120)}`,\n )\n }\n\n switch (target.kind) {\n case 'effect':\n // Side-effect-only binding — the accessor already ran for its\n // side effects in Phase 2. We keep `applyBinding` callable for\n // type-uniform call sites, but there is no DOM write to perform.\n break\n\n case 'text':\n target.node.nodeValue = String(value)\n break\n\n case 'prop': {\n const el = target.node as HTMLElement\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(el as any)[target.key!] = value\n break\n }\n\n case 'attr':\n if (value == null) {\n ;(target.node as Element).removeAttribute(target.key!)\n } else if (value === false) {\n // ARIA attributes need explicit \"false\"; others are removed\n if (target.key!.startsWith('aria-')) {\n ;(target.node as Element).setAttribute(target.key!, 'false')\n } else {\n ;(target.node as Element).removeAttribute(target.key!)\n }\n } else {\n ;(target.node as Element).setAttribute(target.key!, String(value))\n }\n break\n\n case 'class':\n ;(target.node as Element).setAttribute('class', value as string)\n break\n\n case 'style':\n if (value == null) {\n ;(target.node as HTMLElement).style.removeProperty(target.key!)\n } else {\n ;(target.node as HTMLElement).style.setProperty(target.key!, value as string)\n }\n break\n }\n}\n"]}
|
package/dist/devtools.d.ts
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import type { ScopeNode } from './types.js';
|
|
2
|
+
import { type EachDiff } from './tracking/each-diff.js';
|
|
3
|
+
import { type DisposerEvent } from './tracking/disposer-log.js';
|
|
4
|
+
import { type CoverageSnapshot } from './tracking/coverage.js';
|
|
5
|
+
import { type PendingEffect, type EffectTimelineEntry, type EffectMatch } from './tracking/effect-timeline.js';
|
|
6
|
+
export interface StateDiff {
|
|
7
|
+
added: Record<string, unknown>;
|
|
8
|
+
removed: Record<string, unknown>;
|
|
9
|
+
changed: Record<string, {
|
|
10
|
+
from: unknown;
|
|
11
|
+
to: unknown;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
1
14
|
/**
|
|
2
15
|
* Enable devtools auto-installation for every mountApp call. Called by
|
|
3
16
|
* compiler-generated dev code — never imported in production builds.
|
|
@@ -106,6 +119,102 @@ export interface LluiDebugAPI {
|
|
|
106
119
|
restoreState(snap: unknown): void;
|
|
107
120
|
/** Find all bindings whose target node matches or is a child of the selector. */
|
|
108
121
|
getBindingsFor(selector: string): BindingLocation[];
|
|
122
|
+
/** Return a rich structural + style + binding report for the first element matching selector. Returns null if no element matches or document is unavailable. */
|
|
123
|
+
inspectElement(selector: string): ElementReport | null;
|
|
124
|
+
/** Get the outerHTML of the mounted component or a specific element. Pass a selector for a specific node (defaults to the mount root). Pass maxLength to truncate output. */
|
|
125
|
+
getRenderedHtml(selector?: string, maxLength?: number): string;
|
|
126
|
+
/** Synthesize and dispatch a browser event at a DOM element matched by selector. Returns dispatched status, the history indices of any Msgs the handler produced, and the resulting state. */
|
|
127
|
+
dispatchDomEvent(selector: string, type: string, init?: EventInit): {
|
|
128
|
+
dispatched: boolean;
|
|
129
|
+
messagesProducedIndices: number[];
|
|
130
|
+
resultingState: unknown | null;
|
|
131
|
+
};
|
|
132
|
+
/** Return info about the currently focused element: { selector (if it has an id), tagName, selectionStart, selectionEnd }. Useful for catching "focus lost on re-render" bugs. */
|
|
133
|
+
getFocus(): {
|
|
134
|
+
selector: string | null;
|
|
135
|
+
tagName: string | null;
|
|
136
|
+
selectionStart: number | null;
|
|
137
|
+
selectionEnd: number | null;
|
|
138
|
+
};
|
|
139
|
+
/** Re-evaluate every binding's accessor against the current state, apply values that changed to the DOM, and return indices of bindings that changed. */
|
|
140
|
+
forceRerender(): {
|
|
141
|
+
changedBindings: number[];
|
|
142
|
+
};
|
|
143
|
+
/** Per-each-site reconciliation diffs (added/removed/moved/reused keys) from the dev-time diff log. Pass sinceIndex to filter to entries after a specific message history index. */
|
|
144
|
+
getEachDiff(sinceIndex?: number): EachDiff[];
|
|
145
|
+
/** Walk the component's scope tree and return a nested ScopeNode with kind classification. Pass depth to limit traversal depth, scopeId to start from a specific scope. */
|
|
146
|
+
getScopeTree(opts?: {
|
|
147
|
+
depth?: number;
|
|
148
|
+
scopeId?: string;
|
|
149
|
+
}): ScopeNode;
|
|
150
|
+
/** Recent onDispose firings with scope id and cause. Pass 'limit' to cap results to the N most recent entries. Catches 'leak on branch swap' class bugs. */
|
|
151
|
+
getDisposerLog(limit?: number): DisposerEvent[];
|
|
152
|
+
/** Edge list: state path → binding indices that depend on it. Inverts the compiler-emitted mask legend to show, for each top-level state field, which bindings will re-evaluate when it changes. */
|
|
153
|
+
getBindingGraph(): Array<{
|
|
154
|
+
statePath: string;
|
|
155
|
+
bindingIndices: number[];
|
|
156
|
+
}>;
|
|
157
|
+
/** Current queued and in-flight effects. Each entry has { id, type, dispatchedAt, status, payload }. Use 'id' with llui_resolve_effect to manually resolve one. */
|
|
158
|
+
getPendingEffects(): PendingEffect[];
|
|
159
|
+
/** Phased log of effect events: dispatched -> in-flight -> resolved/cancelled/resolved-mocked. Each entry has { effectId, type, phase, timestamp, durationMs? }. Pass 'limit' to cap the tail. */
|
|
160
|
+
getEffectTimeline(limit?: number): EffectTimelineEntry[];
|
|
161
|
+
/** Register a mock for an effect matching 'match'. The next matching effect resolves with 'response' instead of running. Mocks are one-shot by default; pass { persist: true } to keep across matches. Returns { mockId } for later reference. */
|
|
162
|
+
mockEffect(match: EffectMatch, response: unknown, opts?: {
|
|
163
|
+
persist?: boolean;
|
|
164
|
+
}): {
|
|
165
|
+
mockId: string;
|
|
166
|
+
};
|
|
167
|
+
/** Manually resolve a pending effect with a given response. The effect's onSuccess callback (if any) runs as if it had actually resolved. Pass effectId from llui_pending_effects. */
|
|
168
|
+
resolveEffect(effectId: string, response: unknown): {
|
|
169
|
+
resolved: boolean;
|
|
170
|
+
};
|
|
171
|
+
/** Rewind state by replaying from init() with the last N messages excluded. mode='pure' suppresses effects; mode='live' re-fires them. Returns the new state and rewind depth. */
|
|
172
|
+
stepBack(n: number, mode: 'pure' | 'live'): {
|
|
173
|
+
state: unknown;
|
|
174
|
+
rewindDepth: number;
|
|
175
|
+
};
|
|
176
|
+
/** Per-Msg-variant coverage for the current session. Shows which message types have run and which haven't. */
|
|
177
|
+
getCoverage(): CoverageSnapshot;
|
|
178
|
+
/** Run arbitrary JS in page context and return { result, sideEffects }. result is the expression's return value or { error }. sideEffects captures state diff, new history entries, new pending effects, and dirty binding indices. Phase 1 does not support async expressions. */
|
|
179
|
+
evalInPage(code: string): {
|
|
180
|
+
result: unknown | {
|
|
181
|
+
error: string;
|
|
182
|
+
};
|
|
183
|
+
sideEffects: {
|
|
184
|
+
stateChanged: StateDiff | null;
|
|
185
|
+
newHistoryEntries: number;
|
|
186
|
+
newPendingEffects: PendingEffect[];
|
|
187
|
+
dirtyBindingIndices: number[];
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export interface ElementReport {
|
|
192
|
+
selector: string;
|
|
193
|
+
tagName: string;
|
|
194
|
+
attributes: Record<string, string>;
|
|
195
|
+
classes: string[];
|
|
196
|
+
dataset: Record<string, string>;
|
|
197
|
+
text: string;
|
|
198
|
+
computed: {
|
|
199
|
+
display: string;
|
|
200
|
+
visibility: string;
|
|
201
|
+
position: string;
|
|
202
|
+
width: number;
|
|
203
|
+
height: number;
|
|
204
|
+
};
|
|
205
|
+
boundingBox: {
|
|
206
|
+
x: number;
|
|
207
|
+
y: number;
|
|
208
|
+
width: number;
|
|
209
|
+
height: number;
|
|
210
|
+
};
|
|
211
|
+
bindings: Array<{
|
|
212
|
+
bindingIndex: number;
|
|
213
|
+
kind: string;
|
|
214
|
+
mask: number;
|
|
215
|
+
lastValue: unknown;
|
|
216
|
+
relation: 'self' | 'text-child' | 'comment-child';
|
|
217
|
+
}>;
|
|
109
218
|
}
|
|
110
219
|
export interface BindingLocation {
|
|
111
220
|
bindingIndex: number;
|
package/dist/devtools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"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"}
|
package/dist/devtools.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { flushInstance, _forceState } from './update-loop.js';
|
|
2
2
|
import { _setDevToolsInstall } from './mount.js';
|
|
3
|
+
import { _markDisposerLogInstalled } from './scope.js';
|
|
4
|
+
import { applyBinding } from './binding.js';
|
|
5
|
+
import { createRingBuffer } from './tracking/each-diff.js';
|
|
6
|
+
import { createRingBuffer as createDisposerBuffer, } from './tracking/disposer-log.js';
|
|
7
|
+
import { createCoverageTracker } from './tracking/coverage.js';
|
|
8
|
+
import { createRingBuffer as createTimelineBuffer, createMockRegistry, createPendingEffectsList, } from './tracking/effect-timeline.js';
|
|
9
|
+
function diffStateInternal(a, b) {
|
|
10
|
+
const out = { added: {}, removed: {}, changed: {} };
|
|
11
|
+
if (a == null ||
|
|
12
|
+
b == null ||
|
|
13
|
+
typeof a !== 'object' ||
|
|
14
|
+
typeof b !== 'object' ||
|
|
15
|
+
Array.isArray(a) !== Array.isArray(b)) {
|
|
16
|
+
if (a !== b)
|
|
17
|
+
out.changed['<root>'] = { from: a, to: b };
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
const aObj = a;
|
|
21
|
+
const bObj = b;
|
|
22
|
+
const keys = new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
|
|
23
|
+
for (const k of keys) {
|
|
24
|
+
if (!(k in aObj))
|
|
25
|
+
out.added[k] = bObj[k];
|
|
26
|
+
else if (!(k in bObj))
|
|
27
|
+
out.removed[k] = aObj[k];
|
|
28
|
+
else if (!Object.is(aObj[k], bObj[k]))
|
|
29
|
+
out.changed[k] = { from: aObj[k], to: bObj[k] };
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
3
33
|
/**
|
|
4
34
|
* Enable devtools auto-installation for every mountApp call. Called by
|
|
5
35
|
* compiler-generated dev code — never imported in production builds.
|
|
@@ -151,11 +181,62 @@ export function startRelay(port = 5200) {
|
|
|
151
181
|
}
|
|
152
182
|
}
|
|
153
183
|
const MAX_HISTORY = 1000;
|
|
184
|
+
function findScopeById(root, id) {
|
|
185
|
+
const n = Number(id);
|
|
186
|
+
if (root.id === n)
|
|
187
|
+
return root;
|
|
188
|
+
for (const c of root.children) {
|
|
189
|
+
const found = findScopeById(c, id);
|
|
190
|
+
if (found)
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
function walkScope(s, depth, maxDepth) {
|
|
196
|
+
const node = {
|
|
197
|
+
scopeId: String(s.id),
|
|
198
|
+
kind: s._kind ?? 'root',
|
|
199
|
+
active: true,
|
|
200
|
+
children: [],
|
|
201
|
+
};
|
|
202
|
+
if (depth < maxDepth) {
|
|
203
|
+
for (const c of s.children)
|
|
204
|
+
node.children.push(walkScope(c, depth + 1, maxDepth));
|
|
205
|
+
}
|
|
206
|
+
return node;
|
|
207
|
+
}
|
|
154
208
|
export function installDevTools(inst) {
|
|
155
209
|
const ci = inst;
|
|
156
210
|
const history = [];
|
|
157
211
|
let idx = 0;
|
|
158
212
|
let lastDirtyMask = 0;
|
|
213
|
+
// Tracker storage — populated by primitives when they detect dev-mode
|
|
214
|
+
// is active (via `inst._eachDiffLog !== undefined` guard). Zero cost in
|
|
215
|
+
// production where installDevTools never runs.
|
|
216
|
+
ci._eachDiffLog = createRingBuffer(100);
|
|
217
|
+
ci._updateCounter = 0;
|
|
218
|
+
// Disposer log — consumed by `llui_disposer_log` MCP tool. Stamped by
|
|
219
|
+
// `disposeScope` via `findInstance(scope)` — which only works when the
|
|
220
|
+
// rootScope carries an `instance` back-reference.
|
|
221
|
+
ci._disposerLog = createDisposerBuffer(500);
|
|
222
|
+
// Coverage tracker — consumed by `llui_coverage` MCP tool. Records the
|
|
223
|
+
// discriminant of each dispatched message along with the message index
|
|
224
|
+
// it fired at, allowing the tool to surface Msg variants that never
|
|
225
|
+
// fired this session. Recorded inside the update interceptor below.
|
|
226
|
+
ci._coverage = createCoverageTracker();
|
|
227
|
+
// Effect timeline / pending / mocks — consumed by the
|
|
228
|
+
// `llui_effect_timeline`, `llui_pending_effects`, `llui_mock_effect`,
|
|
229
|
+
// and `llui_resolve_effect` MCP tools. Populated by the
|
|
230
|
+
// `dispatchEffectDev` wrapper in `update-loop.ts`; zero cost in
|
|
231
|
+
// production where `_effectTimeline` stays undefined.
|
|
232
|
+
ci._effectTimeline = createTimelineBuffer(500);
|
|
233
|
+
ci._effectMocks = createMockRegistry();
|
|
234
|
+
ci._pendingEffects = createPendingEffectsList();
|
|
235
|
+
ci.rootScope.instance = ci;
|
|
236
|
+
// Flip the scope-module flag so disposeScope starts walking the parent
|
|
237
|
+
// chain to emit disposer events. Before the first installDevTools call
|
|
238
|
+
// the flag stays false and disposeScope skips findInstance entirely.
|
|
239
|
+
_markDisposerLogInstalled();
|
|
159
240
|
const api = {
|
|
160
241
|
getState: () => ci.state,
|
|
161
242
|
send: (msg) => ci.send(msg),
|
|
@@ -191,6 +272,16 @@ export function installDevTools(inst) {
|
|
|
191
272
|
clearLog() {
|
|
192
273
|
history.length = 0;
|
|
193
274
|
idx = 0;
|
|
275
|
+
ci._updateCounter = 0;
|
|
276
|
+
ci._eachDiffLog?.clear();
|
|
277
|
+
ci._disposerLog?.clear();
|
|
278
|
+
ci._coverage?.clear();
|
|
279
|
+
ci._effectTimeline?.clear();
|
|
280
|
+
ci._effectMocks?.clear();
|
|
281
|
+
// NB: `_pendingEffects` is intentionally NOT cleared — pending
|
|
282
|
+
// entries represent in-flight effects that still have to land
|
|
283
|
+
// resolution/cancellation phases. Dropping them here would leak
|
|
284
|
+
// the ids that MCP tools hold onto for `llui_resolve_effect`.
|
|
194
285
|
},
|
|
195
286
|
validateMessage(msg) {
|
|
196
287
|
const schema = ci.def.__msgSchema;
|
|
@@ -398,6 +489,301 @@ export function installDevTools(inst) {
|
|
|
398
489
|
}
|
|
399
490
|
return results;
|
|
400
491
|
},
|
|
492
|
+
inspectElement(selector) {
|
|
493
|
+
if (typeof document === 'undefined')
|
|
494
|
+
return null;
|
|
495
|
+
const el = document.querySelector(selector);
|
|
496
|
+
if (!el)
|
|
497
|
+
return null;
|
|
498
|
+
const attributes = {};
|
|
499
|
+
for (const attr of Array.from(el.attributes)) {
|
|
500
|
+
attributes[attr.name] = attr.value;
|
|
501
|
+
}
|
|
502
|
+
const classes = Array.from(el.classList);
|
|
503
|
+
const dataset = {};
|
|
504
|
+
if (el instanceof HTMLElement) {
|
|
505
|
+
for (const [key, value] of Object.entries(el.dataset)) {
|
|
506
|
+
if (typeof value === 'string')
|
|
507
|
+
dataset[key] = value;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const rawText = el.textContent ?? '';
|
|
511
|
+
const text = rawText.length > 1000 ? rawText.slice(0, 1000) : rawText;
|
|
512
|
+
const rect = el.getBoundingClientRect();
|
|
513
|
+
const boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
514
|
+
let computed;
|
|
515
|
+
try {
|
|
516
|
+
const cs = window.getComputedStyle(el);
|
|
517
|
+
computed = {
|
|
518
|
+
display: cs.display,
|
|
519
|
+
visibility: cs.visibility,
|
|
520
|
+
position: cs.position,
|
|
521
|
+
width: rect.width,
|
|
522
|
+
height: rect.height,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
computed = {
|
|
527
|
+
display: 'unknown',
|
|
528
|
+
visibility: 'unknown',
|
|
529
|
+
position: 'unknown',
|
|
530
|
+
width: 0,
|
|
531
|
+
height: 0,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const rawBindings = api.getBindingsFor(selector);
|
|
535
|
+
const bindings = rawBindings.map((b) => ({
|
|
536
|
+
bindingIndex: b.bindingIndex,
|
|
537
|
+
kind: b.kind,
|
|
538
|
+
mask: b.mask,
|
|
539
|
+
lastValue: b.lastValue,
|
|
540
|
+
relation: b.relation,
|
|
541
|
+
}));
|
|
542
|
+
return {
|
|
543
|
+
selector,
|
|
544
|
+
tagName: el.tagName.toLowerCase(),
|
|
545
|
+
attributes,
|
|
546
|
+
classes,
|
|
547
|
+
dataset,
|
|
548
|
+
text,
|
|
549
|
+
computed,
|
|
550
|
+
boundingBox,
|
|
551
|
+
bindings,
|
|
552
|
+
};
|
|
553
|
+
},
|
|
554
|
+
getRenderedHtml(selector, maxLength) {
|
|
555
|
+
if (typeof document === 'undefined')
|
|
556
|
+
return '';
|
|
557
|
+
const el = selector ? document.querySelector(selector) : document.body;
|
|
558
|
+
if (!(el instanceof Element))
|
|
559
|
+
return '';
|
|
560
|
+
const html = el.outerHTML;
|
|
561
|
+
if (typeof maxLength === 'number' && html.length > maxLength) {
|
|
562
|
+
return html.slice(0, maxLength) + `<!-- truncated; total ${html.length} chars -->`;
|
|
563
|
+
}
|
|
564
|
+
return html;
|
|
565
|
+
},
|
|
566
|
+
dispatchDomEvent(selector, type, init) {
|
|
567
|
+
const noOp = { dispatched: false, messagesProducedIndices: [], resultingState: null };
|
|
568
|
+
if (typeof document === 'undefined')
|
|
569
|
+
return noOp;
|
|
570
|
+
const el = document.querySelector(selector);
|
|
571
|
+
if (!el)
|
|
572
|
+
return noOp;
|
|
573
|
+
const preIndex = history.length > 0 ? history[history.length - 1].index : -1;
|
|
574
|
+
let event;
|
|
575
|
+
if (type === 'click' || type === 'mousedown' || type === 'mouseup') {
|
|
576
|
+
event = new MouseEvent(type, {
|
|
577
|
+
bubbles: true,
|
|
578
|
+
cancelable: true,
|
|
579
|
+
...init,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
else if (type === 'keydown' || type === 'keyup' || type === 'keypress') {
|
|
583
|
+
event = new KeyboardEvent(type, {
|
|
584
|
+
bubbles: true,
|
|
585
|
+
cancelable: true,
|
|
586
|
+
...init,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
event = new Event(type, { bubbles: true, cancelable: true, ...init });
|
|
591
|
+
}
|
|
592
|
+
el.dispatchEvent(event);
|
|
593
|
+
flushInstance(ci);
|
|
594
|
+
const messagesProducedIndices = history.filter((r) => r.index > preIndex).map((r) => r.index);
|
|
595
|
+
return {
|
|
596
|
+
dispatched: true,
|
|
597
|
+
messagesProducedIndices,
|
|
598
|
+
resultingState: ci.state,
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
getFocus() {
|
|
602
|
+
if (typeof document === 'undefined') {
|
|
603
|
+
return { selector: null, tagName: null, selectionStart: null, selectionEnd: null };
|
|
604
|
+
}
|
|
605
|
+
const el = document.activeElement;
|
|
606
|
+
if (!el || el === document.body) {
|
|
607
|
+
return { selector: null, tagName: null, selectionStart: null, selectionEnd: null };
|
|
608
|
+
}
|
|
609
|
+
const id = el.id ? `#${el.id}` : null;
|
|
610
|
+
const tagName = el.tagName.toLowerCase();
|
|
611
|
+
let selectionStart = null;
|
|
612
|
+
let selectionEnd = null;
|
|
613
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
614
|
+
selectionStart = el.selectionStart ?? null;
|
|
615
|
+
selectionEnd = el.selectionEnd ?? null;
|
|
616
|
+
}
|
|
617
|
+
return { selector: id, tagName, selectionStart, selectionEnd };
|
|
618
|
+
},
|
|
619
|
+
forceRerender() {
|
|
620
|
+
const changed = [];
|
|
621
|
+
const allBindings = ci.allBindings;
|
|
622
|
+
for (let i = 0; i < allBindings.length; i++) {
|
|
623
|
+
const b = allBindings[i];
|
|
624
|
+
if (b.dead)
|
|
625
|
+
continue;
|
|
626
|
+
const next = b.accessor(ci.state);
|
|
627
|
+
if (!Object.is(next, b.lastValue)) {
|
|
628
|
+
changed.push(i);
|
|
629
|
+
b.lastValue = next;
|
|
630
|
+
applyBinding(b, next);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return { changedBindings: changed };
|
|
634
|
+
},
|
|
635
|
+
getEachDiff(sinceIndex) {
|
|
636
|
+
const all = ci._eachDiffLog?.toArray() ?? [];
|
|
637
|
+
if (sinceIndex === undefined)
|
|
638
|
+
return all;
|
|
639
|
+
return all.filter((e) => e.updateIndex >= sinceIndex);
|
|
640
|
+
},
|
|
641
|
+
getScopeTree(opts) {
|
|
642
|
+
const maxDepth = opts?.depth ?? Infinity;
|
|
643
|
+
const startScope = opts?.scopeId ? findScopeById(ci.rootScope, opts.scopeId) : ci.rootScope;
|
|
644
|
+
if (!startScope) {
|
|
645
|
+
return { scopeId: '0', kind: 'root', active: false, children: [] };
|
|
646
|
+
}
|
|
647
|
+
return walkScope(startScope, 0, maxDepth);
|
|
648
|
+
},
|
|
649
|
+
getDisposerLog(limit) {
|
|
650
|
+
const all = ci._disposerLog?.toArray() ?? [];
|
|
651
|
+
if (limit === undefined)
|
|
652
|
+
return all;
|
|
653
|
+
return all.slice(-Math.max(0, limit));
|
|
654
|
+
},
|
|
655
|
+
getBindingGraph() {
|
|
656
|
+
const legend = ci.def.__maskLegend;
|
|
657
|
+
if (!legend)
|
|
658
|
+
return [];
|
|
659
|
+
const result = [];
|
|
660
|
+
for (const [path, bit] of Object.entries(legend)) {
|
|
661
|
+
const indices = [];
|
|
662
|
+
for (let i = 0; i < ci.allBindings.length; i++) {
|
|
663
|
+
const b = ci.allBindings[i];
|
|
664
|
+
if ((b.mask & bit) !== 0)
|
|
665
|
+
indices.push(i);
|
|
666
|
+
}
|
|
667
|
+
result.push({ statePath: path, bindingIndices: indices });
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
},
|
|
671
|
+
getPendingEffects() {
|
|
672
|
+
return ci._pendingEffects?.list() ?? [];
|
|
673
|
+
},
|
|
674
|
+
getEffectTimeline(limit) {
|
|
675
|
+
const all = ci._effectTimeline?.toArray() ?? [];
|
|
676
|
+
if (limit === undefined)
|
|
677
|
+
return all;
|
|
678
|
+
return all.slice(-Math.max(0, limit));
|
|
679
|
+
},
|
|
680
|
+
mockEffect(match, response, opts) {
|
|
681
|
+
if (!ci._effectMocks)
|
|
682
|
+
return { mockId: '' };
|
|
683
|
+
return { mockId: ci._effectMocks.add(match, response, Boolean(opts?.persist)) };
|
|
684
|
+
},
|
|
685
|
+
resolveEffect(effectId, response) {
|
|
686
|
+
const pending = ci._pendingEffects?.findById(effectId);
|
|
687
|
+
if (!pending)
|
|
688
|
+
return { resolved: false };
|
|
689
|
+
const payload = pending.payload;
|
|
690
|
+
if (payload?.onSuccess) {
|
|
691
|
+
const msg = payload.onSuccess(response);
|
|
692
|
+
ci.send(msg);
|
|
693
|
+
}
|
|
694
|
+
ci._pendingEffects?.remove(effectId);
|
|
695
|
+
ci._effectTimeline?.push({
|
|
696
|
+
effectId,
|
|
697
|
+
type: pending.type,
|
|
698
|
+
phase: 'resolved',
|
|
699
|
+
timestamp: Date.now(),
|
|
700
|
+
durationMs: Date.now() - pending.dispatchedAt,
|
|
701
|
+
});
|
|
702
|
+
return { resolved: true };
|
|
703
|
+
},
|
|
704
|
+
stepBack(n, mode) {
|
|
705
|
+
const rewindDepth = Math.min(Math.max(0, n), history.length);
|
|
706
|
+
const keep = history.slice(0, history.length - rewindDepth);
|
|
707
|
+
const [initialState] = ci.def.init(undefined);
|
|
708
|
+
let state = initialState;
|
|
709
|
+
const collectedEffects = [];
|
|
710
|
+
for (const record of keep) {
|
|
711
|
+
const [newState, newEffects] = ci.def.update(state, record.msg);
|
|
712
|
+
state = newState;
|
|
713
|
+
if (mode === 'live')
|
|
714
|
+
collectedEffects.push(...newEffects);
|
|
715
|
+
}
|
|
716
|
+
_forceState(ci, state);
|
|
717
|
+
history.length = keep.length;
|
|
718
|
+
if (mode === 'live') {
|
|
719
|
+
for (const eff of collectedEffects) {
|
|
720
|
+
if (ci.def.onEffect)
|
|
721
|
+
ci.def.onEffect({ effect: eff, send: ci.send, signal: ci.signal });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return { state, rewindDepth };
|
|
725
|
+
},
|
|
726
|
+
getCoverage() {
|
|
727
|
+
if (!ci._coverage)
|
|
728
|
+
return { fired: {}, neverFired: [] };
|
|
729
|
+
const schema = ci.def.__msgSchema;
|
|
730
|
+
const known = schema?.variants ? Object.keys(schema.variants) : undefined;
|
|
731
|
+
return ci._coverage.snapshot(known);
|
|
732
|
+
},
|
|
733
|
+
evalInPage(code) {
|
|
734
|
+
const stateBefore = JSON.parse(JSON.stringify(ci.state));
|
|
735
|
+
const historyLenBefore = history.length;
|
|
736
|
+
const pendingBefore = new Set((ci._pendingEffects?.list() ?? []).map((p) => p.id));
|
|
737
|
+
const dirtyMaskBefore = lastDirtyMask;
|
|
738
|
+
let result;
|
|
739
|
+
try {
|
|
740
|
+
const fn = new Function(`return (${code})`);
|
|
741
|
+
const rv = fn();
|
|
742
|
+
if (rv && typeof rv === 'object' && 'then' in rv) {
|
|
743
|
+
result = {
|
|
744
|
+
error: 'llui_eval does not support async expressions in Phase 1. Wrap awaits in an IIFE and expose the result synchronously via globalThis.',
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
result = rv;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
catch (err) {
|
|
752
|
+
result = { error: err instanceof Error ? err.message : String(err) };
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
flushInstance(ci);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
// Best-effort; user's eval may have left the instance in a weird state
|
|
759
|
+
}
|
|
760
|
+
const stateAfter = ci.state;
|
|
761
|
+
const stateDiff = diffStateInternal(stateBefore, stateAfter);
|
|
762
|
+
const stateChanged = Object.keys(stateDiff.added).length === 0 &&
|
|
763
|
+
Object.keys(stateDiff.removed).length === 0 &&
|
|
764
|
+
Object.keys(stateDiff.changed).length === 0
|
|
765
|
+
? null
|
|
766
|
+
: stateDiff;
|
|
767
|
+
const newHistoryEntries = history.length - historyLenBefore;
|
|
768
|
+
const pendingNow = ci._pendingEffects?.list() ?? [];
|
|
769
|
+
const newPendingEffects = pendingNow.filter((p) => !pendingBefore.has(p.id));
|
|
770
|
+
const dirtyBindingIndices = [];
|
|
771
|
+
const maskDiff = lastDirtyMask ^ dirtyMaskBefore;
|
|
772
|
+
for (let i = 0; i < ci.allBindings.length; i++) {
|
|
773
|
+
const b = ci.allBindings[i];
|
|
774
|
+
if ((b.mask & maskDiff) !== 0)
|
|
775
|
+
dirtyBindingIndices.push(i);
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
result,
|
|
779
|
+
sideEffects: {
|
|
780
|
+
stateChanged,
|
|
781
|
+
newHistoryEntries,
|
|
782
|
+
newPendingEffects,
|
|
783
|
+
dirtyBindingIndices,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
},
|
|
401
787
|
};
|
|
402
788
|
// Intercept update to record transitions
|
|
403
789
|
const originalUpdate = ci.def.update;
|
|
@@ -408,7 +794,7 @@ export function installDevTools(inst) {
|
|
|
408
794
|
: -1;
|
|
409
795
|
lastDirtyMask = typeof dirty === 'number' ? dirty : -1;
|
|
410
796
|
const record = {
|
|
411
|
-
index: idx
|
|
797
|
+
index: idx,
|
|
412
798
|
timestamp: Date.now(),
|
|
413
799
|
msg,
|
|
414
800
|
stateBefore: state,
|
|
@@ -416,6 +802,20 @@ export function installDevTools(inst) {
|
|
|
416
802
|
effects,
|
|
417
803
|
dirtyMask: lastDirtyMask,
|
|
418
804
|
};
|
|
805
|
+
// _updateCounter and the history index track the same thing —
|
|
806
|
+
// tie them so EachDiff entries emitted during the ensuing Phase 1
|
|
807
|
+
// reconcile carry the same updateIndex as the message record that
|
|
808
|
+
// caused the reconcile. `each.ts` reads `inst._updateCounter` when
|
|
809
|
+
// stamping EachDiff entries.
|
|
810
|
+
ci._updateCounter = idx;
|
|
811
|
+
// Coverage: record the discriminant of this message at the SAME
|
|
812
|
+
// index stamped on the MessageRecord above, so tools can cross-
|
|
813
|
+
// reference `getMessageHistory()[i]` with `coverage.fired[v].lastIndex`.
|
|
814
|
+
const variant = msg && typeof msg === 'object' && 'type' in msg
|
|
815
|
+
? String(msg.type)
|
|
816
|
+
: '<non-discriminant>';
|
|
817
|
+
ci._coverage?.record(variant, idx);
|
|
818
|
+
idx++;
|
|
419
819
|
if (history.length >= MAX_HISTORY)
|
|
420
820
|
history.shift();
|
|
421
821
|
history.push(record);
|