@saywright/studio 0.2.0 → 0.2.1
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/bridge.js +80 -32
- package/dist/cli.mjs +5193 -4848
- package/dist/client/assets/CommandPalette-BM1BSUxc.js +1 -0
- package/dist/client/assets/CommandPalette-BM1BSUxc.js.br +0 -0
- package/dist/client/assets/CommandPalette-BM1BSUxc.js.gz +0 -0
- package/dist/client/assets/{HelpDialog-CnCVSSNP.js → HelpDialog-Bc6yjiVi.js} +1 -1
- package/dist/client/assets/HelpDialog-Bc6yjiVi.js.br +0 -0
- package/dist/client/assets/HelpDialog-Bc6yjiVi.js.gz +0 -0
- package/dist/client/assets/{ReportView-BwBy4JIG.js → ReportView-DKcggbSt.js} +1 -1
- package/dist/client/assets/{ReportView-BwBy4JIG.js.br → ReportView-DKcggbSt.js.br} +0 -0
- package/dist/client/assets/ReportView-DKcggbSt.js.gz +0 -0
- package/dist/client/assets/{SettingsDialog-CS5w9CxJ.js → SettingsDialog-CIpvauuY.js} +1 -1
- package/dist/client/assets/SettingsDialog-CIpvauuY.js.br +0 -0
- package/dist/client/assets/SettingsDialog-CIpvauuY.js.gz +0 -0
- package/dist/client/assets/{arrow-left-BpsxETMA.js → arrow-left-5LUh_bW3.js} +1 -1
- package/dist/client/assets/brand-BcIB43Uo.js +57 -0
- package/dist/client/assets/brand-BcIB43Uo.js.br +0 -0
- package/dist/client/assets/brand-BcIB43Uo.js.gz +0 -0
- package/dist/client/assets/{footprints-Bc2dzReV.js → footprints-CfMi7MiN.js} +1 -1
- package/dist/client/assets/index-D1ooswLS.js +19 -0
- package/dist/client/assets/index-D1ooswLS.js.br +0 -0
- package/dist/client/assets/index-D1ooswLS.js.gz +0 -0
- package/dist/client/assets/{refresh-cw-DocKnJID.js → refresh-cw-BM1RSPTL.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/sample/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/CommandPalette-VEQNxJ4K.js +0 -1
- package/dist/client/assets/CommandPalette-VEQNxJ4K.js.br +0 -0
- package/dist/client/assets/CommandPalette-VEQNxJ4K.js.gz +0 -0
- package/dist/client/assets/HelpDialog-CnCVSSNP.js.br +0 -0
- package/dist/client/assets/HelpDialog-CnCVSSNP.js.gz +0 -0
- package/dist/client/assets/ReportView-BwBy4JIG.js.gz +0 -0
- package/dist/client/assets/SettingsDialog-CS5w9CxJ.js.br +0 -0
- package/dist/client/assets/SettingsDialog-CS5w9CxJ.js.gz +0 -0
- package/dist/client/assets/brand-BSL9G2NW.js +0 -57
- package/dist/client/assets/brand-BSL9G2NW.js.br +0 -0
- package/dist/client/assets/brand-BSL9G2NW.js.gz +0 -0
- package/dist/client/assets/index-C3xqQUPf.js +0 -19
- package/dist/client/assets/index-C3xqQUPf.js.br +0 -0
- package/dist/client/assets/index-C3xqQUPf.js.gz +0 -0
package/dist/bridge.js
CHANGED
|
@@ -1021,6 +1021,7 @@ function contentName(el) {
|
|
|
1021
1021
|
}
|
|
1022
1022
|
function visibleText(el) {
|
|
1023
1023
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) return collapse(isSecretField(el) ? el.placeholder : el.value || el.placeholder, 80);
|
|
1024
|
+
if (el instanceof HTMLSelectElement) return collapse(Array.from(el.selectedOptions, (o) => o.label || o.text).join(", "), 80);
|
|
1024
1025
|
return collapse(el.innerText ?? el.textContent, 80);
|
|
1025
1026
|
}
|
|
1026
1027
|
function shortLabel(el) {
|
|
@@ -1196,6 +1197,48 @@ function plainElement(el) {
|
|
|
1196
1197
|
return cur;
|
|
1197
1198
|
}
|
|
1198
1199
|
//#endregion
|
|
1200
|
+
//#region ../../packages/vite/src/bridge/own.ts
|
|
1201
|
+
/**
|
|
1202
|
+
* Saywright's own work on the app's page (reading the screen, building
|
|
1203
|
+
* locators, answering the studio): timed, so a slow moment it caused is never
|
|
1204
|
+
* reported as the app's.
|
|
1205
|
+
*/
|
|
1206
|
+
var spans = [];
|
|
1207
|
+
/** Run some of our own work, remembering when it ran if it took long enough to matter. */
|
|
1208
|
+
function own(fn) {
|
|
1209
|
+
const start = performance.now();
|
|
1210
|
+
try {
|
|
1211
|
+
return fn();
|
|
1212
|
+
} finally {
|
|
1213
|
+
const end = performance.now();
|
|
1214
|
+
if (end - start >= 8) {
|
|
1215
|
+
spans.push([start, end]);
|
|
1216
|
+
if (spans.length > 64) spans.splice(0, spans.length - 64);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
/** How much of [start, end] (performance.now() times) was our own work. */
|
|
1221
|
+
function ownTime(start, end) {
|
|
1222
|
+
let ms = 0;
|
|
1223
|
+
for (const [a, b] of spans) ms += Math.max(0, Math.min(b, end) - Math.max(a, start));
|
|
1224
|
+
return ms;
|
|
1225
|
+
}
|
|
1226
|
+
/** When a replay or Autopilot last acted on the page (a click, typing, a key): the times of the last few. */
|
|
1227
|
+
var acted = [];
|
|
1228
|
+
/** A step a replay or Autopilot did, now. */
|
|
1229
|
+
function scriptedInput() {
|
|
1230
|
+
acted.push(performance.now());
|
|
1231
|
+
if (acted.length > 16) acted.shift();
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Whether a replay or Autopilot acted in the half second before `at` (a performance.now() time): as the browser
|
|
1235
|
+
* leaves out what moves right after a person's input (a menu opening where it was clicked), what moves right after a
|
|
1236
|
+
* scripted step is that step's doing, not the page shifting under the reader.
|
|
1237
|
+
*/
|
|
1238
|
+
function scriptedBefore(at, ms = 500) {
|
|
1239
|
+
return acted.some((t) => t <= at && at - t <= ms);
|
|
1240
|
+
}
|
|
1241
|
+
//#endregion
|
|
1199
1242
|
//#region ../../packages/vite/src/bridge/actions.ts
|
|
1200
1243
|
var CLICKABLE$2 = "button, a[href], [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"checkbox\"], [role=\"radio\"], input, select, textarea, label, summary, [tabindex]";
|
|
1201
1244
|
/** The element a click at (x, y) should activate. */
|
|
@@ -1208,6 +1251,7 @@ var lastField = null;
|
|
|
1208
1251
|
function clickAt(x, y) {
|
|
1209
1252
|
const el = clickTargetAt(x, y);
|
|
1210
1253
|
if (!el) return null;
|
|
1254
|
+
scriptedInput();
|
|
1211
1255
|
if (el instanceof HTMLElement) {
|
|
1212
1256
|
if (isField(el) && !(el instanceof HTMLSelectElement) && ![
|
|
1213
1257
|
"checkbox",
|
|
@@ -1235,6 +1279,7 @@ function setNativeValue(el, value) {
|
|
|
1235
1279
|
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1236
1280
|
}
|
|
1237
1281
|
function typeText(text, submit) {
|
|
1282
|
+
scriptedInput();
|
|
1238
1283
|
const active = document.activeElement;
|
|
1239
1284
|
const target = active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement ? active : lastField && lastField.isConnected ? lastField : null;
|
|
1240
1285
|
if (!target) return null;
|
|
@@ -2508,6 +2553,7 @@ var mouse = (type, x, y) => new MouseEvent(type, {
|
|
|
2508
2553
|
*/
|
|
2509
2554
|
function act(el, step) {
|
|
2510
2555
|
if (unusable(el)) throw new Error("disabled");
|
|
2556
|
+
scriptedInput();
|
|
2511
2557
|
const r = el.getBoundingClientRect();
|
|
2512
2558
|
const x = r.left + r.width / 2;
|
|
2513
2559
|
const y = r.top + r.height / 2;
|
|
@@ -2811,33 +2857,6 @@ var Recorder = class {
|
|
|
2811
2857
|
}
|
|
2812
2858
|
};
|
|
2813
2859
|
//#endregion
|
|
2814
|
-
//#region ../../packages/vite/src/bridge/own.ts
|
|
2815
|
-
/**
|
|
2816
|
-
* Saywright's own work on the app's page (reading the screen, building
|
|
2817
|
-
* locators, answering the studio): timed, so a slow moment it caused is never
|
|
2818
|
-
* reported as the app's.
|
|
2819
|
-
*/
|
|
2820
|
-
var spans = [];
|
|
2821
|
-
/** Run some of our own work, remembering when it ran if it took long enough to matter. */
|
|
2822
|
-
function own(fn) {
|
|
2823
|
-
const start = performance.now();
|
|
2824
|
-
try {
|
|
2825
|
-
return fn();
|
|
2826
|
-
} finally {
|
|
2827
|
-
const end = performance.now();
|
|
2828
|
-
if (end - start >= 8) {
|
|
2829
|
-
spans.push([start, end]);
|
|
2830
|
-
if (spans.length > 64) spans.splice(0, spans.length - 64);
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
/** How much of [start, end] (performance.now() times) was our own work. */
|
|
2835
|
-
function ownTime(start, end) {
|
|
2836
|
-
let ms = 0;
|
|
2837
|
-
for (const [a, b] of spans) ms += Math.max(0, Math.min(b, end) - Math.max(a, start));
|
|
2838
|
-
return ms;
|
|
2839
|
-
}
|
|
2840
|
-
//#endregion
|
|
2841
2860
|
//#region ../../packages/vite/src/bridge/proxy.ts
|
|
2842
2861
|
/**
|
|
2843
2862
|
* When the studio shows a site through its proxy, the site's own absolute
|
|
@@ -3422,6 +3441,8 @@ var RequestTally = class {
|
|
|
3422
3441
|
*/
|
|
3423
3442
|
var ACTIVE_KEY = "saywright.session";
|
|
3424
3443
|
var PENDING_KEY = "saywright.session.pending";
|
|
3444
|
+
/** What counts as another target near a small one, as the Experts count it (a box's own label is part of it). */
|
|
3445
|
+
var TARGETS = "a[href], button, input:not([type=hidden]), select, textarea, summary, [role=\"button\"], [role=\"link\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"tab\"], [role=\"menuitem\"], [tabindex]:not([tabindex=\"-1\"])";
|
|
3425
3446
|
var INTERACTIVE = "button, a[href], input, select, textarea, summary, label, [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [role=\"option\"], [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])";
|
|
3426
3447
|
/** Requests that are the dev server or the studio talking, not the app. */
|
|
3427
3448
|
var PLUMBING_URL = /\/@vite\/|\/@fs\/|\/@id\/|\/@react-refresh|\/node_modules\/|__saywright|\.hot-update\.|\/_next\/webpack-hmr|\/__nextjs|sockjs-node|\/api\/sessions\/|\/api\/speech\//;
|
|
@@ -3531,6 +3552,20 @@ function shortUrl(url) {
|
|
|
3531
3552
|
return url;
|
|
3532
3553
|
}
|
|
3533
3554
|
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Whether something else to click is within 12 px of a small target's centre, where a 24 px circle on it would reach
|
|
3557
|
+
* (WCAG 2.5.8): looked for around it, so it costs the same on a page of any size.
|
|
3558
|
+
*/
|
|
3559
|
+
function crowded(el, r) {
|
|
3560
|
+
const cx = r.left + r.width / 2;
|
|
3561
|
+
const cy = r.top + r.height / 2;
|
|
3562
|
+
for (const radius of [6, 11.5]) for (let k = 0; k < 16; k++) {
|
|
3563
|
+
const a = k / 16 * 2 * Math.PI;
|
|
3564
|
+
const other = document.elementFromPoint(cx + radius * Math.cos(a), cy + radius * Math.sin(a))?.closest(TARGETS);
|
|
3565
|
+
if (other && other !== el && !el.contains(other) && !other.contains(el)) return true;
|
|
3566
|
+
}
|
|
3567
|
+
return false;
|
|
3568
|
+
}
|
|
3534
3569
|
var SessionRecorder = class {
|
|
3535
3570
|
onStats;
|
|
3536
3571
|
onMoment;
|
|
@@ -3769,6 +3804,7 @@ var SessionRecorder = class {
|
|
|
3769
3804
|
this.fibers.set(t, fiber);
|
|
3770
3805
|
}
|
|
3771
3806
|
if (tag === "a" && getComputedStyle(el).display === "inline") t.inline = true;
|
|
3807
|
+
if ((r.width < 24 || r.height < 24) && r.width > 0 && r.height > 0 && !crowded(el, r)) t.spaced = true;
|
|
3772
3808
|
return t;
|
|
3773
3809
|
}
|
|
3774
3810
|
interactive(el) {
|
|
@@ -4364,7 +4400,7 @@ var SessionRecorder = class {
|
|
|
4364
4400
|
});
|
|
4365
4401
|
observe("layout-shift", (es) => {
|
|
4366
4402
|
for (const e of es) {
|
|
4367
|
-
if (e.hadRecentInput || e.startTime < this.since) continue;
|
|
4403
|
+
if (e.hadRecentInput || scriptedBefore(e.startTime) || e.startTime < this.since) continue;
|
|
4368
4404
|
this.cls += e.value;
|
|
4369
4405
|
if (e.value < .02) continue;
|
|
4370
4406
|
const node = e.sources?.find((s) => s.node instanceof Element)?.node;
|
|
@@ -4868,7 +4904,17 @@ var ScreenCapture = class {
|
|
|
4868
4904
|
* the studio and by the recorder inside the app's own tab.
|
|
4869
4905
|
*/
|
|
4870
4906
|
var PENDING_AUDIO = "saywright.session.audio";
|
|
4871
|
-
/**
|
|
4907
|
+
/** After `ms`, once the page has drawn again (a page not drawing, in the background, doesn't hold it up). */
|
|
4908
|
+
function painted(ms) {
|
|
4909
|
+
return new Promise((resolve) => setTimeout(() => {
|
|
4910
|
+
if (typeof requestAnimationFrame !== "function") return resolve();
|
|
4911
|
+
const done = setTimeout(resolve, 100);
|
|
4912
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
4913
|
+
clearTimeout(done);
|
|
4914
|
+
resolve();
|
|
4915
|
+
}));
|
|
4916
|
+
}, ms));
|
|
4917
|
+
}
|
|
4872
4918
|
var SCREEN_WAIT_MS = 3e4;
|
|
4873
4919
|
function toBase64(buf) {
|
|
4874
4920
|
const bytes = new Uint8Array(buf);
|
|
@@ -5356,6 +5402,7 @@ var SessionClient = class {
|
|
|
5356
5402
|
id,
|
|
5357
5403
|
countdown: null
|
|
5358
5404
|
});
|
|
5405
|
+
if (this.screen && opts.countdown) await painted(220);
|
|
5359
5406
|
this.screen?.record(opts.video);
|
|
5360
5407
|
try {
|
|
5361
5408
|
await opts.onCreated?.(id, this.key);
|
|
@@ -6267,9 +6314,10 @@ function facts() {
|
|
|
6267
6314
|
label: fieldLabel(f),
|
|
6268
6315
|
type: f.type || f.tagName.toLowerCase(),
|
|
6269
6316
|
autocomplete: f.getAttribute("autocomplete"),
|
|
6270
|
-
required: f.required
|
|
6317
|
+
required: f.required,
|
|
6318
|
+
...f.getAttribute("inputmode") ? { inputmode: f.getAttribute("inputmode") } : {}
|
|
6271
6319
|
})),
|
|
6272
|
-
buttons: Array.from(document.querySelectorAll("button, [role=\"button\"], input[type=submit]")).filter(shown).map((b) => collapse(b
|
|
6320
|
+
buttons: Array.from(document.querySelectorAll("button, [role=\"button\"], input[type=submit]")).filter(shown).map((b) => collapse(accessibleName(b) || b.value || "", 40) || "(no name)").slice(0, 30),
|
|
6273
6321
|
lcp,
|
|
6274
6322
|
cls: watching ? Math.round(cls * 1e3) / 1e3 : null,
|
|
6275
6323
|
readability: readability(text),
|
|
@@ -6311,7 +6359,7 @@ function linkTargets() {
|
|
|
6311
6359
|
const out = [];
|
|
6312
6360
|
for (const a of Array.from(document.querySelectorAll("a[href]")).filter(shown)) {
|
|
6313
6361
|
const link = {
|
|
6314
|
-
text: collapse(a
|
|
6362
|
+
text: collapse(accessibleName(a), 40) || "(no name)",
|
|
6315
6363
|
href: redactUrl(a.getAttribute("href") ?? "").slice(0, 120)
|
|
6316
6364
|
};
|
|
6317
6365
|
if (!out.some((l) => l.text === link.text && l.href === link.href)) out.push(link);
|