@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/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(element2, selector) {
15
- const owner = element2.parentElement ?? element2;
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 = element2.parentElement ? [...element2.parentElement.children] : [element2];
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(element2) {
29
- if (paths.has(element2)) return paths.get(element2);
285
+ function path(element) {
286
+ if (paths.has(element)) return paths.get(element);
30
287
  const candidates = [];
31
- if (element2.id) candidates.push(`#${CSS.escape(element2.id)}`);
32
- const hfId = element2.getAttribute("data-hf-id");
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 = element2.parentElement;
36
- const tag = element2.localName;
37
- const position2 = parent ? [...parent.children].indexOf(element2) + 1 : 1;
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(element2, selector);
296
+ paths.set(element, selector);
40
297
  return selector;
41
298
  }
42
- function visit(element2, parent) {
43
- if (excluded2.has(element2.tagName)) return;
44
- const selector = path(element2);
45
- if (document.querySelectorAll(selector).length !== 1 || document.querySelector(selector) !== element2) {
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 = [...element2.classList].sort();
49
- const tag = element2.localName;
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 = element2.parentElement ? [...element2.parentElement.children] : [element2];
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: element2.id,
59
- hfId: element2.getAttribute("data-hf-id"),
315
+ id: element.id,
316
+ hfId: element.getAttribute("data-hf-id"),
60
317
  classes,
61
- composition: element2.getAttribute("data-composition-id"),
62
- compositionSrc: element2.getAttribute("data-composition-src"),
63
- text: [...element2.childNodes].filter((child) => child.nodeType === 3).map((child) => child.textContent).join("").replace(/\s+/g, " ").trim(),
64
- media: ["img", "video", "audio", "source", "iframe"].includes(tag) ? element2.getAttribute("src") : null,
65
- relative: element2.id && document.querySelectorAll(`#${CSS.escape(element2.id)}`).length === 1 ? `#${CSS.escape(element2.id)}` : siblingCount(element2, simple) === 1 ? simple : `${CSS.escape(tag)}:nth-child(${siblings.indexOf(element2) + 1})`,
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(element2, simple),
325
+ siblingMatchCount: siblingCount(element, simple),
68
326
  children: []
69
327
  };
70
328
  nodes.push(node);
71
- for (const child of element2.children) {
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 describe(node, selector, count) {
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 group of groups) {
136
- const members = group.indices.map((index) => nodes[index]);
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
- lines.push(`${" ".repeat(depth)}${describe(node, selector, members.length)}`);
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, offset2) {
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] <= offset2) lo = mid;
544
+ if (lines[mid] <= offset) lo = mid;
270
545
  else hi = mid - 1;
271
546
  }
272
- return { line: lo + 1, column: offset2 - lines[lo] };
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
- for (const m of text.matchAll(/<([a-z][\w-]*)\b[^>]*>/gi)) {
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, offset2) {
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: offset2 + lead,
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, offset2) {
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: offset2 + start,
446
- end: offset2 + end + 1
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, target2) {
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: [target2],
761
+ targets: [target],
468
762
  code,
469
763
  uniqueInFile: occurrences(source.text, code).length === 1
470
764
  };
471
765
  }
472
- markup(element2, target2) {
473
- const tags = this.htmlTags.filter(
474
- (tag) => tag.tag === element2.tag && (element2.hfId ? tag.hfId === element2.hfId : element2.id ? tag.id === element2.id : element2.classes.length > 0 && element2.classes.every((c) => tag.classes.includes(c)))
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", target2) : null;
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, offset2) => {
496
- const found = (ranges.get(text) ?? []).filter(({ chunk, at: at2 }) => offset2 >= at2 && offset2 < at2 + chunk.text.length).map(({ chunk, at: at2 }) => ({ chunk, local: offset2 - 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, target2) => {
799
+ return (stack, target) => {
504
800
  const cached = cache.get(stack);
505
- if (cached) return cached.map((snippet2) => ({ ...snippet2, targets: [target2] }));
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 offset2 = m ? htmlLines[Number(m[1]) - 1] + Number(m[2]) - 1 : -1;
512
- let hit = m ? locate(html, offset2) : null;
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] : offset2 < map.at || offset2 >= map.at + map.p.output.length)
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, offset2 - map.at);
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 snippet2 = this.slice(
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
- target2
830
+ target
535
831
  );
536
- if (snippet2) result.push(snippet2);
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
- target2
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, element2, target2) {
554
- return sources.markup(element2, target2);
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 snippet2 of sorted) {
855
+ for (const snippet of sorted) {
560
856
  const last = output.at(-1);
561
- if (last && last.file === snippet2.file && snippet2.startLine <= last.endLine + 1) {
562
- last.endLine = Math.max(last.endLine, snippet2.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, ...snippet2.targets])];
570
- last.kind = [.../* @__PURE__ */ new Set([...last.kind, ...snippet2.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({ ...snippet2 });
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: generation2, sourceVersion, reused, start, visualState } = context;
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((snippet2) => ({ ...snippet2, kind: ["timeline attachment"] }))
892
+ ...map(stack, index).map((snippet) => ({ ...snippet, kind: ["timeline attachment"] }))
597
893
  );
598
894
  }
599
895
  for (const el of result.elements) {
600
- const snippet2 = markupSnippet(sources, el, index);
601
- if (snippet2) collected.push(snippet2);
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 snippet2 = sources.slice(
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 (snippet2) collected.push(snippet2);
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.slice(0, 5),
620
- candidatesOmitted: Math.max(0, result.candidates.length - 5),
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((element2) => !result.elements.some((own) => own.ref === element2.ref)).map((element2) => [element2.ref, element2])
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 snippet2 of merged) catalog.set(snippet2.id, snippet2);
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 snippet2 = catalog.get(id);
637
- if (!snippet2) throw new Error(`unknown_snippet: ${id}`);
638
- return snippet2;
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: generation2,
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 offset2 = request.offset ?? 0;
657
- if (!Number.isInteger(offset2) || offset2 < 0)
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
- for (let i = offset2; i < selected.length; i++) {
660
- const snippet2 = selected[i];
661
- response.snippets.push(snippet2);
662
- if (Buffer.byteLength(JSON.stringify(response)) > maxBytes - 700) {
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.nextOffset = i;
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
- while (Buffer.byteLength(JSON.stringify(response)) > maxBytes && response.results.some((r) => r.candidates.length)) {
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 generation2 = inspected.__hfLiveInspection?.generation ?? null;
698
- if (request.boundary === void 0) return { ...result, following, generation: generation2 };
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 target2 of crossing.targets) {
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 === target2.ref) && event.properties.some((p) => crossing.properties.includes(p))
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: generation2 };
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 offset2 = request.offset ?? 0;
725
- const allGroups = groupEvents(raw.events);
726
- const groups = allGroups.slice(offset2, offset2 + GROUPS_PER_PAGE);
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 = offset2 + groups.length < allGroups.length ? offset2 + groups.length : null;
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((c) => `${c.ref} #${c.id} ${c.tag}`) : [],
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(events),
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
- var located = (sources) => sources.map((s) => `${s.file}:${s.startLine}`).join(", ");
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
- return `${value.value}${value.kind === "literal" ? "" : ` (${value.kind})`}`;
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 = component?.id ? `#${component.id}` : component?.composition ?? "unassigned";
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 target2 of targets) {
838
- const name = target2.id ? `#${target2.id}` : target2.hfId ?? (target2.classes.length ? `.${target2.classes.join(".")}` : target2.tag);
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(events) {
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 map = formatElementMap(snapshot, { file });
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 target2 of request.targets) {
949
- if (!target2 || typeof target2 !== "object") throw new Error("Invalid target");
950
- const keys = ["selector", "hfId", "ref"].filter((key) => key in target2);
951
- if (keys.length !== 1 || Object.values(target2).some(
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 offset2 = request.offset ?? 0;
986
- if (!Number.isInteger(offset2) || offset2 < 0)
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 (offset2 && !request.generation) throw new Error("pagination requires generation");
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 generation2 = randomUUID();
1228
- const stamp = `window.__hfLiveInspection=${JSON.stringify({ generation: generation2, project })};`;
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: generation2,
1348
+ generation,
1237
1349
  sourceVersion,
1238
- pageGeneration: generation2,
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) !== generation2)
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: generation2,
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, generation2);
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, generation2);
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
  };