@juxtapose/core 0.2.0 → 0.5.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 (141) hide show
  1. package/dist/agent/handoff.d.ts +8 -1
  2. package/dist/agent/handoff.js +75 -7
  3. package/dist/agent/handoff.js.map +1 -1
  4. package/dist/analysis/affected.d.ts +58 -0
  5. package/dist/analysis/affected.js +134 -22
  6. package/dist/analysis/affected.js.map +1 -1
  7. package/dist/analysis/cause.d.ts +32 -0
  8. package/dist/analysis/cause.js +187 -0
  9. package/dist/analysis/cause.js.map +1 -0
  10. package/dist/baseline/approve.d.ts +24 -0
  11. package/dist/baseline/approve.js +54 -0
  12. package/dist/baseline/approve.js.map +1 -0
  13. package/dist/baseline/git-store.d.ts +4 -0
  14. package/dist/baseline/git-store.js +22 -0
  15. package/dist/baseline/git-store.js.map +1 -1
  16. package/dist/browser/acquire.d.ts +11 -0
  17. package/dist/browser/acquire.js +15 -0
  18. package/dist/browser/acquire.js.map +1 -0
  19. package/dist/browser/browser.d.ts +53 -0
  20. package/dist/browser/browser.js +122 -0
  21. package/dist/browser/browser.js.map +1 -0
  22. package/dist/browser/cdp.d.ts +81 -0
  23. package/dist/browser/cdp.js +230 -0
  24. package/dist/browser/cdp.js.map +1 -0
  25. package/dist/browser/index.d.ts +9 -0
  26. package/dist/browser/index.js +10 -0
  27. package/dist/browser/index.js.map +1 -0
  28. package/dist/browser/install.d.ts +33 -0
  29. package/dist/browser/install.js +167 -0
  30. package/dist/browser/install.js.map +1 -0
  31. package/dist/browser/launch.d.ts +37 -0
  32. package/dist/browser/launch.js +265 -0
  33. package/dist/browser/launch.js.map +1 -0
  34. package/dist/browser/page.d.ts +125 -0
  35. package/dist/browser/page.js +564 -0
  36. package/dist/browser/page.js.map +1 -0
  37. package/dist/browser/pins.d.ts +12 -0
  38. package/dist/browser/pins.js +57 -0
  39. package/dist/browser/pins.js.map +1 -0
  40. package/dist/browser/resolve.d.ts +25 -0
  41. package/dist/browser/resolve.js +115 -0
  42. package/dist/browser/resolve.js.map +1 -0
  43. package/dist/browser/selectors.d.ts +10 -0
  44. package/dist/browser/selectors.js +60 -0
  45. package/dist/browser/selectors.js.map +1 -0
  46. package/dist/browser/transport.d.ts +39 -0
  47. package/dist/browser/transport.js +99 -0
  48. package/dist/browser/transport.js.map +1 -0
  49. package/dist/browser/zip.d.ts +27 -0
  50. package/dist/browser/zip.js +241 -0
  51. package/dist/browser/zip.js.map +1 -0
  52. package/dist/capture/capture.d.ts +7 -1
  53. package/dist/capture/capture.js +27 -3
  54. package/dist/capture/capture.js.map +1 -1
  55. package/dist/capture/env.d.ts +8 -0
  56. package/dist/capture/env.js +38 -0
  57. package/dist/capture/env.js.map +1 -0
  58. package/dist/capture/launch.d.ts +19 -1
  59. package/dist/capture/launch.js +91 -1
  60. package/dist/capture/launch.js.map +1 -1
  61. package/dist/capture/pool.d.ts +1 -1
  62. package/dist/capture/pool.js +41 -8
  63. package/dist/capture/pool.js.map +1 -1
  64. package/dist/capture/settle.d.ts +1 -1
  65. package/dist/capture/settle.js +2 -0
  66. package/dist/capture/settle.js.map +1 -1
  67. package/dist/capture/snap.d.ts +43 -0
  68. package/dist/capture/snap.js +103 -0
  69. package/dist/capture/snap.js.map +1 -0
  70. package/dist/capture/structure.d.ts +77 -0
  71. package/dist/capture/structure.js +498 -0
  72. package/dist/capture/structure.js.map +1 -0
  73. package/dist/diff/compare.d.ts +19 -0
  74. package/dist/diff/compare.js +19 -0
  75. package/dist/diff/compare.js.map +1 -0
  76. package/dist/diff/crops.d.ts +26 -0
  77. package/dist/diff/crops.js +92 -0
  78. package/dist/diff/crops.js.map +1 -0
  79. package/dist/diff/structural/classify.d.ts +29 -0
  80. package/dist/diff/structural/classify.js +113 -0
  81. package/dist/diff/structural/classify.js.map +1 -0
  82. package/dist/diff/structural/coverage.d.ts +11 -0
  83. package/dist/diff/structural/coverage.js +37 -0
  84. package/dist/diff/structural/coverage.js.map +1 -0
  85. package/dist/diff/structural/deltas.d.ts +29 -0
  86. package/dist/diff/structural/deltas.js +218 -0
  87. package/dist/diff/structural/deltas.js.map +1 -0
  88. package/dist/diff/structural/disambiguate.d.ts +3 -0
  89. package/dist/diff/structural/disambiguate.js +89 -0
  90. package/dist/diff/structural/disambiguate.js.map +1 -0
  91. package/dist/diff/structural/label.d.ts +15 -0
  92. package/dist/diff/structural/label.js +64 -0
  93. package/dist/diff/structural/label.js.map +1 -0
  94. package/dist/diff/structural/match.d.ts +13 -0
  95. package/dist/diff/structural/match.js +135 -0
  96. package/dist/diff/structural/match.js.map +1 -0
  97. package/dist/diff/structural/shift.d.ts +2 -0
  98. package/dist/diff/structural/shift.js +144 -0
  99. package/dist/diff/structural/shift.js.map +1 -0
  100. package/dist/diff/structural/summary.d.ts +31 -0
  101. package/dist/diff/structural/summary.js +420 -0
  102. package/dist/diff/structural/summary.js.map +1 -0
  103. package/dist/diff/structural.d.ts +40 -0
  104. package/dist/diff/structural.js +219 -0
  105. package/dist/diff/structural.js.map +1 -0
  106. package/dist/index.d.ts +34 -12
  107. package/dist/index.js +55 -11
  108. package/dist/index.js.map +1 -1
  109. package/dist/platform.d.ts +1 -1
  110. package/dist/platforms/urls.d.ts +3 -0
  111. package/dist/platforms/urls.js +89 -0
  112. package/dist/platforms/urls.js.map +1 -0
  113. package/dist/report/comment.d.ts +2 -1
  114. package/dist/report/comment.js +305 -57
  115. package/dist/report/comment.js.map +1 -1
  116. package/dist/report/format.d.ts +20 -0
  117. package/dist/report/format.js +32 -0
  118. package/dist/report/format.js.map +1 -0
  119. package/dist/report/github.d.ts +12 -0
  120. package/dist/report/github.js +21 -0
  121. package/dist/report/github.js.map +1 -1
  122. package/dist/report/html.d.ts +12 -0
  123. package/dist/report/html.js +202 -0
  124. package/dist/report/html.js.map +1 -0
  125. package/dist/run/groups.d.ts +2 -0
  126. package/dist/run/groups.js +45 -0
  127. package/dist/run/groups.js.map +1 -0
  128. package/dist/run/phases.d.ts +19 -0
  129. package/dist/run/phases.js +50 -0
  130. package/dist/run/phases.js.map +1 -0
  131. package/dist/run/run.d.ts +10 -2
  132. package/dist/run/run.js +97 -9
  133. package/dist/run/run.js.map +1 -1
  134. package/dist/shell-quote.d.ts +2 -0
  135. package/dist/shell-quote.js +10 -0
  136. package/dist/shell-quote.js.map +1 -0
  137. package/dist/types.d.ts +234 -3
  138. package/dist/url-platform.d.ts +2 -2
  139. package/dist/url-platform.js +2 -2
  140. package/dist/url-platform.js.map +1 -1
  141. package/package.json +14 -10
