@hyperframes/inspector 0.0.0-stage → 0.8.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +104 -2
- package/dist/inspection.js +2007 -0
- package/dist/live.js +1288 -0
- package/dist/service.js +63122 -0
- package/dist/types/core/src/audioAutomation.d.ts +202 -0
- package/dist/types/core/src/audioFx.d.ts +181 -0
- package/dist/types/core/src/audioGain.d.ts +44 -0
- package/dist/types/core/src/colorGrading.d.ts +1959 -0
- package/dist/types/core/src/colorGradingCurves.d.ts +8 -0
- package/dist/types/core/src/colorLuts.d.ts +24 -0
- package/dist/types/core/src/compiler/assetPaths.d.ts +1 -0
- package/dist/types/core/src/compiler/compositionAssembly.d.ts +144 -0
- package/dist/types/core/src/compiler/compositionScoping.d.ts +28 -0
- package/dist/types/core/src/compiler/externalScripts.d.ts +8 -0
- package/dist/types/core/src/compiler/htmlBundler.d.ts +74 -0
- package/dist/types/core/src/compiler/htmlCompiler.d.ts +18 -0
- package/dist/types/core/src/compiler/htmlDocument.d.ts +23 -0
- package/dist/types/core/src/compiler/importMaps.d.ts +18 -0
- package/dist/types/core/src/compiler/inlineSubCompositions.d.ts +151 -0
- package/dist/types/core/src/compiler/rewriteSubCompPaths.d.ts +1 -0
- package/dist/types/core/src/compiler/scriptRuns.d.ts +31 -0
- package/dist/types/core/src/compiler/selectorIdTokens.d.ts +70 -0
- package/dist/types/core/src/compiler/standaloneEntry.d.ts +2 -0
- package/dist/types/core/src/compiler/staticGuard.d.ts +7 -0
- package/dist/types/core/src/compiler/svgIdNamespacing.d.ts +80 -0
- package/dist/types/core/src/compiler/svgSelectorAliases.d.ts +30 -0
- package/dist/types/core/src/compiler/timingCompiler.d.ts +89 -0
- package/dist/types/core/src/generated/runtime-inline.d.ts +6 -0
- package/dist/types/core/src/generated/svg-selector-aliases-inline.d.ts +2 -0
- package/dist/types/core/src/inline-scripts/pickerApi.d.ts +32 -0
- package/dist/types/core/src/inline-scripts/runtimeContract.d.ts +13 -0
- package/dist/types/core/src/mediaTiming.d.ts +41 -0
- package/dist/types/core/src/playbackRateBounds.d.ts +1 -0
- package/dist/types/core/src/runtime/analytics.d.ts +18 -0
- package/dist/types/core/src/runtime/applyVariableBindings.d.ts +47 -0
- package/dist/types/core/src/runtime/audioAutomationVolume.d.ts +19 -0
- package/dist/types/core/src/runtime/authoredTiming.d.ts +16 -0
- package/dist/types/core/src/runtime/compositionDimension.d.ts +5 -0
- package/dist/types/core/src/runtime/diagnostics.d.ts +3 -0
- package/dist/types/core/src/runtime/domRealm.d.ts +67 -0
- package/dist/types/core/src/runtime/flattenedRoot.d.ts +9 -0
- package/dist/types/core/src/runtime/getVariables.d.ts +42 -0
- package/dist/types/core/src/runtime/globals.d.ts +11 -0
- package/dist/types/core/src/runtime/inspectionTypes.d.ts +68 -0
- package/dist/types/core/src/runtime/playbackRate.d.ts +26 -0
- package/dist/types/core/src/runtime/protocol.d.ts +30 -0
- package/dist/types/core/src/runtime/proxySrc.d.ts +3 -0
- package/dist/types/core/src/runtime/startExpression.d.ts +14 -0
- package/dist/types/core/src/runtime/startResolver.d.ts +21 -0
- package/dist/types/core/src/runtime/timelineDuration.d.ts +3 -0
- package/dist/types/core/src/runtime/timelineInspection.d.ts +46 -0
- package/dist/types/core/src/runtime/timelineValues.d.ts +15 -0
- package/dist/types/core/src/runtime/types.d.ts +335 -0
- package/dist/types/core/src/runtime/variableScope.d.ts +8 -0
- package/dist/types/core/src/safePath.d.ts +49 -0
- package/dist/types/core/src/sceneParts.d.ts +9 -0
- package/dist/types/core/src/speedRamp.d.ts +59 -0
- package/dist/types/core/src/tokenSlug.d.ts +12 -0
- package/dist/types/inspector/src/elementMap.d.ts +33 -0
- package/dist/types/inspector/src/engine.d.ts +35 -0
- package/dist/types/inspector/src/live.d.ts +18 -0
- package/dist/types/inspector/src/mapInspection.d.ts +76 -0
- package/dist/types/inspector/src/outputSchemas.d.ts +12 -0
- package/dist/types/inspector/src/pageQueries.d.ts +21 -0
- package/dist/types/inspector/src/pageServer.d.ts +2 -0
- package/dist/types/inspector/src/requests.d.ts +5 -0
- package/dist/types/inspector/src/service.d.ts +98 -0
- package/dist/types/inspector/src/sourceDependencies.d.ts +5 -0
- package/dist/types/inspector/src/sources.d.ts +49 -0
- package/dist/types/inspector/src/timelineReport.d.ts +28 -0
- package/dist/types/inspector/src/tools.d.ts +18 -0
- package/dist/types/inspector/src/types.d.ts +1 -0
- package/package.json +54 -3
package/dist/live.js
ADDED
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// src/live.ts
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
// src/elementMap.js
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
function collectElementDom() {
|
|
10
|
+
const excluded2 = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META"]);
|
|
11
|
+
const nodes = [];
|
|
12
|
+
const paths = /* @__PURE__ */ new Map();
|
|
13
|
+
const siblingGroups = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
function siblingCount(element2, selector) {
|
|
15
|
+
const owner = element2.parentElement ?? element2;
|
|
16
|
+
let counts = siblingGroups.get(owner);
|
|
17
|
+
if (!counts) {
|
|
18
|
+
counts = /* @__PURE__ */ new Map();
|
|
19
|
+
siblingGroups.set(owner, counts);
|
|
20
|
+
}
|
|
21
|
+
if (!counts.has(selector)) {
|
|
22
|
+
const siblings = element2.parentElement ? [...element2.parentElement.children] : [element2];
|
|
23
|
+
counts.set(selector, siblings.filter((sibling) => sibling.matches(selector)).length);
|
|
24
|
+
}
|
|
25
|
+
return counts.get(selector);
|
|
26
|
+
}
|
|
27
|
+
const quote = (value) => JSON.stringify(value);
|
|
28
|
+
function path(element2) {
|
|
29
|
+
if (paths.has(element2)) return paths.get(element2);
|
|
30
|
+
const candidates = [];
|
|
31
|
+
if (element2.id) candidates.push(`#${CSS.escape(element2.id)}`);
|
|
32
|
+
const hfId = element2.getAttribute("data-hf-id");
|
|
33
|
+
if (hfId) candidates.push(`[data-hf-id=${quote(hfId)}]`);
|
|
34
|
+
const unique = candidates.find((selector2) => document.querySelectorAll(selector2).length === 1);
|
|
35
|
+
const parent = element2.parentElement;
|
|
36
|
+
const tag = element2.localName;
|
|
37
|
+
const position2 = parent ? [...parent.children].indexOf(element2) + 1 : 1;
|
|
38
|
+
const selector = unique ?? (parent ? `${path(parent)} > ${CSS.escape(tag)}:nth-child(${position2})` : CSS.escape(tag));
|
|
39
|
+
paths.set(element2, selector);
|
|
40
|
+
return selector;
|
|
41
|
+
}
|
|
42
|
+
function visit(element2, parent) {
|
|
43
|
+
if (excluded2.has(element2.tagName)) return;
|
|
44
|
+
const selector = path(element2);
|
|
45
|
+
if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !== element2) {
|
|
46
|
+
throw new Error(`Element map selector does not resolve uniquely: ${selector}`);
|
|
47
|
+
}
|
|
48
|
+
const classes = [...element2.classList].sort();
|
|
49
|
+
const tag = element2.localName;
|
|
50
|
+
const simple = `${CSS.escape(tag)}${classes.map((value) => `.${CSS.escape(value)}`).join("")}`;
|
|
51
|
+
const siblings = element2.parentElement ? [...element2.parentElement.children] : [element2];
|
|
52
|
+
const index = nodes.length;
|
|
53
|
+
const node = {
|
|
54
|
+
index,
|
|
55
|
+
parent,
|
|
56
|
+
selector,
|
|
57
|
+
tag,
|
|
58
|
+
id: element2.id,
|
|
59
|
+
hfId: element2.getAttribute("data-hf-id"),
|
|
60
|
+
classes,
|
|
61
|
+
composition: element2.getAttribute("data-composition-id"),
|
|
62
|
+
compositionSrc: element2.getAttribute("data-composition-src"),
|
|
63
|
+
text: [...element2.childNodes].filter((child) => child.nodeType === 3).map((child) => child.textContent).join("").replace(/\s+/g, " ").trim(),
|
|
64
|
+
media: ["img", "video", "audio", "source", "iframe"].includes(tag) ? element2.getAttribute("src") : null,
|
|
65
|
+
relative: element2.id && document.querySelectorAll(`#${CSS.escape(element2.id)}`).length === 1 ? `#${CSS.escape(element2.id)}` : siblingCount(element2, simple) === 1 ? simple : `${CSS.escape(tag)}:nth-child(${siblings.indexOf(element2) + 1})`,
|
|
66
|
+
groupSelector: simple,
|
|
67
|
+
siblingMatchCount: siblingCount(element2, simple),
|
|
68
|
+
children: []
|
|
69
|
+
};
|
|
70
|
+
nodes.push(node);
|
|
71
|
+
for (const child of element2.children) {
|
|
72
|
+
const childIndex = visit(child, index);
|
|
73
|
+
if (childIndex !== void 0) node.children.push(childIndex);
|
|
74
|
+
}
|
|
75
|
+
return index;
|
|
76
|
+
}
|
|
77
|
+
if (document.body) visit(document.body, null);
|
|
78
|
+
return {
|
|
79
|
+
generation: window.__hfLiveInspection?.generation ?? null,
|
|
80
|
+
nodes,
|
|
81
|
+
limitations: {
|
|
82
|
+
canvas: nodes.filter((node) => node.tag === "canvas").length,
|
|
83
|
+
iframes: nodes.filter((node) => node.tag === "iframe").length,
|
|
84
|
+
shadowRoots: [...document.querySelectorAll("*")].filter((node) => node.shadowRoot).length
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function signature(node, nodes) {
|
|
89
|
+
return [
|
|
90
|
+
node.tag,
|
|
91
|
+
node.id,
|
|
92
|
+
node.classes,
|
|
93
|
+
node.composition,
|
|
94
|
+
node.compositionSrc,
|
|
95
|
+
node.text,
|
|
96
|
+
node.media,
|
|
97
|
+
node.children.map((index) => signature(nodes[index], nodes))
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function mediaIdentifier(source) {
|
|
101
|
+
if (source.startsWith("data:")) {
|
|
102
|
+
const mime = source.slice(5).split(/[;,]/, 1)[0] || "text/plain";
|
|
103
|
+
const hash = createHash("sha256").update(source).digest("hex").slice(0, 16);
|
|
104
|
+
return `data:${mime}#sha256:${hash}`;
|
|
105
|
+
}
|
|
106
|
+
if (source.startsWith("blob:")) return "blob:runtime";
|
|
107
|
+
return source;
|
|
108
|
+
}
|
|
109
|
+
function describe(node, selector, count) {
|
|
110
|
+
const fields = [selector];
|
|
111
|
+
if (!selector.startsWith(node.tag)) fields.push(node.tag);
|
|
112
|
+
if (count > 1) fields.push(`\xD7${count}`);
|
|
113
|
+
if (node.id && selector !== `#${node.id}`) fields.push(`id=${JSON.stringify(node.id)}`);
|
|
114
|
+
if (node.classes.length && !selector.includes("."))
|
|
115
|
+
fields.push(`class=${JSON.stringify(node.classes.join(" "))}`);
|
|
116
|
+
if (node.text) fields.push(JSON.stringify(node.text));
|
|
117
|
+
if (node.media) fields.push(`src=${JSON.stringify(mediaIdentifier(node.media))}`);
|
|
118
|
+
if (node.composition) fields.push(`composition=${JSON.stringify(node.composition)}`);
|
|
119
|
+
if (node.compositionSrc) fields.push(`file=${JSON.stringify(node.compositionSrc)}`);
|
|
120
|
+
return fields.join(" ");
|
|
121
|
+
}
|
|
122
|
+
function formatElementMap(snapshot, { file = "index.html" } = {}) {
|
|
123
|
+
const { nodes } = snapshot;
|
|
124
|
+
const lines = [`File: ${JSON.stringify(file)}`];
|
|
125
|
+
const entries = [];
|
|
126
|
+
function siblings(indices, depth) {
|
|
127
|
+
const groups = [];
|
|
128
|
+
for (const index of indices) {
|
|
129
|
+
const node = nodes[index];
|
|
130
|
+
const key = JSON.stringify(signature(node, nodes));
|
|
131
|
+
const previous = groups.at(-1);
|
|
132
|
+
if (!node.id && previous?.key === key) previous.indices.push(index);
|
|
133
|
+
else groups.push({ key, indices: [index] });
|
|
134
|
+
}
|
|
135
|
+
for (const group of groups) {
|
|
136
|
+
const members = group.indices.map((index) => nodes[index]);
|
|
137
|
+
const node = members[0];
|
|
138
|
+
const exactGroup = members.length > 1 && node.siblingMatchCount === members.length;
|
|
139
|
+
if (members.length > 1 && !exactGroup) {
|
|
140
|
+
for (const member of members) emit([member], depth);
|
|
141
|
+
} else emit(members, depth);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function emit(members, depth, repeatedParents = false) {
|
|
145
|
+
const node = members[0];
|
|
146
|
+
const selector = members.length > 1 && !repeatedParents ? node.groupSelector : node.relative;
|
|
147
|
+
lines.push(`${" ".repeat(depth)}${describe(node, selector, members.length)}`);
|
|
148
|
+
entries.push({
|
|
149
|
+
line: lines.length,
|
|
150
|
+
selectors: members.map((member) => member.selector),
|
|
151
|
+
nodeIndices: members.map((member) => member.index)
|
|
152
|
+
});
|
|
153
|
+
if (members.length === 1) {
|
|
154
|
+
siblings(node.children, depth + 1);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (let position2 = 0; position2 < node.children.length; position2++) {
|
|
158
|
+
emit(
|
|
159
|
+
members.map((member) => nodes[member.children[position2]]),
|
|
160
|
+
depth + 1,
|
|
161
|
+
true
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
siblings(
|
|
166
|
+
nodes.filter((node) => node.parent === null).map((node) => node.index),
|
|
167
|
+
0
|
|
168
|
+
);
|
|
169
|
+
const { canvas, iframes, shadowRoots } = snapshot.limitations;
|
|
170
|
+
if (canvas || iframes || shadowRoots)
|
|
171
|
+
lines.push(
|
|
172
|
+
`Unmapped contents: canvas=${canvas}, iframe=${iframes}, shadow-root=${shadowRoots}.`
|
|
173
|
+
);
|
|
174
|
+
const covered = entries.flatMap((entry) => entry.nodeIndices);
|
|
175
|
+
if (covered.length !== nodes.length || new Set(covered).size !== nodes.length)
|
|
176
|
+
throw new Error("Element map coverage mismatch");
|
|
177
|
+
return {
|
|
178
|
+
text: `${lines.join("\n")}
|
|
179
|
+
`,
|
|
180
|
+
entries,
|
|
181
|
+
elementCount: nodes.length,
|
|
182
|
+
coveredCount: covered.length
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/sources.ts
|
|
187
|
+
import { closeSync, fstatSync, openSync, readFileSync, readdirSync } from "node:fs";
|
|
188
|
+
import { extname, join, relative } from "node:path";
|
|
189
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
190
|
+
import { parse as parse2 } from "acorn";
|
|
191
|
+
import postcss from "postcss";
|
|
192
|
+
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
193
|
+
|
|
194
|
+
// src/sourceDependencies.ts
|
|
195
|
+
import { parse } from "acorn";
|
|
196
|
+
import { analyze } from "eslint-scope";
|
|
197
|
+
function sourceDependencies(text) {
|
|
198
|
+
try {
|
|
199
|
+
const ast = parse(text, {
|
|
200
|
+
ecmaVersion: "latest",
|
|
201
|
+
sourceType: "module",
|
|
202
|
+
allowReturnOutsideFunction: true,
|
|
203
|
+
ranges: true
|
|
204
|
+
});
|
|
205
|
+
const scopes = analyze(ast, {
|
|
206
|
+
ecmaVersion: 2022,
|
|
207
|
+
sourceType: "module",
|
|
208
|
+
optimistic: false
|
|
209
|
+
});
|
|
210
|
+
const references = scopes.scopes.flatMap(
|
|
211
|
+
(scope) => scope.references.flatMap((reference) => {
|
|
212
|
+
const location = reference.identifier.range;
|
|
213
|
+
if (!reference.isRead() || !location) return [];
|
|
214
|
+
const definitions = (reference.resolved?.defs ?? []).flatMap((definition) => {
|
|
215
|
+
const node = definition.type === "Variable" ? definition.parent : definition.type === "FunctionName" ? definition.node : null;
|
|
216
|
+
return node?.range ? [{ start: node.range[0], end: node.range[1] }] : [];
|
|
217
|
+
});
|
|
218
|
+
return definitions.length ? [{ start: location[0], end: location[1], definitions }] : [];
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
return (range) => {
|
|
222
|
+
const pending = [range];
|
|
223
|
+
const found = /* @__PURE__ */ new Map();
|
|
224
|
+
for (let index = 0; index < pending.length; index++) {
|
|
225
|
+
const current = pending[index];
|
|
226
|
+
for (const reference of references) {
|
|
227
|
+
if (reference.start < current.start || reference.end > current.end) continue;
|
|
228
|
+
for (const { start, end } of reference.definitions) {
|
|
229
|
+
if (start >= range.start && end <= range.end) continue;
|
|
230
|
+
const key = `${start}:${end}`;
|
|
231
|
+
if (found.has(key)) continue;
|
|
232
|
+
const dependency = { start, end };
|
|
233
|
+
found.set(key, dependency);
|
|
234
|
+
pending.push(dependency);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return [...found.values()].sort((a, b) => a.start - b.start);
|
|
239
|
+
};
|
|
240
|
+
} catch {
|
|
241
|
+
return () => [];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/sources.ts
|
|
246
|
+
var starts = (text) => [0, ...[...text.matchAll(/\n/g)].map((m) => m.index + 1)];
|
|
247
|
+
function position(lines, offset2) {
|
|
248
|
+
let lo = 0, hi = lines.length - 1;
|
|
249
|
+
while (lo < hi) {
|
|
250
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
251
|
+
if (lines[mid] <= offset2) lo = mid;
|
|
252
|
+
else hi = mid - 1;
|
|
253
|
+
}
|
|
254
|
+
return { line: lo + 1, column: offset2 - lines[lo] };
|
|
255
|
+
}
|
|
256
|
+
function occurrences(text, needle) {
|
|
257
|
+
const hits = [];
|
|
258
|
+
if (!needle) return hits;
|
|
259
|
+
let at2 = text.indexOf(needle);
|
|
260
|
+
while (at2 >= 0) {
|
|
261
|
+
hits.push(at2);
|
|
262
|
+
at2 = text.indexOf(needle, at2 + needle.length);
|
|
263
|
+
}
|
|
264
|
+
return hits;
|
|
265
|
+
}
|
|
266
|
+
var excluded = /* @__PURE__ */ new Set([
|
|
267
|
+
"node_modules",
|
|
268
|
+
".git",
|
|
269
|
+
".hyperframes",
|
|
270
|
+
".agents",
|
|
271
|
+
".claude",
|
|
272
|
+
"evidence",
|
|
273
|
+
"outputs",
|
|
274
|
+
"renders",
|
|
275
|
+
"snapshots"
|
|
276
|
+
]);
|
|
277
|
+
function sourceFiles(project) {
|
|
278
|
+
const files = [];
|
|
279
|
+
function visit(dir) {
|
|
280
|
+
for (const item of readdirSync(dir, { withFileTypes: true })) {
|
|
281
|
+
if (excluded.has(item.name) || item.isSymbolicLink()) continue;
|
|
282
|
+
const path = join(dir, item.name);
|
|
283
|
+
if (item.isDirectory()) visit(path);
|
|
284
|
+
else if ([".html", ".js", ".mjs", ".css", ".json"].includes(extname(path))) files.push(path);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
visit(project);
|
|
288
|
+
return files.sort();
|
|
289
|
+
}
|
|
290
|
+
function fingerprint(project) {
|
|
291
|
+
const hash = createHash2("sha256");
|
|
292
|
+
for (const path of sourceFiles(project)) {
|
|
293
|
+
hash.update(relative(project, path));
|
|
294
|
+
hash.update(readFileSync(path));
|
|
295
|
+
}
|
|
296
|
+
return hash.digest("hex");
|
|
297
|
+
}
|
|
298
|
+
function statements(text) {
|
|
299
|
+
const result = [];
|
|
300
|
+
function visit(value) {
|
|
301
|
+
if (!value || typeof value !== "object") return;
|
|
302
|
+
if (Array.isArray(value)) {
|
|
303
|
+
value.forEach(visit);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if ("type" in value && "start" in value && "end" in value && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number") {
|
|
307
|
+
if ([
|
|
308
|
+
"ExpressionStatement",
|
|
309
|
+
"VariableDeclaration",
|
|
310
|
+
"ReturnStatement",
|
|
311
|
+
"ThrowStatement",
|
|
312
|
+
"FunctionDeclaration"
|
|
313
|
+
].includes(value.type))
|
|
314
|
+
result.push({ start: value.start, end: value.end, type: value.type });
|
|
315
|
+
}
|
|
316
|
+
for (const entry of Object.values(value)) if (typeof entry === "object") visit(entry);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const ast = parse2(text, {
|
|
320
|
+
ecmaVersion: "latest",
|
|
321
|
+
sourceType: "module",
|
|
322
|
+
allowReturnOutsideFunction: true
|
|
323
|
+
});
|
|
324
|
+
visit(ast);
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function rawTextElements(text, tag) {
|
|
330
|
+
const lower = text.toLowerCase();
|
|
331
|
+
const opening = `<${tag}`;
|
|
332
|
+
const closing = `</${tag}`;
|
|
333
|
+
const elements = [];
|
|
334
|
+
let cursor = 0;
|
|
335
|
+
while (cursor < text.length) {
|
|
336
|
+
const start = lower.indexOf(opening, cursor);
|
|
337
|
+
if (start < 0) break;
|
|
338
|
+
const boundary = lower[start + opening.length];
|
|
339
|
+
if (boundary && boundary !== ">" && !/\s/.test(boundary)) {
|
|
340
|
+
cursor = start + opening.length;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
const contentStart = lower.indexOf(">", start + opening.length);
|
|
344
|
+
if (contentStart < 0) break;
|
|
345
|
+
let close = lower.indexOf(closing, contentStart + 1);
|
|
346
|
+
let closeEnd = -1;
|
|
347
|
+
while (close >= 0) {
|
|
348
|
+
let at2 = close + closing.length;
|
|
349
|
+
while (at2 < lower.length && /\s/.test(lower[at2])) at2++;
|
|
350
|
+
if (lower[at2] === ">") {
|
|
351
|
+
closeEnd = at2 + 1;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
close = lower.indexOf(closing, at2);
|
|
355
|
+
}
|
|
356
|
+
if (close < 0) break;
|
|
357
|
+
elements.push({
|
|
358
|
+
attributes: text.slice(start + opening.length, contentStart),
|
|
359
|
+
content: text.slice(contentStart + 1, close),
|
|
360
|
+
contentStart: contentStart + 1
|
|
361
|
+
});
|
|
362
|
+
cursor = closeEnd;
|
|
363
|
+
}
|
|
364
|
+
return elements;
|
|
365
|
+
}
|
|
366
|
+
var Sources = class {
|
|
367
|
+
constructor(project) {
|
|
368
|
+
this.project = project;
|
|
369
|
+
for (const path of sourceFiles(project)) {
|
|
370
|
+
const file = relative(project, path);
|
|
371
|
+
if (/(?:^|\/)(?:vendor|lib)\//.test(file)) continue;
|
|
372
|
+
const fd = openSync(path, "r");
|
|
373
|
+
let text;
|
|
374
|
+
try {
|
|
375
|
+
if (fstatSync(fd).size > 2e6) continue;
|
|
376
|
+
text = readFileSync(fd, "utf8");
|
|
377
|
+
} finally {
|
|
378
|
+
closeSync(fd);
|
|
379
|
+
}
|
|
380
|
+
this.files.set(file, { text, lines: starts(text) });
|
|
381
|
+
if (/\.m?js$/.test(file)) this.addChunk(file, text, 0);
|
|
382
|
+
if (/\.css$/.test(file)) this.addCss(file, text, 0);
|
|
383
|
+
if (/\.html$/.test(file)) {
|
|
384
|
+
for (const script of rawTextElements(text, "script"))
|
|
385
|
+
if (!/\bsrc\s*=/.test(script.attributes))
|
|
386
|
+
this.addChunk(file, script.content, script.contentStart);
|
|
387
|
+
for (const style of rawTextElements(text, "style"))
|
|
388
|
+
this.addCss(file, style.content, style.contentStart);
|
|
389
|
+
for (const m of text.matchAll(/<([a-z][\w-]*)\b[^>]*>/gi)) {
|
|
390
|
+
const attr = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(m[0])?.[1];
|
|
391
|
+
this.htmlTags.push({
|
|
392
|
+
file,
|
|
393
|
+
start: m.index,
|
|
394
|
+
end: m.index + m[0].length,
|
|
395
|
+
tag: m[1].toLowerCase(),
|
|
396
|
+
id: attr("id"),
|
|
397
|
+
hfId: attr("data-hf-id"),
|
|
398
|
+
classes: (attr("class") ?? "").split(/\s+/).filter(Boolean)
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
files = /* @__PURE__ */ new Map();
|
|
405
|
+
chunks = [];
|
|
406
|
+
cssRules = [];
|
|
407
|
+
htmlTags = [];
|
|
408
|
+
addChunk(file, source, offset2) {
|
|
409
|
+
const text = source.trim();
|
|
410
|
+
const lead = source.length - source.trimStart().length;
|
|
411
|
+
if (text) {
|
|
412
|
+
let ranges;
|
|
413
|
+
let dependencies;
|
|
414
|
+
this.chunks.push({
|
|
415
|
+
file,
|
|
416
|
+
text,
|
|
417
|
+
offset: offset2 + lead,
|
|
418
|
+
get statements() {
|
|
419
|
+
return ranges ??= statements(text);
|
|
420
|
+
},
|
|
421
|
+
dependencies(range) {
|
|
422
|
+
return (dependencies ??= sourceDependencies(text))(range);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
addCss(file, text, offset2) {
|
|
428
|
+
try {
|
|
429
|
+
postcss.parse(text).walkRules((rule) => {
|
|
430
|
+
const start = rule.source?.start?.offset;
|
|
431
|
+
const end = rule.source?.end?.offset;
|
|
432
|
+
if (start != null && end != null)
|
|
433
|
+
this.cssRules.push({
|
|
434
|
+
id: `css:${this.cssRules.length}`,
|
|
435
|
+
selector: rule.selector,
|
|
436
|
+
file,
|
|
437
|
+
start: offset2 + start,
|
|
438
|
+
end: offset2 + end + 1
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
slice(file, start, end, kind, target2) {
|
|
445
|
+
const source = this.files.get(file);
|
|
446
|
+
if (!source) return null;
|
|
447
|
+
const first = position(source.lines, start).line;
|
|
448
|
+
const last = position(source.lines, Math.max(start, end - 1)).line;
|
|
449
|
+
const code = source.text.slice(
|
|
450
|
+
source.lines[first - 1],
|
|
451
|
+
source.lines[last] ?? source.text.length
|
|
452
|
+
);
|
|
453
|
+
return {
|
|
454
|
+
id: `${file}:${first}-${last}`,
|
|
455
|
+
file,
|
|
456
|
+
startLine: first,
|
|
457
|
+
endLine: last,
|
|
458
|
+
kind: [kind],
|
|
459
|
+
targets: [target2],
|
|
460
|
+
code,
|
|
461
|
+
uniqueInFile: occurrences(source.text, code).length === 1
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
markup(element2, target2) {
|
|
465
|
+
const tags = this.htmlTags.filter(
|
|
466
|
+
(tag) => tag.tag === element2.tag && (element2.hfId ? tag.hfId === element2.hfId : element2.id ? tag.id === element2.id : element2.classes.length > 0 && element2.classes.every((c) => tag.classes.includes(c)))
|
|
467
|
+
);
|
|
468
|
+
return tags.length === 1 ? this.slice(tags[0].file, tags[0].start, tags[0].end, "html opening tag", target2) : null;
|
|
469
|
+
}
|
|
470
|
+
mapper(html, provenance) {
|
|
471
|
+
const htmlLines = starts(html);
|
|
472
|
+
const maps = provenance.flatMap(
|
|
473
|
+
(p) => occurrences(html, p.output).map((at2) => ({
|
|
474
|
+
at: at2,
|
|
475
|
+
p,
|
|
476
|
+
lines: starts(p.output),
|
|
477
|
+
inputLines: starts(p.input),
|
|
478
|
+
map: new TraceMap(p.sourceMap)
|
|
479
|
+
}))
|
|
480
|
+
);
|
|
481
|
+
const ranges = /* @__PURE__ */ new Map();
|
|
482
|
+
for (const text of [html, ...provenance.map((p) => p.input)])
|
|
483
|
+
ranges.set(
|
|
484
|
+
text,
|
|
485
|
+
this.chunks.flatMap((chunk) => occurrences(text, chunk.text).map((at2) => ({ chunk, at: at2 })))
|
|
486
|
+
);
|
|
487
|
+
const locate = (text, offset2) => {
|
|
488
|
+
const found = (ranges.get(text) ?? []).filter(({ chunk, at: at2 }) => offset2 >= at2 && offset2 < at2 + chunk.text.length).map(({ chunk, at: at2 }) => ({ chunk, local: offset2 - at2 }));
|
|
489
|
+
const unique = new Map(
|
|
490
|
+
found.map((hit) => [`${hit.chunk.file}:${hit.chunk.offset + hit.local}`, hit])
|
|
491
|
+
);
|
|
492
|
+
return unique.size === 1 ? [...unique.values()][0] : null;
|
|
493
|
+
};
|
|
494
|
+
const cache = /* @__PURE__ */ new Map();
|
|
495
|
+
return (stack, target2) => {
|
|
496
|
+
const cached = cache.get(stack);
|
|
497
|
+
if (cached) return cached.map((snippet2) => ({ ...snippet2, targets: [target2] }));
|
|
498
|
+
const result = [];
|
|
499
|
+
for (const frame of stack.split("\n")) {
|
|
500
|
+
const script = /(hf-inspect-[a-f0-9]+\.js):(\d+):(\d+)\)?$/.exec(frame);
|
|
501
|
+
const m = /https?:\/\/[^/]+\/(?:index\.html)?:(\d+):(\d+)\)?$/.exec(frame);
|
|
502
|
+
if (!m && !script) continue;
|
|
503
|
+
const offset2 = m ? htmlLines[Number(m[1]) - 1] + Number(m[2]) - 1 : -1;
|
|
504
|
+
let hit = m ? locate(html, offset2) : null;
|
|
505
|
+
if (!hit)
|
|
506
|
+
for (const map of maps) {
|
|
507
|
+
if (script ? map.p.sourceURL !== script[1] : offset2 < map.at || offset2 >= map.at + map.p.output.length)
|
|
508
|
+
continue;
|
|
509
|
+
const generated = script ? { line: Number(script[2]), column: Number(script[3]) - 1 } : position(map.lines, offset2 - map.at);
|
|
510
|
+
const original = originalPositionFor(map.map, generated);
|
|
511
|
+
if (original.line != null && original.column != null)
|
|
512
|
+
hit = locate(map.p.input, map.inputLines[original.line - 1] + original.column);
|
|
513
|
+
}
|
|
514
|
+
if (!hit) continue;
|
|
515
|
+
const enclosing = hit.chunk.statements.filter((s) => hit.local >= s.start && hit.local < s.end).sort((a, b) => a.end - a.start - (b.end - b.start))[0];
|
|
516
|
+
if (!enclosing) continue;
|
|
517
|
+
const helper = hit.chunk.statements.filter(
|
|
518
|
+
(s) => s.type === "FunctionDeclaration" && hit.local >= s.start && hit.local < s.end && s.end - s.start <= 2e3
|
|
519
|
+
).sort((a, b) => a.end - a.start - (b.end - b.start))[0];
|
|
520
|
+
const range = helper ?? enclosing;
|
|
521
|
+
const snippet2 = this.slice(
|
|
522
|
+
hit.chunk.file,
|
|
523
|
+
hit.chunk.offset + range.start,
|
|
524
|
+
hit.chunk.offset + range.end,
|
|
525
|
+
result.length ? "animation caller" : "animation creation",
|
|
526
|
+
target2
|
|
527
|
+
);
|
|
528
|
+
if (snippet2) result.push(snippet2);
|
|
529
|
+
for (const dependency of hit.chunk.dependencies(range)) {
|
|
530
|
+
const definition = this.slice(
|
|
531
|
+
hit.chunk.file,
|
|
532
|
+
hit.chunk.offset + dependency.start,
|
|
533
|
+
hit.chunk.offset + dependency.end,
|
|
534
|
+
"animation source dependency",
|
|
535
|
+
target2
|
|
536
|
+
);
|
|
537
|
+
if (definition) result.push(definition);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
cache.set(stack, result);
|
|
541
|
+
return result;
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
function markupSnippet(sources, element2, target2) {
|
|
546
|
+
return sources.markup(element2, target2);
|
|
547
|
+
}
|
|
548
|
+
function mergeSnippets(snippets, sources) {
|
|
549
|
+
const sorted = snippets.sort((a, b) => a.file.localeCompare(b.file) || a.startLine - b.startLine);
|
|
550
|
+
const output = [];
|
|
551
|
+
for (const snippet2 of sorted) {
|
|
552
|
+
const last = output.at(-1);
|
|
553
|
+
if (last && last.file === snippet2.file && snippet2.startLine <= last.endLine + 1) {
|
|
554
|
+
last.endLine = Math.max(last.endLine, snippet2.endLine);
|
|
555
|
+
last.id = `${last.file}:${last.startLine}-${last.endLine}`;
|
|
556
|
+
const source = sources.files.get(last.file);
|
|
557
|
+
last.code = source.text.slice(
|
|
558
|
+
source.lines[last.startLine - 1],
|
|
559
|
+
source.lines[last.endLine] ?? source.text.length
|
|
560
|
+
);
|
|
561
|
+
last.targets = [.../* @__PURE__ */ new Set([...last.targets, ...snippet2.targets])];
|
|
562
|
+
last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...snippet2.kind])];
|
|
563
|
+
last.uniqueInFile = occurrences(source.text, last.code).length === 1;
|
|
564
|
+
} else output.push({ ...snippet2 });
|
|
565
|
+
}
|
|
566
|
+
return output;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/mapInspection.ts
|
|
570
|
+
var coverage = [
|
|
571
|
+
"GSAP associations observed since page initialization; callback-created animations may require an explicit time query.",
|
|
572
|
+
"Animation source dependencies include lexically resolved declarations in the same script; later mutations, imports, and cross-script bindings are not traced.",
|
|
573
|
+
"CSS selector matches are candidates, not a full cascade or dependency graph. HTML is the authored opening tag when uniquely located.",
|
|
574
|
+
"Direct constructors, other animation systems, iframe contents, removed animations and unexecuted paths may be absent; missing origins are reported."
|
|
575
|
+
];
|
|
576
|
+
function mapInspection(raw, request, context) {
|
|
577
|
+
const { sources, map, catalog, generation: generation2, sourceVersion, reused, start, visualState } = context;
|
|
578
|
+
const maxBytes = request.maxBytes ?? 12e3;
|
|
579
|
+
const collected = [];
|
|
580
|
+
const results = raw.results.map((result, index) => {
|
|
581
|
+
let unmappedAnimations = 0;
|
|
582
|
+
for (const animation of result.animations) {
|
|
583
|
+
const mapped = animation.stack ? map(animation.stack, index) : [];
|
|
584
|
+
if (!mapped.length) unmappedAnimations++;
|
|
585
|
+
collected.push(...mapped);
|
|
586
|
+
for (const stack of animation.attachmentStacks)
|
|
587
|
+
collected.push(
|
|
588
|
+
...map(stack, index).map((snippet2) => ({ ...snippet2, kind: ["timeline attachment"] }))
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
for (const el of result.elements) {
|
|
592
|
+
const snippet2 = markupSnippet(sources, el, index);
|
|
593
|
+
if (snippet2) collected.push(snippet2);
|
|
594
|
+
}
|
|
595
|
+
for (const id of result.css) {
|
|
596
|
+
const rule = sources.cssRules.find((rule2) => rule2.id === id);
|
|
597
|
+
if (rule) {
|
|
598
|
+
const snippet2 = sources.slice(
|
|
599
|
+
rule.file,
|
|
600
|
+
rule.start,
|
|
601
|
+
rule.end,
|
|
602
|
+
"matched CSS candidate",
|
|
603
|
+
index
|
|
604
|
+
);
|
|
605
|
+
if (snippet2) collected.push(snippet2);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
target: index,
|
|
610
|
+
status: result.status,
|
|
611
|
+
candidates: result.candidates.slice(0, 5),
|
|
612
|
+
candidatesOmitted: Math.max(0, result.candidates.length - 5),
|
|
613
|
+
animations: result.animations.length,
|
|
614
|
+
coTargets: [
|
|
615
|
+
...new Map(
|
|
616
|
+
result.animations.flatMap((animation) => animation.targets).filter((element2) => !result.elements.some((own) => own.ref === element2.ref)).map((element2) => [element2.ref, element2])
|
|
617
|
+
).values()
|
|
618
|
+
].slice(0, 10),
|
|
619
|
+
unmappedAnimations,
|
|
620
|
+
scopeTruncated: result.truncated
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
const merged = mergeSnippets(collected, sources);
|
|
624
|
+
for (const snippet2 of merged) catalog.set(snippet2.id, snippet2);
|
|
625
|
+
let selected = merged;
|
|
626
|
+
if (request.snippets)
|
|
627
|
+
selected = request.snippets.map((id) => {
|
|
628
|
+
const snippet2 = catalog.get(id);
|
|
629
|
+
if (!snippet2) throw new Error(`unknown_snippet: ${id}`);
|
|
630
|
+
return snippet2;
|
|
631
|
+
});
|
|
632
|
+
const response = {
|
|
633
|
+
generation: generation2,
|
|
634
|
+
visualState,
|
|
635
|
+
sourceVersion,
|
|
636
|
+
reused,
|
|
637
|
+
results,
|
|
638
|
+
snippets: [],
|
|
639
|
+
omitted: [],
|
|
640
|
+
omittedCount: 0,
|
|
641
|
+
nextOffset: null,
|
|
642
|
+
coverage,
|
|
643
|
+
recordLimitReached: raw.recordLimitReached,
|
|
644
|
+
hooked: raw.hooked,
|
|
645
|
+
debugAvailable: true,
|
|
646
|
+
elapsedMs: Math.round(performance.now() - start)
|
|
647
|
+
};
|
|
648
|
+
const offset2 = request.offset ?? 0;
|
|
649
|
+
if (!Number.isInteger(offset2) || offset2 < 0)
|
|
650
|
+
throw new Error("offset must be a non-negative integer");
|
|
651
|
+
for (let i = offset2; i < selected.length; i++) {
|
|
652
|
+
const snippet2 = selected[i];
|
|
653
|
+
response.snippets.push(snippet2);
|
|
654
|
+
if (Buffer.byteLength(JSON.stringify(response)) > maxBytes - 700) {
|
|
655
|
+
response.snippets.pop();
|
|
656
|
+
response.omittedCount = selected.length - i;
|
|
657
|
+
response.omitted = selected.slice(i, i + 8).map((item) => ({ id: item.id, bytes: Buffer.byteLength(item.code) }));
|
|
658
|
+
response.nextOffset = i;
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
while (Buffer.byteLength(JSON.stringify(response)) > maxBytes && response.results.some((r) => r.candidates.length)) {
|
|
663
|
+
const r = response.results.find((r2) => r2.candidates.length);
|
|
664
|
+
r.candidates.pop();
|
|
665
|
+
r.candidatesOmitted++;
|
|
666
|
+
}
|
|
667
|
+
if (Buffer.byteLength(JSON.stringify(response)) > maxBytes)
|
|
668
|
+
throw new Error("metadata_exceeds_budget: use fewer targets or increase maxBytes");
|
|
669
|
+
if (request.debug) return { ...response, debug: raw };
|
|
670
|
+
return response;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/pageQueries.ts
|
|
674
|
+
function queryPage(query) {
|
|
675
|
+
const inspected = window;
|
|
676
|
+
const inspection = inspected.__hfInspection;
|
|
677
|
+
if (!inspection) throw new Error("inspector_unavailable");
|
|
678
|
+
return {
|
|
679
|
+
generation: inspected.__hfLiveInspection?.generation ?? null,
|
|
680
|
+
...inspection.query(query)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function timelinePage(request) {
|
|
684
|
+
const inspected = window;
|
|
685
|
+
const inspection = inspected.__hfInspection;
|
|
686
|
+
if (!inspection) throw new Error("inspector_unavailable");
|
|
687
|
+
const result = inspection.timeline(request);
|
|
688
|
+
const following = [];
|
|
689
|
+
const generation2 = inspected.__hfLiveInspection?.generation ?? null;
|
|
690
|
+
if (request.boundary === void 0) return { ...result, following, generation: generation2 };
|
|
691
|
+
const boundary = request.boundary;
|
|
692
|
+
const schedule = inspection.timeline({ ...request, boundary: void 0 });
|
|
693
|
+
for (const crossing of result.events) {
|
|
694
|
+
if (crossing.kind !== "animation" || crossing.relation !== "crosses") continue;
|
|
695
|
+
for (const target2 of crossing.targets) {
|
|
696
|
+
const next = schedule.events.filter(
|
|
697
|
+
(event) => event.kind === "animation" && event.start > boundary && event.targets.some((t) => t.ref === target2.ref) && event.properties.some((p) => crossing.properties.includes(p))
|
|
698
|
+
).sort((a, b) => a.start - b.start)[0];
|
|
699
|
+
if (next && !following.includes(next)) following.push(next);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return { ...result, following, generation: generation2 };
|
|
703
|
+
}
|
|
704
|
+
function liveGenerationPage() {
|
|
705
|
+
return window.__hfLiveInspection?.generation ?? null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/timelineReport.ts
|
|
709
|
+
var GROUPS_PER_PAGE = 60;
|
|
710
|
+
var FOLLOWING_SHOWN = 16;
|
|
711
|
+
var MEMBERS_SHOWN = 8;
|
|
712
|
+
var RELATIONS_SHOWN = 8;
|
|
713
|
+
var COVERAGE_NOTE = "Observed DOM-target GSAP and explicit HF items only; callbacks/object tweens/unexecuted code are not inspected. Scheduled intervals do not establish visibility; no visual verification. at/range are half-open; boundary includes exact endpoints. No seek performed.";
|
|
714
|
+
var BOUNDARY_NOTE = "only actions crossing, starting, ending or instantaneous here. Later stagger members are excluded, not absent. Group spans do not imply simultaneous starts. Target labels T1, T2, ... identify the same element within this response, not DOM order.";
|
|
715
|
+
function formatTimeline(raw, request, view) {
|
|
716
|
+
const offset2 = request.offset ?? 0;
|
|
717
|
+
const allGroups = groupEvents(raw.events);
|
|
718
|
+
const groups = allGroups.slice(offset2, offset2 + GROUPS_PER_PAGE);
|
|
719
|
+
const label = labeler();
|
|
720
|
+
const events = groups.flat().map((event, index) => ({
|
|
721
|
+
...event,
|
|
722
|
+
stack: void 0,
|
|
723
|
+
sources: (event.stack ? view.map(event.stack, index) : []).map((s) => ({
|
|
724
|
+
file: s.file,
|
|
725
|
+
line: s.startLine
|
|
726
|
+
}))
|
|
727
|
+
}));
|
|
728
|
+
const nextOffset = offset2 + groups.length < allGroups.length ? offset2 + groups.length : null;
|
|
729
|
+
const lines = [
|
|
730
|
+
`Timeline \xB7 global seconds \xB7 ${raw.status}`,
|
|
731
|
+
`generation: ${view.generation}`,
|
|
732
|
+
...raw.status !== "found" ? raw.candidates.map((c) => `${c.ref} #${c.id} ${c.tag}`) : [],
|
|
733
|
+
...request.boundary !== void 0 ? [`Boundary ${request.boundary}s: ${BOUNDARY_NOTE}`] : [],
|
|
734
|
+
...describeGroups(groups, request, view.map, label),
|
|
735
|
+
...describeFollowing(raw, view.map, label),
|
|
736
|
+
...describeRelations(events),
|
|
737
|
+
...nextOffset !== null ? [`More groups: repeat with offset=${nextOffset} and generation.`] : [],
|
|
738
|
+
...raw.warnings.map((w) => `Coverage: ${w}`),
|
|
739
|
+
...events.some((e) => e.values) ? [
|
|
740
|
+
"Authored values describe the original tween, not sampled state or reversed repeat direction; ? means unknown. Relative values and expressions are not resolved."
|
|
741
|
+
] : [],
|
|
742
|
+
COVERAGE_NOTE
|
|
743
|
+
];
|
|
744
|
+
return {
|
|
745
|
+
text: lines.join("\n"),
|
|
746
|
+
generation: view.generation,
|
|
747
|
+
reused: view.reused,
|
|
748
|
+
status: raw.status,
|
|
749
|
+
candidates: raw.candidates,
|
|
750
|
+
events,
|
|
751
|
+
eventCount: raw.events.length,
|
|
752
|
+
groupCount: allGroups.length,
|
|
753
|
+
nextOffset,
|
|
754
|
+
warnings: raw.warnings
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function groupEvents(events) {
|
|
758
|
+
const groups = /* @__PURE__ */ new Map();
|
|
759
|
+
for (const event of events) {
|
|
760
|
+
const identity = event.targets.map((t) => t.id || t.hfId || `${t.composition}:${t.tag}:${t.classes.join(".")}`).sort();
|
|
761
|
+
const key = event.kind === "animation" && event.stack ? JSON.stringify([
|
|
762
|
+
event.stack,
|
|
763
|
+
event.component?.ref,
|
|
764
|
+
event.relation,
|
|
765
|
+
[...event.properties].sort(),
|
|
766
|
+
identity
|
|
767
|
+
]) : `unique:${groups.size}`;
|
|
768
|
+
groups.set(key, [...groups.get(key) ?? [], event]);
|
|
769
|
+
}
|
|
770
|
+
return [...groups.values()];
|
|
771
|
+
}
|
|
772
|
+
function labeler() {
|
|
773
|
+
const labels = /* @__PURE__ */ new Map();
|
|
774
|
+
return (ref) => {
|
|
775
|
+
if (!labels.has(ref)) labels.set(ref, `T${labels.size + 1}`);
|
|
776
|
+
return labels.get(ref);
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
var at = (seconds) => seconds.toFixed(4);
|
|
780
|
+
var located = (sources) => sources.map((s) => `${s.file}:${s.startLine}`).join(", ");
|
|
781
|
+
function describeValue(value) {
|
|
782
|
+
if (value.kind === "unknown") return "?";
|
|
783
|
+
return `${value.value}${value.kind === "literal" ? "" : ` (${value.kind})`}`;
|
|
784
|
+
}
|
|
785
|
+
function describeValues(values) {
|
|
786
|
+
return ` authored: ${values.properties.map((p) => `${p.property}: ${describeValue(p.from)} \u2192 ${describeValue(p.to)}`).join("; ")}; ease: ${describeValue(values.ease)}`;
|
|
787
|
+
}
|
|
788
|
+
function describeGroups(groups, request, map, label) {
|
|
789
|
+
const lines = [];
|
|
790
|
+
let owner = "";
|
|
791
|
+
for (const members of groups) {
|
|
792
|
+
const first = members[0];
|
|
793
|
+
const event = {
|
|
794
|
+
...first,
|
|
795
|
+
start: Math.min(...members.map((e) => e.start)),
|
|
796
|
+
end: Math.max(...members.map((e) => e.end)),
|
|
797
|
+
targets: [...new Map(members.flatMap((e) => e.targets).map((t) => [t.ref, t])).values()]
|
|
798
|
+
};
|
|
799
|
+
const component = event.component;
|
|
800
|
+
const heading = component?.id ? `#${component.id}` : component?.composition ?? "unassigned";
|
|
801
|
+
if (owner !== heading) {
|
|
802
|
+
lines.push(`
|
|
803
|
+
${heading}${component ? ` [${component.ref}]` : ""}`);
|
|
804
|
+
owner = heading;
|
|
805
|
+
}
|
|
806
|
+
const targets = targetSummary(event.targets);
|
|
807
|
+
const more = targets.length > 4 ? ` (+${targets.length - 4} groups)` : "";
|
|
808
|
+
lines.push(
|
|
809
|
+
` ${at(event.start)}\u2013${at(event.end)}s ${event.relation} ${event.kind} ${targets.slice(0, 4).join(", ")}${more} ${event.properties.join(",")}`
|
|
810
|
+
);
|
|
811
|
+
const variants = new Map(
|
|
812
|
+
members.filter((m) => m.values).map((m) => [JSON.stringify(m.values), m.values])
|
|
813
|
+
);
|
|
814
|
+
if (variants.size === 1) lines.push(describeValues(variants.values().next().value));
|
|
815
|
+
else if (variants.size > 1)
|
|
816
|
+
lines.push(" authored values vary across events; inspect the per-event JSON values.");
|
|
817
|
+
if (event.kind === "animation") {
|
|
818
|
+
const sources = event.stack ? map(event.stack, 0) : [];
|
|
819
|
+
lines.push(` ${sources.length ? located(sources) : "source unknown"}`);
|
|
820
|
+
}
|
|
821
|
+
if (request.boundary !== void 0 && event.kind === "animation")
|
|
822
|
+
lines.push(...describeMembers(event.targets, members, label));
|
|
823
|
+
if (members.length > 1) lines.push(describeSpacing(members));
|
|
824
|
+
}
|
|
825
|
+
return lines;
|
|
826
|
+
}
|
|
827
|
+
function targetSummary(targets) {
|
|
828
|
+
const counts = /* @__PURE__ */ new Map();
|
|
829
|
+
for (const target2 of targets) {
|
|
830
|
+
const name = target2.id ? `#${target2.id}` : target2.hfId ?? (target2.classes.length ? `.${target2.classes.join(".")}` : target2.tag);
|
|
831
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
832
|
+
}
|
|
833
|
+
return [...counts].map(([name, count]) => count > 1 ? `${name} \xD7${count}` : name);
|
|
834
|
+
}
|
|
835
|
+
function describeMembers(targets, members, label) {
|
|
836
|
+
const lines = [` targets: ${targets.map((t) => label(t.ref)).join(", ")}`];
|
|
837
|
+
if (members.length < 2) return lines;
|
|
838
|
+
for (const member of members.slice(0, MEMBERS_SHOWN))
|
|
839
|
+
lines.push(
|
|
840
|
+
` ${member.targets.map((t) => label(t.ref)).join(", ")}: ${at(member.start)}\u2013${at(member.end)}s ${member.relation}`
|
|
841
|
+
);
|
|
842
|
+
if (members.length > MEMBERS_SHOWN)
|
|
843
|
+
lines.push(
|
|
844
|
+
` ${members.length - MEMBERS_SHOWN} more member intervals omitted; do not infer their exact timing from this span.`
|
|
845
|
+
);
|
|
846
|
+
return lines;
|
|
847
|
+
}
|
|
848
|
+
function describeSpacing(members) {
|
|
849
|
+
const durations = members.map((e) => e.end - e.start);
|
|
850
|
+
const starts2 = [...new Set(members.map((e) => e.start))].sort((a, b) => a - b);
|
|
851
|
+
const step = starts2.length > 1 ? starts2[1] - starts2[0] : null;
|
|
852
|
+
const regular = step !== null && starts2.every((t, i) => i === 0 || Math.abs(t - starts2[i - 1] - step) < 1e-4);
|
|
853
|
+
const uniform = durations.every((d) => Math.abs(d - durations[0]) < 1e-7);
|
|
854
|
+
return ` ${members.length} events; span is not continuous activity; ${uniform ? `each ${at(durations[0])}s` : "varying durations"}${regular ? `; approx. start spacing ${at(step)}s` : "; irregular or simultaneous starts"}`;
|
|
855
|
+
}
|
|
856
|
+
function describeFollowing(raw, map, label) {
|
|
857
|
+
if (!raw.following.length) return [];
|
|
858
|
+
const lines = ["\nFollowing actions (context only; these do NOT start at the queried boundary):"];
|
|
859
|
+
for (const event of raw.following.slice(0, FOLLOWING_SHOWN)) {
|
|
860
|
+
const sources = event.stack ? map(event.stack, 0) : [];
|
|
861
|
+
lines.push(
|
|
862
|
+
` ${event.targets.map((t) => label(t.ref)).join(", ")}: ${at(event.start)}\u2013${at(event.end)}s ${event.properties.join(",")} ${located(sources)}`
|
|
863
|
+
);
|
|
864
|
+
for (const prior of raw.events) {
|
|
865
|
+
if (prior.kind !== "animation" || prior.relation !== "crosses") continue;
|
|
866
|
+
if (!prior.targets.some((t) => event.targets.some((n) => n.ref === t.ref))) continue;
|
|
867
|
+
if (!prior.properties.some((p) => event.properties.includes(p))) continue;
|
|
868
|
+
const overlap = Math.min(prior.end, event.end) - Math.max(prior.start, event.start);
|
|
869
|
+
const handoff = overlap > 1e-7 ? `overlap ${at(overlap)}s` : Math.abs(prior.end - event.start) < 1e-7 ? "endpoint handoff: NO duration overlap on this target" : "NO duration overlap on this target";
|
|
870
|
+
lines.push(` previous action ends ${at(prior.end)}s; ${handoff}.`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (raw.following.length > FOLLOWING_SHOWN)
|
|
874
|
+
lines.push(` ${raw.following.length - FOLLOWING_SHOWN} additional following actions omitted.`);
|
|
875
|
+
return lines;
|
|
876
|
+
}
|
|
877
|
+
function describeRelations(events) {
|
|
878
|
+
const lines = [];
|
|
879
|
+
for (let a = 0; a < events.length; a++)
|
|
880
|
+
for (let b = a + 1; b < events.length; b++) {
|
|
881
|
+
const left = events[a];
|
|
882
|
+
const right = events[b];
|
|
883
|
+
if (left.kind !== "animation" || right.kind !== "animation") continue;
|
|
884
|
+
if (left.end < right.start || right.end < left.start) continue;
|
|
885
|
+
const shared = left.targets.some((t) => right.targets.some((r) => r.ref === t.ref));
|
|
886
|
+
const properties = left.properties.filter((p) => right.properties.includes(p));
|
|
887
|
+
if (!shared || !properties.length || lines.length >= RELATIONS_SHOWN) continue;
|
|
888
|
+
const kind = left.start === left.end || right.start === right.end ? "instantaneous action touches or occurs inside the other interval" : Math.min(left.end, right.end) > Math.max(left.start, right.start) ? "intervals overlap" : "touch at an endpoint only, no positive-duration overlap";
|
|
889
|
+
lines.push(
|
|
890
|
+
`Relation: ${at(left.start)}\u2013${at(left.end)} and ${at(right.start)}\u2013${at(right.end)} share targets and ${properties.join(",")}; ${kind}; not necessarily an error.`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
return lines;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/engine.ts
|
|
897
|
+
function assertPageGeneration(view, actual) {
|
|
898
|
+
if (view.pageGeneration !== void 0 && actual !== view.pageGeneration)
|
|
899
|
+
throw new Error("stale_preview: the preview reloaded; query again");
|
|
900
|
+
}
|
|
901
|
+
async function inspectComponents(runner, view, request, context) {
|
|
902
|
+
const { sources, map } = view.sourceContext();
|
|
903
|
+
const raw = await runner.run(queryPage, {
|
|
904
|
+
targets: request.targets,
|
|
905
|
+
descendants: request.descendants ?? true,
|
|
906
|
+
cssRules: sources.cssRules.map(({ id, selector }) => ({ id, selector }))
|
|
907
|
+
});
|
|
908
|
+
assertPageGeneration(view, raw.generation);
|
|
909
|
+
await view.assertCurrent();
|
|
910
|
+
return mapInspection(raw, request, {
|
|
911
|
+
...context,
|
|
912
|
+
sources,
|
|
913
|
+
map,
|
|
914
|
+
generation: view.generation,
|
|
915
|
+
sourceVersion: view.sourceVersion
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
async function inspectTimeline(runner, view, request, reused) {
|
|
919
|
+
const raw = await runner.run(timelinePage, request);
|
|
920
|
+
assertPageGeneration(view, raw.generation);
|
|
921
|
+
await view.assertCurrent();
|
|
922
|
+
return formatTimeline(raw, request, {
|
|
923
|
+
generation: view.generation,
|
|
924
|
+
map: view.sourceContext().map,
|
|
925
|
+
reused
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
async function inspectElementMap(runner, view, file = "index.html") {
|
|
929
|
+
const snapshot = await runner.run(collectElementDom, void 0);
|
|
930
|
+
assertPageGeneration(view, snapshot.generation);
|
|
931
|
+
const map = formatElementMap(snapshot, { file });
|
|
932
|
+
await view.assertCurrent();
|
|
933
|
+
return { ...map, sourceVersion: view.sourceVersion, generation: view.generation };
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/requests.ts
|
|
937
|
+
function validateInspectRequest(request) {
|
|
938
|
+
if (!Array.isArray(request.targets) || request.targets.length > 20 || !request.targets.length && !request.snippets?.length)
|
|
939
|
+
throw new Error("Provide 1\u201320 targets, or snippet IDs from a previous query.");
|
|
940
|
+
for (const target2 of request.targets) {
|
|
941
|
+
if (!target2 || typeof target2 !== "object") throw new Error("Invalid target");
|
|
942
|
+
const keys = ["selector", "hfId", "ref"].filter((key) => key in target2);
|
|
943
|
+
if (keys.length !== 1 || Object.values(target2).some(
|
|
944
|
+
(value) => typeof value !== "string" || !value || value.length > 1e3
|
|
945
|
+
))
|
|
946
|
+
throw new Error("Each target needs exactly one selector, hfId or ref; within is optional.");
|
|
947
|
+
}
|
|
948
|
+
const maxBytes = request.maxBytes ?? 12e3;
|
|
949
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 2048 || maxBytes > 256e3)
|
|
950
|
+
throw new Error("maxBytes must be 2048\u2013256000");
|
|
951
|
+
if (request.time != null && (!Number.isFinite(request.time) || request.time < 0))
|
|
952
|
+
throw new Error("time must be a finite non-negative number");
|
|
953
|
+
if (request.snippets?.length && !request.generation)
|
|
954
|
+
throw new Error("snippet continuation requires generation");
|
|
955
|
+
}
|
|
956
|
+
var TIMELINE_KEYS = ["component", "at", "range", "boundary", "generation", "offset"];
|
|
957
|
+
var COMPONENT_KEYS = ["selector", "hfId", "ref", "within"];
|
|
958
|
+
function validateTimelineRequest(request) {
|
|
959
|
+
if (!request || typeof request !== "object" || Array.isArray(request))
|
|
960
|
+
throw new Error("Expected query object");
|
|
961
|
+
if (Object.keys(request).some((key) => !TIMELINE_KEYS.includes(key)))
|
|
962
|
+
throw new Error("Unknown timeline query parameter");
|
|
963
|
+
const times = [request.at, request.range, request.boundary].filter((v) => v !== void 0);
|
|
964
|
+
if (times.length > 1) throw new Error("at, range and boundary are mutually exclusive");
|
|
965
|
+
const numbers = [
|
|
966
|
+
request.at,
|
|
967
|
+
request.boundary,
|
|
968
|
+
...Array.isArray(request.range) ? request.range : []
|
|
969
|
+
].filter((v) => v !== void 0);
|
|
970
|
+
if (numbers.some((v) => typeof v !== "number" || !Number.isFinite(v) || v < 0))
|
|
971
|
+
throw new Error("Times must be finite non-negative seconds");
|
|
972
|
+
if (request.range !== void 0 && (!Array.isArray(request.range) || request.range.length !== 2 || request.range[0] >= request.range[1]))
|
|
973
|
+
throw new Error("range must be [start,end] with start < end");
|
|
974
|
+
const component = request.component;
|
|
975
|
+
if (component && (typeof component !== "object" || Object.keys(component).some((key) => !COMPONENT_KEYS.includes(key)) || ["selector", "hfId", "ref"].filter((key) => key in component).length !== 1 || Object.values(component).some((v) => typeof v !== "string" || !v)))
|
|
976
|
+
throw new Error("component requires one selector, hfId or ref");
|
|
977
|
+
const offset2 = request.offset ?? 0;
|
|
978
|
+
if (!Number.isInteger(offset2) || offset2 < 0)
|
|
979
|
+
throw new Error("offset must be non-negative integer");
|
|
980
|
+
if (offset2 && !request.generation) throw new Error("pagination requires generation");
|
|
981
|
+
}
|
|
982
|
+
function assertGeneration(requested, current) {
|
|
983
|
+
if (requested && requested !== current)
|
|
984
|
+
throw new Error("stale_generation: query targets again after source changes or idle disposal");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/outputSchemas.ts
|
|
988
|
+
var string = { type: "string" };
|
|
989
|
+
var number = { type: "number" };
|
|
990
|
+
var boolean = { type: "boolean" };
|
|
991
|
+
var array = (items) => ({ type: "array", items });
|
|
992
|
+
var nullable = (schema) => ({ oneOf: [schema, { type: "null" }] });
|
|
993
|
+
var object = (properties, optional = []) => ({
|
|
994
|
+
type: "object",
|
|
995
|
+
properties,
|
|
996
|
+
required: Object.keys(properties).filter((key) => !optional.includes(key)),
|
|
997
|
+
additionalProperties: false
|
|
998
|
+
});
|
|
999
|
+
var element = object({
|
|
1000
|
+
ref: string,
|
|
1001
|
+
tag: string,
|
|
1002
|
+
id: string,
|
|
1003
|
+
hfId: nullable(string),
|
|
1004
|
+
classes: array(string),
|
|
1005
|
+
composition: nullable(string)
|
|
1006
|
+
});
|
|
1007
|
+
var snippet = object({
|
|
1008
|
+
id: string,
|
|
1009
|
+
file: string,
|
|
1010
|
+
startLine: number,
|
|
1011
|
+
endLine: number,
|
|
1012
|
+
kind: array(string),
|
|
1013
|
+
targets: array(number),
|
|
1014
|
+
code: string,
|
|
1015
|
+
uniqueInFile: boolean
|
|
1016
|
+
});
|
|
1017
|
+
var componentOutputSchema = object(
|
|
1018
|
+
{
|
|
1019
|
+
generation: string,
|
|
1020
|
+
sourceVersion: string,
|
|
1021
|
+
reused: boolean,
|
|
1022
|
+
visualState: object({ time: number, duration: number }, ["time", "duration"]),
|
|
1023
|
+
results: array(
|
|
1024
|
+
object({
|
|
1025
|
+
target: number,
|
|
1026
|
+
status: {
|
|
1027
|
+
type: "string",
|
|
1028
|
+
enum: ["found", "missing", "ambiguous", "stale-ref", "invalid-selector"]
|
|
1029
|
+
},
|
|
1030
|
+
candidates: array(element),
|
|
1031
|
+
candidatesOmitted: number,
|
|
1032
|
+
animations: number,
|
|
1033
|
+
coTargets: array(element),
|
|
1034
|
+
unmappedAnimations: number,
|
|
1035
|
+
scopeTruncated: boolean
|
|
1036
|
+
})
|
|
1037
|
+
),
|
|
1038
|
+
snippets: array(snippet),
|
|
1039
|
+
omitted: array(object({ id: string, bytes: number })),
|
|
1040
|
+
omittedCount: number,
|
|
1041
|
+
nextOffset: nullable(number),
|
|
1042
|
+
coverage: array(string),
|
|
1043
|
+
recordLimitReached: boolean,
|
|
1044
|
+
hooked: boolean,
|
|
1045
|
+
debugAvailable: boolean,
|
|
1046
|
+
elapsedMs: number
|
|
1047
|
+
},
|
|
1048
|
+
["visualState"]
|
|
1049
|
+
);
|
|
1050
|
+
var declaredValue = object({
|
|
1051
|
+
kind: { type: "string", enum: ["literal", "relative", "expression", "unknown"] },
|
|
1052
|
+
value: { oneOf: [number, string, { type: "null" }] }
|
|
1053
|
+
});
|
|
1054
|
+
var animationValues = object({
|
|
1055
|
+
basis: { type: "string", enum: ["authored-tween"] },
|
|
1056
|
+
properties: array(object({ property: string, from: declaredValue, to: declaredValue })),
|
|
1057
|
+
ease: declaredValue
|
|
1058
|
+
});
|
|
1059
|
+
var timelineOutputSchema = object({
|
|
1060
|
+
text: string,
|
|
1061
|
+
generation: string,
|
|
1062
|
+
reused: boolean,
|
|
1063
|
+
status: string,
|
|
1064
|
+
candidates: array(element),
|
|
1065
|
+
events: array(
|
|
1066
|
+
object(
|
|
1067
|
+
{
|
|
1068
|
+
kind: { type: "string", enum: ["animation", "item"] },
|
|
1069
|
+
relation: string,
|
|
1070
|
+
component: nullable(element),
|
|
1071
|
+
targets: array(element),
|
|
1072
|
+
properties: array(string),
|
|
1073
|
+
values: animationValues,
|
|
1074
|
+
start: number,
|
|
1075
|
+
end: number,
|
|
1076
|
+
sources: array(object({ file: string, line: number }))
|
|
1077
|
+
},
|
|
1078
|
+
["values"]
|
|
1079
|
+
)
|
|
1080
|
+
),
|
|
1081
|
+
eventCount: number,
|
|
1082
|
+
groupCount: number,
|
|
1083
|
+
nextOffset: nullable(number),
|
|
1084
|
+
warnings: array(string)
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// src/tools.ts
|
|
1088
|
+
var within = {
|
|
1089
|
+
type: "string",
|
|
1090
|
+
description: "Optional CSS selector restricting target lookup; omit for an unambiguous target."
|
|
1091
|
+
};
|
|
1092
|
+
var target = {
|
|
1093
|
+
oneOf: [
|
|
1094
|
+
{
|
|
1095
|
+
type: "object",
|
|
1096
|
+
required: ["selector"],
|
|
1097
|
+
additionalProperties: false,
|
|
1098
|
+
properties: {
|
|
1099
|
+
selector: { type: "string", description: "CSS selector, such as #title or .word." },
|
|
1100
|
+
within
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
type: "object",
|
|
1105
|
+
required: ["hfId"],
|
|
1106
|
+
additionalProperties: false,
|
|
1107
|
+
properties: {
|
|
1108
|
+
hfId: { type: "string", description: "Existing element data-hf-id value." },
|
|
1109
|
+
within
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
type: "object",
|
|
1114
|
+
required: ["ref"],
|
|
1115
|
+
additionalProperties: false,
|
|
1116
|
+
properties: {
|
|
1117
|
+
ref: {
|
|
1118
|
+
type: "string",
|
|
1119
|
+
description: "Element ref returned by this inspector session; do not invent it. Rediscover after reload."
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
]
|
|
1124
|
+
};
|
|
1125
|
+
var generation = {
|
|
1126
|
+
type: "string",
|
|
1127
|
+
description: "Generation returned by the previous query. Use for continuation; if stale, query targets again."
|
|
1128
|
+
};
|
|
1129
|
+
var offset = {
|
|
1130
|
+
type: "integer",
|
|
1131
|
+
minimum: 0,
|
|
1132
|
+
description: "Returned nextOffset for continuation; repeat the same query with its generation."
|
|
1133
|
+
};
|
|
1134
|
+
var componentsTool = {
|
|
1135
|
+
description: "HyperFrames-native grep for element-related source. Retrieve related HTML/CSS/GSAP snippets with file paths and line numbers to understand, debug, or edit known elements without whole-file lookup. Batch targets. Shared code may affect other elements; unknown animation origins are explicit.",
|
|
1136
|
+
inputSchema: {
|
|
1137
|
+
type: "object",
|
|
1138
|
+
required: ["targets"],
|
|
1139
|
+
additionalProperties: false,
|
|
1140
|
+
properties: {
|
|
1141
|
+
targets: {
|
|
1142
|
+
type: "array",
|
|
1143
|
+
maxItems: 20,
|
|
1144
|
+
description: "Batch 1\u201320 targets. Use an empty array only when retrieving snippets by ID.",
|
|
1145
|
+
items: target
|
|
1146
|
+
},
|
|
1147
|
+
descendants: {
|
|
1148
|
+
type: "boolean",
|
|
1149
|
+
default: true,
|
|
1150
|
+
description: "Include source related to descendant elements."
|
|
1151
|
+
},
|
|
1152
|
+
maxBytes: {
|
|
1153
|
+
type: "integer",
|
|
1154
|
+
minimum: 2048,
|
|
1155
|
+
maximum: 256e3,
|
|
1156
|
+
default: 12e3,
|
|
1157
|
+
description: "Response budget in bytes. Whole snippets may be omitted; use returned continuation data or increase the budget."
|
|
1158
|
+
},
|
|
1159
|
+
snippets: {
|
|
1160
|
+
type: "array",
|
|
1161
|
+
items: { type: "string" },
|
|
1162
|
+
description: "Snippet IDs returned by a prior query; requires its generation. May use targets: []."
|
|
1163
|
+
},
|
|
1164
|
+
generation,
|
|
1165
|
+
offset,
|
|
1166
|
+
time: {
|
|
1167
|
+
type: "number",
|
|
1168
|
+
minimum: 0,
|
|
1169
|
+
description: "Optional exact preview seek in global seconds. Does not filter animations by time; omit for ordinary source lookup."
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
outputSchema: componentOutputSchema
|
|
1174
|
+
};
|
|
1175
|
+
var timelineTool = {
|
|
1176
|
+
description: "HyperFrames-native structured search for animation timing and activity. Use to find animations by time or boundary, including when the target element is unknown. Return scheduled HF entries and observed DOM-target GSAP timing, targets, properties and source locations, not full source blocks. Use at, range or boundary (at most one); omit all for the full schedule. Does not seek. Callbacks and unexecuted animation code are outside coverage.",
|
|
1177
|
+
inputSchema: {
|
|
1178
|
+
type: "object",
|
|
1179
|
+
additionalProperties: false,
|
|
1180
|
+
properties: {
|
|
1181
|
+
component: {
|
|
1182
|
+
...target,
|
|
1183
|
+
description: "Optional element/component scope including descendants; omit for the full project. Uses the same target format as the components tool."
|
|
1184
|
+
},
|
|
1185
|
+
at: {
|
|
1186
|
+
type: "number",
|
|
1187
|
+
minimum: 0,
|
|
1188
|
+
description: "Global second: active scheduled intervals use start <= at < end; includes instantaneous actions at that time."
|
|
1189
|
+
},
|
|
1190
|
+
range: {
|
|
1191
|
+
type: "array",
|
|
1192
|
+
items: { type: "number", minimum: 0 },
|
|
1193
|
+
minItems: 2,
|
|
1194
|
+
maxItems: 2,
|
|
1195
|
+
description: "Global [start, end) seconds, start < end. Return intersecting scheduled intervals and instantaneous actions inside the range."
|
|
1196
|
+
},
|
|
1197
|
+
boundary: {
|
|
1198
|
+
type: "number",
|
|
1199
|
+
minimum: 0,
|
|
1200
|
+
description: "Global second: return only actions crossing, starting, ending or instantaneous here. Later stagger members are excluded. Group spans are not simultaneous per-target schedules; match target labels and member intervals before inferring overlap."
|
|
1201
|
+
},
|
|
1202
|
+
generation,
|
|
1203
|
+
offset
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
outputSchema: timelineOutputSchema
|
|
1207
|
+
};
|
|
1208
|
+
function toolContent(tool, report) {
|
|
1209
|
+
const text = tool === "timeline" && report.text ? report.text : JSON.stringify(report);
|
|
1210
|
+
return { content: [{ type: "text", text }], structuredContent: report };
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// src/live.ts
|
|
1214
|
+
var runtime = () => readFileSync2(new URL(`./inspection.${"js"}`, import.meta.url), "utf8");
|
|
1215
|
+
function prepareLiveInspection(projectDir, html, provenance, sourceVersion) {
|
|
1216
|
+
const project = realpathSync(projectDir);
|
|
1217
|
+
if (fingerprint(project) !== sourceVersion) throw new Error("source_changed_during_load");
|
|
1218
|
+
if (!/<head\b[^>]*>/i.test(html)) throw new Error("preview_head_missing");
|
|
1219
|
+
const generation2 = randomUUID();
|
|
1220
|
+
const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation: generation2, project })};`;
|
|
1221
|
+
const script = `${stamp}
|
|
1222
|
+
${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
1223
|
+
const prepared = html.replace(/<head\b[^>]*>/i, (head) => `${head}<script>${script}</script>`);
|
|
1224
|
+
let document2 = prepared;
|
|
1225
|
+
let context;
|
|
1226
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
1227
|
+
const view = {
|
|
1228
|
+
generation: generation2,
|
|
1229
|
+
sourceVersion,
|
|
1230
|
+
pageGeneration: generation2,
|
|
1231
|
+
sourceContext() {
|
|
1232
|
+
if (!context) {
|
|
1233
|
+
const sources = new Sources(project);
|
|
1234
|
+
context = { sources, map: sources.mapper(document2, [...provenance]) };
|
|
1235
|
+
}
|
|
1236
|
+
return context;
|
|
1237
|
+
},
|
|
1238
|
+
async assertCurrent() {
|
|
1239
|
+
if (fingerprint(project) !== sourceVersion)
|
|
1240
|
+
throw new Error("stale_preview: the project changed; query again once the preview reloads");
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
async function current(runner) {
|
|
1244
|
+
if (await previewGeneration(runner) !== generation2)
|
|
1245
|
+
throw new Error("stale_preview: the preview reloaded; query again");
|
|
1246
|
+
await view.assertCurrent();
|
|
1247
|
+
}
|
|
1248
|
+
return {
|
|
1249
|
+
html: prepared,
|
|
1250
|
+
generation: generation2,
|
|
1251
|
+
finalizeDocument(servedHtml) {
|
|
1252
|
+
document2 = servedHtml;
|
|
1253
|
+
context = void 0;
|
|
1254
|
+
},
|
|
1255
|
+
async inspect(runner, request) {
|
|
1256
|
+
validateInspectRequest(request);
|
|
1257
|
+
if (request.time != null) throw new Error("live_query_does_not_seek");
|
|
1258
|
+
assertGeneration(request.generation, generation2);
|
|
1259
|
+
await current(runner);
|
|
1260
|
+
return inspectComponents(runner, view, request, {
|
|
1261
|
+
catalog,
|
|
1262
|
+
reused: true,
|
|
1263
|
+
start: performance.now()
|
|
1264
|
+
});
|
|
1265
|
+
},
|
|
1266
|
+
async timeline(runner, request) {
|
|
1267
|
+
validateTimelineRequest(request);
|
|
1268
|
+
assertGeneration(request.generation, generation2);
|
|
1269
|
+
await current(runner);
|
|
1270
|
+
return inspectTimeline(runner, view, request, true);
|
|
1271
|
+
},
|
|
1272
|
+
async elementMap(runner) {
|
|
1273
|
+
await current(runner);
|
|
1274
|
+
return inspectElementMap(runner, view);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
function previewGeneration(runner) {
|
|
1279
|
+
return runner.run(liveGenerationPage, void 0);
|
|
1280
|
+
}
|
|
1281
|
+
export {
|
|
1282
|
+
componentsTool,
|
|
1283
|
+
fingerprint,
|
|
1284
|
+
prepareLiveInspection,
|
|
1285
|
+
previewGeneration,
|
|
1286
|
+
timelineTool,
|
|
1287
|
+
toolContent
|
|
1288
|
+
};
|