@hyperframes/inspector 0.8.155 → 0.8.157
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/README.md +22 -8
- package/dist/inspection.js +42 -14
- package/dist/live.js +482 -370
- package/dist/server.js +142 -0
- package/dist/service.js +491 -88
- package/dist/types/core/src/runtime/inspectionTypes.d.ts +1 -0
- package/dist/types/core/src/runtime/timelineValues.d.ts +1 -1
- package/dist/types/inspector/src/authoredState.d.ts +3 -0
- package/dist/types/inspector/src/elementMap.d.ts +2 -0
- package/dist/types/inspector/src/engine.d.ts +2 -1
- package/dist/types/inspector/src/live.d.ts +1 -2
- package/dist/types/inspector/src/pageQueries.d.ts +1 -0
- package/dist/types/inspector/src/server.d.ts +6 -0
- package/dist/types/inspector/src/service.d.ts +1 -0
- package/dist/types/inspector/src/sources.d.ts +17 -9
- package/package.json +11 -4
- package/dist/types/inspector/src/outputSchemas.d.ts +0 -12
- package/dist/types/inspector/src/tools.d.ts +0 -18
package/dist/live.js
CHANGED
|
@@ -4,6 +4,263 @@ import { createRequire } from "node:module"; const require = createRequire(impor
|
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
6
6
|
|
|
7
|
+
// src/authoredState.ts
|
|
8
|
+
var PLAIN_CLASS = /^[a-zA-Z_-][\w-]*$/;
|
|
9
|
+
function withAuthoredState(snapshot, sources) {
|
|
10
|
+
const byHfId = group(sources.htmlTags, (tag) => tag.hfId);
|
|
11
|
+
const byId = group(sources.htmlTags, (tag) => tag.id);
|
|
12
|
+
const live = snapshot.nodes;
|
|
13
|
+
const matched = live.map((node) => sourceTag(node, byHfId, byId));
|
|
14
|
+
const generated = generatedInsideText(live, matched, sources);
|
|
15
|
+
const nodes = live.map(
|
|
16
|
+
(node, index) => generated.has(index) ? steadyGenerated(node, live, generated) : authoredNode(node, matched[index], sources, live)
|
|
17
|
+
);
|
|
18
|
+
return { ...snapshot, nodes };
|
|
19
|
+
}
|
|
20
|
+
function authoredNode(node, tag, sources, live) {
|
|
21
|
+
if (!tag) return node;
|
|
22
|
+
const text = directText(sources, tag) ?? node.text;
|
|
23
|
+
const id = node.id || tag.id || "";
|
|
24
|
+
const classes = authoredClasses(node, tag, live);
|
|
25
|
+
const next = { ...node, text, id };
|
|
26
|
+
return classes ? { ...next, ...classes } : next;
|
|
27
|
+
}
|
|
28
|
+
function generatedInsideText(live, matched, sources) {
|
|
29
|
+
const generated = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const [index, tag] of matched.entries()) {
|
|
31
|
+
if (!tag || !directText(sources, tag)) continue;
|
|
32
|
+
for (const child of live[index].children)
|
|
33
|
+
if (!matched[child]) markGenerated(child, live, matched, generated);
|
|
34
|
+
}
|
|
35
|
+
return generated;
|
|
36
|
+
}
|
|
37
|
+
function markGenerated(index, live, matched, generated) {
|
|
38
|
+
if (generated.has(index) || matched[index]) return;
|
|
39
|
+
generated.add(index);
|
|
40
|
+
for (const child of live[index].children) markGenerated(child, live, matched, generated);
|
|
41
|
+
}
|
|
42
|
+
function steadyGenerated(node, live, generated) {
|
|
43
|
+
const siblings = node.parent === null ? [node] : live[node.parent].children.map((index) => live[index]);
|
|
44
|
+
const siblingMatchCount = siblings.filter(
|
|
45
|
+
(other) => generated.has(other.index) && other.tag === node.tag
|
|
46
|
+
).length;
|
|
47
|
+
return {
|
|
48
|
+
...node,
|
|
49
|
+
id: "",
|
|
50
|
+
classes: [],
|
|
51
|
+
text: "",
|
|
52
|
+
generated: true,
|
|
53
|
+
groupSelector: node.tag,
|
|
54
|
+
relative: `${node.tag}:nth-child(${node.position})`,
|
|
55
|
+
siblingMatchCount
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function group(tags, key) {
|
|
59
|
+
const groups = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const tag of tags) {
|
|
61
|
+
const value = key(tag);
|
|
62
|
+
if (value) groups.set(value, [...groups.get(value) ?? [], tag]);
|
|
63
|
+
}
|
|
64
|
+
return groups;
|
|
65
|
+
}
|
|
66
|
+
function sourceTag(node, byHfId, byId) {
|
|
67
|
+
const named = node.hfId && byHfId.get(node.hfId) || node.id && byId.get(node.id) || [];
|
|
68
|
+
const matches = named.filter((tag) => tag.tag === node.tag);
|
|
69
|
+
return matches.length === 1 ? matches[0] : void 0;
|
|
70
|
+
}
|
|
71
|
+
var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style", "textarea", "title", "xmp", "noembed", "noframes"]);
|
|
72
|
+
var VOID = /* @__PURE__ */ new Set([
|
|
73
|
+
"area",
|
|
74
|
+
"base",
|
|
75
|
+
"br",
|
|
76
|
+
"col",
|
|
77
|
+
"embed",
|
|
78
|
+
"hr",
|
|
79
|
+
"img",
|
|
80
|
+
"input",
|
|
81
|
+
"link",
|
|
82
|
+
"meta",
|
|
83
|
+
"param",
|
|
84
|
+
"source",
|
|
85
|
+
"track",
|
|
86
|
+
"wbr"
|
|
87
|
+
]);
|
|
88
|
+
function directText(sources, tag) {
|
|
89
|
+
const text = sources.files.get(tag.file)?.text;
|
|
90
|
+
if (text === void 0) return null;
|
|
91
|
+
if (VOID.has(tag.tag)) return "";
|
|
92
|
+
return normalizedInner(text, tag);
|
|
93
|
+
}
|
|
94
|
+
function normalizedInner(text, tag) {
|
|
95
|
+
const inner = sliceUntilClose(text, tag.end, tag.tag);
|
|
96
|
+
if (inner === null) return null;
|
|
97
|
+
return decodeEntities(textNodes(inner)).replace(/\s+/g, " ").trim();
|
|
98
|
+
}
|
|
99
|
+
function sliceUntilClose(html, from, tag) {
|
|
100
|
+
const end = closeOffset(html, from, tag, 1);
|
|
101
|
+
return end === null ? null : html.slice(from, end);
|
|
102
|
+
}
|
|
103
|
+
function closeOffset(html, from, tag, depth) {
|
|
104
|
+
let cursor = from;
|
|
105
|
+
let level = depth;
|
|
106
|
+
for (; ; ) {
|
|
107
|
+
const step = closeStep(html, cursor, tag, level);
|
|
108
|
+
if (!step) return null;
|
|
109
|
+
if (step.done) return step.at;
|
|
110
|
+
cursor = step.at;
|
|
111
|
+
level = step.depth;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function closeStep(html, from, tag, depth) {
|
|
115
|
+
const found = nextMarkup(html, from);
|
|
116
|
+
if (!found) return null;
|
|
117
|
+
if (found.kind !== "tag") return { at: found.end, depth, done: false };
|
|
118
|
+
if (isRawOpen(found)) return rawStep(html, found, depth);
|
|
119
|
+
return tagStep(found, tag, depth);
|
|
120
|
+
}
|
|
121
|
+
function isRawOpen(found) {
|
|
122
|
+
return !found.closing && RAW_TEXT.has(found.name);
|
|
123
|
+
}
|
|
124
|
+
function rawStep(html, found, depth) {
|
|
125
|
+
const rawEnd = rawElementEnd(html.toLowerCase(), found.name, found.bracket);
|
|
126
|
+
if (rawEnd < 0) return null;
|
|
127
|
+
return { at: rawEnd, depth, done: false };
|
|
128
|
+
}
|
|
129
|
+
function tagStep(found, tag, depth) {
|
|
130
|
+
const same = found.name === tag && !VOID.has(found.name);
|
|
131
|
+
if (!same || !found.closing) return openStep(found, depth, same);
|
|
132
|
+
return closingStep(found, depth);
|
|
133
|
+
}
|
|
134
|
+
function closingStep(found, depth) {
|
|
135
|
+
if (depth === 1) return { at: found.start, depth, done: true };
|
|
136
|
+
return { at: found.bracket + 1, depth: depth - 1, done: false };
|
|
137
|
+
}
|
|
138
|
+
function openStep(found, depth, same) {
|
|
139
|
+
const nextDepth = same ? depth + 1 : depth;
|
|
140
|
+
return { at: found.bracket + 1, depth: nextDepth, done: false };
|
|
141
|
+
}
|
|
142
|
+
function textNodes(inner) {
|
|
143
|
+
const found = nextMarkup(inner, 0);
|
|
144
|
+
if (!found) return inner;
|
|
145
|
+
return inner.slice(0, found.start) + textAfter(inner, found);
|
|
146
|
+
}
|
|
147
|
+
function textAfter(inner, found) {
|
|
148
|
+
if (found.kind !== "tag") return textNodes(inner.slice(found.end));
|
|
149
|
+
return textAfterTag(inner, found);
|
|
150
|
+
}
|
|
151
|
+
function textAfterTag(inner, found) {
|
|
152
|
+
if (skipsTag(found)) return textNodes(inner.slice(found.bracket + 1));
|
|
153
|
+
if (RAW_TEXT.has(found.name)) return textAfterRaw(inner, found);
|
|
154
|
+
return textAfterChild(inner, found);
|
|
155
|
+
}
|
|
156
|
+
function skipsTag(found) {
|
|
157
|
+
return found.closing || VOID.has(found.name);
|
|
158
|
+
}
|
|
159
|
+
function textAfterRaw(inner, found) {
|
|
160
|
+
const rawEnd = rawElementEnd(inner.toLowerCase(), found.name, found.bracket);
|
|
161
|
+
return textNodes(inner.slice(rawEnd < 0 ? inner.length : rawEnd));
|
|
162
|
+
}
|
|
163
|
+
function textAfterChild(inner, found) {
|
|
164
|
+
const child = sliceUntilClose(inner, found.bracket + 1, found.name);
|
|
165
|
+
if (child === null) return "";
|
|
166
|
+
const closeBracket = inner.indexOf(">", found.bracket + 1 + child.length);
|
|
167
|
+
return textNodes(inner.slice(closeBracket < 0 ? inner.length : closeBracket + 1));
|
|
168
|
+
}
|
|
169
|
+
function nextMarkup(html, from) {
|
|
170
|
+
const start = html.indexOf("<", from);
|
|
171
|
+
if (start < 0) return null;
|
|
172
|
+
return markupAt(html, start);
|
|
173
|
+
}
|
|
174
|
+
function markupAt(html, start) {
|
|
175
|
+
const skipped = skippedMarkup(html, start);
|
|
176
|
+
if (skipped) return skipped;
|
|
177
|
+
return tagMarkup(html, start);
|
|
178
|
+
}
|
|
179
|
+
function skippedMarkup(html, start) {
|
|
180
|
+
if (html.startsWith("<!--", start)) return boundedSkip(html, start, "-->", 4);
|
|
181
|
+
if (html.startsWith("<!", start) || html.startsWith("<?", start))
|
|
182
|
+
return boundedSkip(html, start, ">", 1);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function boundedSkip(html, start, marker, from) {
|
|
186
|
+
const end = html.indexOf(marker, start + from);
|
|
187
|
+
return end < 0 ? null : { kind: "skip", start, end: end + marker.length };
|
|
188
|
+
}
|
|
189
|
+
function tagMarkup(html, start) {
|
|
190
|
+
const name = /^<\/?([a-zA-Z][^\s/>]*)/.exec(html.slice(start, start + 128));
|
|
191
|
+
if (!name) return { kind: "skip", start, end: start + 1 };
|
|
192
|
+
const bracket = closingBracket(html, start + name[0].length);
|
|
193
|
+
if (bracket < 0) return null;
|
|
194
|
+
return {
|
|
195
|
+
kind: "tag",
|
|
196
|
+
start,
|
|
197
|
+
name: name[1].toLowerCase(),
|
|
198
|
+
closing: html[start + 1] === "/",
|
|
199
|
+
bracket
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function rawElementEnd(lower, tag, openBracket) {
|
|
203
|
+
const marker = `</${tag}`;
|
|
204
|
+
const at2 = nextRawClose(lower, marker, openBracket + 1);
|
|
205
|
+
if (at2 < 0) return -1;
|
|
206
|
+
const bracket = lower.indexOf(">", at2 + marker.length);
|
|
207
|
+
return bracket < 0 ? -1 : bracket + 1;
|
|
208
|
+
}
|
|
209
|
+
function nextRawClose(lower, marker, from) {
|
|
210
|
+
const at2 = lower.indexOf(marker, from);
|
|
211
|
+
if (at2 < 0) return -1;
|
|
212
|
+
const next = lower[at2 + marker.length];
|
|
213
|
+
if (closesRawText(next)) return at2;
|
|
214
|
+
return nextRawClose(lower, marker, at2 + marker.length);
|
|
215
|
+
}
|
|
216
|
+
function closesRawText(next) {
|
|
217
|
+
return next !== void 0 && /[\s/>]/.test(next);
|
|
218
|
+
}
|
|
219
|
+
function closingBracket(html, from) {
|
|
220
|
+
let quote = "";
|
|
221
|
+
for (let i = from; i < html.length; i++) {
|
|
222
|
+
const c = html[i];
|
|
223
|
+
if (quote) quote = c === quote ? "" : quote;
|
|
224
|
+
else if (c === '"' || c === "'") quote = c;
|
|
225
|
+
else if (c === ">") return i;
|
|
226
|
+
}
|
|
227
|
+
return -1;
|
|
228
|
+
}
|
|
229
|
+
function decodeEntities(value) {
|
|
230
|
+
return value.replace(
|
|
231
|
+
/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos|nbsp);/gi,
|
|
232
|
+
(entity, body) => {
|
|
233
|
+
const named = {
|
|
234
|
+
amp: "&",
|
|
235
|
+
lt: "<",
|
|
236
|
+
gt: ">",
|
|
237
|
+
quot: '"',
|
|
238
|
+
apos: "'",
|
|
239
|
+
nbsp: " "
|
|
240
|
+
};
|
|
241
|
+
const lower = body.toLowerCase();
|
|
242
|
+
if (lower in named) return named[lower];
|
|
243
|
+
const code = Number.parseInt(
|
|
244
|
+
lower.slice(lower.startsWith("#x") ? 2 : 1),
|
|
245
|
+
lower.startsWith("#x") ? 16 : 10
|
|
246
|
+
);
|
|
247
|
+
return Number.isInteger(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : entity;
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
function authoredClasses(node, tag, live) {
|
|
252
|
+
const classes = [...tag.classes];
|
|
253
|
+
if (classes.join(" ") === node.classes.join(" ")) return null;
|
|
254
|
+
if (!classes.every((c) => PLAIN_CLASS.test(c) && node.classes.includes(c))) return null;
|
|
255
|
+
const simple = `${node.tag}${classes.map((c) => `.${c}`).join("")}`;
|
|
256
|
+
const siblings = node.parent === null ? [node] : live[node.parent].children.map((i) => live[i]);
|
|
257
|
+
const siblingMatchCount = siblings.filter(
|
|
258
|
+
(other) => other.tag === node.tag && classes.every((c) => other.classes.includes(c))
|
|
259
|
+
).length;
|
|
260
|
+
const relative2 = node.relative.startsWith("#") ? node.relative : siblingMatchCount === 1 ? simple : `${node.tag}:nth-child(${node.position})`;
|
|
261
|
+
return { classes, groupSelector: simple, siblingMatchCount, relative: relative2 };
|
|
262
|
+
}
|
|
263
|
+
|
|
7
264
|
// src/elementMap.js
|
|
8
265
|
import { createHash } from "node:crypto";
|
|
9
266
|
function collectElementDom() {
|
|
@@ -11,64 +268,65 @@ function collectElementDom() {
|
|
|
11
268
|
const nodes = [];
|
|
12
269
|
const paths = /* @__PURE__ */ new Map();
|
|
13
270
|
const siblingGroups = /* @__PURE__ */ new WeakMap();
|
|
14
|
-
function siblingCount(
|
|
15
|
-
const owner =
|
|
271
|
+
function siblingCount(element, selector) {
|
|
272
|
+
const owner = element.parentElement ?? element;
|
|
16
273
|
let counts = siblingGroups.get(owner);
|
|
17
274
|
if (!counts) {
|
|
18
275
|
counts = /* @__PURE__ */ new Map();
|
|
19
276
|
siblingGroups.set(owner, counts);
|
|
20
277
|
}
|
|
21
278
|
if (!counts.has(selector)) {
|
|
22
|
-
const siblings =
|
|
279
|
+
const siblings = element.parentElement ? [...element.parentElement.children] : [element];
|
|
23
280
|
counts.set(selector, siblings.filter((sibling) => sibling.matches(selector)).length);
|
|
24
281
|
}
|
|
25
282
|
return counts.get(selector);
|
|
26
283
|
}
|
|
27
284
|
const quote = (value) => JSON.stringify(value);
|
|
28
|
-
function path(
|
|
29
|
-
if (paths.has(
|
|
285
|
+
function path(element) {
|
|
286
|
+
if (paths.has(element)) return paths.get(element);
|
|
30
287
|
const candidates = [];
|
|
31
|
-
if (
|
|
32
|
-
const hfId =
|
|
288
|
+
if (element.id) candidates.push(`#${CSS.escape(element.id)}`);
|
|
289
|
+
const hfId = element.getAttribute("data-hf-id");
|
|
33
290
|
if (hfId) candidates.push(`[data-hf-id=${quote(hfId)}]`);
|
|
34
291
|
const unique = candidates.find((selector2) => document.querySelectorAll(selector2).length === 1);
|
|
35
|
-
const parent =
|
|
36
|
-
const tag =
|
|
37
|
-
const position2 = parent ? [...parent.children].indexOf(
|
|
292
|
+
const parent = element.parentElement;
|
|
293
|
+
const tag = element.localName;
|
|
294
|
+
const position2 = parent ? [...parent.children].indexOf(element) + 1 : 1;
|
|
38
295
|
const selector = unique ?? (parent ? `${path(parent)} > ${CSS.escape(tag)}:nth-child(${position2})` : CSS.escape(tag));
|
|
39
|
-
paths.set(
|
|
296
|
+
paths.set(element, selector);
|
|
40
297
|
return selector;
|
|
41
298
|
}
|
|
42
|
-
function visit(
|
|
43
|
-
if (excluded2.has(
|
|
44
|
-
const selector = path(
|
|
45
|
-
if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !==
|
|
299
|
+
function visit(element, parent) {
|
|
300
|
+
if (excluded2.has(element.tagName)) return;
|
|
301
|
+
const selector = path(element);
|
|
302
|
+
if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !== element) {
|
|
46
303
|
throw new Error(`Element map selector does not resolve uniquely: ${selector}`);
|
|
47
304
|
}
|
|
48
|
-
const classes = [...
|
|
49
|
-
const tag =
|
|
305
|
+
const classes = [...element.classList].sort();
|
|
306
|
+
const tag = element.localName;
|
|
50
307
|
const simple = `${CSS.escape(tag)}${classes.map((value) => `.${CSS.escape(value)}`).join("")}`;
|
|
51
|
-
const siblings =
|
|
308
|
+
const siblings = element.parentElement ? [...element.parentElement.children] : [element];
|
|
52
309
|
const index = nodes.length;
|
|
53
310
|
const node = {
|
|
54
311
|
index,
|
|
55
312
|
parent,
|
|
56
313
|
selector,
|
|
57
314
|
tag,
|
|
58
|
-
id:
|
|
59
|
-
hfId:
|
|
315
|
+
id: element.id,
|
|
316
|
+
hfId: element.getAttribute("data-hf-id"),
|
|
60
317
|
classes,
|
|
61
|
-
composition:
|
|
62
|
-
compositionSrc:
|
|
63
|
-
text: [...
|
|
64
|
-
media: ["img", "video", "audio", "source", "iframe"].includes(tag) ?
|
|
65
|
-
relative:
|
|
318
|
+
composition: element.getAttribute("data-composition-id"),
|
|
319
|
+
compositionSrc: element.getAttribute("data-composition-src"),
|
|
320
|
+
text: [...element.childNodes].filter((child) => child.nodeType === 3).map((child) => child.textContent).join("").replace(/\s+/g, " ").trim(),
|
|
321
|
+
media: ["img", "video", "audio", "source", "iframe"].includes(tag) ? element.getAttribute("src") : null,
|
|
322
|
+
relative: element.id && document.querySelectorAll(`#${CSS.escape(element.id)}`).length === 1 ? `#${CSS.escape(element.id)}` : siblingCount(element, simple) === 1 ? simple : `${CSS.escape(tag)}:nth-child(${siblings.indexOf(element) + 1})`,
|
|
323
|
+
position: siblings.indexOf(element) + 1,
|
|
66
324
|
groupSelector: simple,
|
|
67
|
-
siblingMatchCount: siblingCount(
|
|
325
|
+
siblingMatchCount: siblingCount(element, simple),
|
|
68
326
|
children: []
|
|
69
327
|
};
|
|
70
328
|
nodes.push(node);
|
|
71
|
-
for (const child of
|
|
329
|
+
for (const child of element.children) {
|
|
72
330
|
const childIndex = visit(child, index);
|
|
73
331
|
if (childIndex !== void 0) node.children.push(childIndex);
|
|
74
332
|
}
|
|
@@ -106,10 +364,26 @@ function mediaIdentifier(source) {
|
|
|
106
364
|
if (source.startsWith("blob:")) return "blob:runtime";
|
|
107
365
|
return source;
|
|
108
366
|
}
|
|
109
|
-
function
|
|
367
|
+
function childPlaces(members) {
|
|
368
|
+
const runs = [];
|
|
369
|
+
let start = members[0].position;
|
|
370
|
+
let previous = start;
|
|
371
|
+
for (const member of members.slice(1)) {
|
|
372
|
+
if (member.position === previous + 1) {
|
|
373
|
+
previous = member.position;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
runs.push(start === previous ? `${start}` : `${start}-${previous}`);
|
|
377
|
+
start = previous = member.position;
|
|
378
|
+
}
|
|
379
|
+
runs.push(start === previous ? `${start}` : `${start}-${previous}`);
|
|
380
|
+
return runs.join(",");
|
|
381
|
+
}
|
|
382
|
+
function describe(node, selector, count, places = "") {
|
|
110
383
|
const fields = [selector];
|
|
111
384
|
if (!selector.startsWith(node.tag)) fields.push(node.tag);
|
|
112
|
-
if (count > 1) fields.push(`\xD7${count}`);
|
|
385
|
+
if (count > 1) fields.push(`\xD7${count}${places ? ` @${places}` : ""}`);
|
|
386
|
+
if (node.generated) fields.push("(generated)");
|
|
113
387
|
if (node.id && selector !== `#${node.id}`) fields.push(`id=${JSON.stringify(node.id)}`);
|
|
114
388
|
if (node.classes.length && !selector.includes("."))
|
|
115
389
|
fields.push(`class=${JSON.stringify(node.classes.join(" "))}`);
|
|
@@ -132,8 +406,8 @@ function formatElementMap(snapshot, { file = "index.html" } = {}) {
|
|
|
132
406
|
if (!node.id && previous?.key === key) previous.indices.push(index);
|
|
133
407
|
else groups.push({ key, indices: [index] });
|
|
134
408
|
}
|
|
135
|
-
for (const
|
|
136
|
-
const members =
|
|
409
|
+
for (const group2 of groups) {
|
|
410
|
+
const members = group2.indices.map((index) => nodes[index]);
|
|
137
411
|
const node = members[0];
|
|
138
412
|
const exactGroup = members.length > 1 && node.siblingMatchCount === members.length;
|
|
139
413
|
if (members.length > 1 && !exactGroup) {
|
|
@@ -144,7 +418,8 @@ function formatElementMap(snapshot, { file = "index.html" } = {}) {
|
|
|
144
418
|
function emit(members, depth, repeatedParents = false) {
|
|
145
419
|
const node = members[0];
|
|
146
420
|
const selector = members.length > 1 && !repeatedParents ? node.groupSelector : node.relative;
|
|
147
|
-
|
|
421
|
+
const places = members.length > 1 && !repeatedParents ? childPlaces(members) : "";
|
|
422
|
+
lines.push(`${" ".repeat(depth)}${describe(node, selector, members.length, places)}`);
|
|
148
423
|
entries.push({
|
|
149
424
|
line: lines.length,
|
|
150
425
|
selectors: members.map((member) => member.selector),
|
|
@@ -262,14 +537,14 @@ var PREVIEW_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
262
537
|
|
|
263
538
|
// src/sources.ts
|
|
264
539
|
var starts = (text) => [0, ...[...text.matchAll(/\n/g)].map((m) => m.index + 1)];
|
|
265
|
-
function position(lines,
|
|
540
|
+
function position(lines, offset) {
|
|
266
541
|
let lo = 0, hi = lines.length - 1;
|
|
267
542
|
while (lo < hi) {
|
|
268
543
|
const mid = Math.ceil((lo + hi) / 2);
|
|
269
|
-
if (lines[mid] <=
|
|
544
|
+
if (lines[mid] <= offset) lo = mid;
|
|
270
545
|
else hi = mid - 1;
|
|
271
546
|
}
|
|
272
|
-
return { line: lo + 1, column:
|
|
547
|
+
return { line: lo + 1, column: offset - lines[lo] };
|
|
273
548
|
}
|
|
274
549
|
function occurrences(text, needle) {
|
|
275
550
|
const hits = [];
|
|
@@ -371,6 +646,36 @@ function rawTextElements(text, tag) {
|
|
|
371
646
|
}
|
|
372
647
|
return elements;
|
|
373
648
|
}
|
|
649
|
+
function htmlTagsOf(file, text) {
|
|
650
|
+
return [...text.matchAll(/<([a-z][\w-]*)\b[^>]*>/gi)].map((m) => {
|
|
651
|
+
const attr = (name) => {
|
|
652
|
+
const quoted = new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(m[0]);
|
|
653
|
+
if (quoted) return quoted[1];
|
|
654
|
+
return new RegExp(`\\s${name}\\s*=\\s*([^\\s"'=<>]+)`, "i").exec(m[0])?.[1];
|
|
655
|
+
};
|
|
656
|
+
return {
|
|
657
|
+
file,
|
|
658
|
+
start: m.index,
|
|
659
|
+
end: m.index + m[0].length,
|
|
660
|
+
tag: m[1].toLowerCase(),
|
|
661
|
+
id: attr("id"),
|
|
662
|
+
hfId: attr("data-hf-id"),
|
|
663
|
+
classes: (attr("class") ?? "").split(/\s+/).filter(Boolean)
|
|
664
|
+
};
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
function authoredHtml(project) {
|
|
668
|
+
const html = { files: /* @__PURE__ */ new Map(), htmlTags: [] };
|
|
669
|
+
for (const path of sourceFiles(project)) {
|
|
670
|
+
const file = relative(project, path);
|
|
671
|
+
if (!file.endsWith(".html") || /(?:^|\/)(?:vendor|lib)\//.test(file)) continue;
|
|
672
|
+
const text = readFileSync(path, "utf8");
|
|
673
|
+
if (text.length > 2e6) continue;
|
|
674
|
+
html.files.set(file, { text });
|
|
675
|
+
html.htmlTags.push(...htmlTagsOf(file, text));
|
|
676
|
+
}
|
|
677
|
+
return html;
|
|
678
|
+
}
|
|
374
679
|
var Sources = class {
|
|
375
680
|
constructor(project) {
|
|
376
681
|
this.project = project;
|
|
@@ -394,18 +699,7 @@ var Sources = class {
|
|
|
394
699
|
this.addChunk(file, script.content, script.contentStart);
|
|
395
700
|
for (const style of rawTextElements(text, "style"))
|
|
396
701
|
this.addCss(file, style.content, style.contentStart);
|
|
397
|
-
|
|
398
|
-
const attr = (name) => new RegExp(`\\s${name}\\s*=\\s*["']([^"']*)["']`, "i").exec(m[0])?.[1];
|
|
399
|
-
this.htmlTags.push({
|
|
400
|
-
file,
|
|
401
|
-
start: m.index,
|
|
402
|
-
end: m.index + m[0].length,
|
|
403
|
-
tag: m[1].toLowerCase(),
|
|
404
|
-
id: attr("id"),
|
|
405
|
-
hfId: attr("data-hf-id"),
|
|
406
|
-
classes: (attr("class") ?? "").split(/\s+/).filter(Boolean)
|
|
407
|
-
});
|
|
408
|
-
}
|
|
702
|
+
this.htmlTags.push(...htmlTagsOf(file, text));
|
|
409
703
|
}
|
|
410
704
|
}
|
|
411
705
|
}
|
|
@@ -413,7 +707,7 @@ var Sources = class {
|
|
|
413
707
|
chunks = [];
|
|
414
708
|
cssRules = [];
|
|
415
709
|
htmlTags = [];
|
|
416
|
-
addChunk(file, source,
|
|
710
|
+
addChunk(file, source, offset) {
|
|
417
711
|
const text = source.trim();
|
|
418
712
|
const lead = source.length - source.trimStart().length;
|
|
419
713
|
if (text) {
|
|
@@ -422,7 +716,7 @@ var Sources = class {
|
|
|
422
716
|
this.chunks.push({
|
|
423
717
|
file,
|
|
424
718
|
text,
|
|
425
|
-
offset:
|
|
719
|
+
offset: offset + lead,
|
|
426
720
|
get statements() {
|
|
427
721
|
return ranges ??= statements(text);
|
|
428
722
|
},
|
|
@@ -432,7 +726,7 @@ var Sources = class {
|
|
|
432
726
|
});
|
|
433
727
|
}
|
|
434
728
|
}
|
|
435
|
-
addCss(file, text,
|
|
729
|
+
addCss(file, text, offset) {
|
|
436
730
|
try {
|
|
437
731
|
postcss.parse(text).walkRules((rule) => {
|
|
438
732
|
const start = rule.source?.start?.offset;
|
|
@@ -442,14 +736,14 @@ var Sources = class {
|
|
|
442
736
|
id: `css:${this.cssRules.length}`,
|
|
443
737
|
selector: rule.selector,
|
|
444
738
|
file,
|
|
445
|
-
start:
|
|
446
|
-
end:
|
|
739
|
+
start: offset + start,
|
|
740
|
+
end: offset + end + 1
|
|
447
741
|
});
|
|
448
742
|
});
|
|
449
743
|
} catch {
|
|
450
744
|
}
|
|
451
745
|
}
|
|
452
|
-
slice(file, start, end, kind,
|
|
746
|
+
slice(file, start, end, kind, target) {
|
|
453
747
|
const source = this.files.get(file);
|
|
454
748
|
if (!source) return null;
|
|
455
749
|
const first = position(source.lines, start).line;
|
|
@@ -464,16 +758,18 @@ var Sources = class {
|
|
|
464
758
|
startLine: first,
|
|
465
759
|
endLine: last,
|
|
466
760
|
kind: [kind],
|
|
467
|
-
targets: [
|
|
761
|
+
targets: [target],
|
|
468
762
|
code,
|
|
469
763
|
uniqueInFile: occurrences(source.text, code).length === 1
|
|
470
764
|
};
|
|
471
765
|
}
|
|
472
|
-
markup(
|
|
473
|
-
const
|
|
474
|
-
|
|
766
|
+
markup(element, target) {
|
|
767
|
+
const sameTag = this.htmlTags.filter((tag) => tag.tag === element.tag);
|
|
768
|
+
const byHfId = element.hfId ? sameTag.filter((tag) => tag.hfId === element.hfId) : [];
|
|
769
|
+
const tags = byHfId.length ? byHfId : sameTag.filter(
|
|
770
|
+
(tag) => element.id ? tag.id === element.id : element.classes.length > 0 && element.classes.every((c) => tag.classes.includes(c))
|
|
475
771
|
);
|
|
476
|
-
return tags.length === 1 ? this.slice(tags[0].file, tags[0].start, tags[0].end, "html opening tag",
|
|
772
|
+
return tags.length === 1 ? this.slice(tags[0].file, tags[0].start, tags[0].end, "html opening tag", target) : null;
|
|
477
773
|
}
|
|
478
774
|
mapper(html, provenance) {
|
|
479
775
|
const htmlLines = starts(html);
|
|
@@ -492,29 +788,29 @@ var Sources = class {
|
|
|
492
788
|
text,
|
|
493
789
|
this.chunks.flatMap((chunk) => occurrences(text, chunk.text).map((at2) => ({ chunk, at: at2 })))
|
|
494
790
|
);
|
|
495
|
-
const locate = (text,
|
|
496
|
-
const found = (ranges.get(text) ?? []).filter(({ chunk, at: at2 }) =>
|
|
791
|
+
const locate = (text, offset) => {
|
|
792
|
+
const found = (ranges.get(text) ?? []).filter(({ chunk, at: at2 }) => offset >= at2 && offset < at2 + chunk.text.length).map(({ chunk, at: at2 }) => ({ chunk, local: offset - at2 }));
|
|
497
793
|
const unique = new Map(
|
|
498
794
|
found.map((hit) => [`${hit.chunk.file}:${hit.chunk.offset + hit.local}`, hit])
|
|
499
795
|
);
|
|
500
796
|
return unique.size === 1 ? [...unique.values()][0] : null;
|
|
501
797
|
};
|
|
502
798
|
const cache = /* @__PURE__ */ new Map();
|
|
503
|
-
return (stack,
|
|
799
|
+
return (stack, target) => {
|
|
504
800
|
const cached = cache.get(stack);
|
|
505
|
-
if (cached) return cached.map((
|
|
801
|
+
if (cached) return cached.map((snippet) => ({ ...snippet, targets: [target] }));
|
|
506
802
|
const result = [];
|
|
507
803
|
for (const frame of stack.split("\n")) {
|
|
508
804
|
const script = /(hf-inspect-[a-f0-9]+\.js):(\d+):(\d+)\)?$/.exec(frame);
|
|
509
805
|
const m = /https?:\/\/[^/]+\/(?:index\.html)?:(\d+):(\d+)\)?$/.exec(frame);
|
|
510
806
|
if (!m && !script) continue;
|
|
511
|
-
const
|
|
512
|
-
let hit = m ? locate(html,
|
|
807
|
+
const offset = m ? htmlLines[Number(m[1]) - 1] + Number(m[2]) - 1 : -1;
|
|
808
|
+
let hit = m ? locate(html, offset) : null;
|
|
513
809
|
if (!hit)
|
|
514
810
|
for (const map of maps) {
|
|
515
|
-
if (script ? map.p.sourceURL !== script[1] :
|
|
811
|
+
if (script ? map.p.sourceURL !== script[1] : offset < map.at || offset >= map.at + map.p.output.length)
|
|
516
812
|
continue;
|
|
517
|
-
const generated = script ? { line: Number(script[2]), column: Number(script[3]) - 1 } : position(map.lines,
|
|
813
|
+
const generated = script ? { line: Number(script[2]), column: Number(script[3]) - 1 } : position(map.lines, offset - map.at);
|
|
518
814
|
const original = originalPositionFor(map.map, generated);
|
|
519
815
|
if (original.line != null && original.column != null)
|
|
520
816
|
hit = locate(map.p.input, map.inputLines[original.line - 1] + original.column);
|
|
@@ -526,21 +822,21 @@ var Sources = class {
|
|
|
526
822
|
(s) => s.type === "FunctionDeclaration" && hit.local >= s.start && hit.local < s.end && s.end - s.start <= 2e3
|
|
527
823
|
).sort((a, b) => a.end - a.start - (b.end - b.start))[0];
|
|
528
824
|
const range = helper ?? enclosing;
|
|
529
|
-
const
|
|
825
|
+
const snippet = this.slice(
|
|
530
826
|
hit.chunk.file,
|
|
531
827
|
hit.chunk.offset + range.start,
|
|
532
828
|
hit.chunk.offset + range.end,
|
|
533
829
|
result.length ? "animation caller" : "animation creation",
|
|
534
|
-
|
|
830
|
+
target
|
|
535
831
|
);
|
|
536
|
-
if (
|
|
832
|
+
if (snippet) result.push(snippet);
|
|
537
833
|
for (const dependency of hit.chunk.dependencies(range)) {
|
|
538
834
|
const definition = this.slice(
|
|
539
835
|
hit.chunk.file,
|
|
540
836
|
hit.chunk.offset + dependency.start,
|
|
541
837
|
hit.chunk.offset + dependency.end,
|
|
542
838
|
"animation source dependency",
|
|
543
|
-
|
|
839
|
+
target
|
|
544
840
|
);
|
|
545
841
|
if (definition) result.push(definition);
|
|
546
842
|
}
|
|
@@ -550,26 +846,26 @@ var Sources = class {
|
|
|
550
846
|
};
|
|
551
847
|
}
|
|
552
848
|
};
|
|
553
|
-
function markupSnippet(sources,
|
|
554
|
-
return sources.markup(
|
|
849
|
+
function markupSnippet(sources, element, target) {
|
|
850
|
+
return sources.markup(element, target);
|
|
555
851
|
}
|
|
556
852
|
function mergeSnippets(snippets, sources) {
|
|
557
853
|
const sorted = snippets.sort((a, b) => a.file.localeCompare(b.file) || a.startLine - b.startLine);
|
|
558
854
|
const output = [];
|
|
559
|
-
for (const
|
|
855
|
+
for (const snippet of sorted) {
|
|
560
856
|
const last = output.at(-1);
|
|
561
|
-
if (last && last.file ===
|
|
562
|
-
last.endLine = Math.max(last.endLine,
|
|
857
|
+
if (last && last.file === snippet.file && snippet.startLine <= last.endLine + 1) {
|
|
858
|
+
last.endLine = Math.max(last.endLine, snippet.endLine);
|
|
563
859
|
last.id = `${last.file}:${last.startLine}-${last.endLine}`;
|
|
564
860
|
const source = sources.files.get(last.file);
|
|
565
861
|
last.code = source.text.slice(
|
|
566
862
|
source.lines[last.startLine - 1],
|
|
567
863
|
source.lines[last.endLine] ?? source.text.length
|
|
568
864
|
);
|
|
569
|
-
last.targets = [.../* @__PURE__ */ new Set([...last.targets, ...
|
|
570
|
-
last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...
|
|
865
|
+
last.targets = [.../* @__PURE__ */ new Set([...last.targets, ...snippet.targets])];
|
|
866
|
+
last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...snippet.kind])];
|
|
571
867
|
last.uniqueInFile = occurrences(source.text, last.code).length === 1;
|
|
572
|
-
} else output.push({ ...
|
|
868
|
+
} else output.push({ ...snippet });
|
|
573
869
|
}
|
|
574
870
|
return output;
|
|
575
871
|
}
|
|
@@ -582,7 +878,7 @@ var coverage = [
|
|
|
582
878
|
"Direct constructors, other animation systems, iframe contents, removed animations and unexecuted paths may be absent; missing origins are reported."
|
|
583
879
|
];
|
|
584
880
|
function mapInspection(raw, request, context) {
|
|
585
|
-
const { sources, map, catalog, generation
|
|
881
|
+
const { sources, map, catalog, generation, sourceVersion, reused, start, visualState } = context;
|
|
586
882
|
const maxBytes = request.maxBytes ?? 12e3;
|
|
587
883
|
const collected = [];
|
|
588
884
|
const results = raw.results.map((result, index) => {
|
|
@@ -593,35 +889,35 @@ function mapInspection(raw, request, context) {
|
|
|
593
889
|
collected.push(...mapped);
|
|
594
890
|
for (const stack of animation.attachmentStacks)
|
|
595
891
|
collected.push(
|
|
596
|
-
...map(stack, index).map((
|
|
892
|
+
...map(stack, index).map((snippet) => ({ ...snippet, kind: ["timeline attachment"] }))
|
|
597
893
|
);
|
|
598
894
|
}
|
|
599
895
|
for (const el of result.elements) {
|
|
600
|
-
const
|
|
601
|
-
if (
|
|
896
|
+
const snippet = markupSnippet(sources, el, index);
|
|
897
|
+
if (snippet) collected.push(snippet);
|
|
602
898
|
}
|
|
603
899
|
for (const id of result.css) {
|
|
604
900
|
const rule = sources.cssRules.find((rule2) => rule2.id === id);
|
|
605
901
|
if (rule) {
|
|
606
|
-
const
|
|
902
|
+
const snippet = sources.slice(
|
|
607
903
|
rule.file,
|
|
608
904
|
rule.start,
|
|
609
905
|
rule.end,
|
|
610
906
|
"matched CSS candidate",
|
|
611
907
|
index
|
|
612
908
|
);
|
|
613
|
-
if (
|
|
909
|
+
if (snippet) collected.push(snippet);
|
|
614
910
|
}
|
|
615
911
|
}
|
|
616
912
|
return {
|
|
617
913
|
target: index,
|
|
618
914
|
status: result.status,
|
|
619
|
-
candidates: result.candidates
|
|
620
|
-
candidatesOmitted:
|
|
915
|
+
candidates: result.candidates,
|
|
916
|
+
candidatesOmitted: 0,
|
|
621
917
|
animations: result.animations.length,
|
|
622
918
|
coTargets: [
|
|
623
919
|
...new Map(
|
|
624
|
-
result.animations.flatMap((animation) => animation.targets).filter((
|
|
920
|
+
result.animations.flatMap((animation) => animation.targets).filter((element) => !result.elements.some((own) => own.ref === element.ref)).map((element) => [element.ref, element])
|
|
625
921
|
).values()
|
|
626
922
|
].slice(0, 10),
|
|
627
923
|
unmappedAnimations,
|
|
@@ -629,16 +925,16 @@ function mapInspection(raw, request, context) {
|
|
|
629
925
|
};
|
|
630
926
|
});
|
|
631
927
|
const merged = mergeSnippets(collected, sources);
|
|
632
|
-
for (const
|
|
928
|
+
for (const snippet of merged) catalog.set(snippet.id, snippet);
|
|
633
929
|
let selected = merged;
|
|
634
930
|
if (request.snippets)
|
|
635
931
|
selected = request.snippets.map((id) => {
|
|
636
|
-
const
|
|
637
|
-
if (!
|
|
638
|
-
return
|
|
932
|
+
const snippet = catalog.get(id);
|
|
933
|
+
if (!snippet) throw new Error(`unknown_snippet: ${id}`);
|
|
934
|
+
return snippet;
|
|
639
935
|
});
|
|
640
936
|
const response = {
|
|
641
|
-
generation
|
|
937
|
+
generation,
|
|
642
938
|
visualState,
|
|
643
939
|
sourceVersion,
|
|
644
940
|
reused,
|
|
@@ -653,25 +949,33 @@ function mapInspection(raw, request, context) {
|
|
|
653
949
|
debugAvailable: true,
|
|
654
950
|
elapsedMs: Math.round(performance.now() - start)
|
|
655
951
|
};
|
|
656
|
-
const
|
|
657
|
-
if (!Number.isInteger(
|
|
952
|
+
const offset = request.offset ?? 0;
|
|
953
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
658
954
|
throw new Error("offset must be a non-negative integer");
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
955
|
+
const size = () => Buffer.byteLength(JSON.stringify(response));
|
|
956
|
+
const trimCandidates = (limit, keep) => {
|
|
957
|
+
while (size() > limit) {
|
|
958
|
+
const longest = response.results.reduce(
|
|
959
|
+
(a, b) => b.candidates.length > a.candidates.length ? b : a
|
|
960
|
+
);
|
|
961
|
+
if (longest.candidates.length <= keep) return;
|
|
962
|
+
longest.candidates.pop();
|
|
963
|
+
longest.candidatesOmitted++;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
trimCandidates(maxBytes / 2, 5);
|
|
967
|
+
for (let i = offset; i < selected.length; i++) {
|
|
968
|
+
response.snippets.push(selected[i]);
|
|
969
|
+
if (size() > maxBytes - 700) {
|
|
663
970
|
response.snippets.pop();
|
|
664
971
|
response.omittedCount = selected.length - i;
|
|
665
972
|
response.omitted = selected.slice(i, i + 8).map((item) => ({ id: item.id, bytes: Buffer.byteLength(item.code) }));
|
|
666
|
-
response.
|
|
973
|
+
const nothingFit = response.snippets.length === 0;
|
|
974
|
+
response.nextOffset = nothingFit ? i + 1 < selected.length ? i + 1 : null : i;
|
|
667
975
|
break;
|
|
668
976
|
}
|
|
669
977
|
}
|
|
670
|
-
|
|
671
|
-
const r = response.results.find((r2) => r2.candidates.length);
|
|
672
|
-
r.candidates.pop();
|
|
673
|
-
r.candidatesOmitted++;
|
|
674
|
-
}
|
|
978
|
+
trimCandidates(maxBytes, 0);
|
|
675
979
|
if (Buffer.byteLength(JSON.stringify(response)) > maxBytes)
|
|
676
980
|
throw new Error("metadata_exceeds_budget: use fewer targets or increase maxBytes");
|
|
677
981
|
if (request.debug) return { ...response, debug: raw };
|
|
@@ -694,20 +998,21 @@ function timelinePage(request) {
|
|
|
694
998
|
if (!inspection) throw new Error("inspector_unavailable");
|
|
695
999
|
const result = inspection.timeline(request);
|
|
696
1000
|
const following = [];
|
|
697
|
-
const
|
|
698
|
-
|
|
1001
|
+
const generation = inspected.__hfLiveInspection?.generation ?? null;
|
|
1002
|
+
const duration = inspected.__player?.getDuration();
|
|
1003
|
+
if (request.boundary === void 0) return { ...result, following, generation, duration };
|
|
699
1004
|
const boundary = request.boundary;
|
|
700
1005
|
const schedule = inspection.timeline({ ...request, boundary: void 0 });
|
|
701
1006
|
for (const crossing of result.events) {
|
|
702
1007
|
if (crossing.kind !== "animation" || crossing.relation !== "crosses") continue;
|
|
703
|
-
for (const
|
|
1008
|
+
for (const target of crossing.targets) {
|
|
704
1009
|
const next = schedule.events.filter(
|
|
705
|
-
(event) => event.kind === "animation" && event.start > boundary && event.targets.some((t) => t.ref ===
|
|
1010
|
+
(event) => event.kind === "animation" && event.start > boundary && event.targets.some((t) => t.ref === target.ref) && event.properties.some((p) => crossing.properties.includes(p))
|
|
706
1011
|
).sort((a, b) => a.start - b.start)[0];
|
|
707
1012
|
if (next && !following.includes(next)) following.push(next);
|
|
708
1013
|
}
|
|
709
1014
|
}
|
|
710
|
-
return { ...result, following, generation
|
|
1015
|
+
return { ...result, following, generation, duration };
|
|
711
1016
|
}
|
|
712
1017
|
function liveGenerationPage() {
|
|
713
1018
|
return window.__hfLiveInspection?.generation ?? null;
|
|
@@ -721,27 +1026,30 @@ var RELATIONS_SHOWN = 8;
|
|
|
721
1026
|
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.";
|
|
722
1027
|
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.";
|
|
723
1028
|
function formatTimeline(raw, request, view) {
|
|
724
|
-
const
|
|
725
|
-
const allGroups = groupEvents(raw.events);
|
|
726
|
-
const groups = allGroups.slice(
|
|
1029
|
+
const offset = request.offset ?? 0;
|
|
1030
|
+
const allGroups = byComponent(groupEvents(raw.events));
|
|
1031
|
+
const groups = allGroups.slice(offset, offset + GROUPS_PER_PAGE);
|
|
727
1032
|
const label = labeler();
|
|
728
1033
|
const events = groups.flat().map((event, index) => ({
|
|
729
1034
|
...event,
|
|
730
1035
|
stack: void 0,
|
|
731
|
-
sources: (event.stack ? view.map(event.stack, index) : []).map((s) => ({
|
|
1036
|
+
sources: onePerSourceLine(event.stack ? view.map(event.stack, index) : []).map((s) => ({
|
|
732
1037
|
file: s.file,
|
|
733
1038
|
line: s.startLine
|
|
734
1039
|
}))
|
|
735
1040
|
}));
|
|
736
|
-
const nextOffset =
|
|
1041
|
+
const nextOffset = offset + groups.length < allGroups.length ? offset + groups.length : null;
|
|
737
1042
|
const lines = [
|
|
738
1043
|
`Timeline \xB7 global seconds \xB7 ${raw.status}`,
|
|
739
1044
|
`generation: ${view.generation}`,
|
|
740
|
-
...raw.status !== "found" ? raw.candidates.map(
|
|
1045
|
+
...raw.status !== "found" ? raw.candidates.map(
|
|
1046
|
+
(c) => `${c.ref} ${targetSummary([c])[0]} ${c.tag}${c.composition ? ` in ${c.composition}` : ""}`
|
|
1047
|
+
) : [],
|
|
1048
|
+
...pastTheEnd(request, raw.duration),
|
|
741
1049
|
...request.boundary !== void 0 ? [`Boundary ${request.boundary}s: ${BOUNDARY_NOTE}`] : [],
|
|
742
1050
|
...describeGroups(groups, request, view.map, label),
|
|
743
1051
|
...describeFollowing(raw, view.map, label),
|
|
744
|
-
...describeRelations(
|
|
1052
|
+
...describeRelations(groups),
|
|
745
1053
|
...nextOffset !== null ? [`More groups: repeat with offset=${nextOffset} and generation.`] : [],
|
|
746
1054
|
...raw.warnings.map((w) => `Coverage: ${w}`),
|
|
747
1055
|
...events.some((e) => e.values) ? [
|
|
@@ -777,6 +1085,21 @@ function groupEvents(events) {
|
|
|
777
1085
|
}
|
|
778
1086
|
return [...groups.values()];
|
|
779
1087
|
}
|
|
1088
|
+
var headingOf = (event) => event.component?.id ? `#${event.component.id}` : event.component?.composition ?? "unassigned";
|
|
1089
|
+
function byComponent(groups) {
|
|
1090
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const group2 of groups) {
|
|
1092
|
+
const heading = headingOf(group2[0]);
|
|
1093
|
+
owners.set(heading, [...owners.get(heading) ?? [], group2]);
|
|
1094
|
+
}
|
|
1095
|
+
return [...owners.values()].flat();
|
|
1096
|
+
}
|
|
1097
|
+
function pastTheEnd(request, duration) {
|
|
1098
|
+
const from = request.range?.[0] ?? request.at ?? request.boundary;
|
|
1099
|
+
const filmLength = duration !== void 0 && duration > 0 ? duration : void 0;
|
|
1100
|
+
if (filmLength === void 0 || from === void 0 || from < filmLength) return [];
|
|
1101
|
+
return [`Requested time is past the end: the film is ${Number(filmLength.toFixed(4))}s long.`];
|
|
1102
|
+
}
|
|
780
1103
|
function labeler() {
|
|
781
1104
|
const labels = /* @__PURE__ */ new Map();
|
|
782
1105
|
return (ref) => {
|
|
@@ -785,10 +1108,15 @@ function labeler() {
|
|
|
785
1108
|
};
|
|
786
1109
|
}
|
|
787
1110
|
var at = (seconds) => seconds.toFixed(4);
|
|
788
|
-
|
|
1111
|
+
function onePerSourceLine(sources) {
|
|
1112
|
+
return [...new Map(sources.map((s) => [`${s.file}:${s.startLine}`, s])).values()];
|
|
1113
|
+
}
|
|
1114
|
+
var located = (sources) => onePerSourceLine(sources).map((s) => `${s.file}:${s.startLine}`).join(", ");
|
|
789
1115
|
function describeValue(value) {
|
|
790
1116
|
if (value.kind === "unknown") return "?";
|
|
791
|
-
|
|
1117
|
+
if (value.kind === "expression" && value.value === "custom function") return "custom function";
|
|
1118
|
+
const shown = typeof value.value === "number" ? Number(value.value.toFixed(4)) : value.value;
|
|
1119
|
+
return `${shown}${value.kind === "literal" ? "" : ` (${value.kind})`}`;
|
|
792
1120
|
}
|
|
793
1121
|
function describeValues(values) {
|
|
794
1122
|
return ` authored: ${values.properties.map((p) => `${p.property}: ${describeValue(p.from)} \u2192 ${describeValue(p.to)}`).join("; ")}; ease: ${describeValue(values.ease)}`;
|
|
@@ -805,7 +1133,7 @@ function describeGroups(groups, request, map, label) {
|
|
|
805
1133
|
targets: [...new Map(members.flatMap((e) => e.targets).map((t) => [t.ref, t])).values()]
|
|
806
1134
|
};
|
|
807
1135
|
const component = event.component;
|
|
808
|
-
const heading =
|
|
1136
|
+
const heading = headingOf(event);
|
|
809
1137
|
if (owner !== heading) {
|
|
810
1138
|
lines.push(`
|
|
811
1139
|
${heading}${component ? ` [${component.ref}]` : ""}`);
|
|
@@ -834,8 +1162,9 @@ ${heading}${component ? ` [${component.ref}]` : ""}`);
|
|
|
834
1162
|
}
|
|
835
1163
|
function targetSummary(targets) {
|
|
836
1164
|
const counts = /* @__PURE__ */ new Map();
|
|
837
|
-
for (const
|
|
838
|
-
const
|
|
1165
|
+
for (const target of targets) {
|
|
1166
|
+
const own = target.id ? `#${target.id}` : target.hfId ?? (target.classes.length ? `.${target.classes.join(".")}` : target.tag);
|
|
1167
|
+
const name = !target.id && target.withinId ? `${own} in #${target.withinId}` : own;
|
|
839
1168
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
840
1169
|
}
|
|
841
1170
|
return [...counts].map(([name, count]) => count > 1 ? `${name} \xD7${count}` : name);
|
|
@@ -882,18 +1211,23 @@ function describeFollowing(raw, map, label) {
|
|
|
882
1211
|
lines.push(` ${raw.following.length - FOLLOWING_SHOWN} additional following actions omitted.`);
|
|
883
1212
|
return lines;
|
|
884
1213
|
}
|
|
885
|
-
function describeRelations(
|
|
1214
|
+
function describeRelations(groups) {
|
|
886
1215
|
const lines = [];
|
|
1216
|
+
const events = groups.flatMap((members, group2) => members.map((event) => ({ event, group: group2 })));
|
|
1217
|
+
const described = /* @__PURE__ */ new Set();
|
|
887
1218
|
for (let a = 0; a < events.length; a++)
|
|
888
1219
|
for (let b = a + 1; b < events.length; b++) {
|
|
889
|
-
const left = events[a];
|
|
890
|
-
const right = events[b];
|
|
1220
|
+
const left = events[a].event;
|
|
1221
|
+
const right = events[b].event;
|
|
1222
|
+
const pair = `${events[a].group}:${events[b].group}`;
|
|
1223
|
+
if (described.has(pair)) continue;
|
|
891
1224
|
if (left.kind !== "animation" || right.kind !== "animation") continue;
|
|
892
1225
|
if (left.end < right.start || right.end < left.start) continue;
|
|
893
1226
|
const shared = left.targets.some((t) => right.targets.some((r) => r.ref === t.ref));
|
|
894
1227
|
const properties = left.properties.filter((p) => right.properties.includes(p));
|
|
895
1228
|
if (!shared || !properties.length || lines.length >= RELATIONS_SHOWN) continue;
|
|
896
1229
|
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";
|
|
1230
|
+
described.add(pair);
|
|
897
1231
|
lines.push(
|
|
898
1232
|
`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.`
|
|
899
1233
|
);
|
|
@@ -936,7 +1270,10 @@ async function inspectTimeline(runner, view, request, reused) {
|
|
|
936
1270
|
async function inspectElementMap(runner, view, file = "index.html") {
|
|
937
1271
|
const snapshot = await runner.run(collectElementDom, void 0);
|
|
938
1272
|
assertPageGeneration(view, snapshot.generation);
|
|
939
|
-
const
|
|
1273
|
+
const authored = view.authoredHtml?.();
|
|
1274
|
+
const map = formatElementMap(authored ? withAuthoredState(snapshot, authored) : snapshot, {
|
|
1275
|
+
file
|
|
1276
|
+
});
|
|
940
1277
|
await view.assertCurrent();
|
|
941
1278
|
return { ...map, sourceVersion: view.sourceVersion, generation: view.generation };
|
|
942
1279
|
}
|
|
@@ -945,10 +1282,10 @@ async function inspectElementMap(runner, view, file = "index.html") {
|
|
|
945
1282
|
function validateInspectRequest(request) {
|
|
946
1283
|
if (!Array.isArray(request.targets) || request.targets.length > 20 || !request.targets.length && !request.snippets?.length)
|
|
947
1284
|
throw new Error("Provide 1\u201320 targets, or snippet IDs from a previous query.");
|
|
948
|
-
for (const
|
|
949
|
-
if (!
|
|
950
|
-
const keys = ["selector", "hfId", "ref"].filter((key) => key in
|
|
951
|
-
if (keys.length !== 1 || Object.values(
|
|
1285
|
+
for (const target of request.targets) {
|
|
1286
|
+
if (!target || typeof target !== "object") throw new Error("Invalid target");
|
|
1287
|
+
const keys = ["selector", "hfId", "ref"].filter((key) => key in target);
|
|
1288
|
+
if (keys.length !== 1 || Object.values(target).some(
|
|
952
1289
|
(value) => typeof value !== "string" || !value || value.length > 1e3
|
|
953
1290
|
))
|
|
954
1291
|
throw new Error("Each target needs exactly one selector, hfId or ref; within is optional.");
|
|
@@ -982,260 +1319,35 @@ function validateTimelineRequest(request) {
|
|
|
982
1319
|
const component = request.component;
|
|
983
1320
|
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)))
|
|
984
1321
|
throw new Error("component requires one selector, hfId or ref");
|
|
985
|
-
const
|
|
986
|
-
if (!Number.isInteger(
|
|
1322
|
+
const offset = request.offset ?? 0;
|
|
1323
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
987
1324
|
throw new Error("offset must be non-negative integer");
|
|
988
|
-
if (
|
|
1325
|
+
if (offset && !request.generation) throw new Error("pagination requires generation");
|
|
989
1326
|
}
|
|
990
1327
|
function assertGeneration(requested, current) {
|
|
991
1328
|
if (requested && requested !== current)
|
|
992
1329
|
throw new Error("stale_generation: query targets again after source changes or idle disposal");
|
|
993
1330
|
}
|
|
994
1331
|
|
|
995
|
-
// src/outputSchemas.ts
|
|
996
|
-
var string = { type: "string" };
|
|
997
|
-
var number = { type: "number" };
|
|
998
|
-
var boolean = { type: "boolean" };
|
|
999
|
-
var array = (items) => ({ type: "array", items });
|
|
1000
|
-
var nullable = (schema) => ({ oneOf: [schema, { type: "null" }] });
|
|
1001
|
-
var object = (properties, optional = []) => ({
|
|
1002
|
-
type: "object",
|
|
1003
|
-
properties,
|
|
1004
|
-
required: Object.keys(properties).filter((key) => !optional.includes(key)),
|
|
1005
|
-
additionalProperties: false
|
|
1006
|
-
});
|
|
1007
|
-
var element = object({
|
|
1008
|
-
ref: string,
|
|
1009
|
-
tag: string,
|
|
1010
|
-
id: string,
|
|
1011
|
-
hfId: nullable(string),
|
|
1012
|
-
classes: array(string),
|
|
1013
|
-
composition: nullable(string)
|
|
1014
|
-
});
|
|
1015
|
-
var snippet = object({
|
|
1016
|
-
id: string,
|
|
1017
|
-
file: string,
|
|
1018
|
-
startLine: number,
|
|
1019
|
-
endLine: number,
|
|
1020
|
-
kind: array(string),
|
|
1021
|
-
targets: array(number),
|
|
1022
|
-
code: string,
|
|
1023
|
-
uniqueInFile: boolean
|
|
1024
|
-
});
|
|
1025
|
-
var componentOutputSchema = object(
|
|
1026
|
-
{
|
|
1027
|
-
generation: string,
|
|
1028
|
-
sourceVersion: string,
|
|
1029
|
-
reused: boolean,
|
|
1030
|
-
visualState: object({ time: number, duration: number }, ["time", "duration"]),
|
|
1031
|
-
results: array(
|
|
1032
|
-
object({
|
|
1033
|
-
target: number,
|
|
1034
|
-
status: {
|
|
1035
|
-
type: "string",
|
|
1036
|
-
enum: ["found", "missing", "ambiguous", "stale-ref", "invalid-selector"]
|
|
1037
|
-
},
|
|
1038
|
-
candidates: array(element),
|
|
1039
|
-
candidatesOmitted: number,
|
|
1040
|
-
animations: number,
|
|
1041
|
-
coTargets: array(element),
|
|
1042
|
-
unmappedAnimations: number,
|
|
1043
|
-
scopeTruncated: boolean
|
|
1044
|
-
})
|
|
1045
|
-
),
|
|
1046
|
-
snippets: array(snippet),
|
|
1047
|
-
omitted: array(object({ id: string, bytes: number })),
|
|
1048
|
-
omittedCount: number,
|
|
1049
|
-
nextOffset: nullable(number),
|
|
1050
|
-
coverage: array(string),
|
|
1051
|
-
recordLimitReached: boolean,
|
|
1052
|
-
hooked: boolean,
|
|
1053
|
-
debugAvailable: boolean,
|
|
1054
|
-
elapsedMs: number
|
|
1055
|
-
},
|
|
1056
|
-
["visualState"]
|
|
1057
|
-
);
|
|
1058
|
-
var declaredValue = object({
|
|
1059
|
-
kind: { type: "string", enum: ["literal", "relative", "expression", "unknown"] },
|
|
1060
|
-
value: { oneOf: [number, string, { type: "null" }] }
|
|
1061
|
-
});
|
|
1062
|
-
var animationValues = object({
|
|
1063
|
-
basis: { type: "string", enum: ["authored-tween"] },
|
|
1064
|
-
properties: array(object({ property: string, from: declaredValue, to: declaredValue })),
|
|
1065
|
-
ease: declaredValue
|
|
1066
|
-
});
|
|
1067
|
-
var timelineOutputSchema = object({
|
|
1068
|
-
text: string,
|
|
1069
|
-
generation: string,
|
|
1070
|
-
reused: boolean,
|
|
1071
|
-
status: string,
|
|
1072
|
-
candidates: array(element),
|
|
1073
|
-
events: array(
|
|
1074
|
-
object(
|
|
1075
|
-
{
|
|
1076
|
-
kind: { type: "string", enum: ["animation", "item"] },
|
|
1077
|
-
relation: string,
|
|
1078
|
-
component: nullable(element),
|
|
1079
|
-
targets: array(element),
|
|
1080
|
-
properties: array(string),
|
|
1081
|
-
values: animationValues,
|
|
1082
|
-
start: number,
|
|
1083
|
-
end: number,
|
|
1084
|
-
sources: array(object({ file: string, line: number }))
|
|
1085
|
-
},
|
|
1086
|
-
["values"]
|
|
1087
|
-
)
|
|
1088
|
-
),
|
|
1089
|
-
eventCount: number,
|
|
1090
|
-
groupCount: number,
|
|
1091
|
-
nextOffset: nullable(number),
|
|
1092
|
-
warnings: array(string)
|
|
1093
|
-
});
|
|
1094
|
-
|
|
1095
|
-
// src/tools.ts
|
|
1096
|
-
var within = {
|
|
1097
|
-
type: "string",
|
|
1098
|
-
description: "Optional CSS selector restricting target lookup; omit for an unambiguous target."
|
|
1099
|
-
};
|
|
1100
|
-
var target = {
|
|
1101
|
-
oneOf: [
|
|
1102
|
-
{
|
|
1103
|
-
type: "object",
|
|
1104
|
-
required: ["selector"],
|
|
1105
|
-
additionalProperties: false,
|
|
1106
|
-
properties: {
|
|
1107
|
-
selector: { type: "string", description: "CSS selector, such as #title or .word." },
|
|
1108
|
-
within
|
|
1109
|
-
}
|
|
1110
|
-
},
|
|
1111
|
-
{
|
|
1112
|
-
type: "object",
|
|
1113
|
-
required: ["hfId"],
|
|
1114
|
-
additionalProperties: false,
|
|
1115
|
-
properties: {
|
|
1116
|
-
hfId: { type: "string", description: "Existing element data-hf-id value." },
|
|
1117
|
-
within
|
|
1118
|
-
}
|
|
1119
|
-
},
|
|
1120
|
-
{
|
|
1121
|
-
type: "object",
|
|
1122
|
-
required: ["ref"],
|
|
1123
|
-
additionalProperties: false,
|
|
1124
|
-
properties: {
|
|
1125
|
-
ref: {
|
|
1126
|
-
type: "string",
|
|
1127
|
-
description: "Element ref returned by this inspector session; do not invent it. Rediscover after reload."
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
]
|
|
1132
|
-
};
|
|
1133
|
-
var generation = {
|
|
1134
|
-
type: "string",
|
|
1135
|
-
description: "Generation returned by the previous query. Use for continuation; if stale, query targets again."
|
|
1136
|
-
};
|
|
1137
|
-
var offset = {
|
|
1138
|
-
type: "integer",
|
|
1139
|
-
minimum: 0,
|
|
1140
|
-
description: "Returned nextOffset for continuation; repeat the same query with its generation."
|
|
1141
|
-
};
|
|
1142
|
-
var componentsTool = {
|
|
1143
|
-
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.",
|
|
1144
|
-
inputSchema: {
|
|
1145
|
-
type: "object",
|
|
1146
|
-
required: ["targets"],
|
|
1147
|
-
additionalProperties: false,
|
|
1148
|
-
properties: {
|
|
1149
|
-
targets: {
|
|
1150
|
-
type: "array",
|
|
1151
|
-
maxItems: 20,
|
|
1152
|
-
description: "Batch 1\u201320 targets. Use an empty array only when retrieving snippets by ID.",
|
|
1153
|
-
items: target
|
|
1154
|
-
},
|
|
1155
|
-
descendants: {
|
|
1156
|
-
type: "boolean",
|
|
1157
|
-
default: true,
|
|
1158
|
-
description: "Include source related to descendant elements."
|
|
1159
|
-
},
|
|
1160
|
-
maxBytes: {
|
|
1161
|
-
type: "integer",
|
|
1162
|
-
minimum: 2048,
|
|
1163
|
-
maximum: 256e3,
|
|
1164
|
-
default: 12e3,
|
|
1165
|
-
description: "Response budget in bytes. Whole snippets may be omitted; use returned continuation data or increase the budget."
|
|
1166
|
-
},
|
|
1167
|
-
snippets: {
|
|
1168
|
-
type: "array",
|
|
1169
|
-
items: { type: "string" },
|
|
1170
|
-
description: "Snippet IDs returned by a prior query; requires its generation. May use targets: []."
|
|
1171
|
-
},
|
|
1172
|
-
generation,
|
|
1173
|
-
offset,
|
|
1174
|
-
time: {
|
|
1175
|
-
type: "number",
|
|
1176
|
-
minimum: 0,
|
|
1177
|
-
description: "Optional exact preview seek in global seconds. Does not filter animations by time; omit for ordinary source lookup."
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
},
|
|
1181
|
-
outputSchema: componentOutputSchema
|
|
1182
|
-
};
|
|
1183
|
-
var timelineTool = {
|
|
1184
|
-
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.",
|
|
1185
|
-
inputSchema: {
|
|
1186
|
-
type: "object",
|
|
1187
|
-
additionalProperties: false,
|
|
1188
|
-
properties: {
|
|
1189
|
-
component: {
|
|
1190
|
-
...target,
|
|
1191
|
-
description: "Optional element/component scope including descendants; omit for the full project. Uses the same target format as the components tool."
|
|
1192
|
-
},
|
|
1193
|
-
at: {
|
|
1194
|
-
type: "number",
|
|
1195
|
-
minimum: 0,
|
|
1196
|
-
description: "Global second: active scheduled intervals use start <= at < end; includes instantaneous actions at that time."
|
|
1197
|
-
},
|
|
1198
|
-
range: {
|
|
1199
|
-
type: "array",
|
|
1200
|
-
items: { type: "number", minimum: 0 },
|
|
1201
|
-
minItems: 2,
|
|
1202
|
-
maxItems: 2,
|
|
1203
|
-
description: "Global [start, end) seconds, start < end. Return intersecting scheduled intervals and instantaneous actions inside the range."
|
|
1204
|
-
},
|
|
1205
|
-
boundary: {
|
|
1206
|
-
type: "number",
|
|
1207
|
-
minimum: 0,
|
|
1208
|
-
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."
|
|
1209
|
-
},
|
|
1210
|
-
generation,
|
|
1211
|
-
offset
|
|
1212
|
-
}
|
|
1213
|
-
},
|
|
1214
|
-
outputSchema: timelineOutputSchema
|
|
1215
|
-
};
|
|
1216
|
-
function toolContent(tool, report) {
|
|
1217
|
-
const text = tool === "timeline" && report.text ? report.text : JSON.stringify(report);
|
|
1218
|
-
return { content: [{ type: "text", text }], structuredContent: report };
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
1332
|
// src/live.ts
|
|
1222
1333
|
var runtime = () => readFileSync2(new URL(`./inspection.${"js"}`, import.meta.url), "utf8");
|
|
1223
1334
|
function prepareLiveInspection(projectDir, html, provenance, sourceVersion) {
|
|
1224
1335
|
const project = realpathSync(projectDir);
|
|
1225
1336
|
if (fingerprint(project) !== sourceVersion) throw new Error("source_changed_during_load");
|
|
1226
1337
|
if (!/<head\b[^>]*>/i.test(html)) throw new Error("preview_head_missing");
|
|
1227
|
-
const
|
|
1228
|
-
const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation
|
|
1338
|
+
const generation = randomUUID();
|
|
1339
|
+
const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation, project })};`;
|
|
1229
1340
|
const script = `${stamp}
|
|
1230
1341
|
${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
1231
1342
|
const prepared = html.replace(/<head\b[^>]*>/i, (head) => `${head}<script>${script}</script>`);
|
|
1232
1343
|
let document2 = prepared;
|
|
1233
1344
|
let context;
|
|
1345
|
+
let authored;
|
|
1234
1346
|
const catalog = /* @__PURE__ */ new Map();
|
|
1235
1347
|
const view = {
|
|
1236
|
-
generation
|
|
1348
|
+
generation,
|
|
1237
1349
|
sourceVersion,
|
|
1238
|
-
pageGeneration:
|
|
1350
|
+
pageGeneration: generation,
|
|
1239
1351
|
sourceContext() {
|
|
1240
1352
|
if (!context) {
|
|
1241
1353
|
const sources = new Sources(project);
|
|
@@ -1243,19 +1355,22 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1243
1355
|
}
|
|
1244
1356
|
return context;
|
|
1245
1357
|
},
|
|
1358
|
+
authoredHtml() {
|
|
1359
|
+
return authored ??= authoredHtml(project);
|
|
1360
|
+
},
|
|
1246
1361
|
async assertCurrent() {
|
|
1247
1362
|
if (fingerprint(project) !== sourceVersion)
|
|
1248
1363
|
throw new Error("stale_preview: the project changed; query again once the preview reloads");
|
|
1249
1364
|
}
|
|
1250
1365
|
};
|
|
1251
1366
|
async function current(runner) {
|
|
1252
|
-
if (await previewGeneration(runner) !==
|
|
1367
|
+
if (await previewGeneration(runner) !== generation)
|
|
1253
1368
|
throw new Error("stale_preview: the preview reloaded; query again");
|
|
1254
1369
|
await view.assertCurrent();
|
|
1255
1370
|
}
|
|
1256
1371
|
return {
|
|
1257
1372
|
html: prepared,
|
|
1258
|
-
generation
|
|
1373
|
+
generation,
|
|
1259
1374
|
finalizeDocument(servedHtml) {
|
|
1260
1375
|
document2 = servedHtml;
|
|
1261
1376
|
context = void 0;
|
|
@@ -1263,7 +1378,7 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1263
1378
|
async inspect(runner, request) {
|
|
1264
1379
|
validateInspectRequest(request);
|
|
1265
1380
|
if (request.time != null) throw new Error("live_query_does_not_seek");
|
|
1266
|
-
assertGeneration(request.generation,
|
|
1381
|
+
assertGeneration(request.generation, generation);
|
|
1267
1382
|
await current(runner);
|
|
1268
1383
|
return inspectComponents(runner, view, request, {
|
|
1269
1384
|
catalog,
|
|
@@ -1273,7 +1388,7 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1273
1388
|
},
|
|
1274
1389
|
async timeline(runner, request) {
|
|
1275
1390
|
validateTimelineRequest(request);
|
|
1276
|
-
assertGeneration(request.generation,
|
|
1391
|
+
assertGeneration(request.generation, generation);
|
|
1277
1392
|
await current(runner);
|
|
1278
1393
|
return inspectTimeline(runner, view, request, true);
|
|
1279
1394
|
},
|
|
@@ -1287,10 +1402,7 @@ function previewGeneration(runner) {
|
|
|
1287
1402
|
return runner.run(liveGenerationPage, void 0);
|
|
1288
1403
|
}
|
|
1289
1404
|
export {
|
|
1290
|
-
componentsTool,
|
|
1291
1405
|
fingerprint,
|
|
1292
1406
|
prepareLiveInspection,
|
|
1293
|
-
previewGeneration
|
|
1294
|
-
timelineTool,
|
|
1295
|
-
toolContent
|
|
1407
|
+
previewGeneration
|
|
1296
1408
|
};
|