browser-debugger-cli 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +148 -74
  3. package/dist/commands/css.d.ts +13 -0
  4. package/dist/commands/css.js +53 -0
  5. package/dist/commands/dom/audit.d.ts +14 -0
  6. package/dist/commands/dom/audit.js +87 -0
  7. package/dist/commands/dom/formInteraction.js +36 -6
  8. package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
  9. package/dist/commands/dom/helpers/keyAttributes.js +6 -4
  10. package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
  11. package/dist/commands/dom/helpers/screenshot.js +158 -38
  12. package/dist/commands/dom/index.js +4 -1
  13. package/dist/commands/dom/screenshot.js +10 -6
  14. package/dist/commands/dom/wait.js +5 -3
  15. package/dist/commands/helpJson.js +1 -1
  16. package/dist/commands/installSkill.d.ts +20 -0
  17. package/dist/commands/installSkill.js +87 -0
  18. package/dist/commands/optionBehaviors.js +21 -6
  19. package/dist/commands/page.js +7 -4
  20. package/dist/commands/peek.d.ts +7 -0
  21. package/dist/commands/peek.js +65 -23
  22. package/dist/commands/shared/optionTypes.d.ts +5 -1
  23. package/dist/commands/start.d.ts +13 -0
  24. package/dist/commands/start.js +19 -2
  25. package/dist/commands/tail.d.ts +7 -1
  26. package/dist/commands/tail.js +13 -62
  27. package/dist/commands.js +5 -0
  28. package/dist/daemon/session/commandRegistry.js +7 -1
  29. package/dist/daemon/session/plugins.js +4 -52
  30. package/dist/daemon.js +7986 -6848
  31. package/dist/errors/messages.d.ts +50 -4
  32. package/dist/errors/messages.js +94 -5
  33. package/dist/index.js +709 -190
  34. package/dist/ipc/client.d.ts +4 -0
  35. package/dist/ipc/client.js +8 -0
  36. package/dist/ipc/protocol/auditTypes.d.ts +129 -0
  37. package/dist/ipc/protocol/auditTypes.js +6 -0
  38. package/dist/ipc/protocol/commands.d.ts +23 -0
  39. package/dist/ipc/protocol/commands.js +2 -0
  40. package/dist/ipc/protocol/domTypes.d.ts +4 -0
  41. package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
  42. package/dist/runtime/css/search.d.ts +39 -0
  43. package/dist/runtime/css/search.js +122 -0
  44. package/dist/runtime/dom/actionEffects.d.ts +4 -1
  45. package/dist/runtime/dom/actionEffects.js +8 -4
  46. package/dist/runtime/dom/audit.d.ts +19 -0
  47. package/dist/runtime/dom/audit.js +36 -0
  48. package/dist/runtime/dom/auditModel.d.ts +45 -0
  49. package/dist/runtime/dom/auditModel.js +215 -0
  50. package/dist/runtime/dom/auditScripts.d.ts +107 -0
  51. package/dist/runtime/dom/auditScripts.js +112 -0
  52. package/dist/runtime/dom/elementGeometry.d.ts +8 -2
  53. package/dist/runtime/dom/elementGeometry.js +24 -8
  54. package/dist/runtime/dom/elementInfo.d.ts +3 -2
  55. package/dist/runtime/dom/elementInfo.js +8 -2
  56. package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
  57. package/dist/runtime/dom/inspect.d.ts +7 -0
  58. package/dist/runtime/dom/inspect.js +88 -23
  59. package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
  60. package/dist/runtime/dom/inspectAllStyles.js +89 -7
  61. package/dist/runtime/dom/inspectCascade.d.ts +19 -2
  62. package/dist/runtime/dom/inspectCascade.js +214 -44
  63. package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
  64. package/dist/runtime/dom/inspectCascadeModel.js +108 -34
  65. package/dist/runtime/dom/inspectHints.d.ts +26 -3
  66. package/dist/runtime/dom/inspectHints.js +125 -9
  67. package/dist/runtime/dom/inspectModel.d.ts +3 -0
  68. package/dist/runtime/dom/inspectModel.js +30 -7
  69. package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
  70. package/dist/runtime/dom/inspectPaintModel.js +180 -68
  71. package/dist/runtime/dom/inspectRules.d.ts +19 -0
  72. package/dist/runtime/dom/inspectRules.js +21 -5
  73. package/dist/runtime/dom/inspectScripts.d.ts +85 -12
  74. package/dist/runtime/dom/inspectScripts.js +314 -28
  75. package/dist/runtime/dom/inspectTree.js +10 -2
  76. package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
  77. package/dist/runtime/dom/inspectWhyModel.js +52 -10
  78. package/dist/runtime/dom/layout.js +31 -9
  79. package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
  80. package/dist/runtime/dom/reactEventHelpers.js +27 -9
  81. package/dist/runtime/page/emulation.d.ts +13 -4
  82. package/dist/runtime/page/emulation.js +69 -4
  83. package/dist/runtime/page/userAgent.d.ts +17 -0
  84. package/dist/runtime/page/userAgent.js +57 -0
  85. package/dist/types.d.ts +12 -0
  86. package/dist/ui/formatters/audit.d.ts +19 -0
  87. package/dist/ui/formatters/audit.js +106 -0
  88. package/dist/ui/formatters/dom.d.ts +1 -1
  89. package/dist/ui/formatters/dom.js +6 -3
  90. package/dist/ui/formatters/inspect.js +42 -15
  91. package/dist/ui/formatters/installSkill.d.ts +11 -0
  92. package/dist/ui/formatters/installSkill.js +31 -0
  93. package/dist/ui/formatters/status.js +1 -1
  94. package/dist/ui/messages/commands.d.ts +44 -7
  95. package/dist/ui/messages/commands.js +83 -11
  96. package/dist/ui/messages/preview.d.ts +6 -0
  97. package/dist/ui/messages/preview.js +9 -1
  98. package/dist/utils/cssValues.js +36 -4
  99. package/dist/utils/decisionTrees.js +0 -5
  100. package/dist/utils/suggestions.d.ts +4 -2
  101. package/dist/utils/suggestions.js +7 -5
  102. package/dist/utils/taskMappings.js +1 -1
  103. package/package.json +4 -2
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { containerKind } from '../../runtime/dom/inspectLayoutModel.js';
18
18
  import { joinLines } from '../formatting.js';
