@aayambansal/squint 0.8.0 → 0.8.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/{App-I2MB7S6W.js → App-HXOPKQKZ.js} +5 -5
- package/dist/{cdp-DV4D6PUL.js → cdp-4FWGQ3JR.js} +1 -1
- package/dist/{checks-4OK5YYGW.js → checks-JNJST4ET.js} +1 -1
- package/dist/{chunk-ODSCWVBX.js → chunk-32I2JTFS.js} +9 -9
- package/dist/{chunk-USAZFY36.js → chunk-ASZUQJPE.js} +2 -2
- package/dist/{chunk-WOPJFP4M.js → chunk-B7T5DYRM.js} +2 -2
- package/dist/{chunk-OSKYU4PM.js → chunk-CY34VHDM.js} +1 -1
- package/dist/{chunk-GAWQ7YXC.js → chunk-DAXGJ57E.js} +1 -1
- package/dist/{chunk-PC45PRK3.js → chunk-MG4OPCQO.js} +18 -8
- package/dist/{chunk-HFQZDT65.js → chunk-ZQRBBX7G.js} +128 -2
- package/dist/cli.js +16 -16
- package/dist/{flows-XORMW4DA.js → flows-3PMJYMDM.js} +1 -1
- package/dist/{preview-CDSJ5WBQ.js → preview-YD27QUDE.js} +3 -3
- package/dist/{receipts-HMBEL7NM.js → receipts-HN5HGWMT.js} +1 -1
- package/dist/{remote-WLUNMQRO.js → remote-SZPZH5RL.js} +5 -5
- package/dist/{server-PSXRO2XN.js → server-57MLWLK6.js} +8 -7
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
App
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-CY34VHDM.js";
|
|
5
|
+
import "./chunk-32I2JTFS.js";
|
|
6
6
|
import "./chunk-YKEAKVIS.js";
|
|
7
7
|
import "./chunk-LNJ7O5O4.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-MG4OPCQO.js";
|
|
9
9
|
import "./chunk-UBADCBPT.js";
|
|
10
10
|
import "./chunk-IEQGTVWN.js";
|
|
11
11
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -16,10 +16,10 @@ import "./chunk-K5QJMSJH.js";
|
|
|
16
16
|
import "./chunk-WAJXATCO.js";
|
|
17
17
|
import "./chunk-62JNF5M2.js";
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-ZQRBBX7G.js";
|
|
20
20
|
import "./chunk-55JZKI2M.js";
|
|
21
21
|
import "./chunk-HIUQ4FXO.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-DAXGJ57E.js";
|
|
23
23
|
export {
|
|
24
24
|
App
|
|
25
25
|
};
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
comparePulseAttributed,
|
|
13
13
|
probeRuntime,
|
|
14
14
|
runtimeSummary
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MG4OPCQO.js";
|
|
16
16
|
import {
|
|
17
17
|
enrich
|
|
18
18
|
} from "./chunk-UBADCBPT.js";
|
|
@@ -274,7 +274,7 @@ ${question}`,
|
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
276
|
await this.runTurn(
|
|
277
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank),
|
|
277
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale),
|
|
278
278
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
279
279
|
);
|
|
280
280
|
}
|
|
@@ -665,7 +665,7 @@ ${probe.checkFailures.slice(0, 5).join("\n")}`);
|
|
|
665
665
|
const captureResult = await this.capture();
|
|
666
666
|
if (captureResult) {
|
|
667
667
|
await this.runTurn(
|
|
668
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp, captureResult.jank),
|
|
668
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp, captureResult.jank, captureResult.locale),
|
|
669
669
|
"\u{1F441} auto-review rendered UI"
|
|
670
670
|
);
|
|
671
671
|
}
|
|
@@ -812,7 +812,7 @@ squint verifies every turn \u2014 gates, runtime probe, page audits. Do not decl
|
|
|
812
812
|
this.push("image", pulsePath);
|
|
813
813
|
return null;
|
|
814
814
|
}
|
|
815
|
-
const { previewDir } = await import("./preview-
|
|
815
|
+
const { previewDir } = await import("./preview-YD27QUDE.js");
|
|
816
816
|
const diff = await comparePulseAttributed(
|
|
817
817
|
previous,
|
|
818
818
|
current,
|
|
@@ -1190,7 +1190,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1190
1190
|
this.push("error", "no Chrome/Chromium found");
|
|
1191
1191
|
return;
|
|
1192
1192
|
}
|
|
1193
|
-
const { suggestFlows } = await import("./flows-
|
|
1193
|
+
const { suggestFlows } = await import("./flows-3PMJYMDM.js");
|
|
1194
1194
|
this.push("status", "drafting flows from the live routes\u2026");
|
|
1195
1195
|
const { created, skipped } = await suggestFlows(this.opts.cwd, this.state.devUrl, chrome);
|
|
1196
1196
|
if (created.length > 0) this.push("status", `drafted ${created.length} flow(s): ${created.join(", ")} \u2014 edit .squint/flows/*.flow or ask the engine to deepen them`);
|
|
@@ -1200,7 +1200,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1200
1200
|
break;
|
|
1201
1201
|
}
|
|
1202
1202
|
void (async () => {
|
|
1203
|
-
const { loadFlows } = await import("./flows-
|
|
1203
|
+
const { loadFlows } = await import("./flows-3PMJYMDM.js");
|
|
1204
1204
|
const flows = loadFlows(this.opts.cwd);
|
|
1205
1205
|
if (flows.length === 0) {
|
|
1206
1206
|
this.push("status", "no flows \u2014 add .squint/flows/<name>.flow (goto/click/fill/press/expect/shot lines), or ask the engine to write one");
|
|
@@ -1211,8 +1211,8 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1211
1211
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1212
1212
|
return;
|
|
1213
1213
|
}
|
|
1214
|
-
const { runFlow } = await import("./cdp-
|
|
1215
|
-
const { previewDir } = await import("./preview-
|
|
1214
|
+
const { runFlow } = await import("./cdp-4FWGQ3JR.js");
|
|
1215
|
+
const { previewDir } = await import("./preview-YD27QUDE.js");
|
|
1216
1216
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1217
1217
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1218
1218
|
const failures = [];
|
|
@@ -1351,7 +1351,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1351
1351
|
const result = await this.capture();
|
|
1352
1352
|
if (result) {
|
|
1353
1353
|
await this.runTurn(
|
|
1354
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank),
|
|
1354
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale),
|
|
1355
1355
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1356
1356
|
);
|
|
1357
1357
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
Session
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-32I2JTFS.js";
|
|
5
5
|
|
|
6
6
|
// src/daemon/client.ts
|
|
7
7
|
import net from "net";
|
|
@@ -122,7 +122,7 @@ function startDaemon(opts) {
|
|
|
122
122
|
const state = session.getState();
|
|
123
123
|
if (!state.devUrl || state.running) return;
|
|
124
124
|
try {
|
|
125
|
-
const { runIntervalSweep } = await import("./checks-
|
|
125
|
+
const { runIntervalSweep } = await import("./checks-JNJST4ET.js");
|
|
126
126
|
const failures = await runIntervalSweep(opts.cwd, state.devUrl, lastRun);
|
|
127
127
|
if (failures.length > 0) {
|
|
128
128
|
session.note(`\u23F0 interval check(s) failing:
|
|
@@ -143,8 +143,8 @@ function stepExpression(step) {
|
|
|
143
143
|
async function suggestFlows(cwd, baseUrl, chromePath) {
|
|
144
144
|
const fs2 = await import("fs");
|
|
145
145
|
const path2 = await import("path");
|
|
146
|
-
const { cdpCapture } = await import("./cdp-
|
|
147
|
-
const { loadRoutes } = await import("./preview-
|
|
146
|
+
const { cdpCapture } = await import("./cdp-4FWGQ3JR.js");
|
|
147
|
+
const { loadRoutes } = await import("./preview-YD27QUDE.js");
|
|
148
148
|
const dir = path2.join(cwd, ".squint", "flows");
|
|
149
149
|
fs2.mkdirSync(dir, { recursive: true });
|
|
150
150
|
const created = [];
|
|
@@ -44,7 +44,7 @@ async function runIntervalSweep(cwd, url, lastRun) {
|
|
|
44
44
|
const chrome = findChrome();
|
|
45
45
|
if (!chrome) return [];
|
|
46
46
|
const os = await import("os");
|
|
47
|
-
const { cdpCapture } = await import("./cdp-
|
|
47
|
+
const { cdpCapture } = await import("./cdp-4FWGQ3JR.js");
|
|
48
48
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "squint-interval-"));
|
|
49
49
|
try {
|
|
50
50
|
const result = await cdpCapture(chrome, url, tmp, [], 800, false, due);
|
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
cdpCapture,
|
|
7
7
|
hasWebSocket
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZQRBBX7G.js";
|
|
9
9
|
import {
|
|
10
10
|
findChrome,
|
|
11
11
|
screenshot
|
|
12
12
|
} from "./chunk-55JZKI2M.js";
|
|
13
13
|
import {
|
|
14
14
|
loadChecks
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-DAXGJ57E.js";
|
|
16
16
|
|
|
17
17
|
// src/preview/preview.ts
|
|
18
18
|
import fs from "fs";
|
|
@@ -51,7 +51,7 @@ async function captureViewports(cwd, url) {
|
|
|
51
51
|
if (hasWebSocket()) {
|
|
52
52
|
try {
|
|
53
53
|
const checks = loadChecks(cwd);
|
|
54
|
-
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
54
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
55
55
|
const errors2 = [];
|
|
56
56
|
for (const route of routes.slice(1)) {
|
|
57
57
|
try {
|
|
@@ -66,7 +66,7 @@ async function captureViewports(cwd, url) {
|
|
|
66
66
|
errors2.push(`${route}: capture failed`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank };
|
|
69
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale };
|
|
70
70
|
} catch {
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -139,13 +139,13 @@ async function probeRuntime(url, cwd) {
|
|
|
139
139
|
async function comparePulse(previous, current) {
|
|
140
140
|
const chrome = findChrome();
|
|
141
141
|
if (!chrome || !hasWebSocket()) return null;
|
|
142
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
142
|
+
const { pixelDiffPct } = await import("./cdp-4FWGQ3JR.js");
|
|
143
143
|
return pixelDiffPct(chrome, previous, current);
|
|
144
144
|
}
|
|
145
145
|
async function comparePulseAttributed(previous, current, url, outPath) {
|
|
146
146
|
const chrome = findChrome();
|
|
147
147
|
if (!chrome || !hasWebSocket()) return null;
|
|
148
|
-
const { pixelDiffAttributed } = await import("./cdp-
|
|
148
|
+
const { pixelDiffAttributed } = await import("./cdp-4FWGQ3JR.js");
|
|
149
149
|
return pixelDiffAttributed(chrome, previous, current, url, outPath);
|
|
150
150
|
}
|
|
151
151
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -173,6 +173,16 @@ ${narration.join("\n")}
|
|
|
173
173
|
|
|
174
174
|
Judge this narration as an experience: does the reading order make sense? do names actually describe their targets? is anything announced as "(no accessible name)"? Fix real incoherence \u2014 this is how non-visual users meet the page.`;
|
|
175
175
|
}
|
|
176
|
+
function localeSection(locale) {
|
|
177
|
+
if (!locale || locale.length === 0) return "";
|
|
178
|
+
return `
|
|
179
|
+
|
|
180
|
+
## Locale findings (pseudo-localization + RTL)
|
|
181
|
+
|
|
182
|
+
${locale.join("\n")}
|
|
183
|
+
|
|
184
|
+
Text that clips at +40% expansion truncates in real translations; explicit left-alignment ignores RTL users. Fix with flexible widths and logical properties (text-align: start, margin-inline), not by shortening copy.`;
|
|
185
|
+
}
|
|
176
186
|
function jankSection(jank) {
|
|
177
187
|
if (!jank || jank.length === 0) return "";
|
|
178
188
|
return `
|
|
@@ -233,13 +243,13 @@ ${findings.join("\n")}
|
|
|
233
243
|
|
|
234
244
|
These patterns make the page read as template output. Rework them within the committed design direction \u2014 this is style debt, not a defect list.`;
|
|
235
245
|
}
|
|
236
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp, jank) {
|
|
246
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp, jank, locale) {
|
|
237
247
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
238
248
|
return `Screenshots of the running app were just captured:
|
|
239
249
|
|
|
240
250
|
${list}
|
|
241
251
|
|
|
242
|
-
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}${componentSection(components)}${webmcpSection(webmcp)}${jankSection(jank)}`;
|
|
252
|
+
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}${componentSection(components)}${webmcpSection(webmcp)}${jankSection(jank)}${localeSection(locale)}`;
|
|
243
253
|
}
|
|
244
254
|
|
|
245
255
|
export {
|
|
@@ -211,7 +211,7 @@ function summarizeSoftNav(entries) {
|
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
213
|
async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
214
|
-
const { stepExpression } = await import("./flows-
|
|
214
|
+
const { stepExpression } = await import("./flows-3PMJYMDM.js");
|
|
215
215
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
216
216
|
const shots = [];
|
|
217
217
|
let transitions = [];
|
|
@@ -746,6 +746,60 @@ var SCRIPTED_SCROLL = `(async () => {
|
|
|
746
746
|
.slice(0, 6)
|
|
747
747
|
.map((e) => e.duration + 'ms frame \u2014 ' + (e.fn || e.invoker || 'script') + (e.url ? ' @ ' + e.url : ''));
|
|
748
748
|
})()`;
|
|
749
|
+
var LOCALE_AUDIT = `(async () => {
|
|
750
|
+
const findings = [];
|
|
751
|
+
const label = (el) => {
|
|
752
|
+
let out = el.tagName.toLowerCase();
|
|
753
|
+
if (el.id) out += '#' + el.id;
|
|
754
|
+
else if (el.classList[0]) out += '.' + el.classList[0];
|
|
755
|
+
return out;
|
|
756
|
+
};
|
|
757
|
+
const MAP = { a:'\xE1', e:'\xE9', i:'\xED', o:'\xF3', u:'\xFC', A:'\xC1', E:'\xC9', I:'\xCD', O:'\xD3', U:'\xDC', n:'\xF1', N:'\xD1' };
|
|
758
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
|
|
759
|
+
acceptNode: (n) => {
|
|
760
|
+
const parent = n.parentElement;
|
|
761
|
+
if (!parent || /^(SCRIPT|STYLE|NOSCRIPT)$/.test(parent.tagName)) return NodeFilter.FILTER_REJECT;
|
|
762
|
+
return n.textContent.trim().length > 1 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
const nodes = [];
|
|
766
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
767
|
+
for (const node of nodes.slice(0, 400)) {
|
|
768
|
+
const text = node.textContent;
|
|
769
|
+
const accented = text.replace(/[aeiouAEIOUnN]/g, (c) => MAP[c] || c);
|
|
770
|
+
const pad = '\xB7'.repeat(Math.ceil(text.trim().length * 0.4));
|
|
771
|
+
node.textContent = accented + pad;
|
|
772
|
+
}
|
|
773
|
+
await new Promise((r) => requestAnimationFrame(() => setTimeout(r, 120)));
|
|
774
|
+
const clipped = new Set();
|
|
775
|
+
for (const el of document.querySelectorAll('button, a, h1, h2, h3, [class*="btn"], [class*="badge"], nav *, th, td, li, label, span, p')) {
|
|
776
|
+
if (clipped.size >= 6) break;
|
|
777
|
+
if (el.children.length > 0) continue;
|
|
778
|
+
if (el.scrollWidth > el.clientWidth + 3 && el.clientWidth > 0) {
|
|
779
|
+
const key = label(el);
|
|
780
|
+
if (!clipped.has(key)) {
|
|
781
|
+
clipped.add(key);
|
|
782
|
+
findings.push('locale: <' + key + '> clips at +40% text expansion \u2014 real translations will truncate here');
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
document.documentElement.setAttribute('dir', 'rtl');
|
|
787
|
+
await new Promise((r) => requestAnimationFrame(() => setTimeout(r, 120)));
|
|
788
|
+
let leftHardcodes = 0;
|
|
789
|
+
for (const el of document.querySelectorAll('p, h1, h2, h3, li, td, label, div')) {
|
|
790
|
+
if (leftHardcodes >= 3) break;
|
|
791
|
+
const text = (el.textContent || '').trim();
|
|
792
|
+
if (text.length < 10 || el.children.length > 2) continue;
|
|
793
|
+
if (getComputedStyle(el).textAlign === 'left') {
|
|
794
|
+
findings.push('locale: <' + label(el) + '> hardcodes text-align:left \u2014 ignores RTL (use start)');
|
|
795
|
+
leftHardcodes++;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (document.documentElement.scrollWidth > innerWidth + 4) {
|
|
799
|
+
findings.push('locale: the page overflows horizontally under RTL');
|
|
800
|
+
}
|
|
801
|
+
return findings;
|
|
802
|
+
})()`;
|
|
749
803
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false, checks = []) {
|
|
750
804
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
751
805
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
@@ -759,6 +813,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
759
813
|
let components = [];
|
|
760
814
|
const checkFailures = [];
|
|
761
815
|
let webmcp = [];
|
|
816
|
+
let locale = [];
|
|
762
817
|
let jank = [];
|
|
763
818
|
const requests = /* @__PURE__ */ new Map();
|
|
764
819
|
let connection = null;
|
|
@@ -996,12 +1051,83 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
996
1051
|
fs.writeFileSync(outPath, Buffer.from(data, "base64"));
|
|
997
1052
|
shots.push({ name: viewport.name, path: outPath });
|
|
998
1053
|
}
|
|
1054
|
+
if (audit) {
|
|
1055
|
+
try {
|
|
1056
|
+
await connection.send(
|
|
1057
|
+
"Emulation.setEmulatedMedia",
|
|
1058
|
+
{ features: [{ name: "forced-colors", value: "active" }] },
|
|
1059
|
+
sessionId
|
|
1060
|
+
);
|
|
1061
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1062
|
+
const { result } = await connection.send(
|
|
1063
|
+
"Runtime.evaluate",
|
|
1064
|
+
{
|
|
1065
|
+
expression: `(() => {
|
|
1066
|
+
const out = [];
|
|
1067
|
+
for (const el of document.querySelectorAll('button, a, [role="button"], input[type="submit"]')) {
|
|
1068
|
+
if (out.length >= 5) break;
|
|
1069
|
+
const cs = getComputedStyle(el);
|
|
1070
|
+
if (cs.display === 'none' || !el.textContent || !el.textContent.trim()) continue;
|
|
1071
|
+
if (cs.color === cs.backgroundColor) {
|
|
1072
|
+
let label = el.tagName.toLowerCase();
|
|
1073
|
+
if (el.id) label += '#' + el.id; else if (el.classList[0]) label += '.' + el.classList[0];
|
|
1074
|
+
out.push('forced-colors: <' + label + '> text matches its background \u2014 invisible in high-contrast mode');
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return out;
|
|
1078
|
+
})()`,
|
|
1079
|
+
returnByValue: true
|
|
1080
|
+
},
|
|
1081
|
+
sessionId
|
|
1082
|
+
);
|
|
1083
|
+
if (Array.isArray(result?.value)) a11y.push(...result.value.map(String));
|
|
1084
|
+
await connection.send("Emulation.setEmulatedMedia", { features: [] }, sessionId);
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
try {
|
|
1088
|
+
await connection.send("Emulation.setEmulatedMedia", { media: "print" }, sessionId);
|
|
1089
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1090
|
+
const { result } = await connection.send(
|
|
1091
|
+
"Runtime.evaluate",
|
|
1092
|
+
{
|
|
1093
|
+
expression: `(() => {
|
|
1094
|
+
const out = [];
|
|
1095
|
+
const bodyH = document.body.getBoundingClientRect().height;
|
|
1096
|
+
if (getComputedStyle(document.body).display === 'none' || bodyH < 40) {
|
|
1097
|
+
out.push('print: the page prints blank');
|
|
1098
|
+
}
|
|
1099
|
+
for (const el of document.querySelectorAll('nav, aside')) {
|
|
1100
|
+
if (getComputedStyle(el).display !== 'none' && el.getBoundingClientRect().height > 40) {
|
|
1101
|
+
out.push('print: <' + el.tagName.toLowerCase() + '> still renders in print output');
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return out;
|
|
1106
|
+
})()`,
|
|
1107
|
+
returnByValue: true
|
|
1108
|
+
},
|
|
1109
|
+
sessionId
|
|
1110
|
+
);
|
|
1111
|
+
if (Array.isArray(result?.value)) slop.push(...result.value.map(String));
|
|
1112
|
+
await connection.send("Emulation.setEmulatedMedia", { media: "" }, sessionId);
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1115
|
+
try {
|
|
1116
|
+
const { result } = await connection.send(
|
|
1117
|
+
"Runtime.evaluate",
|
|
1118
|
+
{ expression: LOCALE_AUDIT, returnByValue: true, awaitPromise: true },
|
|
1119
|
+
sessionId
|
|
1120
|
+
);
|
|
1121
|
+
if (Array.isArray(result?.value)) locale = result.value.map(String);
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
999
1125
|
} finally {
|
|
1000
1126
|
connection?.close();
|
|
1001
1127
|
child.kill("SIGKILL");
|
|
1002
1128
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
1003
1129
|
}
|
|
1004
|
-
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank };
|
|
1130
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale };
|
|
1005
1131
|
}
|
|
1006
1132
|
|
|
1007
1133
|
export {
|
package/dist/cli.js
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath,
|
|
5
5
|
startDaemon
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ASZUQJPE.js";
|
|
7
7
|
import {
|
|
8
8
|
App
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-CY34VHDM.js";
|
|
10
|
+
import "./chunk-32I2JTFS.js";
|
|
11
11
|
import "./chunk-YKEAKVIS.js";
|
|
12
12
|
import "./chunk-LNJ7O5O4.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-MG4OPCQO.js";
|
|
14
14
|
import "./chunk-UBADCBPT.js";
|
|
15
15
|
import "./chunk-IEQGTVWN.js";
|
|
16
16
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -25,13 +25,13 @@ import {
|
|
|
25
25
|
} from "./chunk-WAJXATCO.js";
|
|
26
26
|
import "./chunk-62JNF5M2.js";
|
|
27
27
|
import "./chunk-7CAGWFAQ.js";
|
|
28
|
-
import "./chunk-
|
|
28
|
+
import "./chunk-ZQRBBX7G.js";
|
|
29
29
|
import "./chunk-55JZKI2M.js";
|
|
30
30
|
import {
|
|
31
31
|
detectEngines,
|
|
32
32
|
getEngine
|
|
33
33
|
} from "./chunk-HIUQ4FXO.js";
|
|
34
|
-
import "./chunk-
|
|
34
|
+
import "./chunk-DAXGJ57E.js";
|
|
35
35
|
|
|
36
36
|
// src/cli.tsx
|
|
37
37
|
import { Command } from "commander";
|
|
@@ -188,18 +188,18 @@ function registerDaemon(program2) {
|
|
|
188
188
|
process.on("SIGTERM", stop);
|
|
189
189
|
});
|
|
190
190
|
program2.command("mcp").description("serve the gates as MCP tools over stdio (check, shot, flows, context)").action(async () => {
|
|
191
|
-
const { runMcpServer } = await import("./server-
|
|
191
|
+
const { runMcpServer } = await import("./server-57MLWLK6.js");
|
|
192
192
|
runMcpServer(process.cwd());
|
|
193
193
|
});
|
|
194
194
|
program2.command("attach").description("attach this terminal to a running squint daemon (full TUI; --plain for line mode)").option("--plain", "line-mode attach instead of the full TUI").action(async (opts) => {
|
|
195
195
|
const cwd = process.cwd();
|
|
196
196
|
if (!opts.plain) {
|
|
197
197
|
try {
|
|
198
|
-
const { RemoteSession } = await import("./remote-
|
|
198
|
+
const { RemoteSession } = await import("./remote-SZPZH5RL.js");
|
|
199
199
|
const remote = await RemoteSession.connect(cwd);
|
|
200
200
|
const config = loadConfig(defaultPaths(cwd));
|
|
201
201
|
const { render: render2 } = await import("ink");
|
|
202
|
-
const { App: App2 } = await import("./App-
|
|
202
|
+
const { App: App2 } = await import("./App-HXOPKQKZ.js");
|
|
203
203
|
const React = await import("react");
|
|
204
204
|
render2(
|
|
205
205
|
React.createElement(App2, {
|
|
@@ -316,7 +316,7 @@ function registerEnv(program2) {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
const { findChrome } = await import("./chrome-BQ4WOCOH.js");
|
|
319
|
-
const { hasWebSocket } = await import("./cdp-
|
|
319
|
+
const { hasWebSocket } = await import("./cdp-4FWGQ3JR.js");
|
|
320
320
|
const chrome = findChrome();
|
|
321
321
|
console.log(
|
|
322
322
|
chrome ? `${pc3.green("\u2713")} Chrome ${pc3.dim(chrome)}` : `${pc3.yellow("\u25CB")} Chrome ${pc3.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -552,7 +552,7 @@ function registerQuality(program2) {
|
|
|
552
552
|
report.gates = gateResults.map((r) => ({ id: r.gate.id, ok: r.ok, durationMs: r.durationMs, outputTail: r.ok ? void 0 : r.outputTail }));
|
|
553
553
|
if (gateResults.some((r) => !r.ok)) failed = true;
|
|
554
554
|
if (opts.url) {
|
|
555
|
-
const { captureViewports, runtimeSummary, previewDir } = await import("./preview-
|
|
555
|
+
const { captureViewports, runtimeSummary, previewDir } = await import("./preview-YD27QUDE.js");
|
|
556
556
|
const capture = await captureViewports(cwd, opts.url);
|
|
557
557
|
if (!capture) {
|
|
558
558
|
console.log(pc5.red("\u2717 audit: capture failed (Chrome missing or URL unreachable)"));
|
|
@@ -584,14 +584,14 @@ function registerQuality(program2) {
|
|
|
584
584
|
console.log(`${pc5.green("\u2713")} audit ${pc5.dim(`(${advisories.a11y.length} a11y, ${advisories.slop.length} slop, ${advisories.jank.length} jank advisories)`)}`);
|
|
585
585
|
}
|
|
586
586
|
}
|
|
587
|
-
const { loadFlows } = await import("./flows-
|
|
587
|
+
const { loadFlows } = await import("./flows-3PMJYMDM.js");
|
|
588
588
|
const flows = loadFlows(cwd);
|
|
589
589
|
if (flows.length > 0) {
|
|
590
590
|
const { findChrome } = await import("./chrome-BQ4WOCOH.js");
|
|
591
591
|
const chrome = findChrome();
|
|
592
592
|
const flowReport = [];
|
|
593
593
|
if (chrome) {
|
|
594
|
-
const { runFlow } = await import("./cdp-
|
|
594
|
+
const { runFlow } = await import("./cdp-4FWGQ3JR.js");
|
|
595
595
|
for (const flow of flows) {
|
|
596
596
|
const result = await runFlow(chrome, opts.url, flow, previewDir(cwd));
|
|
597
597
|
flowReport.push({ name: flow.name, ok: result.ok, detail: result.ok ? void 0 : `step ${result.failedStep}: ${result.detail}` });
|
|
@@ -605,7 +605,7 @@ function registerQuality(program2) {
|
|
|
605
605
|
report.ok = !failed;
|
|
606
606
|
report.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
607
607
|
try {
|
|
608
|
-
const { writeReceipt } = await import("./receipts-
|
|
608
|
+
const { writeReceipt } = await import("./receipts-HN5HGWMT.js");
|
|
609
609
|
console.log(pc5.dim(`receipt \u2192 ${writeReceipt(cwd, report)}`));
|
|
610
610
|
} catch {
|
|
611
611
|
}
|
|
@@ -617,7 +617,7 @@ function registerQuality(program2) {
|
|
|
617
617
|
if (failed) process.exitCode = 1;
|
|
618
618
|
});
|
|
619
619
|
program2.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports (+ .squint/routes)").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
620
|
-
const { captureViewports } = await import("./preview-
|
|
620
|
+
const { captureViewports } = await import("./preview-YD27QUDE.js");
|
|
621
621
|
const result = await captureViewports(process.cwd(), url);
|
|
622
622
|
if (!result) {
|
|
623
623
|
console.error(pc5.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -804,7 +804,7 @@ function registerTui(program2) {
|
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
// src/cli.tsx
|
|
807
|
-
var VERSION = true ? "0.8.
|
|
807
|
+
var VERSION = true ? "0.8.1" : "0.0.0-dev";
|
|
808
808
|
var program = new Command();
|
|
809
809
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
810
810
|
registerRun(program);
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
probeRuntime,
|
|
12
12
|
routeShotName,
|
|
13
13
|
runtimeSummary
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MG4OPCQO.js";
|
|
15
15
|
import "./chunk-7CAGWFAQ.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-ZQRBBX7G.js";
|
|
17
17
|
import "./chunk-55JZKI2M.js";
|
|
18
18
|
import "./chunk-HIUQ4FXO.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-DAXGJ57E.js";
|
|
20
20
|
export {
|
|
21
21
|
VIEWPORTS,
|
|
22
22
|
buildReviewPrompt,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-ASZUQJPE.js";
|
|
6
|
+
import "./chunk-32I2JTFS.js";
|
|
7
7
|
import "./chunk-YKEAKVIS.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-MG4OPCQO.js";
|
|
9
9
|
import "./chunk-UBADCBPT.js";
|
|
10
10
|
import "./chunk-IEQGTVWN.js";
|
|
11
11
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -16,10 +16,10 @@ import "./chunk-K5QJMSJH.js";
|
|
|
16
16
|
import "./chunk-WAJXATCO.js";
|
|
17
17
|
import "./chunk-62JNF5M2.js";
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-ZQRBBX7G.js";
|
|
20
20
|
import "./chunk-55JZKI2M.js";
|
|
21
21
|
import "./chunk-HIUQ4FXO.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-DAXGJ57E.js";
|
|
23
23
|
|
|
24
24
|
// src/daemon/remote.ts
|
|
25
25
|
var noteId = 0;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
captureViewports,
|
|
8
8
|
previewDir,
|
|
9
9
|
runtimeSummary
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MG4OPCQO.js";
|
|
11
11
|
import "./chunk-UBADCBPT.js";
|
|
12
12
|
import "./chunk-IEQGTVWN.js";
|
|
13
13
|
import "./chunk-WAJXATCO.js";
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
19
|
import {
|
|
20
20
|
loadFlows
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-B7T5DYRM.js";
|
|
22
|
+
import "./chunk-ZQRBBX7G.js";
|
|
23
23
|
import {
|
|
24
24
|
findChrome
|
|
25
25
|
} from "./chunk-55JZKI2M.js";
|
|
26
26
|
import "./chunk-HIUQ4FXO.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-DAXGJ57E.js";
|
|
28
28
|
|
|
29
29
|
// src/mcp/server.ts
|
|
30
30
|
import readline from "readline";
|
|
@@ -58,6 +58,7 @@ ${r.outputTail.slice(-1500)}`}`).join("\n");
|
|
|
58
58
|
["phantom classes", result.phantoms],
|
|
59
59
|
["view transitions", result.viewTransitions],
|
|
60
60
|
["jank", result.jank],
|
|
61
|
+
["locale", result.locale],
|
|
61
62
|
["components", result.components]
|
|
62
63
|
]) {
|
|
63
64
|
if (list && list.length > 0) sections.push(`${label}:
|
|
@@ -76,7 +77,7 @@ ${list.join("\n")}`);
|
|
|
76
77
|
if (flows.length === 0) return "no flows declared \u2014 add .squint/flows/<name>.flow";
|
|
77
78
|
const chrome = findChrome();
|
|
78
79
|
if (!chrome) return "no Chrome/Chromium found";
|
|
79
|
-
const { runFlow } = await import("./cdp-
|
|
80
|
+
const { runFlow } = await import("./cdp-4FWGQ3JR.js");
|
|
80
81
|
const lines = [];
|
|
81
82
|
for (const flow of flows) {
|
|
82
83
|
const result = await runFlow(chrome, url, flow, previewDir(cwd));
|
|
@@ -95,7 +96,7 @@ ${result.transitions.map((t) => ` ${t}`).join("\n")}` : ""}` : `\u2717 ${flow.n
|
|
|
95
96
|
async run(args, cwd) {
|
|
96
97
|
const chrome = findChrome();
|
|
97
98
|
if (!chrome) return "no Chrome/Chromium found";
|
|
98
|
-
const { suggestFlows } = await import("./flows-
|
|
99
|
+
const { suggestFlows } = await import("./flows-3PMJYMDM.js");
|
|
99
100
|
const { created, skipped } = await suggestFlows(cwd, String(args.url ?? ""), chrome);
|
|
100
101
|
return [
|
|
101
102
|
created.length > 0 ? `drafted: ${created.join(", ")}` : "nothing drafted",
|
|
@@ -110,7 +111,7 @@ ${result.transitions.map((t) => ` ${t}`).join("\n")}` : ""}` : `\u2717 ${flow.n
|
|
|
110
111
|
async run(args, cwd) {
|
|
111
112
|
const fs = await import("fs");
|
|
112
113
|
const path = await import("path");
|
|
113
|
-
const { verifyReceipt } = await import("./receipts-
|
|
114
|
+
const { verifyReceipt } = await import("./receipts-HN5HGWMT.js");
|
|
114
115
|
const file = path.isAbsolute(String(args.path)) ? String(args.path) : path.join(cwd, String(args.path));
|
|
115
116
|
let receipt;
|
|
116
117
|
try {
|
package/package.json
CHANGED