aihand 0.0.1 → 0.1.1

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.
Files changed (113) hide show
  1. package/README.md +152 -2
  2. package/dist/chunk-2NTK7H4W.js +10 -0
  3. package/dist/chunk-3X4FTHLC.cjs +369 -0
  4. package/dist/chunk-BXVNR4E2.js +399 -0
  5. package/dist/chunk-C7DGE6MY.cjs +1456 -0
  6. package/dist/chunk-DUUCVLC3.cjs +254 -0
  7. package/dist/chunk-FAHI53KO.cjs +125 -0
  8. package/dist/chunk-G7KVJ7NF.js +369 -0
  9. package/dist/chunk-GNEUSRGP.js +52 -0
  10. package/dist/chunk-IGNEAOLT.cjs +130 -0
  11. package/dist/chunk-IS5XFUDB.js +125 -0
  12. package/dist/chunk-JLYC76XL.js +2448 -0
  13. package/dist/chunk-KQOABC2O.cjs +52 -0
  14. package/dist/chunk-OVMK33AC.cjs +104 -0
  15. package/dist/chunk-OWYK2IGV.js +250 -0
  16. package/dist/chunk-PQSQN4CN.js +126 -0
  17. package/dist/chunk-QF6AG3M5.cjs +410 -0
  18. package/dist/chunk-QSAMLXML.js +1456 -0
  19. package/dist/chunk-VEKYRKPF.cjs +399 -0
  20. package/dist/chunk-Y6H7W7PI.cjs +2451 -0
  21. package/dist/chunk-YKSYW77R.js +410 -0
  22. package/dist/chunk-Z2Y65YOY.cjs +7 -0
  23. package/dist/chunk-ZJQRNIK7.js +104 -0
  24. package/dist/cli-3J7EYI6G.cjs +651 -0
  25. package/dist/cli-FIJLKAGI.js +649 -0
  26. package/dist/cli-JQEIE7RQ.js +120 -0
  27. package/dist/cli-K3OS2QQH.cjs +122 -0
  28. package/dist/cli-OSYG6LJD.cjs +89 -0
  29. package/dist/cli-TXRW5PG6.js +89 -0
  30. package/dist/cli.cjs +81 -0
  31. package/dist/cli.js +81 -0
  32. package/dist/config-5KEQLN6L.cjs +13 -0
  33. package/dist/config-PJPYKDLQ.js +13 -0
  34. package/dist/graph-IH56SCPK.js +8 -0
  35. package/dist/graph-ZUXXCJ5A.cjs +8 -0
  36. package/dist/index.cjs +481 -0
  37. package/dist/index.d.cts +461 -0
  38. package/dist/index.d.ts +461 -0
  39. package/dist/index.js +479 -0
  40. package/dist/locate-5XFSXJ5J.cjs +15 -0
  41. package/dist/locate-NKSUGL3A.js +15 -0
  42. package/dist/refactor-5FWSZIBN.cjs +19 -0
  43. package/dist/refactor-BOB3SZSA.js +19 -0
  44. package/dist/scan-4R7GQG2W.cjs +9 -0
  45. package/dist/scan-VF54GAAX.js +9 -0
  46. package/dist/ui/probe/server.cjs +505 -0
  47. package/dist/ui/probe/server.js +507 -0
  48. package/dist/vite.cjs +12 -0
  49. package/dist/vite.d.cts +12 -0
  50. package/dist/vite.d.ts +12 -0
  51. package/dist/vite.js +12 -0
  52. package/package.json +82 -9
  53. package/src/cli.ts +107 -0
  54. package/src/index.ts +54 -0
  55. package/src/read/cli.ts +650 -0
  56. package/src/read/compact.ts +286 -0
  57. package/src/read/config.ts +62 -0
  58. package/src/read/graph.ts +182 -0
  59. package/src/read/index.ts +12 -0
  60. package/src/read/inject.ts +121 -0
  61. package/src/read/locate.ts +104 -0
  62. package/src/read/panel.ts +335 -0
  63. package/src/read/pipeline.ts +78 -0
  64. package/src/read/refactor.ts +576 -0
  65. package/src/read/render.ts +1118 -0
  66. package/src/read/scan.ts +61 -0
  67. package/src/read/seam.ts +0 -0
  68. package/src/read/security.ts +171 -0
  69. package/src/read/signals.ts +333 -0
  70. package/src/read/state.ts +71 -0
  71. package/src/read/stategraph.ts +205 -0
  72. package/src/read/types.ts +162 -0
  73. package/src/read/vite.ts +77 -0
  74. package/src/ui/babel/line-profiler.ts +197 -0
  75. package/src/ui/babel/source-loc.ts +68 -0
  76. package/src/ui/bridge/cdp-bridge.ts +138 -0
  77. package/src/ui/bridge/compile-probe.ts +80 -0
  78. package/src/ui/bridge/transport.ts +26 -0
  79. package/src/ui/bridge/vite-bridge.ts +116 -0
  80. package/src/ui/client/client-patch.ts +899 -0
  81. package/src/ui/client/client.ts +2562 -0
  82. package/src/ui/core/action.ts +747 -0
  83. package/src/ui/core/candidates.ts +348 -0
  84. package/src/ui/core/canvas.ts +305 -0
  85. package/src/ui/core/check.ts +34 -0
  86. package/src/ui/core/compact.ts +314 -0
  87. package/src/ui/core/detail.ts +244 -0
  88. package/src/ui/core/diff.ts +253 -0
  89. package/src/ui/core/emit.ts +198 -0
  90. package/src/ui/core/knob-exec.ts +137 -0
  91. package/src/ui/core/perf.ts +254 -0
  92. package/src/ui/core/types.ts +164 -0
  93. package/src/ui/core/util.ts +221 -0
  94. package/src/ui/index.ts +5 -0
  95. package/src/ui/probe/cli.ts +139 -0
  96. package/src/ui/probe/server.ts +468 -0
  97. package/src/ui/self/act.ts +47 -0
  98. package/src/ui/self/discover.ts +101 -0
  99. package/src/ui/self/grow.ts +121 -0
  100. package/src/ui/self/install.ts +100 -0
  101. package/src/ui/self/probe.ts +105 -0
  102. package/src/ui/self/screen-hook.ts +44 -0
  103. package/src/ui/self/self.ts +48 -0
  104. package/src/ui/self/store-refs.ts +123 -0
  105. package/src/ui/self/store-schema.ts +65 -0
  106. package/src/ui/self/synth.ts +37 -0
  107. package/src/ui/server/cli.ts +102 -0
  108. package/src/ui/server/dispatch.ts +276 -0
  109. package/src/ui/server/help-text.ts +237 -0
  110. package/src/ui/server/knob-schema.ts +87 -0
  111. package/src/ui/server/plugin.ts +1151 -0
  112. package/src/vite.ts +39 -0
  113. package/index.js +0 -2