19
- import { inspectCascadeNote, inspectAnimatingBadge, inspectMidTransitionNote, inspectDarkThemeBadge, inspectedMatchAction, inspectVisibilityBadges, multipleMatchesWarning, } from '../messages/commands.js';
19
+ import { inspectCascadeNote, inspectAnimatingBadge, inspectMidTransitionNote, inspectDarkThemeBadge, inspectPseudoOfNote, inspectedMatchAction, inspectVisibilityBadges, multipleMatchesWarning, } from '../messages/commands.js';
20
20
  import { shadowText, sidesShorthand } from '../../utils/cssValues.js';
21
21
  import { truncateByLength } from '../../utils/strings.js';
22
22
  /** Longest parent label on the parent line (CSS-module class names run long) */
@@ -50,11 +50,12 @@ export function inspectHeader(data) {
50
50
  data.element,
51
51
  data.content !== undefined && `"${data.content}"`,
52
52
  data.placeholder !== undefined && `placeholder "${data.placeholder}"`,
53
- data.rect && `${data.rect.w}x${data.rect.h} @${data.rect.x},${data.rect.y}`,
53
+ data.rect &&
54
+ `${data.rect.w}x${data.rect.h}${data.rect.screen ? ` (${data.rect.screen.w}x${data.rect.screen.h} on screen)` : ''} @${data.rect.x},${data.rect.y}${data.rect.in ? ' (fixed: viewport position)' : ''}`,
54
55
  kind && `[${kind}]`,
55
56
  ...inspectVisibilityBadges(data.visibility),
56
57
  data.context && `in ${data.context}`,
57
- data.theme === 'dark' && inspectDarkThemeBadge(),
58
+ data.theme === 'dark' && inspectDarkThemeBadge(data.themeFrom === 'emulation'),
58
59
  data.animating && inspectAnimatingBadge(data.animating),
59
60
  ]
60
61
  .filter(Boolean)
@@ -177,9 +178,9 @@ function parentLine(layout) {
177
178
  ]);
178
179
  }
179
180
  /**
180
- * Font, weight and size as one phrase, e.g. `Inter (webfont) 600 italic 16/24`
181
- * or `Inter (rendered "Helvetica") 400 14/normal` when the text was drawn in
182
- * another font (a fallback, or the face's own name); for a container only
181
+ * Font, weight and size as one phrase, e.g. `Inter (webfont) 600 italic 16/24`,
182
+ * `Inter (rendered "Helvetica") 400 14/normal` when the text was drawn in a
183
+ * fallback, or `sans-serif (resolves to "Helvetica")`; for a container only
183
184
  * the fields it has.
184
185
  *
185
186
  * @param text - Text group
@@ -188,6 +189,7 @@ function parentLine(layout) {
188
189
  function fontParts(text) {
189
190
  const loaded = [
190
191
  text.rendered && `rendered "${text.rendered}"`,
192
+ text.resolved && `resolves to "${text.resolved}"`,
191
193
  text.webfont && 'webfont loaded',
192
194
  ].filter(Boolean);
193
195
  const family = text.family && [text.family, loaded.length > 0 && `(${loaded.join(', ')})`];
@@ -218,10 +220,13 @@ function contrastText(contrast) {
218
220
  if (!contrast)
219
221
  return undefined;
220
222
  return [
221
- `contrast ${contrast.ratio} ${contrast.level}`,
222
- contrast.inherited && `on ${contrast.background}`,
223
+ contrast.approximate
224
+ ? `contrast ≈${contrast.ratio} ${contrast.level}`
225
+ : `contrast ${contrast.ratio} ${contrast.level}`,
226
+ `on ${contrast.background}`,
223
227
  contrast.overImage && '(over image)',
224
228
  contrast.opacity !== undefined && `(faded: opacity ${contrast.opacity})`,
229
+ contrast.approximate && `(approximate: ${contrast.approximate.join(', ')})`,
225
230
  ]
226
231
  .filter(Boolean)
227
232
  .join(' ');
@@ -234,6 +239,7 @@ function contrastText(contrast) {
234
239
  */
