@llui/mcp 0.0.24 → 0.0.27
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/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -32
- package/dist/index.js.map +1 -1
- package/dist/tool-registry.d.ts +31 -2
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js +48 -6
- package/dist/tool-registry.js.map +1 -1
- package/dist/tools/cdp.d.ts.map +1 -1
- package/dist/tools/cdp.js +38 -67
- package/dist/tools/cdp.js.map +1 -1
- package/dist/tools/compiler.d.ts.map +1 -1
- package/dist/tools/compiler.js +10 -26
- package/dist/tools/compiler.js.map +1 -1
- package/dist/tools/debug-api.d.ts +9 -4
- package/dist/tools/debug-api.d.ts.map +1 -1
- package/dist/tools/debug-api.js +148 -304
- package/dist/tools/debug-api.js.map +1 -1
- package/dist/tools/source.d.ts.map +1 -1
- package/dist/tools/source.js +26 -44
- package/dist/tools/source.js.map +1 -1
- package/dist/tools/ssr.d.ts.map +1 -1
- package/dist/tools/ssr.js +8 -10
- package/dist/tools/ssr.js.map +1 -1
- package/package.json +6 -4
package/dist/tools/debug-api.js
CHANGED
|
@@ -1,39 +1,35 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { generateReplayTest } from './replay-test-generator.js';
|
|
3
4
|
import { domDiff, diffState } from '../util/diff.js';
|
|
4
5
|
/**
|
|
5
|
-
* Register the
|
|
6
|
-
* `ctx.relay!.call(method, args)` — which transparently dispatches to
|
|
7
|
-
* an in-process `LluiDebugAPI` or the WebSocket bridge,
|
|
8
|
-
* transport was wired.
|
|
6
|
+
* Register the 33 debug-API-backed tools. Every handler routes through
|
|
7
|
+
* `ctx.relay!.call(method, args)` — which transparently dispatches to
|
|
8
|
+
* either an in-process `LluiDebugAPI` or the WebSocket bridge,
|
|
9
|
+
* depending on how the transport was wired.
|
|
10
|
+
*
|
|
11
|
+
* Zod schemas drive both runtime input validation (the registry's
|
|
12
|
+
* `dispatch()` calls `schema.safeParse()` before invoking the handler)
|
|
13
|
+
* and the JSON Schema published in `tools/list` (derived once at
|
|
14
|
+
* registration time). Handlers receive parsed/typed arguments.
|
|
9
15
|
*/
|
|
10
16
|
export function registerDebugApiTools(registry) {
|
|
11
17
|
registry.register({
|
|
12
18
|
name: 'llui_get_state',
|
|
13
19
|
description: 'Get the current state of the LLui component. Returns a JSON-serializable state object.',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
component: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'Component name (defaults to root)',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
20
|
+
schema: z.object({
|
|
21
|
+
component: z.string().optional().describe('Component name (defaults to root)'),
|
|
22
|
+
}),
|
|
23
23
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getState', []));
|
|
24
24
|
registry.register({
|
|
25
25
|
name: 'llui_send_message',
|
|
26
26
|
description: 'Send a message to the component and return the new state and effects. Validates the message first. Calls flush() automatically.',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
required: ['msg'],
|
|
36
|
-
},
|
|
27
|
+
schema: z.object({
|
|
28
|
+
msg: z
|
|
29
|
+
.object({})
|
|
30
|
+
.passthrough()
|
|
31
|
+
.describe('The message to send (must be a valid Msg variant)'),
|
|
32
|
+
}),
|
|
37
33
|
}, 'debug-api', async (args, ctx) => {
|
|
38
34
|
const errors = (await ctx.relay.call('validateMessage', [args.msg]));
|
|
39
35
|
if (errors)
|
|
@@ -45,47 +41,27 @@ export function registerDebugApiTools(registry) {
|
|
|
45
41
|
registry.register({
|
|
46
42
|
name: 'llui_eval_update',
|
|
47
43
|
description: 'Dry-run: call update(state, msg) without applying. Returns what the new state and effects would be without modifying the running app.',
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
msg: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
description: 'The hypothetical message to evaluate',
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
required: ['msg'],
|
|
57
|
-
},
|
|
44
|
+
schema: z.object({
|
|
45
|
+
msg: z.object({}).passthrough().describe('The hypothetical message to evaluate'),
|
|
46
|
+
}),
|
|
58
47
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('evalUpdate', [args.msg]));
|
|
59
48
|
registry.register({
|
|
60
49
|
name: 'llui_validate_message',
|
|
61
50
|
description: 'Validate a message against the component Msg type. Returns errors or null if valid.',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
msg: {
|
|
66
|
-
type: 'object',
|
|
67
|
-
description: 'The message to validate',
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ['msg'],
|
|
71
|
-
},
|
|
51
|
+
schema: z.object({
|
|
52
|
+
msg: z.object({}).passthrough().describe('The message to validate'),
|
|
53
|
+
}),
|
|
72
54
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('validateMessage', [args.msg]));
|
|
73
55
|
registry.register({
|
|
74
56
|
name: 'llui_get_message_history',
|
|
75
57
|
description: 'Get the chronological message history with state transitions, effects, and dirty masks. Supports pagination via `since` (exclusive, return entries with index > since) and `limit` (return at most N most-recent entries). Use both together for tail-fetching.',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
limit: {
|
|
84
|
-
type: 'number',
|
|
85
|
-
description: 'Max entries to return (the N most recent).',
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
58
|
+
schema: z.object({
|
|
59
|
+
since: z
|
|
60
|
+
.number()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe('Return entries with index strictly greater than this.'),
|
|
63
|
+
limit: z.number().optional().describe('Max entries to return (the N most recent).'),
|
|
64
|
+
}),
|
|
89
65
|
}, 'debug-api', async (args, ctx) => {
|
|
90
66
|
const opts = {};
|
|
91
67
|
if (typeof args.since === 'number')
|
|
@@ -97,59 +73,33 @@ export function registerDebugApiTools(registry) {
|
|
|
97
73
|
registry.register({
|
|
98
74
|
name: 'llui_export_trace',
|
|
99
75
|
description: 'Export the current session as a replayable LluiTrace JSON.',
|
|
100
|
-
|
|
101
|
-
type: 'object',
|
|
102
|
-
properties: {},
|
|
103
|
-
},
|
|
76
|
+
schema: z.object({}),
|
|
104
77
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('exportTrace', []));
|
|
105
78
|
registry.register({
|
|
106
79
|
name: 'llui_get_bindings',
|
|
107
80
|
description: 'Get all active reactive bindings with their masks, last values, and DOM targets.',
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
filter: {
|
|
112
|
-
type: 'string',
|
|
113
|
-
description: 'Filter by DOM selector or mask value',
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
81
|
+
schema: z.object({
|
|
82
|
+
filter: z.string().optional().describe('Filter by DOM selector or mask value'),
|
|
83
|
+
}),
|
|
117
84
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getBindings', []));
|
|
118
85
|
registry.register({
|
|
119
86
|
name: 'llui_why_did_update',
|
|
120
87
|
description: 'Explain why a specific binding re-evaluated: which mask bits were dirty, what the accessor returned, what the previous value was.',
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
bindingIndex: {
|
|
125
|
-
type: 'number',
|
|
126
|
-
description: 'The binding index to inspect',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
required: ['bindingIndex'],
|
|
130
|
-
},
|
|
88
|
+
schema: z.object({
|
|
89
|
+
bindingIndex: z.number().describe('The binding index to inspect'),
|
|
90
|
+
}),
|
|
131
91
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('whyDidUpdate', [args.bindingIndex]));
|
|
132
92
|
registry.register({
|
|
133
93
|
name: 'llui_search_state',
|
|
134
94
|
description: 'Search current state using a dot-separated path query. E.g., "cart.items" returns the items array.',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
query: {
|
|
139
|
-
type: 'string',
|
|
140
|
-
description: 'Dot-separated path to search. E.g., "user.name", "items"',
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
required: ['query'],
|
|
144
|
-
},
|
|
95
|
+
schema: z.object({
|
|
96
|
+
query: z.string().describe('Dot-separated path to search. E.g., "user.name", "items"'),
|
|
97
|
+
}),
|
|
145
98
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('searchState', [args.query]));
|
|
146
99
|
registry.register({
|
|
147
100
|
name: 'llui_clear_log',
|
|
148
101
|
description: 'Clear the message and effects history.',
|
|
149
|
-
|
|
150
|
-
type: 'object',
|
|
151
|
-
properties: {},
|
|
152
|
-
},
|
|
102
|
+
schema: z.object({}),
|
|
153
103
|
}, 'debug-api', async (_args, ctx) => {
|
|
154
104
|
await ctx.relay.call('clearLog', []);
|
|
155
105
|
return { cleared: true };
|
|
@@ -157,85 +107,53 @@ export function registerDebugApiTools(registry) {
|
|
|
157
107
|
registry.register({
|
|
158
108
|
name: 'llui_list_messages',
|
|
159
109
|
description: 'List all message variants the component accepts, with their field types. Returns { discriminant, variants: { [name]: { [field]: typeDescriptor } } }. Use this to discover what messages can be sent without reading source code.',
|
|
160
|
-
|
|
161
|
-
type: 'object',
|
|
162
|
-
properties: {},
|
|
163
|
-
},
|
|
110
|
+
schema: z.object({}),
|
|
164
111
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getMessageSchema', []));
|
|
165
112
|
registry.register({
|
|
166
113
|
name: 'llui_decode_mask',
|
|
167
114
|
description: "Decode a dirty-mask value from llui_get_message_history (the 'dirtyMask' field) into the list of top-level state fields that changed. Requires 'mask' param.",
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
mask: { type: 'number', description: 'The dirtyMask value to decode' },
|
|
172
|
-
},
|
|
173
|
-
required: ['mask'],
|
|
174
|
-
},
|
|
115
|
+
schema: z.object({
|
|
116
|
+
mask: z.number().describe('The dirtyMask value to decode'),
|
|
117
|
+
}),
|
|
175
118
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('decodeMask', [args.mask]));
|
|
176
119
|
registry.register({
|
|
177
120
|
name: 'llui_mask_legend',
|
|
178
121
|
description: 'Return the compiler-generated bit→field map for this component. Example: { todos: 1, filter: 2, nextId: 4 } means bit 0 represents `todos`, etc.',
|
|
179
|
-
|
|
180
|
-
type: 'object',
|
|
181
|
-
properties: {},
|
|
182
|
-
},
|
|
122
|
+
schema: z.object({}),
|
|
183
123
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getMaskLegend', []));
|
|
184
124
|
registry.register({
|
|
185
125
|
name: 'llui_component_info',
|
|
186
126
|
description: 'Get component name and source location (file + line) of the component() declaration. Lets you find where to read or edit the component.',
|
|
187
|
-
|
|
188
|
-
type: 'object',
|
|
189
|
-
properties: {},
|
|
190
|
-
},
|
|
127
|
+
schema: z.object({}),
|
|
191
128
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getComponentInfo', []));
|
|
192
129
|
registry.register({
|
|
193
130
|
name: 'llui_describe_state',
|
|
194
131
|
description: "Return the State type's shape (not its value). Fields map to type descriptors: 'string', 'number', 'boolean', {kind:'enum',values:[...]}, {kind:'array',of:...}, {kind:'object',fields:...}, {kind:'optional',of:...}. Use this to know what fields exist and their types even when currently undefined.",
|
|
195
|
-
|
|
196
|
-
type: 'object',
|
|
197
|
-
properties: {},
|
|
198
|
-
},
|
|
132
|
+
schema: z.object({}),
|
|
199
133
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getStateSchema', []));
|
|
200
134
|
registry.register({
|
|
201
135
|
name: 'llui_list_effects',
|
|
202
136
|
description: 'List all effect variants the component emits, with their field types (same format as llui_list_messages). Returns null if no Effect type is declared.',
|
|
203
|
-
|
|
204
|
-
type: 'object',
|
|
205
|
-
properties: {},
|
|
206
|
-
},
|
|
137
|
+
schema: z.object({}),
|
|
207
138
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getEffectSchema', []));
|
|
208
139
|
registry.register({
|
|
209
140
|
name: 'llui_trace_element',
|
|
210
141
|
description: "Find all bindings targeting a DOM element matched by a CSS selector. Returns { bindingIndex, kind, key, mask, lastValue, relation }[] so you can answer 'why is this element wrong?' — combine with llui_why_did_update(bindingIndex) for a full narrative.",
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
selector: {
|
|
215
|
-
type: 'string',
|
|
216
|
-
description: 'CSS selector (e.g. `.todo.active`, `#submit`)',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
required: ['selector'],
|
|
220
|
-
},
|
|
142
|
+
schema: z.object({
|
|
143
|
+
selector: z.string().describe('CSS selector (e.g. `.todo.active`, `#submit`)'),
|
|
144
|
+
}),
|
|
221
145
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getBindingsFor', [args.selector]));
|
|
222
146
|
registry.register({
|
|
223
147
|
name: 'llui_snapshot_state',
|
|
224
148
|
description: 'Capture the current state (deep clone). Returns the snapshot — store it, then call llui_restore_state later to roll back. Useful for safely exploring transitions during a debugging session.',
|
|
225
|
-
|
|
149
|
+
schema: z.object({}),
|
|
226
150
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('snapshotState', []));
|
|
227
151
|
registry.register({
|
|
228
152
|
name: 'llui_restore_state',
|
|
229
153
|
description: 'Overwrite the current state with a previously-captured snapshot. Triggers a full re-render (FULL_MASK). Bypasses update() — snap must already be a valid state value.',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
snapshot: {
|
|
234
|
-
description: 'The state object returned by llui_snapshot_state.',
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
required: ['snapshot'],
|
|
238
|
-
},
|
|
154
|
+
schema: z.object({
|
|
155
|
+
snapshot: z.unknown().describe('The state object returned by llui_snapshot_state.'),
|
|
156
|
+
}),
|
|
239
157
|
}, 'debug-api', async (args, ctx) => {
|
|
240
158
|
await ctx.relay.call('restoreState', [args.snapshot]);
|
|
241
159
|
return { restored: true, state: await ctx.relay.call('getState', []) };
|
|
@@ -243,38 +161,28 @@ export function registerDebugApiTools(registry) {
|
|
|
243
161
|
registry.register({
|
|
244
162
|
name: 'llui_list_components',
|
|
245
163
|
description: 'List all currently-mounted LLui components + which one is active (being targeted by subsequent tool calls). Multi-mount apps show one entry per mount.',
|
|
246
|
-
|
|
164
|
+
schema: z.object({}),
|
|
247
165
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('__listComponents', []));
|
|
248
166
|
registry.register({
|
|
249
167
|
name: 'llui_select_component',
|
|
250
168
|
description: 'Switch the active component (the one all other tool calls target). Use a key from llui_list_components.',
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
key: {
|
|
255
|
-
type: 'string',
|
|
256
|
-
description: 'Component key as returned by llui_list_components',
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
required: ['key'],
|
|
260
|
-
},
|
|
169
|
+
schema: z.object({
|
|
170
|
+
key: z.string().describe('Component key as returned by llui_list_components'),
|
|
171
|
+
}),
|
|
261
172
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('__selectComponent', [args.key]));
|
|
262
173
|
registry.register({
|
|
263
174
|
name: 'llui_replay_trace',
|
|
264
175
|
description: 'Generate a ready-to-run vitest file that replays the current message history via `replayTrace()` from @llui/test. The output is a complete test file with the trace inlined — paste it into packages/<pkg>/test/ to reproduce the exact sequence of messages the component saw in this session. Use this to capture a debugging session as a regression test.',
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
},
|
|
176
|
+
schema: z.object({
|
|
177
|
+
importPath: z
|
|
178
|
+
.string()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'."),
|
|
181
|
+
exportName: z
|
|
182
|
+
.string()
|
|
183
|
+
.optional()
|
|
184
|
+
.describe("Named export that holds the component def (default: the component's name)."),
|
|
185
|
+
}),
|
|
278
186
|
}, 'debug-api', async (args, ctx) => {
|
|
279
187
|
const trace = (await ctx.relay.call('exportTrace', []));
|
|
280
188
|
const importPath = args.importPath ?? '../src/index';
|
|
@@ -288,87 +196,66 @@ export function registerDebugApiTools(registry) {
|
|
|
288
196
|
registry.register({
|
|
289
197
|
name: 'llui_inspect_element',
|
|
290
198
|
description: 'Get a rich report for a DOM element: tag, attributes, classes, data-*, text, bounding box, a computed-style subset (display/visibility/position/dimensions), and the bindings targeting this node. Pass a CSS selector. Returns null if no element matches.',
|
|
291
|
-
|
|
292
|
-
type: 'object',
|
|
293
|
-
properties: { selector: { type: 'string', description: 'CSS selector' } },
|
|
294
|
-
required: ['selector'],
|
|
295
|
-
},
|
|
199
|
+
schema: z.object({ selector: z.string().describe('CSS selector') }),
|
|
296
200
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('inspectElement', [args.selector]));
|
|
297
201
|
registry.register({
|
|
298
202
|
name: 'llui_get_rendered_html',
|
|
299
203
|
description: "Get the outerHTML of the mounted component or a specific element. Pass 'selector' for a specific node (defaults to the mount root). Pass 'maxLength' to truncate output.",
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
maxLength: { type: 'number' },
|
|
305
|
-
},
|
|
306
|
-
},
|
|
204
|
+
schema: z.object({
|
|
205
|
+
selector: z.string().optional(),
|
|
206
|
+
maxLength: z.number().optional(),
|
|
207
|
+
}),
|
|
307
208
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getRenderedHtml', [args.selector, args.maxLength]));
|
|
308
209
|
registry.register({
|
|
309
210
|
name: 'llui_dispatch_event',
|
|
310
211
|
description: "Synthesize and dispatch a browser event at a DOM element. Returns the history indices of any Msgs the handler produced plus the resulting state. 'type' is the event name (e.g. 'click', 'input', 'keydown'). 'init' is an EventInit object (e.g. { key: 'Enter' } for keydown).",
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
init: { type: 'object' },
|
|
317
|
-
},
|
|
318
|
-
required: ['selector', 'type'],
|
|
319
|
-
},
|
|
212
|
+
schema: z.object({
|
|
213
|
+
selector: z.string(),
|
|
214
|
+
type: z.string(),
|
|
215
|
+
init: z.record(z.string(), z.unknown()).optional(),
|
|
216
|
+
}),
|
|
320
217
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('dispatchDomEvent', [args.selector, args.type, args.init]));
|
|
321
218
|
registry.register({
|
|
322
219
|
name: 'llui_dom_diff',
|
|
323
220
|
description: 'Compare expected HTML against the currently rendered HTML (from selector, or the mount root). Returns { match, differences }. Pass ignoreWhitespace=true to normalize whitespace.',
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
ignoreWhitespace: { type: 'boolean' },
|
|
330
|
-
},
|
|
331
|
-
required: ['expected'],
|
|
332
|
-
},
|
|
221
|
+
schema: z.object({
|
|
222
|
+
expected: z.string(),
|
|
223
|
+
selector: z.string().optional(),
|
|
224
|
+
ignoreWhitespace: z.boolean().optional(),
|
|
225
|
+
}),
|
|
333
226
|
}, 'debug-api', async (args, ctx) => {
|
|
334
227
|
const actual = (await ctx.relay.call('getRenderedHtml', [args.selector]));
|
|
335
|
-
return domDiff(
|
|
228
|
+
return domDiff(args.expected, actual, {
|
|
336
229
|
ignoreWhitespace: Boolean(args.ignoreWhitespace),
|
|
337
230
|
});
|
|
338
231
|
});
|
|
339
232
|
registry.register({
|
|
340
233
|
name: 'llui_get_focus',
|
|
341
234
|
description: '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.',
|
|
342
|
-
|
|
235
|
+
schema: z.object({}),
|
|
343
236
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getFocus', []));
|
|
344
237
|
registry.register({
|
|
345
238
|
name: 'llui_lint',
|
|
346
239
|
description: "Lint LLui source code against ESLint LLui rules. Returns violations grouped by rule. Pass 'path' (absolute file path on the dev machine).",
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
path: {
|
|
351
|
-
type: 'string',
|
|
352
|
-
description: 'Absolute file path to read and lint.',
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
required: ['path'],
|
|
356
|
-
},
|
|
240
|
+
schema: z.object({
|
|
241
|
+
path: z.string().describe('Absolute file path to read and lint.'),
|
|
242
|
+
}),
|
|
357
243
|
}, 'debug-api', async (args, _ctx) => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
throw new Error(`llui_lint: only .ts/.tsx files are supported, got: ${pathArg}`);
|
|
244
|
+
if (!args.path.endsWith('.ts') && !args.path.endsWith('.tsx')) {
|
|
245
|
+
throw new Error(`llui_lint: only .ts/.tsx files are supported, got: ${args.path}`);
|
|
361
246
|
}
|
|
362
|
-
if (!existsSync(
|
|
363
|
-
throw new Error(`llui_lint: file not found: ${
|
|
247
|
+
if (!existsSync(args.path)) {
|
|
248
|
+
throw new Error(`llui_lint: file not found: ${args.path}`);
|
|
364
249
|
}
|
|
365
250
|
const { execSync } = await import('node:child_process');
|
|
366
251
|
try {
|
|
367
|
-
const output = execSync(`pnpm exec eslint --format json "${
|
|
252
|
+
const output = execSync(`pnpm exec eslint --format json "${args.path}"`, {
|
|
253
|
+
encoding: 'utf8',
|
|
254
|
+
});
|
|
368
255
|
const results = JSON.parse(output);
|
|
369
256
|
const violations = results[0]?.messages || [];
|
|
370
257
|
return {
|
|
371
|
-
file:
|
|
258
|
+
file: args.path,
|
|
372
259
|
score: Math.max(0, 20 - violations.length),
|
|
373
260
|
violations,
|
|
374
261
|
summary: `${violations.length} violation(s)`,
|
|
@@ -381,7 +268,7 @@ export function registerDebugApiTools(registry) {
|
|
|
381
268
|
const results = JSON.parse(e.stdout);
|
|
382
269
|
const violations = results[0]?.messages || [];
|
|
383
270
|
return {
|
|
384
|
-
file:
|
|
271
|
+
file: args.path,
|
|
385
272
|
score: Math.max(0, 20 - violations.length),
|
|
386
273
|
violations,
|
|
387
274
|
summary: `${violations.length} violation(s)`,
|
|
@@ -397,39 +284,30 @@ export function registerDebugApiTools(registry) {
|
|
|
397
284
|
registry.register({
|
|
398
285
|
name: 'llui_force_rerender',
|
|
399
286
|
description: "Re-evaluate every binding's accessor against the current state, apply changed values to the DOM, and return the indices of bindings that changed. If a binding's DOM value corrects itself after this call but not after a real message, the mask for that binding is wrong.",
|
|
400
|
-
|
|
287
|
+
schema: z.object({}),
|
|
401
288
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('forceRerender', []));
|
|
402
289
|
registry.register({
|
|
403
290
|
name: 'llui_each_diff',
|
|
404
291
|
description: "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.",
|
|
405
|
-
|
|
406
|
-
type: 'object',
|
|
407
|
-
properties: { sinceIndex: { type: 'number' } },
|
|
408
|
-
},
|
|
292
|
+
schema: z.object({ sinceIndex: z.number().optional() }),
|
|
409
293
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getEachDiff', [args.sinceIndex]));
|
|
410
294
|
registry.register({
|
|
411
295
|
name: 'llui_scope_tree',
|
|
412
296
|
description: "Walk the scope tree starting at the component root (or a specific scopeId). Returns a LifetimeNode tree with kind (root/show/each/branch/child/portal/foreign) and children. Pass 'depth' to limit traversal, 'scopeId' to start elsewhere.",
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
scopeId: { type: 'string' },
|
|
418
|
-
},
|
|
419
|
-
},
|
|
297
|
+
schema: z.object({
|
|
298
|
+
depth: z.number().optional(),
|
|
299
|
+
scopeId: z.string().optional(),
|
|
300
|
+
}),
|
|
420
301
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getScopeTree', [{ depth: args.depth, scopeId: args.scopeId }]));
|
|
421
302
|
registry.register({
|
|
422
303
|
name: 'llui_disposer_log',
|
|
423
304
|
description: "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.",
|
|
424
|
-
|
|
425
|
-
type: 'object',
|
|
426
|
-
properties: { limit: { type: 'number' } },
|
|
427
|
-
},
|
|
305
|
+
schema: z.object({ limit: z.number().optional() }),
|
|
428
306
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getDisposerLog', [args.limit]));
|
|
429
307
|
registry.register({
|
|
430
308
|
name: 'llui_list_dead_bindings',
|
|
431
309
|
description: "Bindings that are inactive (scope disposed) OR never matched a dirty mask OR never changed value. Useful for finding wasted work and 'this never updates' bugs. Returns the subset of get_bindings with an annotation on why it's flagged.",
|
|
432
|
-
|
|
310
|
+
schema: z.object({}),
|
|
433
311
|
}, 'debug-api', async (_args, ctx) => {
|
|
434
312
|
const bindings = (await ctx.relay.call('getBindings', []));
|
|
435
313
|
return bindings
|
|
@@ -442,56 +320,42 @@ export function registerDebugApiTools(registry) {
|
|
|
442
320
|
registry.register({
|
|
443
321
|
name: 'llui_binding_graph',
|
|
444
322
|
description: '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.',
|
|
445
|
-
|
|
323
|
+
schema: z.object({}),
|
|
446
324
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getBindingGraph', []));
|
|
447
325
|
registry.register({
|
|
448
326
|
name: 'llui_mock_effect',
|
|
449
327
|
description: "Register a mock for an effect matching 'match' ({ type?, payloadPath?, payloadEquals? }). The next matching effect resolves with 'response' instead of running. Mocks are one-shot; pass { persist: true } to keep across matches. Returns { mockId } for later reference.",
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
opts: { type: 'object' },
|
|
456
|
-
},
|
|
457
|
-
required: ['match', 'response'],
|
|
458
|
-
},
|
|
328
|
+
schema: z.object({
|
|
329
|
+
match: z.record(z.string(), z.unknown()),
|
|
330
|
+
response: z.unknown(),
|
|
331
|
+
opts: z.record(z.string(), z.unknown()).optional(),
|
|
332
|
+
}),
|
|
459
333
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('mockEffect', [args.match, args.response, args.opts]));
|
|
460
334
|
registry.register({
|
|
461
335
|
name: 'llui_resolve_effect',
|
|
462
336
|
description: "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.",
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
response: {},
|
|
468
|
-
},
|
|
469
|
-
required: ['effectId', 'response'],
|
|
470
|
-
},
|
|
337
|
+
schema: z.object({
|
|
338
|
+
effectId: z.string(),
|
|
339
|
+
response: z.unknown(),
|
|
340
|
+
}),
|
|
471
341
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('resolveEffect', [args.effectId, args.response]));
|
|
472
342
|
registry.register({
|
|
473
343
|
name: 'llui_pending_effects',
|
|
474
344
|
description: "Current queued and in-flight effects. Each entry has { id, type, dispatchedAt, status, payload }. Use 'id' with llui_resolve_effect to manually resolve one.",
|
|
475
|
-
|
|
345
|
+
schema: z.object({}),
|
|
476
346
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getPendingEffects', []));
|
|
477
347
|
registry.register({
|
|
478
348
|
name: 'llui_effect_timeline',
|
|
479
349
|
description: "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.",
|
|
480
|
-
|
|
481
|
-
type: 'object',
|
|
482
|
-
properties: { limit: { type: 'number' } },
|
|
483
|
-
},
|
|
350
|
+
schema: z.object({ limit: z.number().optional() }),
|
|
484
351
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getEffectTimeline', [args.limit]));
|
|
485
352
|
registry.register({
|
|
486
353
|
name: 'llui_step_back',
|
|
487
354
|
description: "Rewind state by replaying from init() with the last N messages excluded. 'mode' is 'pure' (default; suppresses effects) or 'live' (re-fires effects from replay). Returns the new state and rewindDepth.",
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
mode: { type: 'string', enum: ['pure', 'live'] },
|
|
493
|
-
},
|
|
494
|
-
},
|
|
355
|
+
schema: z.object({
|
|
356
|
+
n: z.number().optional(),
|
|
357
|
+
mode: z.enum(['pure', 'live']).optional(),
|
|
358
|
+
}),
|
|
495
359
|
}, 'debug-api', async (args, ctx) => {
|
|
496
360
|
const n = typeof args.n === 'number' ? args.n : 1;
|
|
497
361
|
const mode = args.mode === 'live' ? 'live' : 'pure';
|
|
@@ -500,38 +364,29 @@ export function registerDebugApiTools(registry) {
|
|
|
500
364
|
registry.register({
|
|
501
365
|
name: 'llui_coverage',
|
|
502
366
|
description: "Per-Msg-variant coverage for the current session: { fired: { variant: { count, lastIndex } }, neverFired: [variants] }. Shows which message types have run and which haven't — useful for finding untested paths.",
|
|
503
|
-
|
|
367
|
+
schema: z.object({}),
|
|
504
368
|
}, 'debug-api', async (_args, ctx) => ctx.relay.call('getCoverage', []));
|
|
505
369
|
registry.register({
|
|
506
370
|
name: 'llui_diff_state',
|
|
507
371
|
description: "Structured JSON diff between two state values. Pass 'a' and 'b' — plain objects. Returns { added, removed, changed }.",
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
b: {},
|
|
513
|
-
},
|
|
514
|
-
required: ['a', 'b'],
|
|
515
|
-
},
|
|
372
|
+
schema: z.object({
|
|
373
|
+
a: z.unknown(),
|
|
374
|
+
b: z.unknown(),
|
|
375
|
+
}),
|
|
516
376
|
}, 'debug-api', async (args) => diffState(args.a, args.b));
|
|
517
377
|
registry.register({
|
|
518
378
|
name: 'llui_assert',
|
|
519
379
|
description: "Evaluate a predicate against current state. Pass 'path' (dot-separated), 'op' (eq/neq/exists/gt/lt/in), and 'value'. Returns { pass, actual, expected, op }.",
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
value: {},
|
|
526
|
-
},
|
|
527
|
-
required: ['path', 'op'],
|
|
528
|
-
},
|
|
380
|
+
schema: z.object({
|
|
381
|
+
path: z.string(),
|
|
382
|
+
op: z.enum(['eq', 'neq', 'exists', 'gt', 'lt', 'in']),
|
|
383
|
+
value: z.unknown().optional(),
|
|
384
|
+
}),
|
|
529
385
|
}, 'debug-api', async (args, ctx) => {
|
|
530
386
|
const actual = await ctx.relay.call('searchState', [args.path]);
|
|
531
|
-
const op = args.op;
|
|
532
387
|
const expected = args.value;
|
|
533
388
|
let pass = false;
|
|
534
|
-
switch (op) {
|
|
389
|
+
switch (args.op) {
|
|
535
390
|
case 'eq':
|
|
536
391
|
pass = Object.is(actual, expected);
|
|
537
392
|
break;
|
|
@@ -551,30 +406,23 @@ export function registerDebugApiTools(registry) {
|
|
|
551
406
|
pass = Array.isArray(expected) && expected.includes(actual);
|
|
552
407
|
break;
|
|
553
408
|
}
|
|
554
|
-
return { pass, actual, expected, op };
|
|
409
|
+
return { pass, actual, expected, op: args.op };
|
|
555
410
|
});
|
|
556
411
|
registry.register({
|
|
557
412
|
name: 'llui_search_history',
|
|
558
413
|
description: "Filtered message history. Pass 'filter' with { type?, statePath?, effectType?, fromIndex?, toIndex? }. Entries match if all present fields match — type is the Msg discriminant, statePath is a dot path whose value differs pre->post, effectType is a type present in the effects array.",
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
fromIndex: { type: 'number' },
|
|
569
|
-
toIndex: { type: 'number' },
|
|
570
|
-
},
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
required: ['filter'],
|
|
574
|
-
},
|
|
414
|
+
schema: z.object({
|
|
415
|
+
filter: z.object({
|
|
416
|
+
type: z.string().optional(),
|
|
417
|
+
statePath: z.string().optional(),
|
|
418
|
+
effectType: z.string().optional(),
|
|
419
|
+
fromIndex: z.number().optional(),
|
|
420
|
+
toIndex: z.number().optional(),
|
|
421
|
+
}),
|
|
422
|
+
}),
|
|
575
423
|
}, 'debug-api', async (args, ctx) => {
|
|
576
424
|
const history = (await ctx.relay.call('getMessageHistory', [{}]));
|
|
577
|
-
const f =
|
|
425
|
+
const f = args.filter;
|
|
578
426
|
function pathValue(obj, path) {
|
|
579
427
|
const parts = path.split('.');
|
|
580
428
|
let v = obj;
|
|
@@ -612,11 +460,7 @@ export function registerDebugApiTools(registry) {
|
|
|
612
460
|
registry.register({
|
|
613
461
|
name: 'llui_eval',
|
|
614
462
|
description: "Arbitrary JavaScript in the page context via the debug relay. Returns { result, sideEffects }. 'result' is the expression's return value or { error }. 'sideEffects' makes any state changes, new history entries, new pending effects, and dirty bindings visible. Phase 1 does not support async expressions; expose async results via globalThis instead.",
|
|
615
|
-
|
|
616
|
-
type: 'object',
|
|
617
|
-
properties: { code: { type: 'string' } },
|
|
618
|
-
required: ['code'],
|
|
619
|
-
},
|
|
463
|
+
schema: z.object({ code: z.string() }),
|
|
620
464
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('evalInPage', [args.code]));
|
|
621
465
|
}
|
|
622
466
|
//# sourceMappingURL=debug-api.js.map
|