@hyperframes/inspector 0.8.155 → 0.8.156
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/live.js +103 -332
- package/dist/server.js +142 -0
- package/dist/service.js +8 -2
- package/dist/types/inspector/src/live.d.ts +1 -2
- package/dist/types/inspector/src/server.d.ts +6 -0
- package/dist/types/inspector/src/service.d.ts +1 -0
- 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
|
@@ -11,64 +11,64 @@ function collectElementDom() {
|
|
|
11
11
|
const nodes = [];
|
|
12
12
|
const paths = /* @__PURE__ */ new Map();
|
|
13
13
|
const siblingGroups = /* @__PURE__ */ new WeakMap();
|
|
14
|
-
function siblingCount(
|
|
15
|
-
const owner =
|
|
14
|
+
function siblingCount(element, selector) {
|
|
15
|
+
const owner = element.parentElement ?? element;
|
|
16
16
|
let counts = siblingGroups.get(owner);
|
|
17
17
|
if (!counts) {
|
|
18
18
|
counts = /* @__PURE__ */ new Map();
|
|
19
19
|
siblingGroups.set(owner, counts);
|
|
20
20
|
}
|
|
21
21
|
if (!counts.has(selector)) {
|
|
22
|
-
const siblings =
|
|
22
|
+
const siblings = element.parentElement ? [...element.parentElement.children] : [element];
|
|
23
23
|
counts.set(selector, siblings.filter((sibling) => sibling.matches(selector)).length);
|
|
24
24
|
}
|
|
25
25
|
return counts.get(selector);
|
|
26
26
|
}
|
|
27
27
|
const quote = (value) => JSON.stringify(value);
|
|
28
|
-
function path(
|
|
29
|
-
if (paths.has(
|
|
28
|
+
function path(element) {
|
|
29
|
+
if (paths.has(element)) return paths.get(element);
|
|
30
30
|
const candidates = [];
|
|
31
|
-
if (
|
|
32
|
-
const hfId =
|
|
31
|
+
if (element.id) candidates.push(`#${CSS.escape(element.id)}`);
|
|
32
|
+
const hfId = element.getAttribute("data-hf-id");
|
|
33
33
|
if (hfId) candidates.push(`[data-hf-id=${quote(hfId)}]`);
|
|
34
34
|
const unique = candidates.find((selector2) => document.querySelectorAll(selector2).length === 1);
|
|
35
|
-
const parent =
|
|
36
|
-
const tag =
|
|
37
|
-
const position2 = parent ? [...parent.children].indexOf(
|
|
35
|
+
const parent = element.parentElement;
|
|
36
|
+
const tag = element.localName;
|
|
37
|
+
const position2 = parent ? [...parent.children].indexOf(element) + 1 : 1;
|
|
38
38
|
const selector = unique ?? (parent ? `${path(parent)} > ${CSS.escape(tag)}:nth-child(${position2})` : CSS.escape(tag));
|
|
39
|
-
paths.set(
|
|
39
|
+
paths.set(element, selector);
|
|
40
40
|
return selector;
|
|
41
41
|
}
|
|
42
|
-
function visit(
|
|
43
|
-
if (excluded2.has(
|
|
44
|
-
const selector = path(
|
|
45
|
-
if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !==
|
|
42
|
+
function visit(element, parent) {
|
|
43
|
+
if (excluded2.has(element.tagName)) return;
|
|
44
|
+
const selector = path(element);
|
|
45
|
+
if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !== element) {
|
|
46
46
|
throw new Error(`Element map selector does not resolve uniquely: ${selector}`);
|
|
47
47
|
}
|
|
48
|
-
const classes = [...
|
|
49
|
-
const tag =
|
|
48
|
+
const classes = [...element.classList].sort();
|
|
49
|
+
const tag = element.localName;
|
|
50
50
|
const simple = `${CSS.escape(tag)}${classes.map((value) => `.${CSS.escape(value)}`).join("")}`;
|
|
51
|
-
const siblings =
|
|
51
|
+
const siblings = element.parentElement ? [...element.parentElement.children] : [element];
|
|
52
52
|
const index = nodes.length;
|
|
53
53
|
const node = {
|
|
54
54
|
index,
|
|
55
55
|
parent,
|
|
56
56
|
selector,
|
|
57
57
|
tag,
|
|
58
|
-
id:
|
|
59
|
-
hfId:
|
|
58
|
+
id: element.id,
|
|
59
|
+
hfId: element.getAttribute("data-hf-id"),
|
|
60
60
|
classes,
|
|
61
|
-
composition:
|
|
62
|
-
compositionSrc:
|
|
63
|
-
text: [...
|
|
64
|
-
media: ["img", "video", "audio", "source", "iframe"].includes(tag) ?
|
|
65
|
-
relative:
|
|
61
|
+
composition: element.getAttribute("data-composition-id"),
|
|
62
|
+
compositionSrc: element.getAttribute("data-composition-src"),
|
|
63
|
+
text: [...element.childNodes].filter((child) => child.nodeType === 3).map((child) => child.textContent).join("").replace(/\s+/g, " ").trim(),
|
|
64
|
+
media: ["img", "video", "audio", "source", "iframe"].includes(tag) ? element.getAttribute("src") : null,
|
|
65
|
+
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})`,
|
|
66
66
|
groupSelector: simple,
|
|
67
|
-
siblingMatchCount: siblingCount(
|
|
67
|
+
siblingMatchCount: siblingCount(element, simple),
|
|
68
68
|
children: []
|
|
69
69
|
};
|
|
70
70
|
nodes.push(node);
|
|
71
|
-
for (const child of
|
|
71
|
+
for (const child of element.children) {
|
|
72
72
|
const childIndex = visit(child, index);
|
|
73
73
|
if (childIndex !== void 0) node.children.push(childIndex);
|
|
74
74
|
}
|
|
@@ -262,14 +262,14 @@ var PREVIEW_IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
|
262
262
|
|
|
263
263
|
// src/sources.ts
|
|
264
264
|
var starts = (text) => [0, ...[...text.matchAll(/\n/g)].map((m) => m.index + 1)];
|
|
265
|
-
function position(lines,
|
|
265
|
+
function position(lines, offset) {
|
|
266
266
|
let lo = 0, hi = lines.length - 1;
|
|
267
267
|
while (lo < hi) {
|
|
268
268
|
const mid = Math.ceil((lo + hi) / 2);
|
|
269
|
-
if (lines[mid] <=
|
|
269
|
+
if (lines[mid] <= offset) lo = mid;
|
|
270
270
|
else hi = mid - 1;
|
|
271
271
|
}
|
|
272
|
-
return { line: lo + 1, column:
|
|
272
|
+
return { line: lo + 1, column: offset - lines[lo] };
|
|
273
273
|
}
|
|
274
274
|
function occurrences(text, needle) {
|
|
275
275
|
const hits = [];
|
|
@@ -413,7 +413,7 @@ var Sources = class {
|
|
|
413
413
|
chunks = [];
|
|
414
414
|
cssRules = [];
|
|
415
415
|
htmlTags = [];
|
|
416
|
-
addChunk(file, source,
|
|
416
|
+
addChunk(file, source, offset) {
|
|
417
417
|
const text = source.trim();
|
|
418
418
|
const lead = source.length - source.trimStart().length;
|
|
419
419
|
if (text) {
|
|
@@ -422,7 +422,7 @@ var Sources = class {
|
|
|
422
422
|
this.chunks.push({
|
|
423
423
|
file,
|
|
424
424
|
text,
|
|
425
|
-
offset:
|
|
425
|
+
offset: offset + lead,
|
|
426
426
|
get statements() {
|
|
427
427
|
return ranges ??= statements(text);
|
|
428
428
|
},
|
|
@@ -432,7 +432,7 @@ var Sources = class {
|
|
|
432
432
|
});
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
|
-
addCss(file, text,
|
|
435
|
+
addCss(file, text, offset) {
|
|
436
436
|
try {
|
|
437
437
|
postcss.parse(text).walkRules((rule) => {
|
|
438
438
|
const start = rule.source?.start?.offset;
|
|
@@ -442,14 +442,14 @@ var Sources = class {
|
|
|
442
442
|
id: `css:${this.cssRules.length}`,
|
|
443
443
|
selector: rule.selector,
|
|
444
444
|
file,
|
|
445
|
-
start:
|
|
446
|
-
end:
|
|
445
|
+
start: offset + start,
|
|
446
|
+
end: offset + end + 1
|
|
447
447
|
});
|
|
448
448
|
});
|
|
449
449
|
} catch {
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
|
-
slice(file, start, end, kind,
|
|
452
|
+
slice(file, start, end, kind, target) {
|
|
453
453
|
const source = this.files.get(file);
|
|
454
454
|
if (!source) return null;
|
|
455
455
|
const first = position(source.lines, start).line;
|
|
@@ -464,16 +464,16 @@ var Sources = class {
|
|
|
464
464
|
startLine: first,
|
|
465
465
|
endLine: last,
|
|
466
466
|
kind: [kind],
|
|
467
|
-
targets: [
|
|
467
|
+
targets: [target],
|
|
468
468
|
code,
|
|
469
469
|
uniqueInFile: occurrences(source.text, code).length === 1
|
|
470
470
|
};
|
|
471
471
|
}
|
|
472
|
-
markup(
|
|
472
|
+
markup(element, target) {
|
|
473
473
|
const tags = this.htmlTags.filter(
|
|
474
|
-
(tag) => tag.tag ===
|
|
474
|
+
(tag) => tag.tag === element.tag && (element.hfId ? tag.hfId === element.hfId : element.id ? tag.id === element.id : element.classes.length > 0 && element.classes.every((c) => tag.classes.includes(c)))
|
|
475
475
|
);
|
|
476
|
-
return tags.length === 1 ? this.slice(tags[0].file, tags[0].start, tags[0].end, "html opening tag",
|
|
476
|
+
return tags.length === 1 ? this.slice(tags[0].file, tags[0].start, tags[0].end, "html opening tag", target) : null;
|
|
477
477
|
}
|
|
478
478
|
mapper(html, provenance) {
|
|
479
479
|
const htmlLines = starts(html);
|
|
@@ -492,29 +492,29 @@ var Sources = class {
|
|
|
492
492
|
text,
|
|
493
493
|
this.chunks.flatMap((chunk) => occurrences(text, chunk.text).map((at2) => ({ chunk, at: at2 })))
|
|
494
494
|
);
|
|
495
|
-
const locate = (text,
|
|
496
|
-
const found = (ranges.get(text) ?? []).filter(({ chunk, at: at2 }) =>
|
|
495
|
+
const locate = (text, offset) => {
|
|
496
|
+
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
497
|
const unique = new Map(
|
|
498
498
|
found.map((hit) => [`${hit.chunk.file}:${hit.chunk.offset + hit.local}`, hit])
|
|
499
499
|
);
|
|
500
500
|
return unique.size === 1 ? [...unique.values()][0] : null;
|
|
501
501
|
};
|
|
502
502
|
const cache = /* @__PURE__ */ new Map();
|
|
503
|
-
return (stack,
|
|
503
|
+
return (stack, target) => {
|
|
504
504
|
const cached = cache.get(stack);
|
|
505
|
-
if (cached) return cached.map((
|
|
505
|
+
if (cached) return cached.map((snippet) => ({ ...snippet, targets: [target] }));
|
|
506
506
|
const result = [];
|
|
507
507
|
for (const frame of stack.split("\n")) {
|
|
508
508
|
const script = /(hf-inspect-[a-f0-9]+\.js):(\d+):(\d+)\)?$/.exec(frame);
|
|
509
509
|
const m = /https?:\/\/[^/]+\/(?:index\.html)?:(\d+):(\d+)\)?$/.exec(frame);
|
|
510
510
|
if (!m && !script) continue;
|
|
511
|
-
const
|
|
512
|
-
let hit = m ? locate(html,
|
|
511
|
+
const offset = m ? htmlLines[Number(m[1]) - 1] + Number(m[2]) - 1 : -1;
|
|
512
|
+
let hit = m ? locate(html, offset) : null;
|
|
513
513
|
if (!hit)
|
|
514
514
|
for (const map of maps) {
|
|
515
|
-
if (script ? map.p.sourceURL !== script[1] :
|
|
515
|
+
if (script ? map.p.sourceURL !== script[1] : offset < map.at || offset >= map.at + map.p.output.length)
|
|
516
516
|
continue;
|
|
517
|
-
const generated = script ? { line: Number(script[2]), column: Number(script[3]) - 1 } : position(map.lines,
|
|
517
|
+
const generated = script ? { line: Number(script[2]), column: Number(script[3]) - 1 } : position(map.lines, offset - map.at);
|
|
518
518
|
const original = originalPositionFor(map.map, generated);
|
|
519
519
|
if (original.line != null && original.column != null)
|
|
520
520
|
hit = locate(map.p.input, map.inputLines[original.line - 1] + original.column);
|
|
@@ -526,21 +526,21 @@ var Sources = class {
|
|
|
526
526
|
(s) => s.type === "FunctionDeclaration" && hit.local >= s.start && hit.local < s.end && s.end - s.start <= 2e3
|
|
527
527
|
).sort((a, b) => a.end - a.start - (b.end - b.start))[0];
|
|
528
528
|
const range = helper ?? enclosing;
|
|
529
|
-
const
|
|
529
|
+
const snippet = this.slice(
|
|
530
530
|
hit.chunk.file,
|
|
531
531
|
hit.chunk.offset + range.start,
|
|
532
532
|
hit.chunk.offset + range.end,
|
|
533
533
|
result.length ? "animation caller" : "animation creation",
|
|
534
|
-
|
|
534
|
+
target
|
|
535
535
|
);
|
|
536
|
-
if (
|
|
536
|
+
if (snippet) result.push(snippet);
|
|
537
537
|
for (const dependency of hit.chunk.dependencies(range)) {
|
|
538
538
|
const definition = this.slice(
|
|
539
539
|
hit.chunk.file,
|
|
540
540
|
hit.chunk.offset + dependency.start,
|
|
541
541
|
hit.chunk.offset + dependency.end,
|
|
542
542
|
"animation source dependency",
|
|
543
|
-
|
|
543
|
+
target
|
|
544
544
|
);
|
|
545
545
|
if (definition) result.push(definition);
|
|
546
546
|
}
|
|
@@ -550,26 +550,26 @@ var Sources = class {
|
|
|
550
550
|
};
|
|
551
551
|
}
|
|
552
552
|
};
|
|
553
|
-
function markupSnippet(sources,
|
|
554
|
-
return sources.markup(
|
|
553
|
+
function markupSnippet(sources, element, target) {
|
|
554
|
+
return sources.markup(element, target);
|
|
555
555
|
}
|
|
556
556
|
function mergeSnippets(snippets, sources) {
|
|
557
557
|
const sorted = snippets.sort((a, b) => a.file.localeCompare(b.file) || a.startLine - b.startLine);
|
|
558
558
|
const output = [];
|
|
559
|
-
for (const
|
|
559
|
+
for (const snippet of sorted) {
|
|
560
560
|
const last = output.at(-1);
|
|
561
|
-
if (last && last.file ===
|
|
562
|
-
last.endLine = Math.max(last.endLine,
|
|
561
|
+
if (last && last.file === snippet.file && snippet.startLine <= last.endLine + 1) {
|
|
562
|
+
last.endLine = Math.max(last.endLine, snippet.endLine);
|
|
563
563
|
last.id = `${last.file}:${last.startLine}-${last.endLine}`;
|
|
564
564
|
const source = sources.files.get(last.file);
|
|
565
565
|
last.code = source.text.slice(
|
|
566
566
|
source.lines[last.startLine - 1],
|
|
567
567
|
source.lines[last.endLine] ?? source.text.length
|
|
568
568
|
);
|
|
569
|
-
last.targets = [.../* @__PURE__ */ new Set([...last.targets, ...
|
|
570
|
-
last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...
|
|
569
|
+
last.targets = [.../* @__PURE__ */ new Set([...last.targets, ...snippet.targets])];
|
|
570
|
+
last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...snippet.kind])];
|
|
571
571
|
last.uniqueInFile = occurrences(source.text, last.code).length === 1;
|
|
572
|
-
} else output.push({ ...
|
|
572
|
+
} else output.push({ ...snippet });
|
|
573
573
|
}
|
|
574
574
|
return output;
|
|
575
575
|
}
|
|
@@ -582,7 +582,7 @@ var coverage = [
|
|
|
582
582
|
"Direct constructors, other animation systems, iframe contents, removed animations and unexecuted paths may be absent; missing origins are reported."
|
|
583
583
|
];
|
|
584
584
|
function mapInspection(raw, request, context) {
|
|
585
|
-
const { sources, map, catalog, generation
|
|
585
|
+
const { sources, map, catalog, generation, sourceVersion, reused, start, visualState } = context;
|
|
586
586
|
const maxBytes = request.maxBytes ?? 12e3;
|
|
587
587
|
const collected = [];
|
|
588
588
|
const results = raw.results.map((result, index) => {
|
|
@@ -593,24 +593,24 @@ function mapInspection(raw, request, context) {
|
|
|
593
593
|
collected.push(...mapped);
|
|
594
594
|
for (const stack of animation.attachmentStacks)
|
|
595
595
|
collected.push(
|
|
596
|
-
...map(stack, index).map((
|
|
596
|
+
...map(stack, index).map((snippet) => ({ ...snippet, kind: ["timeline attachment"] }))
|
|
597
597
|
);
|
|
598
598
|
}
|
|
599
599
|
for (const el of result.elements) {
|
|
600
|
-
const
|
|
601
|
-
if (
|
|
600
|
+
const snippet = markupSnippet(sources, el, index);
|
|
601
|
+
if (snippet) collected.push(snippet);
|
|
602
602
|
}
|
|
603
603
|
for (const id of result.css) {
|
|
604
604
|
const rule = sources.cssRules.find((rule2) => rule2.id === id);
|
|
605
605
|
if (rule) {
|
|
606
|
-
const
|
|
606
|
+
const snippet = sources.slice(
|
|
607
607
|
rule.file,
|
|
608
608
|
rule.start,
|
|
609
609
|
rule.end,
|
|
610
610
|
"matched CSS candidate",
|
|
611
611
|
index
|
|
612
612
|
);
|
|
613
|
-
if (
|
|
613
|
+
if (snippet) collected.push(snippet);
|
|
614
614
|
}
|
|
615
615
|
}
|
|
616
616
|
return {
|
|
@@ -621,7 +621,7 @@ function mapInspection(raw, request, context) {
|
|
|
621
621
|
animations: result.animations.length,
|
|
622
622
|
coTargets: [
|
|
623
623
|
...new Map(
|
|
624
|
-
result.animations.flatMap((animation) => animation.targets).filter((
|
|
624
|
+
result.animations.flatMap((animation) => animation.targets).filter((element) => !result.elements.some((own) => own.ref === element.ref)).map((element) => [element.ref, element])
|
|
625
625
|
).values()
|
|
626
626
|
].slice(0, 10),
|
|
627
627
|
unmappedAnimations,
|
|
@@ -629,16 +629,16 @@ function mapInspection(raw, request, context) {
|
|
|
629
629
|
};
|
|
630
630
|
});
|
|
631
631
|
const merged = mergeSnippets(collected, sources);
|
|
632
|
-
for (const
|
|
632
|
+
for (const snippet of merged) catalog.set(snippet.id, snippet);
|
|
633
633
|
let selected = merged;
|
|
634
634
|
if (request.snippets)
|
|
635
635
|
selected = request.snippets.map((id) => {
|
|
636
|
-
const
|
|
637
|
-
if (!
|
|
638
|
-
return
|
|
636
|
+
const snippet = catalog.get(id);
|
|
637
|
+
if (!snippet) throw new Error(`unknown_snippet: ${id}`);
|
|
638
|
+
return snippet;
|
|
639
639
|
});
|
|
640
640
|
const response = {
|
|
641
|
-
generation
|
|
641
|
+
generation,
|
|
642
642
|
visualState,
|
|
643
643
|
sourceVersion,
|
|
644
644
|
reused,
|
|
@@ -653,12 +653,12 @@ function mapInspection(raw, request, context) {
|
|
|
653
653
|
debugAvailable: true,
|
|
654
654
|
elapsedMs: Math.round(performance.now() - start)
|
|
655
655
|
};
|
|
656
|
-
const
|
|
657
|
-
if (!Number.isInteger(
|
|
656
|
+
const offset = request.offset ?? 0;
|
|
657
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
658
658
|
throw new Error("offset must be a non-negative integer");
|
|
659
|
-
for (let i =
|
|
660
|
-
const
|
|
661
|
-
response.snippets.push(
|
|
659
|
+
for (let i = offset; i < selected.length; i++) {
|
|
660
|
+
const snippet = selected[i];
|
|
661
|
+
response.snippets.push(snippet);
|
|
662
662
|
if (Buffer.byteLength(JSON.stringify(response)) > maxBytes - 700) {
|
|
663
663
|
response.snippets.pop();
|
|
664
664
|
response.omittedCount = selected.length - i;
|
|
@@ -694,20 +694,20 @@ function timelinePage(request) {
|
|
|
694
694
|
if (!inspection) throw new Error("inspector_unavailable");
|
|
695
695
|
const result = inspection.timeline(request);
|
|
696
696
|
const following = [];
|
|
697
|
-
const
|
|
698
|
-
if (request.boundary === void 0) return { ...result, following, generation
|
|
697
|
+
const generation = inspected.__hfLiveInspection?.generation ?? null;
|
|
698
|
+
if (request.boundary === void 0) return { ...result, following, generation };
|
|
699
699
|
const boundary = request.boundary;
|
|
700
700
|
const schedule = inspection.timeline({ ...request, boundary: void 0 });
|
|
701
701
|
for (const crossing of result.events) {
|
|
702
702
|
if (crossing.kind !== "animation" || crossing.relation !== "crosses") continue;
|
|
703
|
-
for (const
|
|
703
|
+
for (const target of crossing.targets) {
|
|
704
704
|
const next = schedule.events.filter(
|
|
705
|
-
(event) => event.kind === "animation" && event.start > boundary && event.targets.some((t) => t.ref ===
|
|
705
|
+
(event) => event.kind === "animation" && event.start > boundary && event.targets.some((t) => t.ref === target.ref) && event.properties.some((p) => crossing.properties.includes(p))
|
|
706
706
|
).sort((a, b) => a.start - b.start)[0];
|
|
707
707
|
if (next && !following.includes(next)) following.push(next);
|
|
708
708
|
}
|
|
709
709
|
}
|
|
710
|
-
return { ...result, following, generation
|
|
710
|
+
return { ...result, following, generation };
|
|
711
711
|
}
|
|
712
712
|
function liveGenerationPage() {
|
|
713
713
|
return window.__hfLiveInspection?.generation ?? null;
|
|
@@ -721,9 +721,9 @@ var RELATIONS_SHOWN = 8;
|
|
|
721
721
|
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
722
|
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
723
|
function formatTimeline(raw, request, view) {
|
|
724
|
-
const
|
|
724
|
+
const offset = request.offset ?? 0;
|
|
725
725
|
const allGroups = groupEvents(raw.events);
|
|
726
|
-
const groups = allGroups.slice(
|
|
726
|
+
const groups = allGroups.slice(offset, offset + GROUPS_PER_PAGE);
|
|
727
727
|
const label = labeler();
|
|
728
728
|
const events = groups.flat().map((event, index) => ({
|
|
729
729
|
...event,
|
|
@@ -733,7 +733,7 @@ function formatTimeline(raw, request, view) {
|
|
|
733
733
|
line: s.startLine
|
|
734
734
|
}))
|
|
735
735
|
}));
|
|
736
|
-
const nextOffset =
|
|
736
|
+
const nextOffset = offset + groups.length < allGroups.length ? offset + groups.length : null;
|
|
737
737
|
const lines = [
|
|
738
738
|
`Timeline \xB7 global seconds \xB7 ${raw.status}`,
|
|
739
739
|
`generation: ${view.generation}`,
|
|
@@ -834,8 +834,8 @@ ${heading}${component ? ` [${component.ref}]` : ""}`);
|
|
|
834
834
|
}
|
|
835
835
|
function targetSummary(targets) {
|
|
836
836
|
const counts = /* @__PURE__ */ new Map();
|
|
837
|
-
for (const
|
|
838
|
-
const name =
|
|
837
|
+
for (const target of targets) {
|
|
838
|
+
const name = target.id ? `#${target.id}` : target.hfId ?? (target.classes.length ? `.${target.classes.join(".")}` : target.tag);
|
|
839
839
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
840
840
|
}
|
|
841
841
|
return [...counts].map(([name, count]) => count > 1 ? `${name} \xD7${count}` : name);
|
|
@@ -945,10 +945,10 @@ async function inspectElementMap(runner, view, file = "index.html") {
|
|
|
945
945
|
function validateInspectRequest(request) {
|
|
946
946
|
if (!Array.isArray(request.targets) || request.targets.length > 20 || !request.targets.length && !request.snippets?.length)
|
|
947
947
|
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(
|
|
948
|
+
for (const target of request.targets) {
|
|
949
|
+
if (!target || typeof target !== "object") throw new Error("Invalid target");
|
|
950
|
+
const keys = ["selector", "hfId", "ref"].filter((key) => key in target);
|
|
951
|
+
if (keys.length !== 1 || Object.values(target).some(
|
|
952
952
|
(value) => typeof value !== "string" || !value || value.length > 1e3
|
|
953
953
|
))
|
|
954
954
|
throw new Error("Each target needs exactly one selector, hfId or ref; within is optional.");
|
|
@@ -982,250 +982,24 @@ function validateTimelineRequest(request) {
|
|
|
982
982
|
const component = request.component;
|
|
983
983
|
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
984
|
throw new Error("component requires one selector, hfId or ref");
|
|
985
|
-
const
|
|
986
|
-
if (!Number.isInteger(
|
|
985
|
+
const offset = request.offset ?? 0;
|
|
986
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
987
987
|
throw new Error("offset must be non-negative integer");
|
|
988
|
-
if (
|
|
988
|
+
if (offset && !request.generation) throw new Error("pagination requires generation");
|
|
989
989
|
}
|
|
990
990
|
function assertGeneration(requested, current) {
|
|
991
991
|
if (requested && requested !== current)
|
|
992
992
|
throw new Error("stale_generation: query targets again after source changes or idle disposal");
|
|
993
993
|
}
|
|
994
994
|
|
|
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
995
|
// src/live.ts
|
|
1222
996
|
var runtime = () => readFileSync2(new URL(`./inspection.${"js"}`, import.meta.url), "utf8");
|
|
1223
997
|
function prepareLiveInspection(projectDir, html, provenance, sourceVersion) {
|
|
1224
998
|
const project = realpathSync(projectDir);
|
|
1225
999
|
if (fingerprint(project) !== sourceVersion) throw new Error("source_changed_during_load");
|
|
1226
1000
|
if (!/<head\b[^>]*>/i.test(html)) throw new Error("preview_head_missing");
|
|
1227
|
-
const
|
|
1228
|
-
const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation
|
|
1001
|
+
const generation = randomUUID();
|
|
1002
|
+
const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation, project })};`;
|
|
1229
1003
|
const script = `${stamp}
|
|
1230
1004
|
${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
1231
1005
|
const prepared = html.replace(/<head\b[^>]*>/i, (head) => `${head}<script>${script}</script>`);
|
|
@@ -1233,9 +1007,9 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1233
1007
|
let context;
|
|
1234
1008
|
const catalog = /* @__PURE__ */ new Map();
|
|
1235
1009
|
const view = {
|
|
1236
|
-
generation
|
|
1010
|
+
generation,
|
|
1237
1011
|
sourceVersion,
|
|
1238
|
-
pageGeneration:
|
|
1012
|
+
pageGeneration: generation,
|
|
1239
1013
|
sourceContext() {
|
|
1240
1014
|
if (!context) {
|
|
1241
1015
|
const sources = new Sources(project);
|
|
@@ -1249,13 +1023,13 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1249
1023
|
}
|
|
1250
1024
|
};
|
|
1251
1025
|
async function current(runner) {
|
|
1252
|
-
if (await previewGeneration(runner) !==
|
|
1026
|
+
if (await previewGeneration(runner) !== generation)
|
|
1253
1027
|
throw new Error("stale_preview: the preview reloaded; query again");
|
|
1254
1028
|
await view.assertCurrent();
|
|
1255
1029
|
}
|
|
1256
1030
|
return {
|
|
1257
1031
|
html: prepared,
|
|
1258
|
-
generation
|
|
1032
|
+
generation,
|
|
1259
1033
|
finalizeDocument(servedHtml) {
|
|
1260
1034
|
document2 = servedHtml;
|
|
1261
1035
|
context = void 0;
|
|
@@ -1263,7 +1037,7 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1263
1037
|
async inspect(runner, request) {
|
|
1264
1038
|
validateInspectRequest(request);
|
|
1265
1039
|
if (request.time != null) throw new Error("live_query_does_not_seek");
|
|
1266
|
-
assertGeneration(request.generation,
|
|
1040
|
+
assertGeneration(request.generation, generation);
|
|
1267
1041
|
await current(runner);
|
|
1268
1042
|
return inspectComponents(runner, view, request, {
|
|
1269
1043
|
catalog,
|
|
@@ -1273,7 +1047,7 @@ ${runtime()}`.replace(/<\/script/gi, "<\\/script");
|
|
|
1273
1047
|
},
|
|
1274
1048
|
async timeline(runner, request) {
|
|
1275
1049
|
validateTimelineRequest(request);
|
|
1276
|
-
assertGeneration(request.generation,
|
|
1050
|
+
assertGeneration(request.generation, generation);
|
|
1277
1051
|
await current(runner);
|
|
1278
1052
|
return inspectTimeline(runner, view, request, true);
|
|
1279
1053
|
},
|
|
@@ -1287,10 +1061,7 @@ function previewGeneration(runner) {
|
|
|
1287
1061
|
return runner.run(liveGenerationPage, void 0);
|
|
1288
1062
|
}
|
|
1289
1063
|
export {
|
|
1290
|
-
componentsTool,
|
|
1291
1064
|
fingerprint,
|
|
1292
1065
|
prepareLiveInspection,
|
|
1293
|
-
previewGeneration
|
|
1294
|
-
timelineTool,
|
|
1295
|
-
toolContent
|
|
1066
|
+
previewGeneration
|
|
1296
1067
|
};
|