235
240
  function textLine(text) {
236
241
  return groupLine('text', [
242
+ text.holder && `in ${text.holder}`,
237
243
  ...fontParts(text),
238
244
  text.color && `color ${text.color}`,
239
245
  contrastText(text.contrast),
@@ -244,6 +250,8 @@ function textLine(text) {
244
250
  text.whiteSpace && `ws ${text.whiteSpace}`,
245
251
  text.overflow && `text-overflow ${text.overflow}`,
246
252
  text.clamp && `clamp ${text.clamp}`,
253
+ text.truncated && 'truncated',
254
+ text.gradientFill && 'filled by its background (background-clip: text; no contrast)',
247
255
  text.shadow && `shadow ${text.shadow}`,
248
256
  text.features && `features ${text.features}`,
249
257
  ]);
@@ -257,9 +265,13 @@ function textLine(text) {
257
265
  function fillText(fill) {
258
266
  if (fill.type === 'solid')
259
267
  return `bg ${fill.color}`;
260
- if (fill.type === 'gradient')
261
- return `bg-image ${fill.value}`;
262
- return `bg-image ${fill.value}${fill.size ? ` size ${fill.size}` : ''}`;
268
+ return [
269
+ `bg-image ${fill.value}`,
270
+ fill.size && `size ${fill.size}`,
271
+ fill.position && `at ${fill.position}`,
272
+ ]
273
+ .filter(Boolean)
274
+ .join(' ');
263
275
  }
264
276
  /**
265
277
  * The fill line: backgrounds, opacity and blend mode.
@@ -268,7 +280,11 @@ function fillText(fill) {
268
280
  * @returns Line
269
281
  */
270
282
  function fillLine(data) {
283
+ const paint = data.paint;
271
284
  return groupLine('fill', [
285
+ paint && `fill ${paint.fill}`,
286
+ paint &&
287
+ `stroke ${paint.stroke}${paint.strokeWidth !== undefined ? ` ${paint.strokeWidth}` : ''}`,
272
288
  ...(data.fills ?? []).map(fillText),
273
289
  data.opacity !== undefined && `opacity ${data.opacity}`,
274
290
  data.blend && `blend ${data.blend}`,
@@ -337,6 +353,7 @@ function pseudoText(pseudo) {
337
353
  pseudo.content !== undefined && `content ${pseudo.content}`,
338
354
  pseudo.display,
339
355
  pseudo.position,
356
+ pseudo.inset && `inset ${pseudo.inset}`,
340
357
  pseudo.size && `${pseudo.size.w}x${pseudo.size.h}`,
341
358
  pseudo.color && `color ${pseudo.color}`,
342
359
  pseudo.fontStyle,
@@ -370,7 +387,9 @@ function treeRow(node) {
370
387
  return [
371
388
  node.element,
372
389
  node.count !== undefined && `×${node.count}`,
373
- `${node.w}x${node.h}`,
390
+ node.contents ? '(contents)' : `${node.w}x${node.h}`,
391
+ node.shadow && '(shadow root)',
392
+ node.via && `via ${node.via}`,
374
393
  node.layout && `[${node.layout}]`,
375
394
  node.text && `"${node.text}"`,
376
395
  node.childCount !== undefined && `(${node.childCount})`,
@@ -422,7 +441,7 @@ function ruleScope(entry) {
422
441
  * @returns Lines
423
442
  */
424
443
  function cascadeBlock(data) {
425
- const hints = (data.hints ?? []).map((hint) => `${hint.property}: ${hint.value} ${hint.kind === 'not-inherited' ? "is the browser's" : 'has no effect'}: ${hint.reason} → ${hint.fix} · in ${hint.source}`);
444
+ const hints = (data.hints ?? []).map((hint) => `${hint.property}: ${hint.value} ${hint.kind === 'not-inherited' ? "is the browser's" : hint.only ? `has no effect on ${hint.only.join(', ')}` : 'has no effect'}: ${hint.reason} → ${hint.fix} · in ${hint.source}`);
426
445
  return [
427
446
  ...labelledLines('hints', data.hints?.length === 0 ? ['none'] : hints),
428
447
  ...labelledLines('rules', (data.rules ?? []).map(ruleLine)),
@@ -472,14 +491,19 @@ function whyLines(why, data) {
472
491
  entry.important ? ' !important' : '',
473
492
  ` ${entry.source}${entry.specificity ? ` [${entry.specificity.join(',')}]` : ''}${ruleScope(entry)}`,
474
493
  entry.status === 'inherited' ? ' (inherited)' : '',
494
+ entry.note ? ` (${entry.note})` : '',
475
495
  ].join('');
476
496
  });
477
497
  const variables = (why.variables ?? []).map((variable) => ` ${variable.name}: ${truncateByLength(variable.value, CASCADE_VALUE_WIDTH)} ${variable.source}${variable.inherited !== undefined ? ` (inherited from ${levelsUp(variable.inherited)})` : ''}`);
478
498
  return [
479
499
  `${why.property} = ${why.computed}${midTransition(why.property, data.animating) ? ` ${inspectMidTransitionNote()}` : ''}`,
480
500
  ...entries.slice(0, 1),
501
+ ...(why.chain[0]?.rule
502
+ ? [` in ${truncateByLength(why.chain[0].rule, RULE_LINE_WIDTH)}`]
503
+ : []),
481
504
  ...variables,
482
505
  ...entries.slice(1),
506
+ ...(why.inactive ?? []).map((rule) => ` - ${truncateByLength(rule.value, CASCADE_VALUE_WIDTH)} ${rule.selector} ${rule.condition} (does not apply now)`),
483
507
  ...(why.chain.length === 0 ? [' no author declaration: the default or inherited value'] : []),
484
508
  ];
485
509
  }
@@ -505,6 +529,8 @@ function midTransition(property, animating) {
505
529
  function levelsUp(levels) {
506
530
  return levels === 1 ? 'the parent' : `${levels} levels up`;
507
531
  }
532
+ /** Longest rule text shown under the winner in `--why` (JSON has up to 300 characters) */
533
+ const RULE_LINE_WIDTH = 120;
508
534
  /** Longest declared value shown in `--rules` and `--why` lines (font stacks run long) */
509
535
  const CASCADE_VALUE_WIDTH = 60;
510
536
  /**
@@ -589,8 +615,9 @@ export function formatInspect(data) {
589
615
  const note = data.picked
590
616
  ? multipleMatchesWarning(data.count, inspectedMatchAction(data.picked, data.index))
591
617
  : undefined;
618
+ const pseudoNote = data.pseudoOf && inspectPseudoOfNote(data.pseudoOf);
592
619
  if (data.why)
593
- return joinLines(inspectHeader(data), ...cascadeBlock(data), note);
594
- return joinLines(inspectHeader(data), ...body, ...cascadeBlock(data), ...treeBlock(data), note);
620
+ return joinLines(inspectHeader(data), ...cascadeBlock(data), note, pseudoNote);
621
+ return joinLines(inspectHeader(data), ...body, ...cascadeBlock(data), ...treeBlock(data), note, pseudoNote);
595
622
  }
596
623
  //# sourceMappingURL=inspect.js.map
@@ -0,0 +1,11 @@
1
+ import type { InstalledSkill } from '../../types.js';
2
+ /**
3
+ * Format where the skill was installed, one line per agent.
4
+ *
5
+ * @param data - Install results
6
+ * @returns Human-readable summary
7
+ */
8
+ export declare function formatInstalledSkills(data: {
9
+ skills: InstalledSkill[];
10
+ }): string;
11
+ //# sourceMappingURL=installSkill.d.ts.map
@@ -0,0 +1,31 @@
1
+ import { homedir } from 'os';
2
+ import { OutputFormatter } from '../formatting.js';
3
+ /**
4
+ * Shorten a path under the home directory to `~/...`.
5
+ *
6
+ * @param path - Absolute path
7
+ * @returns Path as the user would type it
8
+ */
9
+ function tildePath(path) {
10
+ const home = homedir();
11
+ return path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
12
+ }
13
+ /**
14
+ * Format where the skill was installed, one line per agent.
15
+ *
16
+ * @param data - Install results
17
+ * @returns Human-readable summary
18
+ */
19
+ export function formatInstalledSkills(data) {
20
+ const fmt = new OutputFormatter().text('bdg skill:');
21
+ for (const skill of data.skills) {
22
+ fmt.text(` ${skill.target.padEnd(6)} ${skill.status.padEnd(9)} ${tildePath(skill.path)}`);
23
+ }
24
+ return fmt
25
+ .hints('Next:', [
26
+ 'Start a new agent session to load it (running sessions keep the old list)',
27
+ 'After upgrading bdg, run bdg install-skill again',
28
+ ])
29
+ .build();
30
+ }
31
+ //# sourceMappingURL=installSkill.js.map
@@ -98,7 +98,7 @@ export function appearanceLines(metadata, pageState) {
98
98
  ? [
99
99
  [
100
100
  'Viewport',
101
- `${viewport.width}×${viewport.height}${metadata.viewport ? ` (emulated ${metadata.viewport.width}x${metadata.viewport.height})` : ''}`,
101
+ `${viewport.width}×${viewport.height}${metadata.viewport ? ` (emulated ${metadata.viewport.width}x${metadata.viewport.height}${metadata.viewport.mobile ? ', phone' : ''})` : ''}`,
102
102
  ],
103
103
  ]
104
104
  : []),
@@ -94,6 +94,17 @@ export declare function shownElementText(element: ShownElement): string;
94
94
  * "URL changed to https://example.com/#/active (same document)"
95
95
  */
96
96
  export declare function pageNavigationText(navigation: PageNavigation): string;
97
+ /**
98
+ * Last `New text:` row when an action made more messages appear than are listed.
99
+ *
100
+ * @param count - Messages not listed
101
+ * @returns e.g. `(+4 more)`
102
+ */
103
+ export declare function moreMessagesText(count: number): string;
104
+ /** Result line of `bdg dom hover --off` */
105
+ export declare const HOVER_OFF_DONE = "\u2713 Mouse moved off the page (hover styles and menus that close on mouseleave are gone)";
106
+ /** How `bdg dom hover` is called */
107
+ export declare const HOVER_USAGE = "bdg dom hover <selector|index>, or bdg dom hover --off to move the mouse away";
97
108
  /**
98
109
  * A message an action made appear, for its `New text:` rows.
99
110
  *
@@ -252,7 +263,7 @@ export declare function layoutHeadline(count: number, listed: number, selector:
252
263
  */
253
264
  export declare function indexLayoutHeadline(target: string): string;
254
265
  /** Help text explaining `bdg dom inspect`'s output notation */
255
- export declare const INSPECT_OUTPUT_LEGEND = "\nOutput notation:\n WxH @x,y rendered border box size and page position (CSS px, no unit)\n m / p / b margin / padding / border widths, 1-4 values in CSS order (top right bottom left)\n in-parent distances to the parent's content edges (l t r b); sib: gaps to the sibling on each side\n scroll WxH the content (pseudo-elements too) is larger than the box\n 16/24 font size / line height; 'webfont loaded' = drawn with a downloaded font;\n (rendered \"X\") = drawn with another font than declared (a fallback)\n contrast 4.47 WCAG ratio, rounded down, against the background behind the text\n (+N not rendered) children with display: none (or not in the layout)\n hints declarations on this element that have no effect, why, the fix and where they are\n ('none': checked, nothing found)\n \u2190 sel (file:N) --rules: the declaration that sets the value (file:line, or file:line:column in\n minified files); 'over X': rules it beats; '= v': the value of a var() expression\n \u2713 / \u2717 --why: the winning declaration / ones it beats, highest precedence first;\n [0,2,0]: selector specificity (ids, classes, types); indented --name lines: where\n the winner's custom properties are set\nSessions follow the system color scheme; start with --color-scheme light|dark to choose.";
266
+ export declare const INSPECT_OUTPUT_LEGEND = "\nOutput notation:\n WxH @x,y rendered border box size and page position (CSS px, no unit)\n m / p / b margin / padding / border widths, 1-4 values in CSS order (top right bottom left)\n in-parent distances to the parent's content edges (l t r b); sib: gaps to the sibling on each side\n scroll WxH the content (pseudo-elements too) is larger than the box\n 16/24 font size / line height; 'webfont loaded' = drawn with a downloaded font;\n (rendered \"X\") = drawn with another font than declared (a fallback);\n (resolves to \"X\") = the font a generic family (sans-serif, system-ui) became\n contrast 4.47 WCAG ratio, rounded down, against the background behind the text\n text in X the text is drawn by descendant X (the one with most of it): its font, color, contrast\n truncated the text is cut off (overflow clip, ellipsis or line clamp); a \"\u2026\" in the header is\n only bdg shortening the text\n .a.b(+3) the first two classes and how many more the element has\n sizing content-box padding and border add to the CSS size (shown only then; border-box is not)\n (+N not rendered) children with display: none (or not in the layout)\n hints declarations on this element that have no effect, why, the fix and where they are\n ('none': checked, nothing found)\n \u2190 sel (file:N) --rules: the declaration that sets the value (file:line, or file:line:column in\n minified files); 'over X': rules it beats; '= v': the value of a var() expression\n \u2713 / \u2717 --why: the winning declaration / ones it beats, highest precedence first;\n [0,2,0]: selector specificity (ids, classes, types); indented --name lines: where\n the winner's custom properties are set\nSessions follow the system color scheme; start with --color-scheme light|dark to choose.";
256
267
  /**
257
268
  * What covers an element: a cover that paints nothing at that point (a
258
269
  * transparent box over it) does not hide it, but takes its clicks.
@@ -270,14 +281,24 @@ export declare function coverText(cover: string, transparent: boolean | undefine
270
281
  * @returns Note
271
282
  */
272
283
  export declare function inspectCascadeNote(reason: 'timeout' | 'failed'): string;
284
+ /**
285
+ * Note of `bdg dom inspect` when the selector named a pseudo-element: its
286
+ * element is inspected and the pseudo-element is on the `pseudo` line.
287
+ *
288
+ * @param pseudo - `::before` or `::after`
289
+ * @returns Note
290
+ */
291
+ export declare function inspectPseudoOfNote(pseudo: string): string;
273
292
  /**
274
293
  * Header badge of `bdg dom inspect` when the page is shown in its dark theme
275
- * because the session follows the system's dark preference: the colors are
276
- * the dark theme's, not what a light-mode visitor sees.
294
+ * because the session follows the system's dark preference, or because
295
+ * `page emulate` asked for dark: the colors are the dark theme's, not what a
296
+ * light-mode visitor sees.
277
297
  *
298
+ * @param emulated - The dark preference comes from `page emulate --color-scheme dark`
278
299
  * @returns Badge
279
300
  */
280
- export declare function inspectDarkThemeBadge(): string;
301
+ export declare function inspectDarkThemeBadge(emulated?: boolean): string;
281
302
  /**
282
303
  * Header badges of `bdg dom inspect` for what keeps an element from being seen.
283
304
  *
@@ -538,13 +559,24 @@ export declare function elementTextLine(text: string): string;
538
559
  * @returns e.g. `No text; holds 1 element: iframe (see its HTML with --raw)`
539
560
  */
540
561
  export declare function emptyElementLine(children: string[], count: number): string;
562
+ /**
563
+ * Note on a screenshot scaled down to keep its image token cost bounded.
564
+ *
565
+ * @param originalWidth - Captured width (CSS px)
566
+ * @param originalHeight - Captured height
567
+ * @param width - Image width
568
+ * @param height - Image height
569
+ * @returns e.g. `scaled from 1920×993 to 1568×811; --no-resize for full size`
570
+ */
571
+ export declare function screenshotScaledNote(originalWidth: number, originalHeight: number, width: number, height: number): string;
541
572
  /**
542
573
  * Note on an element screenshot that captured more than the element's border
543
- * box, because content (floats, positioned children) overflows it.
574
+ * box, because content (floats, positioned children, text, shadows) overflows it.
544
575
  *
545
576
  * @param box - Border box
546
577
  * @param captured - Area captured
547
- * @returns e.g. `grown from 940×37 to 940×285 to include content overflowing the element`
578
+ * @param padding - `--padding` (px), which is not the element's own
579
+ * @returns e.g. `grown from 940×37 to 940×285 to include what it paints outside its box (…)`
548
580
  */
549
581
  export declare function screenshotGrownNote(box: {
550
582
  width: number;
@@ -552,7 +584,7 @@ export declare function screenshotGrownNote(box: {
552
584
  }, captured: {
553
585
  width: number;
554
586
  height: number;
555
- }): string;
587
+ }, padding?: number): string;
556
588
  /**
557
589
  * Next commands after `bdg dom query`, by index so they reach matches in
558
590
  * shadow roots and iframes too.
@@ -655,6 +687,7 @@ export declare function pageEmulationLines(result: {
655
687
  viewport?: {
656
688
  width: number;
657
689
  height: number;
690
+ mobile?: boolean;
658
691
  };
659
692
  colorScheme?: string;
660
693
  };
@@ -678,5 +711,9 @@ export declare function inspectAnimatingBadge(animating: readonly string[]): str
678
711
  * @returns Note
679
712
  */
680
713
  export declare function inspectMidTransitionNote(): string;
714
+ /** Examples under `bdg dom audit --help` */
715
+ export declare const AUDIT_HELP_EXAMPLES = "\nExamples:\n bdg dom audit All checks\n bdg dom audit contrast --level AAA Text below WCAG AAA, weakest first\n bdg dom audit overflow What scrolls sideways, cut-off text, scaled images\n bdg dom audit layers animations Fixed/sticky elements and running animations\n\nFollow up on a finding with bdg dom inspect <element> (e.g. --why color).";
716
+ /** Examples under `bdg css search --help` */
717
+ export declare const CSS_SEARCH_HELP_EXAMPLES = "\nExamples:\n bdg css search -- --brand Where a custom property is set and used (-- before a text\n that starts with -; options go before it:\n bdg css search --limit 50 -- --brand)\n bdg css search \"oklch(\" Rules that use oklch colors\n bdg css search \".btn-primary\" Rules of a class, in every stylesheet";
681
718
  export {};
682
719
  //# sourceMappingURL=commands.d.ts.map
@@ -136,6 +136,19 @@ export function pageNavigationText(navigation) {
136
136
  const status = navigation.status === undefined ? '' : ` (${navigation.status})`;
137
137
  return `navigated to ${navigation.url}${status}`;
138
138
  }
139
+ /**
140
+ * Last `New text:` row when an action made more messages appear than are listed.
141
+ *
142
+ * @param count - Messages not listed
143
+ * @returns e.g. `(+4 more)`
144
+ */
145
+ export function moreMessagesText(count) {
146
+ return `(+${count} more)`;
147
+ }
148
+ /** Result line of `bdg dom hover --off` */
149
+ export const HOVER_OFF_DONE = '✓ Mouse moved off the page (hover styles and menus that close on mouseleave are gone)';
150
+ /** How `bdg dom hover` is called */
151
+ export const HOVER_USAGE = 'bdg dom hover <selector|index>, or bdg dom hover --off to move the mouse away';
139
152
  /**
140
153
  * A message an action made appear, for its `New text:` rows.
141
154
  *
@@ -432,8 +445,14 @@ Output notation:
432
445
  in-parent distances to the parent's content edges (l t r b); sib: gaps to the sibling on each side
433
446
  scroll WxH the content (pseudo-elements too) is larger than the box
434
447
  16/24 font size / line height; 'webfont loaded' = drawn with a downloaded font;
435
- (rendered "X") = drawn with another font than declared (a fallback)
448
+ (rendered "X") = drawn with another font than declared (a fallback);
449
+ (resolves to "X") = the font a generic family (sans-serif, system-ui) became
436
450
  contrast 4.47 WCAG ratio, rounded down, against the background behind the text
451
+ text in X the text is drawn by descendant X (the one with most of it): its font, color, contrast
452
+ truncated the text is cut off (overflow clip, ellipsis or line clamp); a "…" in the header is
453
+ only bdg shortening the text
454
+ .a.b(+3) the first two classes and how many more the element has
455
+ sizing content-box padding and border add to the CSS size (shown only then; border-box is not)
437
456
  (+N not rendered) children with display: none (or not in the layout)
438
457
  hints declarations on this element that have no effect, why, the fix and where they are
439
458
  ('none': checked, nothing found)
@@ -466,15 +485,29 @@ export function inspectCascadeNote(reason) {
466
485
  ? "CSS rules not read: the page's stylesheets took too long (hints wait 1 s; --rules and --why 5 s)"
467
486
  : 'CSS rules not read: Chrome could not report the rules matching this element';
468
487
  }
488
+ /**
489
+ * Note of `bdg dom inspect` when the selector named a pseudo-element: its
490
+ * element is inspected and the pseudo-element is on the `pseudo` line.
491
+ *
492
+ * @param pseudo - `::before` or `::after`
493
+ * @returns Note
494
+ */
495
+ export function inspectPseudoOfNote(pseudo) {
496
+ return `Inspected the element of ${pseudo}: pseudo-elements cannot be selected; ${pseudo} is on the pseudo line (content, size, position, inset, colors)`;
497
+ }
469
498
  /**
470
499
  * Header badge of `bdg dom inspect` when the page is shown in its dark theme
471
- * because the session follows the system's dark preference: the colors are
472
- * the dark theme's, not what a light-mode visitor sees.
500
+ * because the session follows the system's dark preference, or because
501
+ * `page emulate` asked for dark: the colors are the dark theme's, not what a
502
+ * light-mode visitor sees.
473
503
  *
504
+ * @param emulated - The dark preference comes from `page emulate --color-scheme dark`
474
505
  * @returns Badge
475
506
  */
476
- export function inspectDarkThemeBadge() {
477
- return '[dark theme from system; --color-scheme light for light]';
507
+ export function inspectDarkThemeBadge(emulated = false) {
508
+ return emulated
509
+ ? '[dark theme, emulated; bdg page emulate --color-scheme light for light]'
510
+ : '[dark theme from system; --color-scheme light for light]';
478
511
  }
479
512
  /**
480
513
  * Header badges of `bdg dom inspect` for what keeps an element from being seen.
@@ -887,16 +920,35 @@ export function emptyElementLine(children, count) {
887
920
  const more = count > children.length ? `, … ${count - children.length} more` : '';
888
921
  return `No text; holds ${pluralize(count, 'element')}: ${children.join(', ')}${more} (see its HTML with --raw)`;
889
922
  }
923
+ /**
924
+ * Note on a screenshot scaled down to keep its image token cost bounded.
925
+ *
926
+ * @param originalWidth - Captured width (CSS px)
927
+ * @param originalHeight - Captured height
928
+ * @param width - Image width
929
+ * @param height - Image height
930
+ * @returns e.g. `scaled from 1920×993 to 1568×811; --no-resize for full size`
931
+ */
932
+ export function screenshotScaledNote(originalWidth, originalHeight, width, height) {
933
+ return `scaled from ${originalWidth}×${originalHeight} to ${width}×${height}; --no-resize for full size`;
934
+ }
890
935
  /**
891
936
  * Note on an element screenshot that captured more than the element's border
892
- * box, because content (floats, positioned children) overflows it.
937
+ * box, because content (floats, positioned children, text, shadows) overflows it.
893
938
  *
894
939
  * @param box - Border box
895
940
  * @param captured - Area captured
896
- * @returns e.g. `grown from 940×37 to 940×285 to include content overflowing the element`
941
+ * @param padding - `--padding` (px), which is not the element's own
942
+ * @returns e.g. `grown from 940×37 to 940×285 to include what it paints outside its box (…)`
897
943
  */
898
- export function screenshotGrownNote(box, captured) {
899
- return `grown from ${box.width}×${box.height} to ${captured.width}×${captured.height} to include content overflowing the element`;
944
+ export function screenshotGrownNote(box, captured, padding = 0) {
945
+ const painted = { width: captured.width - 2 * padding, height: captured.height - 2 * padding };
946
+ const grew = painted.width > box.width + 0.5 || painted.height > box.height + 0.5;
947
+ const ink = `grown from ${box.width}×${box.height} to ${painted.width}×${painted.height} to include what it paints outside its box (overflowing content, shadows, outline)`;
948
+ const pad = `${padding}px of page around it (--padding)`;
949
+ if (!padding)
950
+ return ink;
951
+ return grew ? `${ink}, plus ${pad}` : `with ${pad}: ${captured.width}×${captured.height}`;
900
952
  }
901
953
  /**
902
954
  * Next commands after `bdg dom query`, by index so they reach matches in
@@ -1010,7 +1062,7 @@ export function startCommandHelpMessage() {
1010
1062
  export function pageEmulateNothingError() {
1011
1063
  return {
1012
1064
  message: 'Nothing to emulate',
1013
- suggestion: 'Give --viewport <WxH>, --color-scheme light|dark, or --reset, e.g. bdg page emulate --viewport 900x700',
1065
+ suggestion: 'Give --viewport <WxH>, --mobile, --color-scheme light|dark, or --reset, e.g. bdg page emulate --viewport 900x700',
1014
1066
  };
1015
1067
  }
1016
1068
  /**
@@ -1022,8 +1074,11 @@ export function pageEmulateNothingError() {
1022
1074
  export function pageEmulationLines(result) {
1023
1075
  const size = (v) => `${v.width}x${v.height}`;
1024
1076
  const { emulated } = result;
1077
+ const phone = emulated.viewport?.mobile
1078
+ ? ' (phone: mobile layout, touch, mobile user agent)'
1079
+ : '';
1025
1080
  return [
1026
- ['Viewport', emulated.viewport ? size(emulated.viewport) : 'the browser window'],
1081
+ ['Viewport', emulated.viewport ? `${size(emulated.viewport)}${phone}` : 'the browser window'],
1027
1082
  ...(result.viewport
1028
1083
  ? [['Layout', `${size(result.viewport)} (without scrollbars)`]]
1029
1084
  : []),
@@ -1052,4 +1107,21 @@ export function inspectAnimatingBadge(animating) {
1052
1107
  export function inspectMidTransitionNote() {
1053
1108
  return '(mid-transition: inspect again for the final value)';
1054
1109
  }
1110
+ /** Examples under `bdg dom audit --help` */
1111
+ export const AUDIT_HELP_EXAMPLES = `
1112
+ Examples:
1113
+ bdg dom audit All checks
1114
+ bdg dom audit contrast --level AAA Text below WCAG AAA, weakest first
1115
+ bdg dom audit overflow What scrolls sideways, cut-off text, scaled images
1116
+ bdg dom audit layers animations Fixed/sticky elements and running animations
1117
+
1118
+ Follow up on a finding with bdg dom inspect <element> (e.g. --why color).`;
1119
+ /** Examples under `bdg css search --help` */
1120
+ export const CSS_SEARCH_HELP_EXAMPLES = `
1121
+ Examples:
1122
+ bdg css search -- --brand Where a custom property is set and used (-- before a text
1123
+ that starts with -; options go before it:
1124
+ bdg css search --limit 50 -- --brand)
1125
+ bdg css search "oklch(" Rules that use oklch colors
1126
+ bdg css search ".btn-primary" Rules of a class, in every stylesheet`;
1055
1127
  //# sourceMappingURL=commands.js.map
@@ -61,4 +61,10 @@ export declare function connectionLostRetryMessage(timestamp: string, retryLabel
61
61
  * @returns Message instructing the user how to stop follow mode
62
62
  */
63
63
  export declare function connectionLostStopHintMessage(): string;
64
+ /**
65
+ * Notice that `bdg tail` is deprecated (it still runs).
66
+ *
67
+ * @returns Notice for stderr
68
+ */
69
+ export declare function tailDeprecatedNotice(): string;
64
70
  //# sourceMappingURL=preview.d.ts.map
@@ -30,7 +30,7 @@ export function verboseCommandsMessage() {
30
30
  return [
31
31
  'Commands:',
32
32
  ` Full preview: ${sessionCommand('bdg peek --last 50')}`,
33
- ` Watch live: ${sessionCommand('bdg tail')}`,
33
+ ` Watch live: ${sessionCommand('bdg peek --follow')}`,
34
34
  ` End session: ${sessionCommand('bdg stop')}`,
35
35
  ].join('\n');
36
36
  }
@@ -79,4 +79,12 @@ export function connectionLostRetryMessage(timestamp, retryLabel) {
79
79
  export function connectionLostStopHintMessage() {
80
80
  return 'Press Ctrl+C to stop';
81
81
  }
82
+ /**
83
+ * Notice that `bdg tail` is deprecated (it still runs).
84
+ *
85
+ * @returns Notice for stderr
86
+ */
87
+ export function tailDeprecatedNotice() {
88
+ return 'Note: "bdg tail" is deprecated and will be removed; use "bdg peek --follow" (same options: --last, --network, --console, --interval, --verbose)';
89
+ }
82
90
  //# sourceMappingURL=preview.js.map
@@ -44,11 +44,42 @@ export function cssLength(value) {
44
44
  * @returns Normalized value
45
45
  */
46
46
  export function normalizeCssValue(value) {
47
- return hexColorsIn(value)
48
- .replace(PX_IN_VALUE, (_match, number) => String(round1(Number(number))))
47
+ return mapOutsideFunctions(hexColorsIn(value), UNIT_KEEPING_FUNCTIONS, (part) => part.replace(PX_IN_VALUE, (_match, number) => String(round1(Number(number)))))
49
48
  .replace(/\s+/g, ' ')
50
49
  .trim();
51
50
  }
51
+ /** Functions whose px stay written: math mixes units, URLs are names */
52
+ const UNIT_KEEPING_FUNCTIONS = new Set(['calc', 'min', 'max', 'clamp', 'url']);
53
+ /**
54
+ * Change the parts of a value that are not inside the given functions.
55
+ *
56
+ * @param value - CSS value
57
+ * @param functions - Function names whose arguments are kept as written
58
+ * @param change - Change for the other parts
59
+ * @returns Value
60
+ */
61
+ function mapOutsideFunctions(value, functions, change) {
62
+ let result = '';
63
+ let start = 0;
64
+ let depth = 0;
65
+ for (let i = 0; i < value.length; i++) {
66
+ if (value[i] === '(') {
67
+ const name = /([\w-]+)$/.exec(value.slice(start, i))?.[1]?.toLowerCase() ?? '';
68
+ if (depth === 0 && functions.has(name)) {
69
+ result += change(value.slice(start, i));
70
+ start = i;
71
+ depth = 1;
72
+ }
73
+ else if (depth > 0)
74
+ depth++;
75
+ }
76
+ else if (value[i] === ')' && depth > 0 && --depth === 0) {
77
+ result += value.slice(start, i + 1);
78
+ start = i + 1;
79
+ }
80
+ }
81
+ return result + (depth > 0 ? value.slice(start) : change(value.slice(start)));
82
+ }
52
83
  /**
53
84
  * 1-4 values the way CSS shorthands write them (top, right, bottom, left).
54
85
  *
@@ -190,9 +221,10 @@ function round3(value) {
190
221
  function decomposeMatrix([a = 1, b = 0, c = 0, d = 1, e = 0, f = 0]) {
191
222
  if (Math.abs(a * c + b * d) > 1e-6)
192
223
  return undefined;
193
- const scaleX = Math.hypot(a, b);
224
+ const mirrored = a * d - b * c < 0 && a < 0;
225
+ const scaleX = mirrored ? -Math.hypot(a, b) : Math.hypot(a, b);
194
226
  const scaleY = scaleX === 0 ? 0 : (a * d - b * c) / scaleX;
195
- const angle = round3((Math.atan2(b, a) * 180) / Math.PI);
227
+ const angle = round3((Math.atan2(mirrored ? -b : b, mirrored ? -a : a) * 180) / Math.PI);
196
228
  const parts = [];
197
229
  if (e !== 0 || f !== 0)
198
230
  parts.push(`translate(${round1(e)},${round1(f)})`);
@@ -121,11 +121,6 @@ export const DECISION_TREES = {
121
121
  yesCommand: 'peek --follow',
122
122
  noAction: 'next',
123
123
  },
124
- {
125
- question: 'Need continuous monitoring (like tail -f)?',
126
- yesCommand: 'tail',
127
- noAction: 'next',
128
- },
129
124
  {
130
125
  question: 'Need quick preview of recent data?',
131
126
  yesCommand: 'peek',
@@ -18,8 +18,10 @@ export declare function getSuggestion(input: string, candidates: readonly string
18
18
  /**
19
19
  * Names (ids or classes) similar to one that matched nothing, best first, at
20
20
  * most three: near-typos first (Levenshtein distance up to a fifth of the
21
- * length, at least 2), then names sharing a long end, then names sharing a
22
- * long start (at least a third of the name, at least 4 characters). The end
21
+ * length, at least 2), then names sharing a long end (at least a third of
22
+ * the name, at least 4 characters), then names sharing a long start (at
23
+ * least half: `--color-btn-inset-shadow` and `--color-bg-discussions-…` share
24
+ * only a namespace). The end
23
25
  * ranks before the start because ids tend to name an action before the item
24
26
  * (`add-to-cart-backpack` becomes `remove-backpack` once clicked).
25
27
  *
@@ -65,8 +65,10 @@ function commonSuffixLength(a, b) {
65
65
  /**
66
66
  * Names (ids or classes) similar to one that matched nothing, best first, at
67
67
  * most three: near-typos first (Levenshtein distance up to a fifth of the
68
- * length, at least 2), then names sharing a long end, then names sharing a
69
- * long start (at least a third of the name, at least 4 characters). The end
68
+ * length, at least 2), then names sharing a long end (at least a third of
69
+ * the name, at least 4 characters), then names sharing a long start (at
70
+ * least half: `--color-btn-inset-shadow` and `--color-bg-discussions-…` share
71
+ * only a namespace). The end
70
72
  * ranks before the start because ids tend to name an action before the item
71
73
  * (`add-to-cart-backpack` becomes `remove-backpack` once clicked).
72
74
  *
@@ -79,15 +81,15 @@ export function findSimilarNames(name, candidates) {
79
81
  const maxDistance = Math.max(2, Math.floor(name.length / 5));
80
82
  const minAffix = Math.max(4, Math.ceil(name.length / 3));
81
83
  const typos = findMatches(name, others, maxDistance, false).map((match) => match.value);
82
- const byAffix = (length) => others
84
+ const byAffix = (length, min = minAffix) => others
83
85
  .map((candidate) => ({ candidate, length: length(candidate) }))
84
- .filter((entry) => entry.length >= minAffix)
86
+ .filter((entry) => entry.length >= min)
85
87
  .sort((a, b) => b.length - a.length)
86
88
  .map((entry) => entry.candidate);
87
89
  const ranked = [
88
90
  ...typos,
89
91
  ...byAffix((candidate) => commonSuffixLength(name, candidate)),
90
- ...byAffix((candidate) => commonPrefixLength(name, candidate)),
92
+ ...byAffix((candidate) => commonPrefixLength(name, candidate), Math.max(minAffix, Math.ceil(name.length / 2))),
91
93
  ];
92
94
  return [...new Set(ranked)].slice(0, MAX_SIMILAR_NAMES);
93
95
  }