@@ -0,0 +1,2451 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+ var _chunk3X4FTHLCcjs = require('./chunk-3X4FTHLC.cjs');
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+ var _chunkQF6AG3M5cjs = require('./chunk-QF6AG3M5.cjs');
29
+
30
+
31
+ var _chunkIGNEAOLTcjs = require('./chunk-IGNEAOLT.cjs');
32
+
33
+
34
+ var _chunkKQOABC2Ocjs = require('./chunk-KQOABC2O.cjs');
35
+
36
+
37
+ var _chunkZ2Y65YOYcjs = require('./chunk-Z2Y65YOY.cjs');
38
+
39
+ // src/vite.ts
40
+ var _path = require('path'); var _path2 = _interopRequireDefault(_path);
41
+
42
+ // src/read/vite.ts
43
+ var _child_process = require('child_process');
44
+ var _fs = require('fs');
45
+
46
+ var _url = require('url');
47
+ function resolveCliCmd() {
48
+ try {
49
+ const here = _path.dirname.call(void 0, _url.fileURLToPath.call(void 0, _chunkZ2Y65YOYcjs.importMetaUrl));
50
+ const src = _path.resolve.call(void 0, here, "..", "cli.ts");
51
+ if (_fs.existsSync.call(void 0, src)) {
52
+ return { cmd: process.execPath, args: ["--import", "tsx", src, "read"] };
53
+ }
54
+ for (const rel of ["../cli.js", "../../dist/cli.js"]) {
55
+ const p = _path.resolve.call(void 0, here, rel);
56
+ if (_fs.existsSync.call(void 0, p))
57
+ return { cmd: process.execPath, args: [p, "read"] };
58
+ }
59
+ } catch (e2) {
60
+ }
61
+ return { cmd: "npx", args: ["aihand", "read"] };
62
+ }
63
+ function repodexWatchPlugin(opts = {}) {
64
+ const { args = [], stdio = "inherit" } = opts;
65
+ return {
66
+ name: "aihand-read-watch",
67
+ apply: "serve",
68
+ configureServer(server) {
69
+ const { cmd, args: prefix } = resolveCliCmd();
70
+ const child = _child_process.spawn.call(void 0, cmd, [...prefix, "--watch", ...args], {
71
+ stdio,
72
+ env: { ...process.env, FORCE_COLOR: _nullishCoalesce(process.env.FORCE_COLOR, () => ( "1")) }
73
+ });
74
+ let done = false;
75
+ const cleanup = () => {
76
+ if (done)
77
+ return;
78
+ done = true;
79
+ if (!child.killed)
80
+ child.kill();
81
+ };
82
+ child.once("exit", () => {
83
+ done = true;
84
+ });
85
+ _optionalChain([server, 'access', _ => _.httpServer, 'optionalAccess', _2 => _2.once, 'call', _3 => _3("close", cleanup)]);
86
+ process.once("exit", cleanup);
87
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
88
+ process.once(sig, () => {
89
+ cleanup();
90
+ process.kill(process.pid, sig);
91
+ });
92
+ }
93
+ }
94
+ };
95
+ }
96
+
97
+ // src/ui/self/store-refs.ts
98
+
99
+
100
+ // src/ui/self/discover.ts
101
+
102
+
103
+ function projectRootFor(start) {
104
+ let dir = _fs.statSync.call(void 0, start).isDirectory() ? start : _path.dirname.call(void 0, start);
105
+ for (; ; ) {
106
+ if (["tsconfig.app.json", "tsconfig.json"].some((f) => _fs.existsSync.call(void 0, _path.resolve.call(void 0, dir, f))))
107
+ return dir;
108
+ const up = _path.dirname.call(void 0, dir);
109
+ if (up === dir)
110
+ return start;
111
+ dir = up;
112
+ }
113
+ }
114
+ function scopePrefix(root) {
115
+ const abs = _path.resolve.call(void 0, root);
116
+ try {
117
+ return _fs.statSync.call(void 0, abs).isDirectory() ? `${abs}/` : abs;
118
+ } catch (e3) {
119
+ return abs;
120
+ }
121
+ }
122
+ async function discover(roots, marker = "_loading") {
123
+ return (await discoverWithPaths(roots, marker)).map(({ abs: _abs, ...n }) => {
124
+ const i = n.anchor.lastIndexOf(":");
125
+ const abs = n.anchor.slice(0, i);
126
+ return { ...n, anchor: `${abs.split("/").slice(-2).join("/")}${n.anchor.slice(i)}` };
127
+ });
128
+ }
129
+ async function discoverWithPaths(roots, marker = "_loading") {
130
+ const rawRoots = Array.isArray(roots) ? roots : [roots];
131
+ const list = rawRoots.map(scopePrefix);
132
+ const project = await _chunk3X4FTHLCcjs.openProject.call(void 0, projectRootFor(_path.resolve.call(void 0, rawRoots[0])));
133
+ const { nodes } = _chunk3X4FTHLCcjs.buildStateGraph.call(void 0, project, (abs) => abs, marker);
134
+ return nodes.filter((n) => {
135
+ const fp = n.anchor.slice(0, n.anchor.lastIndexOf(":"));
136
+ return list.some((p) => p.endsWith("/") ? fp.startsWith(p) : fp === p);
137
+ }).map((n) => ({ ...n, abs: n.anchor.slice(0, n.anchor.lastIndexOf(":")) }));
138
+ }
139
+ async function locateExports(roots, names) {
140
+ const rawRoots = Array.isArray(roots) ? roots : [roots];
141
+ const project = await _chunk3X4FTHLCcjs.openProject.call(void 0, projectRootFor(_path.resolve.call(void 0, rawRoots[0])));
142
+ const out = /* @__PURE__ */ new Map();
143
+ for (const sf of project.getSourceFiles()) {
144
+ const fp = sf.getFilePath();
145
+ if (fp.endsWith(".d.ts") || fp.includes("/node_modules/"))
146
+ continue;
147
+ for (const v of sf.getVariableDeclarations()) {
148
+ const name = v.getName();
149
+ if (!names.has(name) || out.has(name))
150
+ continue;
151
+ const stmt = v.getVariableStatement();
152
+ if (stmt && stmt.isExported())
153
+ out.set(name, fp);
154
+ }
155
+ }
156
+ return out;
157
+ }
158
+
159
+ // src/ui/self/store-refs.ts
160
+ var VIRTUAL = "virtual:store-refs";
161
+ var RESOLVED = `\0${VIRTUAL}`;
162
+ function toAlias(abs, srcRoot) {
163
+ let rel = _path.relative.call(void 0, srcRoot, abs);
164
+ for (const ext of [".tsx", ".ts", ".jsx", ".js"]) {
165
+ if (rel.endsWith(ext)) {
166
+ rel = rel.slice(0, -ext.length);
167
+ break;
168
+ }
169
+ }
170
+ return `@/${rel}`;
171
+ }
172
+ function pickFacades(knobNames, discoveredNames) {
173
+ return [...knobNames].filter((n) => !discoveredNames.has(n));
174
+ }
175
+ function emitModule(refs) {
176
+ const byAlias = /* @__PURE__ */ new Map();
177
+ for (const r of refs) {
178
+ const names = _nullishCoalesce(byAlias.get(r.alias), () => ( []));
179
+ names.push(r.name);
180
+ byAlias.set(r.alias, names);
181
+ }
182
+ const imports = [];
183
+ for (const [alias, names] of byAlias)
184
+ imports.push(`import { ${[...new Set(names)].sort().join(", ")} } from '${alias}'`);
185
+ const all = [...new Set(refs.map((r) => r.name))].sort();
186
+ return `${imports.join("\n")}
187
+ export const stores = { ${all.join(", ")} }
188
+ `;
189
+ }
190
+ function storeRefs(options) {
191
+ const roots = Array.isArray(options.roots) ? options.roots : [options.roots];
192
+ let cache = null;
193
+ const build = async () => {
194
+ if (cache)
195
+ return cache;
196
+ const discovered = await discoverWithPaths(roots, options.storeMarker);
197
+ const byName = new Map(discovered.map((d) => [d.store, d.abs]));
198
+ const knobs = await _chunk3X4FTHLCcjs.buildPanel.call(void 0, options.knobFiles);
199
+ const knobNames = new Set(knobs.map((k) => k.store).filter(Boolean));
200
+ const facadeNames = new Set(pickFacades(knobNames, new Set(byName.keys())));
201
+ const facadePaths = facadeNames.size ? await locateExports(roots, facadeNames) : /* @__PURE__ */ new Map();
202
+ const refs = [];
203
+ const missing = [];
204
+ for (const name of knobNames) {
205
+ const abs = _nullishCoalesce(byName.get(name), () => ( facadePaths.get(name)));
206
+ if (!abs) {
207
+ missing.push(name);
208
+ continue;
209
+ }
210
+ refs.push({ name, alias: toAlias(abs, options.srcRoot) });
211
+ }
212
+ if (missing.length)
213
+ console.warn(`[aihand:store-refs] \u65CB\u94AE\u5F15\u7528\u4F46\u5B9A\u4F4D\u4E0D\u5230\u58F0\u660E\u7684 store(\u5DF2\u8DF3\u8FC7): ${missing.join(", ")}`);
214
+ cache = emitModule(refs);
215
+ return cache;
216
+ };
217
+ return {
218
+ name: "aihand:store-refs",
219
+ resolveId(id) {
220
+ if (id === VIRTUAL)
221
+ return RESOLVED;
222
+ },
223
+ async load(id) {
224
+ if (id !== RESOLVED)
225
+ return;
226
+ return build();
227
+ },
228
+ // store 文件或组件改动 → 失效缓存,下次 cold load 重 codegen(加旋钮引新 store → 自动并入)。
229
+ handleHotUpdate(ctx) {
230
+ if (roots.some((r) => ctx.file.startsWith(r)) || ctx.file.endsWith(".tsx") || ctx.file.endsWith(".jsx"))
231
+ cache = null;
232
+ }
233
+ };
234
+ }
235
+
236
+ // src/ui/self/store-schema.ts
237
+ var VIRTUAL2 = "virtual:store-schema";
238
+ var RESOLVED2 = "\0" + VIRTUAL2;
239
+ function storeSchema(options) {
240
+ let cache = null;
241
+ const roots = Array.isArray(options.roots) ? options.roots : [options.roots];
242
+ const build = async () => {
243
+ if (!cache)
244
+ cache = await discover(roots, options.storeMarker);
245
+ return cache;
246
+ };
247
+ return {
248
+ name: "aihand:store-schema",
249
+ resolveId(id) {
250
+ if (id === VIRTUAL2)
251
+ return RESOLVED2;
252
+ },
253
+ async load(id) {
254
+ if (id !== RESOLVED2)
255
+ return;
256
+ const grown = await build();
257
+ const byStore = {};
258
+ for (const g of grown)
259
+ byStore[g.store] = g;
260
+ return `export const schema = ${JSON.stringify(byStore)}
261
+ `;
262
+ },
263
+ // store 文件改动 → 失效缓存。下次 cold load 重跑引擎(~2s),值域改了 schema 跟着变。
264
+ // 不在 handleHotUpdate 里同步重投影:引擎离线纪律,绝不让 ts-morph 进同步 HMR 热路径 ——
265
+ // 值域是类型注解(改动罕见),下次 load 拿到即可,不值得为它在热路径养一个常驻 Project。
266
+ handleHotUpdate(ctx) {
267
+ if (roots.some((r) => ctx.file.startsWith(r)))
268
+ cache = null;
269
+ }
270
+ };
271
+ }
272
+
273
+ // src/ui/server/knob-schema.ts
274
+ var _glob = require('glob');
275
+ var VIRTUAL3 = "virtual:aihand-knobs";
276
+ var RESOLVED3 = "\0" + VIRTUAL3;
277
+ function keyOf(label, tag, file) {
278
+ return `${_nullishCoalesce(label, () => ( `<${tag}>`))} ${file}`;
279
+ }
280
+ function knobSchema(options = {}) {
281
+ const include = _nullishCoalesce(options.include, () => ( ["src/**/*.tsx", "src/**/*.jsx"]));
282
+ let cache = null;
283
+ const build = async () => {
284
+ if (cache)
285
+ return cache;
286
+ const files = _glob.globSync.call(void 0, include, { nodir: true, ignore: ["**/node_modules/**"] });
287
+ const knobs = await _chunk3X4FTHLCcjs.buildPanel.call(void 0, files);
288
+ const byKey = {};
289
+ for (const k of knobs) {
290
+ const file = k.filePath.split("/").slice(-2).join("/");
291
+ const { ops, arity, executable } = _chunk3X4FTHLCcjs.classifyKnob.call(void 0, k.transitions);
292
+ byKey[keyOf(k.label, k.tag, file)] = {
293
+ transitions: _chunk3X4FTHLCcjs.fmtTransitions.call(void 0, k.transitions),
294
+ store: k.store,
295
+ line: k.line,
296
+ ops,
297
+ arity,
298
+ executable
299
+ };
300
+ }
301
+ cache = byKey;
302
+ return byKey;
303
+ };
304
+ return {
305
+ name: "aihand:knob-schema",
306
+ resolveId(id) {
307
+ if (id === VIRTUAL3)
308
+ return RESOLVED3;
309
+ },
310
+ async load(id) {
311
+ if (id !== RESOLVED3)
312
+ return;
313
+ const knobs = await build();
314
+ return `export const knobs = ${JSON.stringify(knobs)}
315
+ `;
316
+ },
317
+ // 组件改动 → 失效缓存,HMR 重投影(onClick 改了 → 态射跟着变)。
318
+ handleHotUpdate(ctx) {
319
+ if (ctx.file.endsWith(".tsx") || ctx.file.endsWith(".jsx"))
320
+ cache = null;
321
+ }
322
+ };
323
+ }
324
+
325
+ // src/ui/server/plugin.ts
326
+ var _buffer = require('buffer');
327
+
328
+
329
+
330
+ var _esbuild = require('esbuild');
331
+
332
+ // src/ui/babel/source-loc.ts
333
+
334
+ function sourceLocPlugin(t) {
335
+ return {
336
+ name: "aihand-source-loc",
337
+ visitor: {
338
+ JSXOpeningElement(nodePath, state) {
339
+ const filename = _nullishCoalesce(_optionalChain([state, 'access', _4 => _4.file, 'optionalAccess', _5 => _5.opts, 'optionalAccess', _6 => _6.filename]), () => ( ""));
340
+ if (!filename || filename.includes("node_modules"))
341
+ return;
342
+ if (filename.includes("packages/aidev/"))
343
+ return;
344
+ const nameNode = nodePath.node.name;
345
+ if (!t.isJSXIdentifier(nameNode))
346
+ return;
347
+ const tag = nameNode.name;
348
+ if (tag[0] === tag[0].toUpperCase())
349
+ return;
350
+ if (hasInspAttr(t, nodePath.node))
351
+ return;
352
+ const loc = nodePath.node.loc;
353
+ if (!loc)
354
+ return;
355
+ const root = _nullishCoalesce(_optionalChain([state, 'access', _7 => _7.file, 'optionalAccess', _8 => _8.opts, 'optionalAccess', _9 => _9.root]), () => ( ""));
356
+ const rel = root ? _path2.default.relative(root, filename) : filename;
357
+ const value = `${rel}:${loc.start.line}:${loc.start.column + 1}:${_path2.default.basename(rel, _path2.default.extname(rel))}`;
358
+ nodePath.node.attributes.push(
359
+ t.jsxAttribute(t.jsxIdentifier("data-insp-path"), t.stringLiteral(value))
360
+ );
361
+ }
362
+ }
363
+ };
364
+ }
365
+ function hasInspAttr(t, node) {
366
+ return node.attributes.some((a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name) && a.name.name === "data-insp-path");
367
+ }
368
+
369
+ // src/ui/bridge/vite-bridge.ts
370
+ var VISIBLE_MS = 400;
371
+ function viteBridge(server, deps) {
372
+ const progressAt = /* @__PURE__ */ new Map();
373
+ server.hot.on("aihand:action-progress", (data) => {
374
+ if (typeof data.id === "number")
375
+ progressAt.set(data.id, Date.now());
376
+ });
377
+ const request = (reqEvent, replyEvent, payload, opts) => {
378
+ const tab = _optionalChain([opts, 'optionalAccess', _10 => _10.tab]);
379
+ const fullMs = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _11 => _11.timeoutMs]), () => ( 3e3));
380
+ const id = payload.id;
381
+ const lastProgress = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _12 => _12.lastProgress]), () => ( (id !== void 0 ? () => progressAt.get(id) : void 0)));
382
+ return new Promise((resolve5, reject) => {
383
+ let settled = false;
384
+ const onReply = (data) => {
385
+ if (settled)
386
+ return;
387
+ if (id !== void 0 && data.id !== id)
388
+ return;
389
+ settled = true;
390
+ cleanup();
391
+ resolve5(data);
392
+ };
393
+ server.hot.on(replyEvent, onReply);
394
+ const cleanup = () => {
395
+ server.hot.off(replyEvent, onReply);
396
+ if (id !== void 0)
397
+ progressAt.delete(id);
398
+ };
399
+ if (tab) {
400
+ const RETRY_MS = 500;
401
+ const ABSENT_CEILING_MS = 1e4;
402
+ const startedAt = Date.now();
403
+ const deliver = () => server.hot.send(reqEvent, { ...payload, tab });
404
+ deliver();
405
+ const iv = setInterval(() => {
406
+ if (settled) {
407
+ clearInterval(iv);
408
+ return;
409
+ }
410
+ const t = deps.tabs.get(tab);
411
+ const live = !!t && _chunkQF6AG3M5cjs.isLive.call(void 0, t, Date.now());
412
+ const progress = _optionalChain([lastProgress, 'optionalCall', _13 => _13()]);
413
+ const sinceLife = Date.now() - Math.max(startedAt, _nullishCoalesce(progress, () => ( 0)));
414
+ const elapsed = Date.now() - startedAt;
415
+ if (live) {
416
+ if (sinceLife > fullMs) {
417
+ clearInterval(iv);
418
+ cleanup();
419
+ reject(new Error(deps.diagnose(tab)));
420
+ }
421
+ } else if (elapsed > ABSENT_CEILING_MS) {
422
+ clearInterval(iv);
423
+ cleanup();
424
+ reject(new Error(deps.diagnose(tab)));
425
+ } else {
426
+ deliver();
427
+ }
428
+ }, RETRY_MS);
429
+ return;
430
+ }
431
+ server.hot.send(reqEvent, { ...payload, requireVisible: true });
432
+ setTimeout(() => {
433
+ if (settled)
434
+ return;
435
+ server.hot.send(reqEvent, { ...payload, requireVisible: false });
436
+ setTimeout(() => {
437
+ if (settled)
438
+ return;
439
+ cleanup();
440
+ reject(new Error(deps.diagnose(tab)));
441
+ }, fullMs);
442
+ }, VISIBLE_MS);
443
+ });
444
+ };
445
+ const emit2 = (event, payload) => server.hot.send(event, payload);
446
+ return { request, emit: emit2 };
447
+ }
448
+
449
+ // src/ui/core/check.ts
450
+ function check(raw) {
451
+ const consoleErrors = raw.console.filter((l) => l.level === "error");
452
+ const failedRequests = raw.network.filter((r) => r.status >= 400 || r.failed);
453
+ const assertions = [
454
+ {
455
+ name: "no-console-errors",
456
+ pass: consoleErrors.length === 0,
457
+ ...consoleErrors.length && { detail: consoleErrors[0].text }
458
+ },
459
+ {
460
+ name: "no-uncaught-errors",
461
+ pass: raw.errors.length === 0,
462
+ ...raw.errors.length && { detail: raw.errors[0].message }
463
+ },
464
+ {
465
+ name: "no-failed-requests",
466
+ pass: failedRequests.length === 0,
467
+ ...failedRequests.length && { detail: `${failedRequests[0].method} ${failedRequests[0].url} ${failedRequests[0].status}` }
468
+ },
469
+ {
470
+ name: "ui-not-empty",
471
+ pass: raw.ui.trim().length > 0,
472
+ ...!raw.ui.trim().length && { detail: "UI tree is empty" }
473
+ }
474
+ ];
475
+ return {
476
+ pass: assertions.every((a) => a.pass),
477
+ assertions
478
+ };
479
+ }
480
+
481
+ // src/ui/core/perf.ts
482
+ var FRAME_NOISE_PCT = 3;
483
+ var SELF_NOISE_REL = 0.1;
484
+ var SELF_NOISE_MS = 5;
485
+ var avgOf = (samples) => samples.length > 0 ? samples.reduce((s, v) => s + v, 0) / samples.length : 0;
486
+ function summarizeStat(stat) {
487
+ const avg = stat.n > 0 ? stat.total / stat.n : 0;
488
+ return {
489
+ total: stat.total,
490
+ n: stat.n,
491
+ avg,
492
+ max: stat.max
493
+ };
494
+ }
495
+ function compactPerformance(p) {
496
+ const all = p.buckets.find((b) => b.name === "__all__");
497
+ if (!all) return "perf: (no data)";
498
+ const { total, long, samples } = all.frames;
499
+ const fps = total > 0 ? Math.round(1e3 / avgOf(samples) * 10) / 10 : 0;
500
+ const droppedPct = total > 0 ? Math.round(long / total * 100) : 0;
501
+ const parts = [`${fps}fps`, `${droppedPct}% dropped`];
502
+ if (p.longtasks.count > 0) {
503
+ parts.push(`${p.longtasks.count} longtasks(max ${Math.round(p.longtasks.max)}ms)`);
504
+ }
505
+ const topComps = Object.entries(all.components).map(([name, stat]) => ({ name, total: stat.total })).sort((a, b) => b.total - a.total).slice(0, 2);
506
+ if (topComps.length > 0 && p.mobxPatched) {
507
+ const hot = topComps.map((c) => `${c.name} ${Math.round(c.total)}ms`).join(", ");
508
+ parts.push(`hot: ${hot}`);
509
+ }
510
+ return `perf: ${parts.join(" \xB7 ")}`;
511
+ }
512
+ function detailPerformance(p) {
513
+ const lines = [];
514
+ const windowSec = (p.windowMs / 1e3).toFixed(1);
515
+ lines.push(`=== Performance (${windowSec}s window) ===`);
516
+ if (p.hiddenFrames > 0) {
517
+ lines.push(`\u26A0 Tab was backgrounded or unfocused \u2014 ${p.hiddenFrames} throttled frames skipped (rAF throttling lies about timing)`);
518
+ lines.push("For accurate profiling: keep tab FOREGROUND + FOCUSED + run /profile/reset before reproducing");
519
+ }
520
+ lines.push(`MobX attribution: ${p.mobxPatched ? "ON" : "OFF"}`);
521
+ if (!p.mobxPatched) {
522
+ lines.push("(Component attribution disabled \u2014 set window.__AIPEEK_MOBX__={Reaction} in src/lib/mobx.ts to enable)");
523
+ }
524
+ lines.push("");
525
+ if (p.longtasks.count > 0) {
526
+ lines.push(`Longtasks (>50ms): ${p.longtasks.count}, max ${Math.round(p.longtasks.max)}ms`);
527
+ lines.push("");
528
+ }
529
+ const showPerView = p.buckets.length > 1;
530
+ for (const bucket of p.buckets) {
531
+ if (bucket.name !== "__all__" && !showPerView) continue;
532
+ lines.push(`--- ${bucket.name} ---`);
533
+ const { total, long, max, samples } = bucket.frames;
534
+ if (total === 0) {
535
+ lines.push("Frames: none sampled (tab unfocused/hidden \u2014 no frame data this window)");
536
+ } else {
537
+ const droppedPct = (long / total * 100).toFixed(1);
538
+ const avgFrame = avgOf(samples).toFixed(1);
539
+ lines.push(`Frames: ${total} total, ${long} dropped (${droppedPct}%), max ${Math.round(max)}ms, avg ${avgFrame}ms`);
540
+ }
541
+ const comps = Object.entries(bucket.components).map(([name, stat]) => ({ name, ...summarizeStat(stat) })).sort((a, b) => b.total - a.total).slice(0, 20);
542
+ if (comps.length > 0 && p.mobxPatched) {
543
+ lines.push("Components (by total self-time):");
544
+ for (const c of comps) {
545
+ lines.push(` ${c.name.padEnd(30)} ${Math.round(c.total)}ms / ${c.n}\xD7 = ${c.avg.toFixed(1)}ms avg, max ${Math.round(c.max)}ms`);
546
+ }
547
+ } else if (!p.mobxPatched) {
548
+ lines.push("(no component data \u2014 attribution OFF)");
549
+ }
550
+ const hotLines = Object.entries(_nullishCoalesce(bucket.lines, () => ( {}))).map(([label, stat]) => ({ label, ...summarizeStat(stat) })).sort((a, b) => b.total - a.total).slice(0, 15);
551
+ if (hotLines.length > 0) {
552
+ lines.push("Hot lines (by total inclusive time \u2014 contains nested calls):");
553
+ for (const l of hotLines) {
554
+ lines.push(` ${l.label.padEnd(36)} ${Math.round(l.total)}ms / ${l.n}\xD7 = ${l.avg.toFixed(1)}ms avg, max ${Math.round(l.max)}ms`);
555
+ }
556
+ }
557
+ lines.push("");
558
+ }
559
+ return lines.join("\n");
560
+ }
561
+ function diffPerformance(before, after) {
562
+ const out = [];
563
+ const beforeAll = before.buckets.find((b) => b.name === "__all__");
564
+ const afterAll = after.buckets.find((b) => b.name === "__all__");
565
+ const droppedPct = (b) => b && b.frames.total > 0 ? b.frames.long / b.frames.total * 100 : 0;
566
+ const beforeDrop = droppedPct(beforeAll);
567
+ const afterDrop = droppedPct(afterAll);
568
+ const dropDelta = afterDrop - beforeDrop;
569
+ const sumTotal = (b) => b ? Object.values(b.components).reduce((s, c) => s + c.total, 0) : 0;
570
+ const beforeSelf = sumTotal(beforeAll);
571
+ const afterSelf = sumTotal(afterAll);
572
+ const selfDelta = afterSelf - beforeSelf;
573
+ const bothSampledFrames = (_nullishCoalesce(_optionalChain([beforeAll, 'optionalAccess', _14 => _14.frames, 'access', _15 => _15.total]), () => ( 0))) > 0 && (_nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _16 => _16.frames, 'access', _17 => _17.total]), () => ( 0))) > 0;
574
+ const sawDrops = (_nullishCoalesce(_optionalChain([beforeAll, 'optionalAccess', _18 => _18.frames, 'access', _19 => _19.long]), () => ( 0))) > 0 || (_nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _20 => _20.frames, 'access', _21 => _21.long]), () => ( 0))) > 0;
575
+ const framesExercised = bothSampledFrames && sawDrops;
576
+ const afterBlind = afterSelf === 0 && (beforeSelf > 0 || (_nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _22 => _22.frames, 'access', _23 => _23.total]), () => ( 0))) === 0);
577
+ let verdict;
578
+ if (afterBlind) {
579
+ verdict = "NO DATA";
580
+ } else if (framesExercised) {
581
+ if (dropDelta < -FRAME_NOISE_PCT) verdict = "IMPROVED";
582
+ else if (dropDelta > FRAME_NOISE_PCT) verdict = "REGRESSED";
583
+ else verdict = "UNCHANGED";
584
+ } else {
585
+ const floor = Math.max(SELF_NOISE_MS, beforeSelf * SELF_NOISE_REL);
586
+ if (selfDelta < -floor) verdict = "IMPROVED";
587
+ else if (selfDelta > floor) verdict = "REGRESSED";
588
+ else verdict = "UNCHANGED";
589
+ }
590
+ out.push(`=== Perf diff: ${verdict} ===`);
591
+ if (afterBlind) {
592
+ const why = (_nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _24 => _24.frames, 'access', _25 => _25.total]), () => ( 0))) === 0 ? "recorded no frames and no re-renders" : `recorded frames but zero re-renders (was ${Math.round(beforeSelf)}ms self-time)`;
593
+ out.push(`after-window ${why} \u2014 tab backgrounded or workload not reproduced. Re-run with the tab focused and the interaction repeated.`);
594
+ out.push("");
595
+ return out.join("\n").trimEnd();
596
+ }
597
+ out.push(`Dropped frames: ${beforeDrop.toFixed(1)}% \u2192 ${afterDrop.toFixed(1)}% (${dropDelta >= 0 ? "+" : ""}${dropDelta.toFixed(1)}%)`);
598
+ if (!framesExercised && Math.round(selfDelta) !== 0) {
599
+ out.push(`Total self-time: ${Math.round(beforeSelf)}ms \u2192 ${Math.round(afterSelf)}ms (${selfDelta >= 0 ? "+" : ""}${Math.round(selfDelta)}ms) \u2014 frames flat, verdict by self-time`);
600
+ }
601
+ if (before.longtasks.count !== after.longtasks.count) {
602
+ out.push(`Longtasks: ${before.longtasks.count} \u2192 ${after.longtasks.count}`);
603
+ }
604
+ out.push("");
605
+ const compDelta = keyedTotalDelta(_nullishCoalesce(_optionalChain([beforeAll, 'optionalAccess', _26 => _26.components]), () => ( {})), _nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _27 => _27.components]), () => ( {})));
606
+ if (compDelta.length > 0) {
607
+ out.push("Components (\u0394 total self-time):");
608
+ for (const d of compDelta) out.push(" " + formatDelta(d));
609
+ out.push("");
610
+ }
611
+ const lineDelta = keyedTotalDelta(_nullishCoalesce(_optionalChain([beforeAll, 'optionalAccess', _28 => _28.lines]), () => ( {})), _nullishCoalesce(_optionalChain([afterAll, 'optionalAccess', _29 => _29.lines]), () => ( {})));
612
+ if (lineDelta.length > 0) {
613
+ out.push("Hot lines (\u0394 total inclusive time):");
614
+ for (const d of lineDelta) out.push(" " + formatDelta(d));
615
+ }
616
+ return out.join("\n").trimEnd();
617
+ }
618
+ function keyedTotalDelta(a, b) {
619
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
620
+ const rows = [];
621
+ for (const key of keys) {
622
+ const beforeT = _nullishCoalesce(_optionalChain([a, 'access', _30 => _30[key], 'optionalAccess', _31 => _31.total]), () => ( 0));
623
+ const afterT = _nullishCoalesce(_optionalChain([b, 'access', _32 => _32[key], 'optionalAccess', _33 => _33.total]), () => ( 0));
624
+ const delta = afterT - beforeT;
625
+ if (Math.round(delta) === 0) continue;
626
+ rows.push({ key, before: beforeT, after: afterT, delta, isNew: !a[key], gone: !b[key] });
627
+ }
628
+ return rows.sort((x, y) => Math.abs(y.delta) - Math.abs(x.delta)).slice(0, 20);
629
+ }
630
+ function formatDelta(d) {
631
+ const sign = d.delta >= 0 ? "+" : "";
632
+ const tag = d.isNew ? " (new)" : d.gone ? " (gone)" : "";
633
+ return `${d.key.padEnd(36)} ${Math.round(d.before)}ms \u2192 ${Math.round(d.after)}ms (${sign}${Math.round(d.delta)}ms)${tag}`;
634
+ }
635
+
636
+ // src/ui/core/compact.ts
637
+ var SLOW_THRESHOLD = 1e3;
638
+ var MAX_UI_DEPTH = 6;
639
+ var UI_PRIMITIVES = /* @__PURE__ */ new Set([
640
+ "Button",
641
+ "Input",
642
+ "Label",
643
+ "Badge",
644
+ "Checkbox",
645
+ "Skeleton",
646
+ "Spinner",
647
+ "Switch",
648
+ "Tabs",
649
+ "Tooltip",
650
+ "Popover",
651
+ "Dialog",
652
+ "Select",
653
+ "Card",
654
+ "Table",
655
+ "Slider",
656
+ "Progress",
657
+ "RadioGroup",
658
+ "HoverCard",
659
+ "DropdownMenu",
660
+ "ContextMenu",
661
+ "Command",
662
+ "Form",
663
+ "Alert",
664
+ "Pagination",
665
+ "Textarea",
666
+ "TooltipProvider",
667
+ "DialogPortal",
668
+ "Router",
669
+ "RenderErrorBoundary",
670
+ "RouterProvider",
671
+ "RouterProvider2",
672
+ "PanelGroup",
673
+ "Panel"
674
+ ]);
675
+ function nameOf(line) {
676
+ let end = 0;
677
+ while (end < line.length) {
678
+ const c = line[end];
679
+ if (c === " " || c === " " || c === "[" || c === "\u2014")
680
+ break;
681
+ end++;
682
+ }
683
+ return line.slice(0, end);
684
+ }
685
+ function compactUI(tree) {
686
+ if (!tree)
687
+ return "";
688
+ const lines = tree.split("\n");
689
+ const result = [];
690
+ const repeatTracker = /* @__PURE__ */ new Map();
691
+ for (let i = 0; i < lines.length; i++) {
692
+ const line = lines[i];
693
+ const trimmed = line.trimStart();
694
+ if (!trimmed)
695
+ continue;
696
+ const indent = line.length - trimmed.length;
697
+ const depth = Math.floor(indent / 2);
698
+ if (depth > MAX_UI_DEPTH)
699
+ continue;
700
+ const componentName = nameOf(trimmed);
701
+ if (UI_PRIMITIVES.has(componentName))
702
+ continue;
703
+ const key = `${depth}:${componentName}`;
704
+ const tracker = repeatTracker.get(key);
705
+ if (tracker && i - tracker.lastIndex <= 2) {
706
+ tracker.count++;
707
+ tracker.lastIndex = i;
708
+ continue;
709
+ }
710
+ for (const [k, t] of repeatTracker) {
711
+ if (t.count > 1) {
712
+ const d = Number.parseInt(k.split(":")[0]);
713
+ const name = k.split(":").slice(1).join(":");
714
+ result.push(`${" ".repeat(d)}${name} \xD7${t.count}`);
715
+ }
716
+ if (t.count > 1 || i - t.lastIndex > 2) {
717
+ repeatTracker.delete(k);
718
+ }
719
+ }
720
+ repeatTracker.set(key, { count: 1, lastIndex: i });
721
+ result.push(line);
722
+ }
723
+ for (const [k, t] of repeatTracker) {
724
+ if (t.count > 1) {
725
+ const d = Number.parseInt(k.split(":")[0]);
726
+ const name = k.split(":").slice(1).join(":");
727
+ result.push(`${" ".repeat(d)}${name} \xD7${t.count}`);
728
+ }
729
+ }
730
+ return result.join("\n");
731
+ }
732
+ var NOISE_SUBSTRINGS = [
733
+ "[hmr]",
734
+ "[vite]",
735
+ "hot module",
736
+ "react-devtools",
737
+ "download the react devtools",
738
+ "warning: react does not recognize",
739
+ "source map",
740
+ "favicon.ico",
741
+ "webpack"
742
+ ];
743
+ function compactConsole(logs) {
744
+ if (!logs.length)
745
+ return "";
746
+ const filtered = logs.filter((l) => {
747
+ const lower = l.text.toLowerCase();
748
+ return !NOISE_SUBSTRINGS.some((s) => lower.includes(s));
749
+ });
750
+ if (!filtered.length)
751
+ return "";
752
+ const deduped = [];
753
+ for (const log of filtered) {
754
+ const last = deduped[deduped.length - 1];
755
+ if (last && last.entry.text === log.text && last.entry.level === log.level) {
756
+ last.count++;
757
+ } else {
758
+ deduped.push({ entry: log, count: 1 });
759
+ }
760
+ }
761
+ const errors = deduped.filter((d) => d.entry.level === "error");
762
+ const warns = deduped.filter((d) => d.entry.level === "warn");
763
+ const rest = deduped.filter((d) => d.entry.level !== "error" && d.entry.level !== "warn");
764
+ const recentRest = rest.slice(-10);
765
+ const lines = [];
766
+ for (const group of [...errors, ...warns, ...recentRest]) {
767
+ const prefix = `[${group.entry.level}]`;
768
+ const count = group.count > 1 ? ` \xD7${group.count}` : "";
769
+ const source = group.entry.source ? ` (${group.entry.source})` : "";
770
+ const text = _chunkQF6AG3M5cjs.truncate.call(void 0, group.entry.text, 200);
771
+ lines.push(`${prefix}${count} ${text}${source}`);
772
+ }
773
+ return lines.join("\n");
774
+ }
775
+ function compactNetwork(requests) {
776
+ if (!requests.length)
777
+ return "";
778
+ const relevant = requests.filter(
779
+ (r) => r.resourceType === "fetch" || r.resourceType === "xhr" || r.resourceType === "websocket" || r.resourceType === "eventsource" || isApiUrl(r.url)
780
+ );
781
+ if (!relevant.length)
782
+ return "";
783
+ const lines = [];
784
+ for (const req of relevant) {
785
+ const duration = req.duration > 0 ? ` ${formatDuration(req.duration)}` : "";
786
+ const slow = req.duration >= SLOW_THRESHOLD ? " [SLOW]" : "";
787
+ const url = _chunkQF6AG3M5cjs.compactUrl.call(void 0, req.url, 50);
788
+ const headers = diagnosticHeaders(req);
789
+ if (req.failed || req.status >= 400) {
790
+ const body = req.responseBody ? ` "${_chunkQF6AG3M5cjs.truncate.call(void 0, req.responseBody, 100)}"` : "";
791
+ const failure = req.failureText ? ` (${req.failureText})` : "";
792
+ lines.push(`${req.method} ${url} ${req.status}${failure}${body}${headers}${duration}${slow}`);
793
+ } else {
794
+ lines.push(`${req.method} ${url} ${req.status}${headers}${duration}${slow}`);
795
+ }
796
+ }
797
+ return lines.join("\n");
798
+ }
799
+ function isApiUrl(url) {
800
+ try {
801
+ const u = new URL(url);
802
+ return u.pathname.startsWith("/api") || u.pathname.includes("/graphql");
803
+ } catch (e4) {
804
+ return false;
805
+ }
806
+ }
807
+ var DIAGNOSTIC_HEADERS = ["content-type", "x-error", "www-authenticate", "access-control-allow-origin"];
808
+ function diagnosticHeaders(req) {
809
+ const h = req.responseHeaders;
810
+ if (!h)
811
+ return "";
812
+ const parts = [];
813
+ for (const key of DIAGNOSTIC_HEADERS) {
814
+ const val = h[key];
815
+ if (!val)
816
+ continue;
817
+ if (key === "content-type" && req.status < 400 && val.includes("application/json"))
818
+ continue;
819
+ parts.push(`${key}: ${_chunkQF6AG3M5cjs.truncate.call(void 0, val, 60)}`);
820
+ }
821
+ if (!parts.length)
822
+ return "";
823
+ return ` [${parts.join(", ")}]`;
824
+ }
825
+ function formatDuration(ms) {
826
+ return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${Math.round(ms)}ms`;
827
+ }
828
+ function compactErrors(errors) {
829
+ if (!errors.length)
830
+ return "";
831
+ const seen = /* @__PURE__ */ new Map();
832
+ for (const err of errors) {
833
+ if (!seen.has(err.message)) {
834
+ seen.set(err.message, err);
835
+ }
836
+ }
837
+ const lines = [];
838
+ for (const err of seen.values()) {
839
+ lines.push(err.message);
840
+ if (err.stack) {
841
+ for (const frame of _chunkQF6AG3M5cjs.appStackFrames.call(void 0, err.stack, 5))
842
+ lines.push(` ${frame}`);
843
+ }
844
+ }
845
+ return lines.join("\n");
846
+ }
847
+ function compactState(state) {
848
+ if (!state || !Object.keys(state).length)
849
+ return "";
850
+ const lines = [];
851
+ for (const [name, value] of Object.entries(state)) {
852
+ lines.push(`${name}:`);
853
+ if (typeof value === "object" && value !== null) {
854
+ for (const [k, v] of Object.entries(value)) {
855
+ lines.push(` ${k}: ${_chunkQF6AG3M5cjs.truncate.call(void 0, _chunkQF6AG3M5cjs.formatValue.call(void 0, v), 120)}`);
856
+ }
857
+ } else {
858
+ lines.push(` ${String(value)}`);
859
+ }
860
+ }
861
+ return lines.join("\n");
862
+ }
863
+ function compact(raw) {
864
+ return {
865
+ url: raw.url,
866
+ ui: compactUI(raw.ui),
867
+ console: compactConsole(raw.console),
868
+ network: compactNetwork(raw.network),
869
+ errors: compactErrors(raw.errors),
870
+ state: compactState(raw.state),
871
+ performance: raw.performance ? detailPerformance(raw.performance) : void 0,
872
+ timestamp: raw.timestamp,
873
+ counts: {
874
+ console: raw.console.length,
875
+ network: raw.network.length,
876
+ errors: raw.errors.length,
877
+ state: Object.keys(raw.state).length
878
+ }
879
+ };
880
+ }
881
+
882
+ // src/ui/core/detail.ts
883
+ var headerLine = (k, v) => ` ${k}: ${_chunkQF6AG3M5cjs.isSecretKey.call(void 0, k) ? _chunkQF6AG3M5cjs.redactSecretValue.call(void 0, v) : v}`;
884
+ function detail(raw, section, index, full) {
885
+ switch (section) {
886
+ case "ui":
887
+ return full ? raw.ui || null : compactUI(raw.ui) || null;
888
+ case "console":
889
+ return detailConsole(raw.console, index, full);
890
+ case "network":
891
+ return detailNetwork(raw.network, index, full);
892
+ case "errors":
893
+ return detailError(raw.errors, index, full);
894
+ case "state":
895
+ return detailState(raw.state, index, full);
896
+ case "profile":
897
+ return raw.performance ? detailPerformance(raw.performance) : "(no perf data \u2014 is the tab foreground?)";
898
+ default:
899
+ return null;
900
+ }
901
+ }
902
+ function detailConsole(logs, index, full) {
903
+ if (index === void 0) {
904
+ if (!logs.length)
905
+ return "(empty)";
906
+ return logs.map((log2, i2) => `[${i2}] [${log2.level}] ${_chunkQF6AG3M5cjs.truncate.call(void 0, log2.text, 120)}`).join("\n");
907
+ }
908
+ const i = Number.parseInt(index);
909
+ if (Number.isNaN(i) || i < 0 || i >= logs.length)
910
+ return null;
911
+ const log = logs[i];
912
+ if (full) {
913
+ const parts = [`[${log.level}] ${log.text}`];
914
+ if (log.timestamp)
915
+ parts.push(`timestamp: ${new Date(log.timestamp).toISOString()}`);
916
+ if (log.source)
917
+ parts.push(`source: ${log.source}`);
918
+ return parts.join("\n");
919
+ }
920
+ return `[${log.level}] ${_chunkQF6AG3M5cjs.truncate.call(void 0, log.text, 200)}`;
921
+ }
922
+ function detailNetwork(requests, index, full) {
923
+ if (index === void 0) {
924
+ if (!requests.length)
925
+ return "(empty)";
926
+ return requests.map((req2, i2) => `[${i2}] ${req2.method} ${req2.status} ${_chunkQF6AG3M5cjs.truncate.call(void 0, req2.url, 80)} ${req2.duration}ms${req2.failed ? " FAILED" : ""}`).join("\n");
927
+ }
928
+ const i = Number.parseInt(index);
929
+ if (Number.isNaN(i) || i < 0 || i >= requests.length)
930
+ return null;
931
+ const req = requests[i];
932
+ const lines = [
933
+ `${req.method} ${req.url}`,
934
+ `status: ${req.status}`,
935
+ `duration: ${req.duration}ms`,
936
+ `type: ${req.resourceType}`
937
+ ];
938
+ if (req.failed)
939
+ lines.push(`failed: ${req.failureText || "true"}`);
940
+ if (full) {
941
+ if (req.requestHeaders && Object.keys(req.requestHeaders).length) {
942
+ lines.push("request-headers:");
943
+ for (const [k, v] of Object.entries(req.requestHeaders)) lines.push(headerLine(k, v));
944
+ }
945
+ if (req.requestBody)
946
+ lines.push(`request-body:
947
+ ${req.requestBody}`);
948
+ if (req.responseHeaders && Object.keys(req.responseHeaders).length) {
949
+ lines.push("response-headers:");
950
+ for (const [k, v] of Object.entries(req.responseHeaders)) lines.push(headerLine(k, v));
951
+ }
952
+ if (req.responseBody)
953
+ lines.push(`response-body:
954
+ ${req.responseBody}`);
955
+ } else {
956
+ if (req.requestBody) {
957
+ lines.push(`request-body: ${byteSize(req.requestBody)}`);
958
+ if (req.requestSample) {
959
+ const schema = jsonSchema(req.requestSample);
960
+ if (schema)
961
+ lines.push(schema);
962
+ }
963
+ }
964
+ if (req.responseBody) {
965
+ if (req.status >= 400) {
966
+ lines.push(`response-body: ${byteSize(req.responseBody)} "${_chunkQF6AG3M5cjs.truncate.call(void 0, req.responseBody, 100)}"`);
967
+ } else {
968
+ if (req.responseSample) {
969
+ lines.push(`response-body: ${byteSize(req.responseBody)}`);
970
+ const schema = jsonSchema(req.responseSample);
971
+ if (schema)
972
+ lines.push(schema);
973
+ } else {
974
+ lines.push(`response-body: ${byteSize(req.responseBody)} ${_chunkQF6AG3M5cjs.truncate.call(void 0, req.responseBody, 100)}`);
975
+ }
976
+ }
977
+ }
978
+ }
979
+ return lines.join("\n");
980
+ }
981
+ function detailError(errors, index, full) {
982
+ if (index === void 0) {
983
+ if (!errors.length)
984
+ return "(empty)";
985
+ return errors.map((err2, i2) => `[${i2}] ${_chunkQF6AG3M5cjs.truncate.call(void 0, err2.message, 120)}`).join("\n");
986
+ }
987
+ const i = Number.parseInt(index);
988
+ if (Number.isNaN(i) || i < 0 || i >= errors.length)
989
+ return null;
990
+ const err = errors[i];
991
+ if (full) {
992
+ const lines2 = [err.message];
993
+ if (err.stack)
994
+ lines2.push(err.stack);
995
+ if (err.source)
996
+ lines2.push(`source: ${err.source}`);
997
+ if (err.line != null)
998
+ lines2.push(`location: ${err.source || ""}:${err.line}:${_nullishCoalesce(err.column, () => ( 0))}`);
999
+ return lines2.join("\n");
1000
+ }
1001
+ const lines = [err.message];
1002
+ if (err.stack) {
1003
+ const all = _chunkQF6AG3M5cjs.appStackFrames.call(void 0, err.stack, Infinity);
1004
+ lines.push(...all.slice(0, 3));
1005
+ if (all.length > 3)
1006
+ lines.push(` ... ${all.length - 3} more app frames`);
1007
+ }
1008
+ if (err.line != null)
1009
+ lines.push(`location: ${err.source || ""}:${err.line}:${_nullishCoalesce(err.column, () => ( 0))}`);
1010
+ return lines.join("\n");
1011
+ }
1012
+ function detailState(state, name, full) {
1013
+ if (!name) {
1014
+ const keys = Object.keys(state);
1015
+ if (!keys.length)
1016
+ return "(empty)";
1017
+ return keys.map((k) => `${k}: ${formatSummaryValue(state[k])}`).join("\n");
1018
+ }
1019
+ if (!(name in state))
1020
+ return null;
1021
+ const value = state[name];
1022
+ if (full) {
1023
+ try {
1024
+ return _nullishCoalesce(JSON.stringify(value, null, 2), () => ( _chunkQF6AG3M5cjs.formatValue.call(void 0, value)));
1025
+ } catch (e5) {
1026
+ return _chunkQF6AG3M5cjs.formatValue.call(void 0, value);
1027
+ }
1028
+ }
1029
+ if (typeof value !== "object" || value === null)
1030
+ return `${name}: ${typeof value}`;
1031
+ const lines = [];
1032
+ for (const [k, v] of Object.entries(value)) {
1033
+ lines.push(`${k}: ${formatSummaryValue(v)}`);
1034
+ }
1035
+ return lines.join("\n");
1036
+ }
1037
+ function isArraySentinel(v) {
1038
+ if (!v.startsWith("Array(") || !v.endsWith(")"))
1039
+ return false;
1040
+ const digits = v.slice(6, -1);
1041
+ return digits.length > 0 && [...digits].every((c) => c >= "0" && c <= "9");
1042
+ }
1043
+ function formatSummaryValue(v) {
1044
+ if (typeof v === "string" && isArraySentinel(v))
1045
+ return v;
1046
+ return _chunkQF6AG3M5cjs.truncate.call(void 0, _chunkQF6AG3M5cjs.formatValue.call(void 0, v), 80);
1047
+ }
1048
+ function jsonSchema(sample) {
1049
+ try {
1050
+ return schemaOf(JSON.parse(sample), 0);
1051
+ } catch (e6) {
1052
+ return null;
1053
+ }
1054
+ }
1055
+ function schemaOf(v, d) {
1056
+ if (v === null)
1057
+ return "null";
1058
+ if (typeof v === "string")
1059
+ return "string";
1060
+ if (typeof v === "number")
1061
+ return "number";
1062
+ if (typeof v === "boolean")
1063
+ return "boolean";
1064
+ if (Array.isArray(v)) {
1065
+ if (!v.length)
1066
+ return "[]";
1067
+ if (d >= 3)
1068
+ return "[\u2026]";
1069
+ return `${schemaOf(v[0], d + 1)}[]`;
1070
+ }
1071
+ if (typeof v === "object") {
1072
+ if (d >= 3)
1073
+ return "{\u2026}";
1074
+ const entries = Object.entries(v);
1075
+ if (!entries.length)
1076
+ return "{}";
1077
+ const max = d === 0 ? 12 : 6;
1078
+ const fields = entries.slice(0, max).map(([k, val]) => `${k}: ${schemaOf(val, d + 1)}`);
1079
+ if (entries.length > max)
1080
+ fields.push(`\u2026 ${entries.length - max} more`);
1081
+ return `{ ${fields.join(", ")} }`;
1082
+ }
1083
+ return typeof v;
1084
+ }
1085
+ function byteSize(s) {
1086
+ const bytes = new TextEncoder().encode(s).length;
1087
+ if (bytes < 1024)
1088
+ return `${bytes}B`;
1089
+ if (bytes < 1024 * 1024)
1090
+ return `${(bytes / 1024).toFixed(1)}KB`;
1091
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
1092
+ }
1093
+
1094
+ // src/ui/core/diff.ts
1095
+ var GONE_FRAC = 0.15;
1096
+ function regionsChanged(before, after) {
1097
+ if (!before.length || !after.length)
1098
+ return false;
1099
+ const afterKeys = new Set(after.map((r) => r.key));
1100
+ const beforeKeys = new Set(before.map((r) => r.key));
1101
+ const gone = before.filter((r) => !afterKeys.has(r.key)).length;
1102
+ const appeared = after.filter((r) => !beforeKeys.has(r.key)).length;
1103
+ return gone >= before.length * GONE_FRAC && appeared > 0;
1104
+ }
1105
+ var POPOVER_MIN_CHILDREN = 2;
1106
+ function popoverChange(before, after) {
1107
+ const beforeKeys = new Set(before.map((r) => r.key));
1108
+ const afterKeys = new Set(after.map((r) => r.key));
1109
+ const appeared = after.filter((r) => !beforeKeys.has(r.key));
1110
+ const gone = before.filter((r) => !afterKeys.has(r.key));
1111
+ const top = (rs) => rs.reduce((m, r) => !m || childCount(r) > childCount(m) ? r : m, void 0);
1112
+ const add = top(appeared);
1113
+ if (appeared.length > gone.length && add && childCount(add) >= POPOVER_MIN_CHILDREN)
1114
+ return `panel: appeared (${add.label})`;
1115
+ const rm = top(gone);
1116
+ if (gone.length > appeared.length && rm && childCount(rm) >= POPOVER_MIN_CHILDREN)
1117
+ return `panel: dismissed (${rm.label})`;
1118
+ return void 0;
1119
+ }
1120
+ function childCount(r) {
1121
+ const i = r.key.lastIndexOf("#");
1122
+ return i < 0 ? 0 : Number(r.key.slice(i + 1)) || 0;
1123
+ }
1124
+ function diffScreen(before, after, newErrors, newExceptions, newFailedRequests) {
1125
+ const lines = [];
1126
+ let bodyOrModalNamed = false;
1127
+ if (after.view !== before.view) {
1128
+ lines.push(`view: ${before.view} \u2192 ${after.view}`);
1129
+ bodyOrModalNamed = true;
1130
+ } else if (before.view === "(unknown)" && after.modal === "none" && regionsChanged(_nullishCoalesce(before.regions, () => ( [])), _nullishCoalesce(after.regions, () => ( [])))) {
1131
+ lines.push("view: changed (DOM \u533A\u57DF\u6307\u7EB9)");
1132
+ bodyOrModalNamed = true;
1133
+ }
1134
+ if (after.modal !== before.modal) {
1135
+ if (after.modal === "none")
1136
+ lines.push(`modal: closed (${before.modal})`);
1137
+ else if (before.modal === "none")
1138
+ lines.push(`modal: opened ${after.modal}`);
1139
+ else
1140
+ lines.push(`modal: ${before.modal} \u2192 ${after.modal}`);
1141
+ bodyOrModalNamed = true;
1142
+ }
1143
+ if (!bodyOrModalNamed) {
1144
+ const pop = popoverChange(_nullishCoalesce(before.regions, () => ( [])), _nullishCoalesce(after.regions, () => ( [])));
1145
+ if (pop)
1146
+ lines.push(pop);
1147
+ }
1148
+ if (after.focus !== before.focus)
1149
+ lines.push(`focus: ${after.focus}`);
1150
+ const beforeDomain = _nullishCoalesce(before.domain, () => ( {}));
1151
+ const afterDomain = _nullishCoalesce(after.domain, () => ( {}));
1152
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(beforeDomain), ...Object.keys(afterDomain)])) {
1153
+ const b = redactDomain(key, beforeDomain[key]);
1154
+ const a = redactDomain(key, afterDomain[key]);
1155
+ if (b !== a)
1156
+ lines.push(`${key}: ${b} \u2192 ${a}`);
1157
+ }
1158
+ for (const e of newErrors)
1159
+ lines.push(`+error: ${e.text}`);
1160
+ for (const e of newExceptions)
1161
+ lines.push(`+exception: ${e.message}`);
1162
+ for (const r of newFailedRequests)
1163
+ lines.push(`+failed: ${r.method} ${r.url} \u2192 ${r.status || "failed"}`);
1164
+ return lines;
1165
+ }
1166
+ function stringifyDomain(v) {
1167
+ if (v === null || v === void 0)
1168
+ return String(v);
1169
+ if (typeof v === "object")
1170
+ return JSON.stringify(v).slice(0, 80);
1171
+ return String(v).slice(0, 80);
1172
+ }
1173
+ function redactDomain(key, v) {
1174
+ const s = stringifyDomain(v);
1175
+ if (!_chunkQF6AG3M5cjs.isSecretKey.call(void 0, key))
1176
+ return s;
1177
+ const raw = v === null || v === void 0 ? "" : String(v);
1178
+ return raw ? _chunkQF6AG3M5cjs.redactSecretValue.call(void 0, raw) : s;
1179
+ }
1180
+ function diffState(prev, curr) {
1181
+ if (!prev) {
1182
+ return {
1183
+ newErrors: curr.console.filter((l) => l.level === "error"),
1184
+ newExceptions: [...curr.errors],
1185
+ newFailedRequests: curr.network.filter((r) => r.status >= 400 || r.failed),
1186
+ uiGone: false,
1187
+ clean: curr.console.filter((l) => l.level === "error").length === 0 && curr.errors.length === 0 && curr.network.filter((r) => r.status >= 400 || r.failed).length === 0
1188
+ };
1189
+ }
1190
+ const prevErrorTexts = new Set(prev.console.filter((l) => l.level === "error").map((l) => l.text));
1191
+ const newErrors = curr.console.filter((l) => l.level === "error" && !prevErrorTexts.has(l.text));
1192
+ const prevExceptionMsgs = new Set(prev.errors.map((e) => e.message));
1193
+ const newExceptions = curr.errors.filter((e) => !prevExceptionMsgs.has(e.message));
1194
+ const prevFailedKeys = new Set(
1195
+ prev.network.filter((r) => r.status >= 400 || r.failed).map((r) => `${r.method}|${r.url}|${r.status}`)
1196
+ );
1197
+ const newFailedRequests = curr.network.filter((r) => r.status >= 400 || r.failed).filter((r) => !prevFailedKeys.has(`${r.method}|${r.url}|${r.status}`));
1198
+ const uiGone = prev.ui.trim().length > 0 && curr.ui.trim().length === 0;
1199
+ return {
1200
+ newErrors,
1201
+ newExceptions,
1202
+ newFailedRequests,
1203
+ uiGone,
1204
+ clean: newErrors.length === 0 && newExceptions.length === 0 && newFailedRequests.length === 0 && !uiGone
1205
+ };
1206
+ }
1207
+
1208
+ // src/ui/core/emit.ts
1209
+ var _picocolors = require('picocolors'); var _picocolors2 = _interopRequireDefault(_picocolors);
1210
+ var SECTIONS = ["ui", "console", "network", "errors", "state", "performance"];
1211
+ var COUNTED_SECTIONS = {
1212
+ console: "console",
1213
+ network: "network",
1214
+ errors: "errors",
1215
+ state: "state"
1216
+ };
1217
+ function emit(state) {
1218
+ const sections = [];
1219
+ for (const key of SECTIONS) {
1220
+ if (state[key]) {
1221
+ const countKey = COUNTED_SECTIONS[key];
1222
+ const count = countKey ? _nullishCoalesce(_optionalChain([state, 'access', _34 => _34.counts, 'optionalAccess', _35 => _35[countKey]]), () => ( 0)) : 0;
1223
+ const attr = count ? ` count="${count}"` : "";
1224
+ sections.push(`<${key}${attr}>
1225
+ ${state[key]}
1226
+ </${key}>`);
1227
+ }
1228
+ }
1229
+ if (!sections.length) {
1230
+ sections.push("<empty/>");
1231
+ }
1232
+ return `<aihand url="${state.url}">
1233
+
1234
+ ${sections.join("\n\n")}
1235
+
1236
+ </aihand>
1237
+
1238
+ detail: GET /__aihand/{section}/{index}?full`;
1239
+ }
1240
+ function emitSummary(raw) {
1241
+ const consoleErrors = raw.console.filter((l) => l.level === "error");
1242
+ const consoleWarns = raw.console.filter((l) => l.level === "warn");
1243
+ const failedReqs = raw.network.filter((r) => r.status >= 400 || r.failed);
1244
+ const hasIssues = consoleErrors.length > 0 || raw.errors.length > 0 || failedReqs.length > 0;
1245
+ const lines = [];
1246
+ if (raw.ui.trim()) {
1247
+ lines.push(`ui: ${summarizeUI(raw.ui)}`);
1248
+ }
1249
+ if (raw.console.length) {
1250
+ if (consoleErrors.length || consoleWarns.length) {
1251
+ const parts = [];
1252
+ for (const e of consoleErrors) parts.push(` [error] ${_chunkQF6AG3M5cjs.truncate.call(void 0, e.text, 150)}`);
1253
+ for (const w of consoleWarns) parts.push(` [warn] ${_chunkQF6AG3M5cjs.truncate.call(void 0, w.text, 150)}`);
1254
+ const rest = raw.console.length - consoleErrors.length - consoleWarns.length;
1255
+ if (rest > 0)
1256
+ parts.push(` \u2026 ${rest} more`);
1257
+ lines.push(`console (${raw.console.length}):`);
1258
+ lines.push(...parts);
1259
+ } else {
1260
+ lines.push(`console: ${raw.console.length} logs`);
1261
+ }
1262
+ }
1263
+ if (raw.network.length) {
1264
+ if (failedReqs.length) {
1265
+ lines.push(`network (${raw.network.length}):`);
1266
+ for (const r of failedReqs) {
1267
+ const body = r.responseBody ? ` "${_chunkQF6AG3M5cjs.truncate.call(void 0, r.responseBody, 80)}"` : "";
1268
+ lines.push(` ${r.method} ${_chunkQF6AG3M5cjs.compactUrl.call(void 0, r.url)} ${r.status}${body}`);
1269
+ }
1270
+ const ok = raw.network.length - failedReqs.length;
1271
+ if (ok > 0)
1272
+ lines.push(` \u2026 ${ok} ok`);
1273
+ } else {
1274
+ lines.push(`network: ${raw.network.length} ok`);
1275
+ }
1276
+ }
1277
+ if (raw.errors.length) {
1278
+ lines.push(`errors (${raw.errors.length}):`);
1279
+ for (const e of raw.errors) lines.push(` ${_chunkQF6AG3M5cjs.truncate.call(void 0, e.message, 150)}`);
1280
+ }
1281
+ if (raw.performance) {
1282
+ const all = raw.performance.buckets.find((b) => b.name === "__all__");
1283
+ if (all) {
1284
+ const droppedPct = all.frames.total > 0 ? all.frames.long / all.frames.total * 100 : 0;
1285
+ if (droppedPct > 10) {
1286
+ lines.push(compactPerformance(raw.performance));
1287
+ }
1288
+ }
1289
+ }
1290
+ const storeNames = Object.keys(raw.state);
1291
+ if (storeNames.length) {
1292
+ const parts = storeNames.map((n) => {
1293
+ const v = raw.state[n];
1294
+ const keys = typeof v === "object" && v !== null ? Object.keys(v).length : 0;
1295
+ return keys > 0 ? `${n}(${keys})` : n;
1296
+ });
1297
+ lines.push(`state: ${parts.join(", ")}`);
1298
+ }
1299
+ if (!lines.length)
1300
+ return "<aihand>empty</aihand>";
1301
+ const status = hasIssues ? `${consoleErrors.length + raw.errors.length + failedReqs.length} issues` : "ok";
1302
+ return `<aihand url="${raw.url}" status="${status}">
1303
+ ${lines.join("\n")}
1304
+ </aihand>${hasIssues ? "\n\ndetail: GET /__aihand/{section}/{index}" : ""}`;
1305
+ }
1306
+ function summarizeUI(tree) {
1307
+ const components = [];
1308
+ const counts = /* @__PURE__ */ new Map();
1309
+ for (const line of tree.split("\n")) {
1310
+ const trimmed = line.trimStart();
1311
+ if (!trimmed)
1312
+ continue;
1313
+ const indent = line.length - trimmed.length;
1314
+ if (indent > 2)
1315
+ continue;
1316
+ const name = nameOf(trimmed);
1317
+ if (!name)
1318
+ continue;
1319
+ const focused = trimmed.includes("[focused]");
1320
+ const generating = trimmed.includes("[generating]");
1321
+ const loading = trimmed.includes("[loading]");
1322
+ const existing = counts.get(name) || 0;
1323
+ if (existing > 0) {
1324
+ counts.set(name, existing + 1);
1325
+ continue;
1326
+ }
1327
+ counts.set(name, 1);
1328
+ let label = name;
1329
+ if (focused)
1330
+ label += "[focused]";
1331
+ if (generating)
1332
+ label += "[generating]";
1333
+ if (loading)
1334
+ label += "[loading]";
1335
+ components.push(label);
1336
+ }
1337
+ const result = components.map((c) => {
1338
+ const name = c.split("[")[0];
1339
+ const count = counts.get(name) || 1;
1340
+ return count > 1 ? `${c}(\xD7${count})` : c;
1341
+ });
1342
+ return result.join(", ") || "empty";
1343
+ }
1344
+ function emitCheck(result) {
1345
+ const lines = result.assertions.map(
1346
+ (a) => a.pass ? `\u2713 ${a.name}` : `\u2717 ${a.name}${a.detail ? `: ${a.detail}` : ""}`
1347
+ );
1348
+ return `<aihand-check pass="${result.pass}">
1349
+ ${lines.join("\n")}
1350
+ </aihand-check>`;
1351
+ }
1352
+ function emitDiff(diff) {
1353
+ const issues = [];
1354
+ for (const e of diff.newErrors) issues.push(_picocolors2.default.red(` [error] ${e.text}`));
1355
+ for (const e of diff.newExceptions) issues.push(_picocolors2.default.red(` [exception] ${e.message}`));
1356
+ for (const r of diff.newFailedRequests) issues.push(_picocolors2.default.yellow(` [network] ${r.method} ${r.url} ${r.status}`));
1357
+ if (diff.uiGone)
1358
+ issues.push(_picocolors2.default.magenta(" [ui] component tree disappeared"));
1359
+ if (!issues.length)
1360
+ return "";
1361
+ const count = issues.length;
1362
+ return `${_picocolors2.default.bold("[aihand]")} ${_picocolors2.default.red(`\u2717 ${count} issue${count > 1 ? "s" : ""} after HMR`)}
1363
+ ${issues.join("\n")}`;
1364
+ }
1365
+
1366
+ // src/ui/server/help-text.ts
1367
+ function helpText(port) {
1368
+ const base = `http://localhost:${port}/__aihand`;
1369
+ return `
1370
+ # aihand \u2014 Runtime Browser Inspector
1371
+
1372
+ IMPORTANT: Before debugging any UI issue, visual bug, or runtime error, ALWAYS fetch the live app state first. Do NOT guess \u2014 look at the actual browser state.
1373
+
1374
+ ## Read state \u2014 cheapest first
1375
+
1376
+ \`\`\`bash
1377
+ curl ${base}/screen # semantic layout tree (indent=nesting, \u5DE6/\u4E2D/\u53F3) + {view, modal, focus, knobs} \u2014 START HERE (returns token: tN)
1378
+ curl '${base}/screen?form=visual' # same screen as an ASCII art sketch instead of the layout tree
1379
+ curl '${base}/screen?form=knobs' # only the control surface: clickable knobs + their store morphisms, grouped by column
1380
+ curl '${base}/screen?since=tN' # only what moved since that token (view/modal/focus + new errors), not a full snapshot
1381
+ curl ${base}/ui # React component tree \u2014 deep-dive when /screen isn't enough
1382
+ curl '${base}/dom?scope=ChatInput' # semantic DOM scoped to a component \u2014 UI as text, src locations
1383
+ curl ${base} # high-density summary (ok sections \u2192 1 line, issues \u2192 expanded)
1384
+ curl ${base}?full # full dump: UI tree + console + network + errors + state
1385
+ curl ${base}/check # pass/fail health check \u2014 use after code changes
1386
+ curl ${base}/console # console logs (errors, warnings, info)
1387
+ curl ${base}/network # fetch/XHR requests with status and timing
1388
+ curl ${base}/errors # uncaught errors and unhandled rejections
1389
+ curl ${base}/state # registered store snapshots (overview)
1390
+ curl ${base}/state/imStore # drill into one store (domain)
1391
+ curl '${base}/state?path=imStore.conversations.0' # expand a nested value the overview collapsed to Array(N) (point)
1392
+ curl ${base}/tabs # list live tabs (id, visible/background, title) for ?tab= addressing
1393
+ curl ${base}/timeline # interleaved action stream across all tabs (who clicked what, in order)
1394
+ curl '${base}/query?sel=[role=menuitem]' # this selector right now: count + each element's text/visible/attrs
1395
+ curl ${base}/profile # performance profiler: which component/function is burning frames (+ source lines with AIPEEK_LINES=1)
1396
+ curl ${base}/profile/reset # clear the profiler window, then reproduce the interaction
1397
+ curl ${base}/profile/diff # closed loop: 1st call marks baseline, fix+reproduce, 2nd call \u2192 IMPROVED/REGRESSED verdict
1398
+ \`\`\`
1399
+
1400
+ \`/profile\` is a verdict, not a flame graph \u2014 read it, fix, re-read, don't eyeball frames.
1401
+ Three tiers, escalate only as needed:
1402
+
1403
+ \`\`\`bash
1404
+ # 1. Diagnose \u2014 one read names the component/function burning frames (self-time \xD7 render count)
1405
+ curl ${base}/profile
1406
+
1407
+ # 2. Closed-loop A/B \u2014 proves a fix worked instead of guessing
1408
+ curl ${base}/profile/reset # clear the window
1409
+ # \u2026reproduce the slow interaction (scroll, stream, type)\u2026
1410
+ curl ${base}/profile # 1st call = baseline snapshot
1411
+ # \u2026edit code + reproduce the SAME interaction\u2026
1412
+ curl ${base}/profile/diff # 2nd call \u2192 IMPROVED / REGRESSED / NO DATA verdict
1413
+
1414
+ # 3. Line-level \u2014 break a hot component down to the exact source line
1415
+ AIPEEK_LINES=1 <your dev command> # opt-in: line instrumentation taxes every render, off by default
1416
+ curl ${base}/profile
1417
+ \`\`\`
1418
+
1419
+ Profiling is the ONE read that needs the tab FOREGROUND + FOCUSED \u2014 the OS throttles rAF
1420
+ for backgrounded/unfocused windows, so timing there is a lie. The profiler refuses to launder
1421
+ that lie: a throttled window reads \`Frames: none sampled\` and a window that didn't reproduce
1422
+ the workload reads \`NO DATA\`, never a fabricated \`0.0% dropped\` / \`IMPROVED\`. A real verdict
1423
+ requires: tab in front, \`/profile/reset\`, then repeat the exact interaction. (\`/screen\` and
1424
+ \`/dom\` work fine backgrounded \u2014 only profiling needs focus.)
1425
+
1426
+ \`/query\` is the read-side twin of click/fill's \`sel=\` \u2014 assert on a specific element
1427
+ (how many match, its text, \`data-state\`, \`aria-*\`/\`data-*\`, value, disabled) without \`/eval\`.
1428
+ Secret fields (password inputs, API-key/token fields) show \`\u2039redacted N chars\u203A\` instead of
1429
+ their value across \`/dom\`, \`/query\` and \`/screen\` \u2014 length stays visible, the secret doesn't.
1430
+
1431
+ \`/screen\` projects the whole UI to a few state variables \u2014 start there, not \`/ui\`. Append
1432
+ \`?full\` for untruncated output. Each read prints a \`token: tN\` line; pass it back as
1433
+ \`/screen?since=tN\` to get only the transition since that read (view/modal/focus + new
1434
+ errors/failed requests), \`(no state change)\` if nothing moved \u2014 the cheap "what changed
1435
+ after I acted" read, without re-paying for the unchanged 99%.
1436
+
1437
+ To inspect or edit a component, work top-down \u2014 the full DOM is huge, a scoped view is
1438
+ accurate: \`/screen\` or \`/ui\` to find the component, then \`/dom?scope=<Name>\` (matches the
1439
+ source path) or \`/dom?sel=<css>\` for just that subtree. Each line carries its source
1440
+ location (\`@File.tsx:line\`), so the DOM view tells you exactly where to edit.
1441
+
1442
+ ## UI \u2192 code reason (reverse morphism)
1443
+
1444
+ \`\`\`bash
1445
+ curl '${base}/source?path=src/features/Sidebar/index.tsx:78' # which symbol owns this line + its callers/callees
1446
+ curl -G ${base}/source --data-urlencode 'path=<data-insp-path>' # feed /dom's data-insp-path raw (rel:line:col:Name)
1447
+ \`\`\`
1448
+
1449
+ \`/dom\` tells you *where* a UI element lives (\`@File.tsx:line\`); \`/source\` tells you *what code
1450
+ backs it* \u2014 the enclosing symbol, who calls it, what it calls. Paste a \`data-insp-path\`
1451
+ (\`rel:line:col:Name\`) straight from \`/dom\`. This is the static twin of \`/open\` (which sends the
1452
+ same coordinate to your editor): point at a rendered element \u2192 get its code reason, without grepping.
1453
+
1454
+ ## Drive the page (acts on the currently-open tab \u2014 no separate browser)
1455
+
1456
+ Pass \`text=\`/\`sel=\`/\`value=\` via \`-G --data-urlencode\` \u2014 **always**, not just for CJK. A raw
1457
+ \`?text=\u7FA4\u804A\` is an illegal HTTP request-target, rejected with an empty 400 *before aihand runs*
1458
+ (a wasted round-trip); \`--data-urlencode\` escapes it for you, so the same form works for \`New\`
1459
+ and \`\u7FA4\u804A\` alike \u2014 write it right the first time.
1460
+
1461
+ \`\`\`bash
1462
+ curl -G ${base}/click --data-urlencode 'text=New' # click by visible text (or 'sel=<css>')
1463
+ curl -G ${base}/dblclick --data-urlencode 'text=todo' # double-click (inline edit / rename / select); fires dblclick handlers click can't
1464
+ curl -G ${base}/hover --data-urlencode 'text=More' # hover only (reveal a menu/tooltip; no click)
1465
+ curl -G ${base}/fill --data-urlencode 'sel=textarea' --data-urlencode 'value=hi' # set input/textarea; <select> matches by option text
1466
+ curl -G ${base}/press --data-urlencode 'key=Enter' # key on focused element (e.g. Control+a)
1467
+ curl -G ${base}/wait --data-urlencode 'text=Done' -d 'timeout=8000' # poll until text/sel appears (gone=1 until it disappears; enabled=1 until not [disabled])
1468
+ curl '${base}/screenshot?out=shot.png' # DOM\u2192PNG into .aidev/ (html-to-image; lossy)
1469
+ \`\`\`
1470
+
1471
+ ASCII-only values may use the bare \`?text=New\` form, but \`-G --data-urlencode\` is never wrong \u2014
1472
+ prefer it so a value that turns out to contain CJK/space/\`&\` doesn't silently 400.
1473
+
1474
+ \`click\`/\`fill\`/\`press\` settle the DOM and append \`--- changed ---\`: only the state-machine
1475
+ transition this action caused (\`view: a \u2192 b\`, \`modal: opened X\`, \`focus: \u2026\`) plus any new
1476
+ errors/failed requests \u2014 not a fresh snapshot. \`(no state change)\` means nothing moved. Read
1477
+ the delta, then drill into /ui or /dom for detail if you need it. On a miss, the response lists
1478
+ the reachable clickable elements so you can re-target.
1479
+
1480
+ Each \`click\`/\`fill\`/\`press\` response also carries a \`--- recent actions ---\` timeline:
1481
+ the semantic page actions (yours and the user's) in order, \`T\`=trusted human / \`S\`=synthetic
1482
+ aihand, each with its resulting UI change (\`\u2192 \u5F39\u7A97\u6253\u5F00\u300C\u2026\u300D\`/\`\u2192 \u5F39\u7A97\u5173\u95ED\`). Your own action is
1483
+ bracketed by \`\u4F60\u5F53\u524D\u7684\u884C\u4E3A\` dividers. So if the user closed a dialog you just opened, you see
1484
+ their \`T key:Escape \u2192 \u5F39\u7A97\u5173\u95ED\` right after your \`S\` action \u2014 no need to query for it.
1485
+
1486
+ **Beyond click/fill/press** \u2014 four more interactions for what those can't reach:
1487
+
1488
+ \`\`\`bash
1489
+ curl '${base}/scrollIntoView?text=Row 99' # scroll a target into view (off-screen list rows)
1490
+ curl '${base}/drag?sel=.item&to=.slot' # synthetic pointer drag, source \u2192 destination
1491
+ curl '${base}/drop?sel=.dropzone&files=a.png,b.pdf' # fire a file-drop (DataTransfer) on a target
1492
+ curl '${base}/clipboard?mode=write&value=hi' # seed the clipboard (mode=read reports it back)
1493
+ \`\`\`
1494
+
1495
+ \`drag\` fires a real pointer sequence (down \u2192 stepped moves past dnd-kit's activation
1496
+ distance \u2192 up); if a dnd-kit reorder doesn't take, retry the same gesture via \`realclick\`
1497
+ (trusted events). \`drop\` delivers the drop event with the named files (synthetic Files have
1498
+ no byte content \u2014 fine for triggering handlers, not for real uploads). \`clipboard\` needs the
1499
+ tab focused (browser security) and says so plainly when it isn't, rather than hanging.
1500
+
1501
+ A control tagged \`{needs-trusted?}\` in \`/screen\` or \`/dom\` opens a popup (\`aria-haspopup\`)
1502
+ that a synthetic click may not trigger \u2014 reach for \`realclick\` on it from the start instead
1503
+ of discovering it via a dead click. (Right-click-only menus carry no DOM marker, so they
1504
+ still surface only on a miss \u2014 use \`realclick\` with \`button=right\` there.)
1505
+
1506
+ **Green channel \u2014 replay a knob's morphism, no DOM.** \`/screen?form=knobs\` lists every
1507
+ control with its store morphism (\`\u300C\u7FA4\u804A\u300D\u2192 appUIStore.{ mode=im }\`). \`/action?knob=<label>\`
1508
+ replays that morphism by writing the store directly \u2014 bypassing selector resolution, disabled
1509
+ gating, and the silent-false-success a covered \`/click\` can return. It's smoother than
1510
+ \`/click\`+\`/fill\` for any control whose effect is a store write:
1511
+
1512
+ \`\`\`bash
1513
+ curl -G ${base}/action --data-urlencode 'knob=\u7FA4\u804A' # literal: appUIStore.mode='im'
1514
+ curl -G ${base}/action --data-urlencode 'knob=\u6DF1\u5EA6\u7814\u7A76' # toggle: flips the bool
1515
+ curl -G ${base}/action --data-urlencode 'knob=\u6700\u5927Token' --data-urlencode 'value=8' # param: needs ?value=
1516
+ curl ${base}/action?knob=@k3 # @kN: addressable ref from /screen, no CJK escaping
1517
+ \`\`\`
1518
+
1519
+ Every \`/screen\` projection tags each knob with a stable \`@kN\` ref (the number in \`?form=knobs\`,
1520
+ shared across the ASCII art / tree / legend). \`/action?knob=@k3\` resolves it back to the label \u2014
1521
+ saving the CJK \`--data-urlencode\` round-trip and any \`&file=\` disambiguation. A stale/unknown
1522
+ \`@kN\` is refused (422 listing the current numbers) \u2014 re-read \`/screen\` for fresh ones; it never
1523
+ silently hits the wrong knob.
1524
+
1525
+ Returns the same \`--- changed ---\` trajectory a \`/click\` does (waitForStable + diffScreen),
1526
+ not a full state dump. A knob whose value depends on runtime context (\`e.target.value\`, a
1527
+ mapped array, a method with args) is **refused** (422 + reason) \u2014 fall back to \`/click\`/\`/fill\`
1528
+ there; it never fake-replays. A label two components share returns 422 + candidates; add
1529
+ \`&file=<path-fragment>\` to disambiguate.
1530
+
1531
+ **Multiple tabs.** Every read/drive command takes \`?tab=<id>\` to address one specific tab \u2014
1532
+ including a **background** one (you can drive the Chat tab while the user is looking at a
1533
+ different tab). Run \`${base}/tabs\` to see the live ids. With one tab open, omit \`?tab=\` and
1534
+ it just works. With several tabs open and no \`?tab=\`, the command returns \`409\` + the tab
1535
+ list (rather than randomly hitting one) \u2014 pick an id from it and retry with \`?tab=\`.
1536
+
1537
+ **Multiple servers (federation).** When several dev servers run at once \u2014 a micro-frontend,
1538
+ separate front/back servers, or a teammate's machine \u2014 every command also takes
1539
+ \`?host=<host:port>\` to reach a *sibling* aihand. The plugin you curl reverse-proxies the
1540
+ request to that peer (server-side, no browser): \`${base}/screen?host=localhost:5174\` reads
1541
+ the app on :5174; combine with \`?tab=\` to point at one tab over there
1542
+ (\`?host=192.168.1.9:5173&tab=t3\`). Omit \`?host=\` and it's the local server as always. There's
1543
+ no registry \u2014 you name the peer, so list its tabs with \`/tabs?host=<host:port>\` first.
1544
+
1545
+ **Cross-tab timeline.** \`${base}/timeline\` interleaves the semantic actions of *every* tab
1546
+ in time order \u2014 each line \`<tab> [T|S] <action> \u2192 <ui change>\` (\`T\`=trusted human,
1547
+ \`S\`=synthetic aihand). The per-action \`--- recent actions ---\` tail only shows the acting
1548
+ tab; \`/timeline\` is the group view, so an A/B comparison across two tabs (drive A, watch B
1549
+ react) is one read. \`?tab=<id>\` filters to one tab's history.
1550
+
1551
+ **Chain \u2014 a whole interaction in one round-trip.** POST a JSON array; runs in sequence,
1552
+ each step settles before the next, stops on first failure. A failed step carries the same
1553
+ \`clickable:\` fallback list a single-shot \`/click\` gives, and a trailing
1554
+ \`[n..m] skipped (\u2026 not run)\` line names every step the fail-fast stop left unrun \u2014 the page
1555
+ is parked in a half-done state, not rolled back (page actions aren't transactional):
1556
+
1557
+ \`\`\`bash
1558
+ curl -X POST ${base}/chain -d '[
1559
+ {"type":"knob","knob":"\u7FA4\u804A"},
1560
+ {"type":"assert","screen":"appUIStore.mode","equals":"im"},
1561
+ {"type":"click","sel":"button[title=\\"\u77E5\u8BC6\u5E93\\"]"},
1562
+ {"type":"wait","text":"Done"},
1563
+ {"type":"fill","sel":"textarea","value":"hi"},
1564
+ {"type":"assert","screen":"<a domain key from /screen>","equals":"false"},
1565
+ {"type":"press","key":"Enter"}
1566
+ ]'
1567
+ \`\`\`
1568
+
1569
+ The \`screen\` key in an \`assert\` must be a real field from \`/screen\`'s \`domain:\` block (e.g.
1570
+ \`streaming\`, \`appUIStore.mode\`) \u2014 copy it verbatim, the names are app-specific. A wrong key
1571
+ fails with \`no domain key "X" \u2014 available: \u2026\` listing every real one.
1572
+
1573
+ A \`knob\` step (\`{type:"knob",knob:"<panel label>"[,value,file]}\`) replays a panel morphism
1574
+ via the green channel \u2014 the store-direct optimal path \`/action?knob=\` takes, now inside a
1575
+ batch. Prefer it over \`click\` when a knob exists: no selector resolution, no overlay gating.
1576
+
1577
+ \`assert\` is the chain's mid-step judge: \`{type,screen,equals}\` checks a domain variable
1578
+ (from the app's \`window.__AIPEEK_SCREEN__\`), or \`{type,sel,equals}\` an element's text. On
1579
+ mismatch the chain stops and reports \`asserted X=="Y", actual "Z"\` \u2014 a test, not a guess.
1580
+ Domain variables also show up in \`/screen\`'s \`domain:\` block and in every \`--- changed ---\`
1581
+ diff (e.g. \`\u6D41\u5F0F\u4E2D: false \u2192 true\`) \u2014 the app's own state machine, which a DOM-only inspector
1582
+ can't see. The app opts in by setting \`window.__AIPEEK_SCREEN__ = () => ({...})\`.
1583
+
1584
+ **Escape hatch.** \`curl '${base}/eval?code=...'\` (or POST the code as the body) runs arbitrary
1585
+ JS in the page and returns the result \u2014 for what the typed endpoints can't do (install listeners,
1586
+ read closures, probe event flow). For count/text/state/attr assertions reach for \`/query\` first.
1587
+ When the code is a hand-rolled \`/state\` (or the selector is illegal), the response tail carries a \`hint:\` line pointing at the typed twin.
1588
+
1589
+ aihand auto-detects errors after HMR and prints them to the terminal \u2014 watch for \`[aihand]\` messages.
1590
+
1591
+ **Iterating on aihand itself.** Editing this server's own code (\`plugin.ts\` and anything in the Vite
1592
+ config dependency graph) makes Vite AUTO-restart the dev server in-process \u2014 terminal prints
1593
+ \`\u2026changed, restarting server\u2026\` then \`server restarted.\`, PID unchanged. You do NOT restart it by
1594
+ hand and you do NOT ask the user to; just edit and wait ~2-4s, then poll \`${base}/tabs\` for the page
1595
+ to re-register (it self-heals via an unthrottled heartbeat). The client probe (\`client.ts\`) instead
1596
+ hot-swaps in place keeping app state. Server stdout goes to the user's terminal (you can't read it) \u2014
1597
+ to make server-side state curl-observable, add a temporary debug route, let Vite auto-restart, then
1598
+ remove it.
1599
+ `;
1600
+ }
1601
+
1602
+ // src/ui/server/plugin.ts
1603
+ function readBody(req) {
1604
+ return new Promise((resolve5) => {
1605
+ let s = "";
1606
+ req.on("data", (c) => s += c);
1607
+ req.on("end", () => resolve5(s));
1608
+ });
1609
+ }
1610
+ function send(res, status, body) {
1611
+ res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
1612
+ res.end(body);
1613
+ }
1614
+ var __dirname = _path.dirname.call(void 0, _url.fileURLToPath.call(void 0, _chunkZ2Y65YOYcjs.importMetaUrl));
1615
+ var clientDir = _nullishCoalesce([
1616
+ _path.resolve.call(void 0, __dirname, "../client"),
1617
+ _path.resolve.call(void 0, __dirname, "../src/ui/client")
1618
+ ].find(_fs.existsSync), () => ( _path.resolve.call(void 0, __dirname, "../src/ui/client")));
1619
+ var clientPath = _path.resolve.call(void 0, clientDir, "client.ts");
1620
+ var patchPath = _path.resolve.call(void 0, clientDir, "client-patch.ts");
1621
+ function compilePatch() {
1622
+ const source = _fs.readFileSync.call(void 0, patchPath, "utf-8");
1623
+ const result = _esbuild.transformSync.call(void 0, source, {
1624
+ loader: "ts",
1625
+ target: "es2020",
1626
+ format: "iife",
1627
+ minify: false
1628
+ });
1629
+ return result.code;
1630
+ }
1631
+ function sourceInjectPlugin() {
1632
+ let babel = null;
1633
+ let plugin = null;
1634
+ let root = "";
1635
+ return {
1636
+ name: "aihand:source-loc",
1637
+ apply: "serve",
1638
+ enforce: "pre",
1639
+ async configResolved(config) {
1640
+ root = config.root;
1641
+ },
1642
+ async transform(code, id) {
1643
+ const file = id.split("?")[0];
1644
+ if (!file.endsWith(".tsx") && !file.endsWith(".jsx"))
1645
+ return;
1646
+ if (file.includes("node_modules") || file.includes("packages/aidev/"))
1647
+ return;
1648
+ if (!babel) {
1649
+ babel = await Promise.resolve().then(() => _interopRequireWildcard(require("@babel/core")));
1650
+ plugin = sourceLocPlugin(babel.types);
1651
+ }
1652
+ const result = await babel.transformAsync(code, {
1653
+ filename: file,
1654
+ root,
1655
+ plugins: [plugin],
1656
+ parserOpts: { plugins: ["jsx", "typescript"] },
1657
+ sourceMaps: true,
1658
+ configFile: false,
1659
+ babelrc: false
1660
+ });
1661
+ if (!_optionalChain([result, 'optionalAccess', _36 => _36.code]))
1662
+ return;
1663
+ return { code: result.code, map: result.map };
1664
+ }
1665
+ };
1666
+ }
1667
+ function aihandPlugin() {
1668
+ return [aihandMainPlugin(), sourceInjectPlugin()];
1669
+ }
1670
+ function aihandMainPlugin() {
1671
+ let pendingResolve = null;
1672
+ let server;
1673
+ let lastRaw = null;
1674
+ let perfBaseline = null;
1675
+ let pushTimer;
1676
+ const pendingActions = /* @__PURE__ */ new Map();
1677
+ const actionProgressAt = /* @__PURE__ */ new Map();
1678
+ let actionId = 0;
1679
+ let graphCache = null;
1680
+ let graphBuilding = null;
1681
+ async function cachedGraph() {
1682
+ if (graphCache)
1683
+ return graphCache;
1684
+ if (!graphBuilding) {
1685
+ graphBuilding = (async () => {
1686
+ const { loadConfig: loadConfig2, isTsFile } = await Promise.resolve().then(() => _interopRequireWildcard(require("./config-5KEQLN6L.cjs")));
1687
+ const { scan: scan2 } = await Promise.resolve().then(() => _interopRequireWildcard(require("./scan-4R7GQG2W.cjs")));
1688
+ const { buildCallGraph } = await Promise.resolve().then(() => _interopRequireWildcard(require("./graph-ZUXXCJ5A.cjs")));
1689
+ const config = await loadConfig2(server.config.root);
1690
+ const g = await buildCallGraph(scan2(config).filter(isTsFile));
1691
+ graphCache = g;
1692
+ graphBuilding = null;
1693
+ return g;
1694
+ })();
1695
+ }
1696
+ return graphBuilding;
1697
+ }
1698
+ const tabs = /* @__PURE__ */ new Map();
1699
+ function seen(data) {
1700
+ if (!data.tab)
1701
+ return;
1702
+ const prev = tabs.get(data.tab);
1703
+ tabs.set(data.tab, {
1704
+ id: data.tab,
1705
+ url: _nullishCoalesce(_nullishCoalesce(data.url, () => ( _optionalChain([prev, 'optionalAccess', _37 => _37.url]))), () => ( "")),
1706
+ title: _nullishCoalesce(_nullishCoalesce(data.title, () => ( _optionalChain([prev, 'optionalAccess', _38 => _38.title]))), () => ( "")),
1707
+ visible: _nullishCoalesce(_nullishCoalesce(data.visible, () => ( _optionalChain([prev, 'optionalAccess', _39 => _39.visible]))), () => ( false)),
1708
+ lastSeen: Date.now()
1709
+ });
1710
+ }
1711
+ const liveTabs = () => [...tabs.values()].filter((t) => _chunkQF6AG3M5cjs.isLive.call(void 0, t, Date.now()));
1712
+ const diagnose2 = (tab) => _chunkQF6AG3M5cjs.diagnose.call(void 0, tab, [...tabs.values()], Date.now(), server.ws.clients.size, server.config.server.port || 5173);
1713
+ const actionLog = [];
1714
+ const BOOT_ID = Date.now().toString(36);
1715
+ const droppedByTab = /* @__PURE__ */ new Map();
1716
+ const mergeActions = (tab, actions, dropped) => {
1717
+ if (!tab)
1718
+ return;
1719
+ if (typeof dropped === "number")
1720
+ droppedByTab.set(tab, dropped);
1721
+ if (!_optionalChain([actions, 'optionalAccess', _40 => _40.length]))
1722
+ return;
1723
+ for (const entry of actions) {
1724
+ if (!actionLog.some((e) => e.tab === tab && e.ts === entry.ts))
1725
+ _chunkQF6AG3M5cjs.appendAction.call(void 0, actionLog, tab, entry, 200);
1726
+ }
1727
+ };
1728
+ const cdpQueue = [];
1729
+ let cdpWaiter = null;
1730
+ const cdpResults = /* @__PURE__ */ new Map();
1731
+ let cdpId = 0;
1732
+ function runCdpClick(x, y, button) {
1733
+ const id = ++cdpId;
1734
+ const cmd = { id, x, y, button };
1735
+ return new Promise((resolve5, reject) => {
1736
+ cdpResults.set(id, resolve5);
1737
+ if (cdpWaiter) {
1738
+ cdpWaiter(cmd);
1739
+ cdpWaiter = null;
1740
+ } else {
1741
+ cdpQueue.push(cmd);
1742
+ }
1743
+ setTimeout(() => {
1744
+ if (cdpResults.delete(id))
1745
+ reject(new Error("cdp timeout: no extension result within 10s (is the aihand extension loaded and the debugger attached?)"));
1746
+ }, 1e4);
1747
+ });
1748
+ }
1749
+ let pendingDom = null;
1750
+ let pendingStatePath = null;
1751
+ let pendingScreen = null;
1752
+ const pendingEvals = /* @__PURE__ */ new Map();
1753
+ let evalId = 0;
1754
+ const pendingSemantic = /* @__PURE__ */ new Map();
1755
+ let semanticId = 0;
1756
+ const pendingKnob = /* @__PURE__ */ new Map();
1757
+ let knobId = 0;
1758
+ const knobProgressAt = /* @__PURE__ */ new Map();
1759
+ const screenStash = /* @__PURE__ */ new Map();
1760
+ let screenToken = 0;
1761
+ function stashScreen(r) {
1762
+ const token = `t${++screenToken}`;
1763
+ screenStash.set(token, { snap: r.snap, console: r.console, network: r.network, errors: r.errors });
1764
+ if (screenStash.size > 32)
1765
+ screenStash.delete(screenStash.keys().next().value);
1766
+ return token;
1767
+ }
1768
+ const VISIBLE_MS2 = 400;
1769
+ function twoPhase(event, payload, arm, fullMs = 3e3, tab, lastProgress) {
1770
+ return new Promise((resolve5, reject) => {
1771
+ let settled = false;
1772
+ const clear = arm((v) => {
1773
+ settled = true;
1774
+ resolve5(v);
1775
+ });
1776
+ if (tab) {
1777
+ const RETRY_MS = 500;
1778
+ const ABSENT_CEILING_MS = 1e4;
1779
+ const startedAt = Date.now();
1780
+ const deliver = () => server.hot.send(event, { ...payload, tab });
1781
+ deliver();
1782
+ const iv = setInterval(() => {
1783
+ if (settled) {
1784
+ clearInterval(iv);
1785
+ return;
1786
+ }
1787
+ const t = tabs.get(tab);
1788
+ const live = !!t && _chunkQF6AG3M5cjs.isLive.call(void 0, t, Date.now());
1789
+ const progressAt = _optionalChain([lastProgress, 'optionalCall', _41 => _41()]);
1790
+ const sinceLife = Date.now() - Math.max(startedAt, _nullishCoalesce(progressAt, () => ( 0)));
1791
+ const elapsed = Date.now() - startedAt;
1792
+ if (live) {
1793
+ if (sinceLife > fullMs) {
1794
+ clearInterval(iv);
1795
+ clear();
1796
+ reject(new Error(diagnose2(tab)));
1797
+ }
1798
+ } else if (elapsed > ABSENT_CEILING_MS) {
1799
+ clearInterval(iv);
1800
+ clear();
1801
+ reject(new Error(diagnose2(tab)));
1802
+ } else {
1803
+ deliver();
1804
+ }
1805
+ }, RETRY_MS);
1806
+ return;
1807
+ }
1808
+ server.hot.send(event, { ...payload, requireVisible: true });
1809
+ setTimeout(() => {
1810
+ if (settled)
1811
+ return;
1812
+ server.hot.send(event, { ...payload, requireVisible: false });
1813
+ setTimeout(() => {
1814
+ if (settled)
1815
+ return;
1816
+ clear();
1817
+ reject(new Error(diagnose2(tab)));
1818
+ }, fullMs);
1819
+ }, VISIBLE_MS2);
1820
+ });
1821
+ }
1822
+ function collectFromClient(tab) {
1823
+ return twoPhase("aihand:collect", {}, (resolve5) => {
1824
+ pendingResolve = resolve5;
1825
+ return () => {
1826
+ pendingResolve = null;
1827
+ };
1828
+ }, 3e3, tab);
1829
+ }
1830
+ function collectDomFromClient(scope, sel, tab) {
1831
+ return twoPhase("aihand:collect-dom", { scope, sel }, (resolve5) => {
1832
+ pendingDom = resolve5;
1833
+ return () => {
1834
+ pendingDom = null;
1835
+ };
1836
+ }, 3e3, tab);
1837
+ }
1838
+ function collectStatePathFromClient(path2, tab) {
1839
+ return twoPhase("aihand:collect-state-path", { path: path2 }, (resolve5) => {
1840
+ pendingStatePath = resolve5;
1841
+ return () => {
1842
+ pendingStatePath = null;
1843
+ };
1844
+ }, 3e3, tab);
1845
+ }
1846
+ function collectScreenFromClient(tab, form) {
1847
+ return twoPhase("aihand:collect-screen", { form }, (resolve5) => {
1848
+ pendingScreen = resolve5;
1849
+ return () => {
1850
+ pendingScreen = null;
1851
+ };
1852
+ }, 3e3, tab);
1853
+ }
1854
+ function sendAction(type, args, tab) {
1855
+ const id = ++actionId;
1856
+ const fullMs = Math.max(_nullishCoalesce(args.timeout, () => ( 0)), 3e3) + 2e3;
1857
+ return twoPhase("aihand:action", { id, type, args }, (resolve5) => {
1858
+ pendingActions.set(id, resolve5);
1859
+ return () => {
1860
+ pendingActions.delete(id);
1861
+ actionProgressAt.delete(id);
1862
+ };
1863
+ }, fullMs, tab, () => actionProgressAt.get(id));
1864
+ }
1865
+ async function runAction(type, args, tab) {
1866
+ const result = await sendAction(type, args, tab);
1867
+ lastRaw = null;
1868
+ if (type === "realclick" && result.ok && !result.fired) {
1869
+ const cdp = await runCdpClick(result.x, result.y, _nullishCoalesce(args.button, () => ( "left")));
1870
+ if (!cdp.ok)
1871
+ return { ok: false, error: `cdp click failed: ${_nullishCoalesce(cdp.error, () => ( "unknown"))}` };
1872
+ result.detail = `${result.detail} \u2192 clicked via extension`;
1873
+ result.screen = (await collectScreenFromClient(tab)).screen;
1874
+ }
1875
+ return result;
1876
+ }
1877
+ function evalInClient(code, tab) {
1878
+ const id = ++evalId;
1879
+ return twoPhase("aihand:eval", { id, code }, (resolve5) => {
1880
+ pendingEvals.set(id, resolve5);
1881
+ return () => {
1882
+ pendingEvals.delete(id);
1883
+ };
1884
+ }, 8e3, tab);
1885
+ }
1886
+ function semanticInClient(name, args, tab) {
1887
+ const id = ++semanticId;
1888
+ return twoPhase("aihand:semantic-action", { id, name, args }, (resolve5) => {
1889
+ pendingSemantic.set(id, resolve5);
1890
+ return () => {
1891
+ pendingSemantic.delete(id);
1892
+ };
1893
+ }, 15e3, tab);
1894
+ }
1895
+ function knobInClient(knob, file, value, tab) {
1896
+ const id = ++knobId;
1897
+ return twoPhase("aihand:knob-action", { id, knob, file, value }, (resolve5) => {
1898
+ pendingKnob.set(id, resolve5);
1899
+ return () => {
1900
+ pendingKnob.delete(id);
1901
+ knobProgressAt.delete(id);
1902
+ };
1903
+ }, 15e3, tab, () => knobProgressAt.get(id));
1904
+ }
1905
+ return {
1906
+ name: "aihand",
1907
+ apply: "serve",
1908
+ transformIndexHtml() {
1909
+ const patchCode = compilePatch();
1910
+ return [
1911
+ // Synchronous inline script — patches console/fetch/XHR/errors
1912
+ // BEFORE any ES modules execute
1913
+ {
1914
+ tag: "script",
1915
+ children: patchCode,
1916
+ injectTo: "head-prepend"
1917
+ },
1918
+ // Module script — collectors + HMR channel (can be deferred)
1919
+ {
1920
+ tag: "script",
1921
+ attrs: { type: "module" },
1922
+ children: `import '/@fs/${clientPath}'`,
1923
+ injectTo: "body"
1924
+ }
1925
+ ];
1926
+ },
1927
+ configureServer(_server) {
1928
+ server = _server;
1929
+ const bridge = viteBridge(server, { tabs, diagnose: diagnose2 });
1930
+ const isTs = (f) => f.endsWith(".ts") || f.endsWith(".tsx") || f.endsWith(".js") || f.endsWith(".jsx");
1931
+ const invalidateGraph = (f) => {
1932
+ if (isTs(f)) graphCache = null;
1933
+ };
1934
+ server.watcher.on("change", invalidateGraph);
1935
+ server.watcher.on("add", invalidateGraph);
1936
+ server.watcher.on("unlink", invalidateGraph);
1937
+ server.hot.on("aihand:state", (data) => {
1938
+ seen(data);
1939
+ mergeActions(data.tab, data.actions, data.actionsDropped);
1940
+ if (pendingResolve) {
1941
+ pendingResolve(data);
1942
+ pendingResolve = null;
1943
+ }
1944
+ });
1945
+ server.hot.on("aihand:hello", (data) => {
1946
+ seen(data);
1947
+ if (data.tab)
1948
+ server.hot.send("aihand:hello-ack", { tab: data.tab });
1949
+ });
1950
+ server.hot.on("aihand:result", (data) => {
1951
+ seen(data);
1952
+ const resolve5 = pendingActions.get(data.id);
1953
+ if (resolve5) {
1954
+ pendingActions.delete(data.id);
1955
+ resolve5(data);
1956
+ }
1957
+ });
1958
+ server.hot.on("aihand:action-progress", (data) => {
1959
+ seen(data);
1960
+ if (data.kind === "knob") {
1961
+ if (pendingKnob.has(data.id))
1962
+ knobProgressAt.set(data.id, Date.now());
1963
+ } else if (pendingActions.has(data.id)) {
1964
+ actionProgressAt.set(data.id, Date.now());
1965
+ }
1966
+ });
1967
+ server.hot.on("aihand:eval-result", (data) => {
1968
+ seen(data);
1969
+ const resolve5 = pendingEvals.get(data.id);
1970
+ if (resolve5) {
1971
+ pendingEvals.delete(data.id);
1972
+ resolve5(data);
1973
+ }
1974
+ });
1975
+ server.hot.on("aihand:semantic-result", (data) => {
1976
+ seen(data);
1977
+ const resolve5 = pendingSemantic.get(data.id);
1978
+ if (resolve5) {
1979
+ pendingSemantic.delete(data.id);
1980
+ resolve5(data);
1981
+ }
1982
+ });
1983
+ server.hot.on("aihand:knob-result", (data) => {
1984
+ seen(data);
1985
+ const resolve5 = pendingKnob.get(data.id);
1986
+ if (resolve5) {
1987
+ pendingKnob.delete(data.id);
1988
+ resolve5(data);
1989
+ }
1990
+ });
1991
+ server.hot.on("aihand:dom", (data) => {
1992
+ seen(data);
1993
+ if (pendingDom) {
1994
+ pendingDom(data.dom);
1995
+ pendingDom = null;
1996
+ }
1997
+ });
1998
+ server.hot.on("aihand:state-path", (data) => {
1999
+ seen(data);
2000
+ if (pendingStatePath) {
2001
+ pendingStatePath(data);
2002
+ pendingStatePath = null;
2003
+ }
2004
+ });
2005
+ server.hot.on("aihand:screen", (data) => {
2006
+ seen(data);
2007
+ if (pendingScreen) {
2008
+ pendingScreen(data);
2009
+ pendingScreen = null;
2010
+ }
2011
+ });
2012
+ server.hot.on("vite:afterUpdate", () => {
2013
+ clearTimeout(pushTimer);
2014
+ pushTimer = setTimeout(async () => {
2015
+ try {
2016
+ const raw = await collectFromClient();
2017
+ const diff = diffState(lastRaw, raw);
2018
+ lastRaw = raw;
2019
+ if (!diff.clean) {
2020
+ const msg = emitDiff(diff);
2021
+ if (msg)
2022
+ server.config.logger.warn(msg);
2023
+ }
2024
+ } catch (e7) {
2025
+ }
2026
+ }, 500);
2027
+ });
2028
+ server.middlewares.use("/__aihand", async (req, res) => {
2029
+ const url = new URL(req.url || "/", "http://localhost");
2030
+ const parts = url.pathname.split("/").filter(Boolean);
2031
+ const full = url.searchParams.has("full");
2032
+ const explicitTab = url.searchParams.get("tab") || void 0;
2033
+ const live = liveTabs();
2034
+ const tab = _nullishCoalesce(explicitTab, () => ( (live.length === 1 ? live[0].id : void 0)));
2035
+ const host = url.searchParams.get("host") || void 0;
2036
+ const selfPort = server.config.server.port || 5173;
2037
+ const selfHosts = /* @__PURE__ */ new Set([`localhost:${selfPort}`, `127.0.0.1:${selfPort}`, `:${selfPort}`, `${selfPort}`]);
2038
+ if (host && !selfHosts.has(host)) {
2039
+ const fwd = new URL(url);
2040
+ fwd.searchParams.delete("host");
2041
+ const target = `http://${host}/__aihand/${parts.join("/")}${fwd.search}`;
2042
+ try {
2043
+ const body = req.method === "POST" ? await readBody(req) : void 0;
2044
+ const r = await fetch(target, { method: req.method, body });
2045
+ send(res, r.status, await r.text());
2046
+ } catch (e) {
2047
+ const code = _optionalChain([e, 'access', _42 => _42.cause, 'optionalAccess', _43 => _43.code]);
2048
+ const why = code === "ECONNREFUSED" ? `nothing is listening on ${host} \u2014 its dev server isn't running (start it), or the port is wrong.` : code === "ENOTFOUND" ? `host '${host}' doesn't resolve \u2014 check the hostname.` : code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" ? `connection to ${host} timed out \u2014 it's unreachable (firewall, or wrong host).` : `${e.message} (code ${_nullishCoalesce(code, () => ( "unknown"))}).`;
2049
+ send(res, 502, `cannot reach aihand peer at ${host}: ${why}`);
2050
+ }
2051
+ return;
2052
+ }
2053
+ if (parts[0] === "ping") {
2054
+ send(res, 200, BOOT_ID);
2055
+ return;
2056
+ }
2057
+ if (parts[0] === "help") {
2058
+ send(res, 200, helpText(selfPort).trim());
2059
+ return;
2060
+ }
2061
+ if (parts[0] === "open") {
2062
+ const file = url.searchParams.get("file") || "";
2063
+ const line = Number(url.searchParams.get("line")) || 1;
2064
+ const column = Number(url.searchParams.get("column")) || 1;
2065
+ const root = server.config.root;
2066
+ const abs = _path.isAbsolute.call(void 0, file) ? file : _path.resolve.call(void 0, root, file);
2067
+ const rel = _path.relative.call(void 0, root, abs);
2068
+ if (!file || rel.startsWith("..") || _path.isAbsolute.call(void 0, rel)) {
2069
+ send(res, 400, `refusing to open '${file}' \u2014 not under project root`);
2070
+ return;
2071
+ }
2072
+ try {
2073
+ const { launchIDE } = await Promise.resolve().then(() => _interopRequireWildcard(require("launch-ide")));
2074
+ launchIDE({ file: abs, line, column });
2075
+ send(res, 200, `opening ${rel}:${line}:${column}`);
2076
+ } catch (err) {
2077
+ send(res, 500, `launch-ide failed: ${err instanceof Error ? err.message : "unknown"}`);
2078
+ }
2079
+ return;
2080
+ }
2081
+ if (parts[0] === "source") {
2082
+ const path2 = url.searchParams.get("path") || "";
2083
+ if (!path2) {
2084
+ send(res, 400, "usage: /__aihand/source?path=<file:line> (path \u53EF\u76F4\u63A5\u662F /dom \u7684 data-insp-path)");
2085
+ return;
2086
+ }
2087
+ const { parseInspPath, locate, renderLocate } = await Promise.resolve().then(() => _interopRequireWildcard(require("./locate-5XFSXJ5J.cjs")));
2088
+ const { file, line } = parseInspPath(path2);
2089
+ if (!file || !Number.isFinite(line)) {
2090
+ send(res, 400, `bad path '${path2}' \u2014 expected file:line (or data-insp-path)`);
2091
+ return;
2092
+ }
2093
+ try {
2094
+ const graph = await cachedGraph();
2095
+ const result = locate(graph, file, line);
2096
+ if (!result) {
2097
+ send(res, 404, `no symbol covers ${file}:${line} \u2014 file not in scan scope, or line outside any symbol`);
2098
+ return;
2099
+ }
2100
+ send(res, 200, renderLocate(result, url.searchParams.has("json")));
2101
+ } catch (err) {
2102
+ send(res, 500, `source lookup failed: ${err instanceof Error ? err.message : "unknown"}`);
2103
+ }
2104
+ return;
2105
+ }
2106
+ const ambiguous = () => !tab && !["tabs", "timeline", "cdp"].includes(parts[0]) && liveTabs().length > 1;
2107
+ const refuse = () => send(res, 409, `multiple live tabs \u2014 add ?tab=<id>:
2108
+
2109
+ ${_chunkQF6AG3M5cjs.formatTabs.call(void 0, liveTabs(), Date.now())}`);
2110
+ try {
2111
+ if (parts[0] === "tabs") {
2112
+ const roster = _chunkQF6AG3M5cjs.formatTabs.call(void 0, liveTabs(), Date.now());
2113
+ send(res, 200, roster === "(no live tabs)" ? `(${diagnose2()})` : roster);
2114
+ return;
2115
+ }
2116
+ if (parts[0] === "timeline") {
2117
+ const targets = tab ? [tab] : liveTabs().map((t) => t.id);
2118
+ for (const id of targets)
2119
+ await collectFromClient(id).catch(() => {
2120
+ });
2121
+ const entries = tab ? actionLog.filter((e) => e.tab === tab) : actionLog;
2122
+ const dropped = targets.reduce((n, id) => n + (_nullishCoalesce(droppedByTab.get(id), () => ( 0))), 0);
2123
+ send(res, 200, _chunkQF6AG3M5cjs.formatActions.call(void 0, entries, void 0, dropped));
2124
+ return;
2125
+ }
2126
+ if (ambiguous()) {
2127
+ refuse();
2128
+ return;
2129
+ }
2130
+ if (parts[0] === "eval") {
2131
+ let code = url.searchParams.get("code") || "";
2132
+ if (!code && req.method === "POST")
2133
+ code = await readBody(req);
2134
+ if (!code) {
2135
+ send(res, 400, "eval needs ?code= or a POST body");
2136
+ return;
2137
+ }
2138
+ const r = await evalInClient(code, tab);
2139
+ const body = r.ok ? _nullishCoalesce(r.value, () => ( "undefined")) : `error: ${r.error}`;
2140
+ send(res, r.ok ? 200 : 422, r.hint ? `${body}
2141
+
2142
+ hint: ${r.hint}` : body);
2143
+ return;
2144
+ }
2145
+ if (parts[0] === "action") {
2146
+ const knob = url.searchParams.get("knob") || "";
2147
+ if (knob) {
2148
+ lastRaw = null;
2149
+ const r2 = await knobInClient(
2150
+ knob,
2151
+ url.searchParams.get("file") || void 0,
2152
+ _nullishCoalesce(url.searchParams.get("value"), () => ( void 0)),
2153
+ tab
2154
+ );
2155
+ send(res, r2.ok ? 200 : 422, r2.ok ? _nullishCoalesce(r2.value, () => ( "undefined")) : `error: ${r2.error}`);
2156
+ return;
2157
+ }
2158
+ const name = url.searchParams.get("name") || "";
2159
+ if (!name) {
2160
+ const hint = _chunkQF6AG3M5cjs.misroutedAction.call(void 0, [...url.searchParams.keys()]);
2161
+ const base = "action needs ?knob= (a panel label) or ?name= (a __AIHAND_ACTIONS__ key)";
2162
+ send(res, 400, hint ? `${base}
2163
+
2164
+ hint: ${hint}` : base);
2165
+ return;
2166
+ }
2167
+ let args = [];
2168
+ const rawArgs = url.searchParams.get("args") || (req.method === "POST" ? await readBody(req) : "");
2169
+ if (rawArgs) {
2170
+ try {
2171
+ const parsed = JSON.parse(rawArgs);
2172
+ args = Array.isArray(parsed) ? parsed : [parsed];
2173
+ } catch (e8) {
2174
+ send(res, 400, `action args= must be a JSON array, got: ${rawArgs.slice(0, 80)}`);
2175
+ return;
2176
+ }
2177
+ }
2178
+ lastRaw = null;
2179
+ const r = await semanticInClient(name, args, tab);
2180
+ send(res, r.ok ? 200 : 422, r.ok ? _nullishCoalesce(r.value, () => ( "undefined")) : `error: ${r.error}`);
2181
+ return;
2182
+ }
2183
+ if (parts[0] === "dom") {
2184
+ const dom = await collectDomFromClient(
2185
+ url.searchParams.get("scope") || void 0,
2186
+ url.searchParams.get("sel") || void 0,
2187
+ tab
2188
+ );
2189
+ send(res, 200, dom || "(empty)");
2190
+ return;
2191
+ }
2192
+ if (parts[0] === "state" && url.searchParams.has("path")) {
2193
+ const path2 = url.searchParams.get("path") || "";
2194
+ const r = await collectStatePathFromClient(path2, tab);
2195
+ if (!r.ok) {
2196
+ send(res, 404, `path not found: ${r.error}`);
2197
+ return;
2198
+ }
2199
+ let body;
2200
+ try {
2201
+ body = _nullishCoalesce(JSON.stringify(r.value, null, 2), () => ( String(r.value)));
2202
+ } catch (e9) {
2203
+ body = String(r.value);
2204
+ }
2205
+ send(res, 200, body);
2206
+ return;
2207
+ }
2208
+ if (parts[0] === "screen") {
2209
+ const { reply, result } = await _chunkQF6AG3M5cjs.dispatchScreen.call(void 0, bridge, { tab, form: url.searchParams.get("form") || void 0 });
2210
+ const since = url.searchParams.get("since");
2211
+ const token = stashScreen(reply);
2212
+ if (since) {
2213
+ const prev = screenStash.get(since);
2214
+ if (!prev) {
2215
+ send(res, 422, `unknown since token "${since}" (expired or never issued) \u2014 read /screen first for a fresh token`);
2216
+ return;
2217
+ }
2218
+ const d = diffState(
2219
+ { ui: "", console: prev.console, network: prev.network, errors: prev.errors, state: {}, url: "", timestamp: 0 },
2220
+ { ui: "", console: reply.console, network: reply.network, errors: reply.errors, state: {}, url: "", timestamp: 0 }
2221
+ );
2222
+ const changed = diffScreen(prev.snap, reply.snap, d.newErrors, d.newExceptions, d.newFailedRequests);
2223
+ send(res, 200, `token: ${token}
2224
+ ${changed.length ? changed.join("\n") : "(no state change)"}`);
2225
+ return;
2226
+ }
2227
+ send(res, 200, result.body === "(empty)" ? "(empty)" : `token: ${token}
2228
+ ${result.body}`);
2229
+ return;
2230
+ }
2231
+ if (parts[0] === "cdp" && parts[1] === "poll") {
2232
+ const queued = cdpQueue.shift();
2233
+ if (queued) {
2234
+ send(res, 200, JSON.stringify(queued));
2235
+ return;
2236
+ }
2237
+ const cmd = await new Promise((resolve5) => {
2238
+ cdpWaiter = resolve5;
2239
+ setTimeout(() => {
2240
+ if (cdpWaiter === resolve5) {
2241
+ cdpWaiter = null;
2242
+ resolve5(null);
2243
+ }
2244
+ }, 25e3);
2245
+ });
2246
+ if (cmd)
2247
+ send(res, 200, JSON.stringify(cmd));
2248
+ else
2249
+ send(res, 204, "");
2250
+ return;
2251
+ }
2252
+ if (parts[0] === "cdp" && parts[1] === "result") {
2253
+ const body = await readBody(req);
2254
+ let data;
2255
+ try {
2256
+ data = JSON.parse(body);
2257
+ } catch (e10) {
2258
+ send(res, 400, "cdp/result needs a JSON body {id, ok, error?}");
2259
+ return;
2260
+ }
2261
+ const resolveCdp = cdpResults.get(data.id);
2262
+ if (resolveCdp) {
2263
+ cdpResults.delete(data.id);
2264
+ resolveCdp({ ok: data.ok, error: data.error });
2265
+ }
2266
+ send(res, 200, "ok");
2267
+ return;
2268
+ }
2269
+ if (parts[0] === "chain") {
2270
+ const body = await readBody(req);
2271
+ let steps;
2272
+ try {
2273
+ steps = JSON.parse(body);
2274
+ if (!Array.isArray(steps))
2275
+ throw new Error("body must be a JSON array");
2276
+ } catch (e) {
2277
+ send(res, 400, `invalid chain body: ${e instanceof Error ? e.message : String(e)}`);
2278
+ return;
2279
+ }
2280
+ lastRaw = null;
2281
+ const dr = await _chunkQF6AG3M5cjs.runChain.call(void 0, steps, async (type, args) => {
2282
+ if (type === "knob") {
2283
+ const k = args;
2284
+ if (!k.knob)
2285
+ return { ok: false, label: "knob", error: "needs knob=<panel label>" };
2286
+ const kr = await knobInClient(k.knob, k.file, k.value, tab);
2287
+ return { ok: kr.ok, label: "knob", detail: kr.ok ? `replayed ${k.knob}` : void 0, error: kr.ok ? void 0 : kr.error, screen: kr.ok ? kr.value : void 0 };
2288
+ }
2289
+ const check2 = _chunkQF6AG3M5cjs.resolveAction.call(void 0, type, args);
2290
+ if (!check2.valid)
2291
+ return { ok: false, label: type, error: check2.error };
2292
+ const r = await runAction(type, args, tab);
2293
+ return { ok: r.ok, label: type, detail: r.detail, error: r.error, screen: r.screen, actions: r.actions };
2294
+ });
2295
+ send(res, dr.status, dr.body);
2296
+ return;
2297
+ }
2298
+ if (_chunkQF6AG3M5cjs.TYPES.includes(parts[0])) {
2299
+ const args = _chunkQF6AG3M5cjs.argsFromQuery.call(void 0, url.searchParams);
2300
+ if (_chunkQF6AG3M5cjs.isDispatchAction.call(void 0, parts[0])) {
2301
+ const dr = await _chunkQF6AG3M5cjs.dispatchAction.call(void 0, bridge, parts[0], args, { nextId: () => ++actionId, tab });
2302
+ lastRaw = null;
2303
+ send(res, dr.status, dr.body);
2304
+ return;
2305
+ }
2306
+ const check2 = _chunkQF6AG3M5cjs.resolveAction.call(void 0, parts[0], args);
2307
+ if (!check2.valid) {
2308
+ send(res, 400, _nullishCoalesce(check2.error, () => ( "invalid action")));
2309
+ return;
2310
+ }
2311
+ const result = await runAction(parts[0], args, tab);
2312
+ if (parts[0] === "screenshot" && result.dataUrl) {
2313
+ const dir = _path.resolve.call(void 0, server.config.root, ".aidev");
2314
+ _fs.mkdirSync.call(void 0, dir, { recursive: true });
2315
+ const name = url.searchParams.get("out") || `shot-${result.dataUrl.length}.png`;
2316
+ const file = _path.resolve.call(void 0, dir, name);
2317
+ _fs.writeFileSync.call(void 0, file, _buffer.Buffer.from(result.dataUrl.split(",")[1], "base64"));
2318
+ send(res, 200, `saved: ${file}`);
2319
+ return;
2320
+ }
2321
+ const head = result.ok ? result.detail || "ok" : `${result.error}${result.detail ? `
2322
+
2323
+ clickable: ${result.detail}` : ""}`;
2324
+ const actionsTail = result.actions ? `
2325
+
2326
+ --- recent actions ---
2327
+ ${result.actions}` : "";
2328
+ const changedTail = result.screen ? `
2329
+
2330
+ --- changed ---
2331
+ ${result.screen}` : "";
2332
+ const flowTail = result.flow ? `
2333
+
2334
+ --- flow ---
2335
+ ${result.flow}` : "";
2336
+ send(res, result.ok ? 200 : 422, `${head}${actionsTail}${changedTail}${flowTail}`);
2337
+ return;
2338
+ }
2339
+ if (parts[0] === "check") {
2340
+ const raw2 = await collectFromClient(tab);
2341
+ lastRaw = raw2;
2342
+ const result = check(raw2);
2343
+ const output = emitCheck(result);
2344
+ send(res, result.pass ? 200 : 417, output);
2345
+ return;
2346
+ }
2347
+ if (parts[0] === "profile") {
2348
+ const noPerfMsg = (t) => {
2349
+ const info = t ? tabs.get(t) : liveTabs()[0];
2350
+ return info ? `tab '${info.id}' is connected but BACKGROUNDED \u2014 the browser throttles rAF to ~1fps for hidden tabs, so there are no real frames to profile. You don't need to open anything: the page is already running. Ask the user to click that browser tab to the foreground and keep it there ~2s, then re-run /profile. (Profiling is the only read needing foreground \u2014 /screen and /dom work backgrounded.)` : `(${diagnose2(t)})`;
2351
+ };
2352
+ const resetPerfWindow = () => new Promise((resolve5, reject) => {
2353
+ const timeout = setTimeout(() => reject(new Error("timeout waiting for perf-reset-ack")), 3e3);
2354
+ const handler = (data) => {
2355
+ if (tab && data.tab !== tab) return;
2356
+ clearTimeout(timeout);
2357
+ server.hot.off("aihand:perf-reset-ack", handler);
2358
+ resolve5();
2359
+ };
2360
+ server.hot.on("aihand:perf-reset-ack", handler);
2361
+ server.hot.send("aihand:perf-reset", { tab, requireVisible: false });
2362
+ });
2363
+ if (parts[1] === "reset") {
2364
+ await resetPerfWindow();
2365
+ send(res, 200, "perf window cleared \u2014 reproduce the interaction, then GET /profile");
2366
+ return;
2367
+ }
2368
+ if (parts[1] === "diff") {
2369
+ const raw3 = await collectFromClient(tab);
2370
+ lastRaw = raw3;
2371
+ if (!raw3.performance) {
2372
+ send(res, 200, noPerfMsg(tab));
2373
+ return;
2374
+ }
2375
+ if (!perfBaseline) {
2376
+ perfBaseline = raw3.performance;
2377
+ await resetPerfWindow();
2378
+ send(res, 200, "baseline captured + window cleared \u2014 make your fix, reproduce the interaction, then GET /profile/diff again for the verdict");
2379
+ return;
2380
+ }
2381
+ const report = diffPerformance(perfBaseline, raw3.performance);
2382
+ perfBaseline = null;
2383
+ send(res, 200, report);
2384
+ return;
2385
+ }
2386
+ const raw2 = await collectFromClient(tab);
2387
+ lastRaw = raw2;
2388
+ if (!raw2.performance) {
2389
+ send(res, 200, noPerfMsg(tab));
2390
+ return;
2391
+ }
2392
+ const hiddenNote = raw2.performance.hiddenFrames > 10 ? `
2393
+
2394
+ \u26A0 ${raw2.performance.hiddenFrames} frames skipped while tab was hidden \u2014 bring it to foreground and /profile/reset for accurate data.` : "";
2395
+ send(res, 200, detail(raw2, "profile", void 0, false) + hiddenNote);
2396
+ return;
2397
+ }
2398
+ if (parts.length >= 1) {
2399
+ if (!lastRaw)
2400
+ lastRaw = await collectFromClient(tab);
2401
+ const result = detail(lastRaw, parts[0], parts[1], full);
2402
+ if (result !== null) {
2403
+ send(res, 200, result);
2404
+ return;
2405
+ }
2406
+ const SECTIONS2 = ["ui", "console", "network", "errors", "state", "profile"];
2407
+ send(res, 404, SECTIONS2.includes(parts[0]) ? `'${parts[0]}' is empty right now \u2014 nothing captured this window (not an error).` : `unknown section '${parts[0]}'. Valid: ${SECTIONS2.join(", ")}. (Or /screen, /dom, /tabs, /timeline, /check.)`);
2408
+ return;
2409
+ }
2410
+ const raw = await collectFromClient(tab);
2411
+ lastRaw = raw;
2412
+ if (full) {
2413
+ const compacted = compact(raw);
2414
+ send(res, 200, emit(compacted));
2415
+ } else {
2416
+ send(res, 200, emitSummary(raw));
2417
+ }
2418
+ } catch (err) {
2419
+ send(res, 504, err instanceof Error ? err.message : "unknown error");
2420
+ }
2421
+ });
2422
+ }
2423
+ };
2424
+ }
2425
+
2426
+ // src/vite.ts
2427
+ async function aihand(opts = {}) {
2428
+ const config = await _chunkIGNEAOLTcjs.loadConfig.call(void 0, );
2429
+ const plugins = [];
2430
+ if (config.runtime.enabled) {
2431
+ plugins.push(...aihandPlugin(), knobSchema());
2432
+ const files = _chunkKQOABC2Ocjs.scan.call(void 0, config).map((f) => _path.resolve.call(void 0, f));
2433
+ plugins.push(storeSchema({ roots: files, storeMarker: config.runtime.storeMarker }));
2434
+ const knobFiles = files.filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx"));
2435
+ plugins.push(storeRefs({ roots: files, knobFiles, srcRoot: _path.resolve.call(void 0, "src"), storeMarker: config.runtime.storeMarker }));
2436
+ }
2437
+ if (config.read.enabled)
2438
+ plugins.push(repodexWatchPlugin(opts));
2439
+ return plugins;
2440
+ }
2441
+
2442
+
2443
+
2444
+
2445
+
2446
+
2447
+
2448
+
2449
+
2450
+
2451
+ exports.discover = discover; exports.storeSchema = storeSchema; exports.check = check; exports.diffState = diffState; exports.emitSummary = emitSummary; exports.emitCheck = emitCheck; exports.emitDiff = emitDiff; exports.aihand = aihand;