@@ -0,0 +1,202 @@
1
+ // A single self-contained report.html.
2
+ //
3
+ // The constraint that shapes everything here: it must work opened from
4
+ // `file://`, because that is how someone downloads a CI artifact and looks at
5
+ // it. No external stylesheet, no script src, no sibling image directory —
6
+ // images inline as data URIs and the whole thing is one file you can email.
7
+ //
8
+ // This complements rather than duplicates the addon panel. The panel is for
9
+ // local review with a dev server running; this is for a reviewer looking at a
10
+ // pull request who has checked nothing out.
11
+ import { describeMagnitude } from './format.js';
12
+ export function buildHtmlReport({ report, images }) {
13
+ const needsReview = report.results.filter((r) => r.status === 'fail' || r.status === 'new' || r.status === 'stale');
14
+ const s = report.summary;
15
+ return `<!doctype html>
16
+ <meta charset="utf-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <title>juxtapose — ${escapeHtml(report.branch)} @ ${escapeHtml(report.commitSha.slice(0, 7))}</title>
19
+ <style>${STYLE}</style>
20
+
21
+ <header>
22
+ <h1>${needsReview.length === 0 ? 'Visual tests passed' : 'Visual changes need review'}</h1>
23
+ <dl>
24
+ ${stat('pass', s.pass)}
25
+ ${s.fail ? stat('changed', s.fail, 'bad') : ''}
26
+ ${s.new ? stat('new', s.new, 'warn') : ''}
27
+ ${s.stale ? stat('stale', s.stale, 'warn') : ''}
28
+ ${s.skip ? stat('skipped', s.skip) : ''}
29
+ </dl>
30
+ <p class="meta">
31
+ ${escapeHtml(report.branch)} ·
32
+ <code>${escapeHtml(report.commitSha.slice(0, 7))}</code> ·
33
+ ${report.capturedCount}/${report.totalCount} captured ·
34
+ ${(report.durationMs / 1000).toFixed(1)}s
35
+ </p>
36
+ </header>
37
+
38
+ ${needsReview.length === 0
39
+ ? `<p class="empty">Every captured story matches its approved baseline.</p>`
40
+ : needsReview.map((r) => storySection(r, images.get(r.storyId))).join('\n')}
41
+
42
+ <script>${SCRIPT}</script>
43
+ `;
44
+ }
45
+ function stat(label, value, tone = '') {
46
+ return `<div class="${tone}"><dt>${label}</dt><dd>${value}</dd></div>`;
47
+ }
48
+ function storySection(result, imgs) {
49
+ const baseline = dataUri(imgs?.baseline);
50
+ const current = dataUri(imgs?.current);
51
+ const diff = dataUri(imgs?.diff);
52
+ // A new story has no baseline, so there is nothing to compare — show the
53
+ // capture alone rather than an empty half. Same reasoning as the panel:
54
+ // controls that compare nothing read as broken.
55
+ const comparable = Boolean(baseline && current);
56
+ return `
57
+ <section>
58
+ <h2>
59
+ <span class="badge ${result.status}">${statusLabel(result.status)}</span>
60
+ ${escapeHtml(result.storyId)}
61
+ ${result.status === 'fail' ? `<span class="pct">${escapeHtml(describeMagnitude(result))}</span>` : ''}
62
+ </h2>
63
+ ${result.error ? `<p class="error">${escapeHtml(result.error)}</p>` : ''}
64
+ ${comparable
65
+ ? `
66
+ <div class="tabs">
67
+ <button data-mode="sxs" class="on">Side by side</button>
68
+ <button data-mode="slider">Slider</button>
69
+ ${diff ? `<button data-mode="diff">Diff</button>` : ''}
70
+ </div>
71
+
72
+ <div class="views">
73
+ <div class="view sxs on">
74
+ <figure><figcaption>Baseline</figcaption><img src="${baseline}" alt="baseline"></figure>
75
+ <figure><figcaption>Current</figcaption><img src="${current}" alt="current"></figure>
76
+ </div>
77
+
78
+ <div class="view slider">
79
+ <div class="wipe" style="--pos:50%">
80
+ <img src="${baseline}" alt="baseline">
81
+ <img class="over" src="${current}" alt="current">
82
+ <div class="handle"></div>
83
+ </div>
84
+ <input type="range" min="0" max="100" value="50" aria-label="Wipe between baseline and current">
85
+ </div>
86
+
87
+ ${diff ? `<div class="view diff"><img src="${diff}" alt="diff overlay"></div>` : ''}
88
+ </div>`
89
+ : current
90
+ ? `<div class="single"><img src="${current}" alt="capture"></div>`
91
+ : `<p class="error">No capture available for this story.</p>`}
92
+ </section>`;
93
+ }
94
+ /**
95
+ * Inline an image.
96
+ *
97
+ * Data URIs are what make the file portable — a sibling images/ directory
98
+ * breaks the moment someone moves or emails the html. The cost is roughly 33%
99
+ * inflation from base64, acceptable for a CI artifact.
100
+ */
101
+ function dataUri(bytes) {
102
+ return bytes ? `data:image/webp;base64,${bytes.toString('base64')}` : null;
103
+ }
104
+ function statusLabel(status) {
105
+ switch (status) {
106
+ case 'fail':
107
+ return 'changed';
108
+ case 'new':
109
+ return 'no baseline';
110
+ case 'stale':
111
+ return 'stale';
112
+ default:
113
+ return status;
114
+ }
115
+ }
116
+ /** Escape anything interpolated into markup. Story ids come from the platform
117
+ * and are not ours to trust. */
118
+ function escapeHtml(value) {
119
+ return value
120
+ .replace(/&/g, '&amp;')
121
+ .replace(/</g, '&lt;')
122
+ .replace(/>/g, '&gt;')
123
+ .replace(/"/g, '&quot;');
124
+ }
125
+ const STYLE = `
126
+ :root {
127
+ --fg: #0f172a; --muted: #64748b; --line: #e2e8f0; --bg: #fff;
128
+ --bad: #dc2626; --warn: #d97706;
129
+ font-family: system-ui, -apple-system, sans-serif;
130
+ }
131
+ @media (prefers-color-scheme: dark) {
132
+ :root { --fg: #e2e8f0; --muted: #94a3b8; --line: #334155; --bg: #0f172a; }
133
+ }
134
+ * { box-sizing: border-box; }
135
+ body { margin: 0 auto; padding: 24px; max-width: 1200px;
136
+ color: var(--fg); background: var(--bg); }
137
+ h1 { font-size: 20px; margin: 0 0 12px; }
138
+ dl { display: flex; gap: 20px; margin: 0 0 8px; padding: 0; }
139
+ dl div { display: flex; gap: 6px; align-items: baseline; }
140
+ dt { color: var(--muted); font-size: 13px; }
141
+ dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
142
+ dl .bad dd { color: var(--bad); } dl .warn dd { color: var(--warn); }
143
+ .meta { color: var(--muted); font-size: 13px; margin: 0 0 28px; }
144
+ .empty { color: var(--muted); }
145
+ section { border-top: 1px solid var(--line); padding: 20px 0; }
146
+ h2 { font-size: 15px; font-weight: 600; display: flex; align-items: center;
147
+ gap: 10px; margin: 0 0 12px; }
148
+ .badge { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
149
+ padding: 2px 8px; border-radius: 999px; color: #fff; }
150
+ .badge.fail { background: var(--bad); }
151
+ .badge.new, .badge.stale { background: var(--warn); }
152
+ .pct { color: var(--muted); font-weight: 400; font-size: 13px; }
153
+ .error { color: var(--bad); font-size: 13px; margin: 0 0 12px; }
154
+ .tabs { display: flex; gap: 4px; margin-bottom: 12px; }
155
+ .tabs button { font: inherit; font-size: 12px; padding: 4px 10px; cursor: pointer;
156
+ border: 1px solid var(--line); background: transparent;
157
+ color: var(--muted); border-radius: 4px; }
158
+ .tabs button.on { background: var(--fg); color: var(--bg); border-color: var(--fg); }
159
+ .view { display: none; }
160
+ .view.on { display: block; }
161
+ .view.sxs.on { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
162
+ figure { margin: 0; }
163
+ figcaption { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
164
+ color: var(--muted); margin-bottom: 6px; }
165
+ img { max-width: 100%; height: auto; display: block; border: 1px solid var(--line); }
166
+ .single img { max-width: 640px; }
167
+ .wipe { position: relative; display: inline-block; max-width: 100%; }
168
+ .wipe .over { position: absolute; inset: 0; clip-path: inset(0 0 0 var(--pos)); }
169
+ .wipe .handle { position: absolute; top: 0; bottom: 0; left: var(--pos);
170
+ width: 2px; background: var(--bad); pointer-events: none; }
171
+ input[type=range] { width: 100%; margin-top: 8px; }
172
+ `;
173
+ /**
174
+ * Tab switching and the wipe slider.
175
+ *
176
+ * Inline and dependency-free on purpose: a `file://` page cannot load anything
177
+ * external, and a report needing a build step to view defeats the point of
178
+ * being an artifact.
179
+ */
180
+ const SCRIPT = `
181
+ document.querySelectorAll('section').forEach(function (section) {
182
+ section.querySelectorAll('.tabs button').forEach(function (btn) {
183
+ btn.addEventListener('click', function () {
184
+ section.querySelectorAll('.tabs button').forEach(function (b) {
185
+ b.classList.toggle('on', b === btn);
186
+ });
187
+ section.querySelectorAll('.view').forEach(function (v) {
188
+ v.classList.toggle('on', v.classList.contains(btn.dataset.mode));
189
+ });
190
+ });
191
+ });
192
+
193
+ var range = section.querySelector('input[type=range]');
194
+ var wipe = section.querySelector('.wipe');
195
+ if (range && wipe) {
196
+ range.addEventListener('input', function () {
197
+ wipe.style.setProperty('--pos', range.value + '%');
198
+ });
199
+ }
200
+ });
201
+ `;
202
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/report/html.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,uEAAuE;AACvE,8EAA8E;AAC9E,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,4CAA4C;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAShD,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAqB;IACnE,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,CACzE,CAAC;IACF,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;IAEzB,OAAO;;;qBAGY,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;SACnF,KAAK;;;QAGN,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,4BAA4B;;MAEjF,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;MACpB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;MAC5C,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;MACvC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;MAC7C,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;;;MAGrC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;YACnB,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;MAC9C,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,UAAU;MACzC,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;;;;EAKzC,WAAW,CAAC,MAAM,KAAK,CAAC;QACtB,CAAC,CAAC,0EAA0E;QAC5E,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAC9E;;UAEU,MAAM;CACf,CAAC;AACF,CAAC;AAED,SAAS,IAAI,CAAC,KAAa,EAAE,KAAa,EAAE,IAAI,GAAG,EAAE;IACnD,OAAO,eAAe,IAAI,SAAS,KAAK,YAAY,KAAK,aAAa,CAAC;AACzE,CAAC;AAED,SAAS,YAAY,CACnB,MAAkB,EAClB,IAAwE;IAExE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEjC,yEAAyE;IACzE,wEAAwE;IACxE,gDAAgD;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;IAEhD,OAAO;;;yBAGgB,MAAM,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC;MAC/D,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC;MAC1B,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,qBAAqB,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;IAErG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;IAEtE,UAAU;QACR,CAAC,CAAC;;;;MAIF,IAAI,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC,CAAC,EAAE;;;;;2DAKC,QAAQ;0DACT,OAAO;;;;;oBAK7C,QAAQ;iCACK,OAAO;;;;;;MAMlC,IAAI,CAAC,CAAC,CAAC,oCAAoC,IAAI,6BAA6B,CAAC,CAAC,CAAC,EAAE;SAC9E;QACH,CAAC,CAAC,OAAO;YACP,CAAC,CAAC,iCAAiC,OAAO,wBAAwB;YAClE,CAAC,CAAC,2DACR;WACS,CAAC;AACZ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,KAAyB;IACxC,OAAO,KAAK,CAAC,CAAC,CAAC,0BAA0B,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7E,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QACnB,KAAK,KAAK;YACR,OAAO,aAAa,CAAC;QACvB,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED;iCACiC;AACjC,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Cb,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBd,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { DiffResult, RunGroup } from '../types.js';
2
+ export declare function computeGroups(results: DiffResult[]): RunGroup[];
@@ -0,0 +1,45 @@
1
+ // Which failing stories changed the same way. The verdict's signature
2
+ // already abstracts the element to role|tag and keeps the values, so "every
3
+ // button background #2563eb→#1d4ed8" hashes alike on every page that made
4
+ // that change; grouping is a bucket sort on it. Cold path: runs once per
5
+ // report, after every story is judged.
6
+ import { compareCodeUnits } from '../diff/structural/deltas.js';
7
+ export function computeGroups(results) {
8
+ const bySignature = new Map();
9
+ for (const r of results) {
10
+ // A verdict with no deltas has a signature that depends on the
11
+ // classification alone: unrelated identical, drifting or resized
12
+ // stories would "share" it, and there is no change to state.
13
+ if (r.status !== 'fail' || !r.structural || r.structural.deltaCount === 0)
14
+ continue;
15
+ const { signature, change, summary } = r.structural;
16
+ const text = change ?? summary;
17
+ const g = bySignature.get(signature);
18
+ if (!g)
19
+ bySignature.set(signature, { ids: [r.storyId], first: r.storyId, change: text });
20
+ else {
21
+ g.ids.push(r.storyId);
22
+ // The wording of the first story in code-unit order, whatever the
23
+ // input order: two verdicts of one signature can word it differently.
24
+ if (compareCodeUnits(r.storyId, g.first) < 0) {
25
+ g.first = r.storyId;
26
+ g.change = text;
27
+ }
28
+ }
29
+ }
30
+ // Collected through a Map, emitted through a sort: insertion order never
31
+ // reaches the report.
32
+ const groups = [];
33
+ for (const [signature, g] of bySignature) {
34
+ if (g.ids.length < 2)
35
+ continue;
36
+ const storyIds = [...g.ids].sort(compareCodeUnits);
37
+ groups.push({
38
+ signature,
39
+ summary: `${g.change} — ${storyIds.length} stories`,
40
+ storyIds,
41
+ });
42
+ }
43
+ return groups.sort((x, y) => y.storyIds.length - x.storyIds.length || compareCodeUnits(x.signature, y.signature));
44
+ }
45
+ //# sourceMappingURL=groups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groups.js","sourceRoot":"","sources":["../../src/run/groups.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,4EAA4E;AAC5E,0EAA0E;AAC1E,yEAAyE;AACzE,uCAAuC;AAEvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGhE,MAAM,UAAU,aAAa,CAAC,OAAqB;IACjD,MAAM,WAAW,GAAG,IAAI,GAAG,EAA4D,CAAC;IACxF,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,+DAA+D;QAC/D,iEAAiE;QACjE,6DAA6D;QAC7D,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,KAAK,CAAC;YAAE,SAAS;QACpF,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAC;QAC/B,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC;YACJ,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;aAC9E,CAAC;YACJ,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACtB,kEAAkE;YAClE,sEAAsE;YACtE,IAAI,gBAAgB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7C,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;gBACpB,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IACD,yEAAyE;IACzE,sBAAsB;IACtB,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC/B,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACnD,MAAM,CAAC,IAAI,CAAC;YACV,SAAS;YACT,OAAO,EAAE,GAAG,CAAC,CAAC,MAAM,MAAM,QAAQ,CAAC,MAAM,UAAU;YACnD,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAChB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CACtF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ export interface Phases {
2
+ /** Time a promise-returning step, recording it under `name`. */
3
+ time<T>(name: string, fn: () => Promise<T>): Promise<T>;
4
+ /** Add to a phase directly, for work not shaped like a single call. */
5
+ add(name: string, ms: number): void;
6
+ /** Count of things a phase handled, e.g. how many diffs actually ran. */
7
+ count(name: string, n?: number): void;
8
+ readonly timings: Record<string, number>;
9
+ readonly counts: Record<string, number>;
10
+ }
11
+ export declare function phases(): Phases;
12
+ /**
13
+ * `enumerate 12ms | capture 48.2s | diff(7) 810ms`
14
+ *
15
+ * Phases are printed in insertion order, which is execution order, so the line
16
+ * reads as the run happened. Sub-millisecond phases are dropped: they are noise
17
+ * next to a capture measured in seconds.
18
+ */
19
+ export declare function formatPhases(p: Pick<Phases, 'timings' | 'counts'>): string;
@@ -0,0 +1,50 @@
1
+ // Wall-time per stage of a run.
2
+ //
3
+ // A run reports one total today, which is enough to know it was slow and not
4
+ // enough to know why. The phases have very different cost profiles — capture
5
+ // dominates, diff is a cold path that only runs for stories that already
6
+ // failed, and enumerate/affected are fixed overhead — so the total alone
7
+ // cannot distinguish "700 stories" from "7 stories and a broken diff".
8
+ export function phases() {
9
+ const timings = {};
10
+ const counts = {};
11
+ const add = (name, ms) => {
12
+ timings[name] = (timings[name] ?? 0) + ms;
13
+ };
14
+ return {
15
+ timings,
16
+ counts,
17
+ add,
18
+ count(name, n = 1) {
19
+ counts[name] = (counts[name] ?? 0) + n;
20
+ },
21
+ async time(name, fn) {
22
+ const started = Date.now();
23
+ try {
24
+ return await fn();
25
+ }
26
+ finally {
27
+ add(name, Date.now() - started);
28
+ }
29
+ },
30
+ };
31
+ }
32
+ /**
33
+ * `enumerate 12ms | capture 48.2s | diff(7) 810ms`
34
+ *
35
+ * Phases are printed in insertion order, which is execution order, so the line
36
+ * reads as the run happened. Sub-millisecond phases are dropped: they are noise
37
+ * next to a capture measured in seconds.
38
+ */
39
+ export function formatPhases(p) {
40
+ const parts = [];
41
+ for (const [name, ms] of Object.entries(p.timings)) {
42
+ if (ms < 1)
43
+ continue;
44
+ const n = p.counts[name];
45
+ const label = n === undefined ? name : `${name}(${n})`;
46
+ parts.push(`${label} ${ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`}`);
47
+ }
48
+ return parts.join(' | ');
49
+ }
50
+ //# sourceMappingURL=phases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phases.js","sourceRoot":"","sources":["../../src/run/phases.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,yEAAyE;AACzE,yEAAyE;AACzE,uEAAuE;AAavE,MAAM,UAAU,MAAM;IACpB,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE;QACvC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;IAC5C,CAAC,CAAC;IAEF,OAAO;QACL,OAAO;QACP,MAAM;QACN,GAAG;QACH,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE;YACjB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,OAAO,MAAM,EAAE,EAAE,CAAC;YACpB,CAAC;oBAAS,CAAC;gBACT,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,CAAqC;IAChE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,EAAE,GAAG,CAAC;YAAE,SAAS;QACrB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
package/dist/run/run.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { Browser } from 'playwright';
1
+ import type { Browser } from '../browser/browser.js';
2
2
  import type { Platform } from '../platform.js';
3
- import type { BaselineStore, DiffStatus, ProjectConfig, RunReport, StoryManifest, Viewport } from '../types.js';
3
+ import type { BaselineStore, DiffStatus, ProjectConfig, RunReport, StoryManifest, StructureSnapshot, Viewport } from '../types.js';
4
4
  import { type AncestryCheck } from '../baseline/status.js';
5
+ import { type Phases } from './phases.js';
6
+ import type { CauseIndex } from '../analysis/cause.js';
5
7
  export declare const DEFAULT_VIEWPORT: Viewport;
6
8
  export interface RunOptions {
7
9
  browser: Browser;
@@ -20,11 +22,17 @@ export interface RunOptions {
20
22
  onArtifact?: (artifact: RunArtifact) => Promise<void>;
21
23
  onProgress?: (done: number, total: number, storyId: string) => void;
22
24
  signal?: AbortSignal;
25
+ /** Collects per-phase wall time. Pass one to fold in phases the caller timed
26
+ * itself (enumerate, affected, launch); omit and one is made internally. */
27
+ phases?: Phases;
28
+ /** From `jux run --since`: consulted only for a story that failed. */
29
+ cause?: CauseIndex;
23
30
  }
24
31
  export interface RunArtifact {
25
32
  storyId: string;
26
33
  status: DiffStatus;
27
34
  current: Buffer;
28
35
  diff?: Buffer;
36
+ structure?: StructureSnapshot;
29
37
  }
30
38
  export declare function runCapture(opts: RunOptions): Promise<RunReport>;
package/dist/run/run.js CHANGED
@@ -4,15 +4,23 @@
4
4
  // so it runs for every story. Decoding and diffing are expensive and explain
5
5
  // nothing about a passing story, so they run only for stories that already
6
6
  // failed. On a green run this function never decodes a single image.
7
+ import { captureEnv } from '../capture/env.js';
7
8
  import { capturePool } from '../capture/pool.js';
8
9
  import { resolveStatus, toDiffStatus } from '../baseline/status.js';
9
- import { diffImages, resolveThreshold, resolveMinDiffPercent } from '../diff/diff.js';
10
+ import { decodeStructure } from '../capture/structure.js';
11
+ import { structuralDiff } from '../diff/structural.js';
12
+ import { compareCodeUnits } from '../diff/structural/deltas.js';
13
+ import { computeGroups } from './groups.js';
14
+ import { diffImages, resolveThreshold, resolveMinDiffPercent, } from '../diff/diff.js';
15
+ import { phases } from './phases.js';
16
+ import { shellQuote } from '../shell-quote.js';
10
17
  export const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
11
18
  export async function runCapture(opts) {
12
19
  const startedAt = new Date();
13
20
  const started = Date.now();
14
21
  const viewport = opts.config?.viewport ?? DEFAULT_VIEWPORT;
15
- const manifest = await opts.store.readManifest();
22
+ const p = opts.phases ?? phases();
23
+ const manifest = await p.time('baselines', () => opts.store.readManifest());
16
24
  const results = [];
17
25
  const pool = capturePool(opts.stories, {
18
26
  browser: opts.browser,
@@ -24,6 +32,10 @@ export async function runCapture(opts) {
24
32
  onProgress: opts.onProgress,
25
33
  signal: opts.signal,
26
34
  });
35
+ // Capture is measured as the whole streamed loop rather than per story: the
36
+ // pool runs `concurrency` pages at once, so summing per-story durations would
37
+ // report several times the wall-clock actually spent.
38
+ const captureStarted = Date.now();
27
39
  for await (const outcome of pool) {
28
40
  if (outcome.skipped) {
29
41
  results.push({
@@ -48,23 +60,35 @@ export async function runCapture(opts) {
48
60
  });
49
61
  continue;
50
62
  }
51
- results.push(await judge(outcome.snapshot, outcome.story, manifest, opts));
63
+ results.push(await judge(outcome.snapshot, outcome.story, manifest, opts, p));
52
64
  }
65
+ // Diffing and cause tracing happen inside the loop; subtract both to
66
+ // leave capture alone.
67
+ p.add('capture', Date.now() - captureStarted - (p.timings.diff ?? 0) - (p.timings.cause ?? 0));
53
68
  const summary = tally(results);
69
+ // Code-unit order: the report is byte-stable across machines, and
70
+ // localeCompare is not.
71
+ results.sort((a, b) => compareCodeUnits(a.storyId, b.storyId));
72
+ const groups = computeGroups(results);
54
73
  return {
55
74
  runId: opts.runId,
56
75
  branch: opts.branch,
57
76
  commitSha: opts.commitSha,
77
+ // Carried on the report because `approve` has no browser of its own — it
78
+ // reads captured images off disk, so this is the only path by which the
79
+ // fingerprint reaches a baseline entry.
80
+ env: captureEnv(opts.browser),
58
81
  capturedCount: opts.stories.length,
59
82
  totalCount: opts.totalCount,
60
- results: results.sort((a, b) => a.storyId.localeCompare(b.storyId)),
83
+ results,
61
84
  summary,
85
+ ...(groups.length > 0 ? { groups } : {}),
62
86
  durationMs: Date.now() - started,
63
87
  startedAt: startedAt.toISOString(),
64
88
  };
65
89
  }
66
90
  /** Decide one story's fate. Cheap path first. */
67
- async function judge(snapshot, story, manifest, opts) {
91
+ async function judge(snapshot, story, manifest, opts, p) {
68
92
  const baselineStatus = resolveStatus(story.storyId, snapshot.image, manifest, opts.ancestry);
69
93
  const status = toDiffStatus(baselineStatus);
70
94
  const base = {
@@ -73,38 +97,102 @@ async function judge(snapshot, story, manifest, opts) {
73
97
  mismatchPixels: 0,
74
98
  mismatchPercent: 0,
75
99
  changedRegions: [],
100
+ // The effective viewport, including any per-story override. Recording it
101
+ // with the regions means nobody downstream has to re-enumerate stories to
102
+ // find out what the coordinates are relative to.
103
+ viewport: snapshot.viewport,
76
104
  durationMs: snapshot.durationMs,
77
105
  };
78
106
  // pass / new / stale need no pixel work: there is either nothing to explain
79
107
  // or nothing to compare against.
80
108
  if (status !== 'fail') {
81
- await opts.onArtifact?.({ storyId: story.storyId, status, current: snapshot.image });
109
+ await opts.onArtifact?.({
110
+ storyId: story.storyId,
111
+ status,
112
+ current: snapshot.image,
113
+ structure: snapshot.structure,
114
+ });
82
115
  return base;
83
116
  }
84
117
  const baseline = await opts.store.get(story.storyId);
85
118
  if (!baseline) {
86
119
  // The manifest claimed a baseline the store can't produce.
87
- await opts.onArtifact?.({ storyId: story.storyId, status, current: snapshot.image });
120
+ await opts.onArtifact?.({
121
+ storyId: story.storyId,
122
+ status,
123
+ current: snapshot.image,
124
+ structure: snapshot.structure,
125
+ });
88
126
  return { ...base, error: 'baseline missing from store' };
89
127
  }
90
- const diff = await diffImages(baseline, snapshot.image, {
128
+ // Counted as well as timed: under a byte-exact hash the number of diffs is
129
+ // how broken the run was, and it is the figure that explains a slow one.
130
+ p.count('diff');
131
+ const diff = await p.time('diff', () => diffImages(baseline, snapshot.image, {
91
132
  threshold: resolveThreshold(story.params, opts.config),
92
133
  minDiffPercent: resolveMinDiffPercent(story.params, opts.config),
93
- });
134
+ }));
94
135
  await opts.onArtifact?.({
95
136
  storyId: story.storyId,
96
137
  status,
97
138
  current: snapshot.image,
98
139
  diff: diff.diffImage,
140
+ structure: snapshot.structure,
99
141
  });
142
+ // Structure explains the failure after the pixels have decided it. The
143
+ // baseline sidecar is read only here, so a green run never touches one. A
144
+ // manifest hash is not a file: read the store, and treat anything
145
+ // unreadable as absent. This code runs only on a hash mismatch.
146
+ const structural = await tryStructural(opts, story, snapshot, diff, p);
100
147
  return {
101
148
  ...base,
102
149
  mismatchPixels: diff.mismatchPixels,
103
150
  mismatchPercent: diff.mismatchPercent,
104
151
  changedRegions: diff.changedRegions,
105
152
  error: diff.dimensionMismatch ? 'viewport size changed since baseline' : undefined,
153
+ ...(structural ? { structural } : {}),
106
154
  };
107
155
  }
156
+ /** A sidecar that is absent or unreadable is simply no baseline structure. */
157
+ async function readBaselineStructure(store, storyId) {
158
+ try {
159
+ const gz = store.getStructure ? await store.getStructure(storyId) : null;
160
+ return gz ? decodeStructure(gz) : undefined;
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ }
166
+ /** Structure explains a failure, never decides one, so nothing in here may
167
+ * fail the story: a missing, corrupt or malformed sidecar, a cause index
168
+ * that throws, or a bug in the diff, yields no verdict (or one without a
169
+ * cause) and the pixel result stands. */
170
+ async function tryStructural(opts, story, snapshot, pixels, p) {
171
+ if (!snapshot.structure)
172
+ return undefined;
173
+ let cause;
174
+ if (opts.cause) {
175
+ try {
176
+ cause = await p.time('cause', () => opts.cause.causeFor(story.componentPath));
177
+ }
178
+ catch {
179
+ cause = undefined;
180
+ }
181
+ }
182
+ try {
183
+ return structuralDiff({
184
+ baseline: await readBaselineStructure(opts.store, story.storyId),
185
+ current: snapshot.structure,
186
+ imagesIdentical: false,
187
+ pixels,
188
+ escalate: { command: `jux handoff ${shellQuote(story.storyId)}` },
189
+ ...(cause ? { cause } : {}),
190
+ });
191
+ }
192
+ catch {
193
+ return undefined;
194
+ }
195
+ }
108
196
  function tally(results) {
109
197
  const summary = {
110
198
  pass: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** Single-quote a value for a POSIX shell unless it is plainly safe. */
2
+ export declare function shellQuote(v: string): string;
@@ -0,0 +1,10 @@
1
+ // Every hint that prints a command prints one an agent can paste. A snap
2
+ // name with a space (`jux diff 'a b' x`) printed bare made the suggested
3
+ // `jux snap <url> --as a b` a different command. In core so the verdict's
4
+ // `escalate.command` and the CLI's hints share one implementation.
5
+ const SHELL_SAFE = /^[A-Za-z0-9_@%+=:,./-]+$/;
6
+ /** Single-quote a value for a POSIX shell unless it is plainly safe. */
7
+ export function shellQuote(v) {
8
+ return SHELL_SAFE.test(v) ? v : `'${v.replaceAll("'", "'\\''")}'`;
9
+ }
10
+ //# sourceMappingURL=shell-quote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-quote.js","sourceRoot":"","sources":["../src/shell-quote.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,mEAAmE;AAEnE,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAE9C,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AACpE,CAAC"}