forge-cc 0.1.7 → 0.1.9
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/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/schema.d.ts +22 -2
- package/dist/config/schema.js +4 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/gates/index.d.ts +2 -1
- package/dist/gates/index.js +8 -1
- package/dist/gates/index.js.map +1 -1
- package/dist/gates/lint-gate.js +5 -0
- package/dist/gates/lint-gate.js.map +1 -1
- package/dist/gates/remediation.d.ts +38 -0
- package/dist/gates/remediation.js +390 -0
- package/dist/gates/remediation.js.map +1 -0
- package/dist/gates/review-gate.d.ts +16 -0
- package/dist/gates/review-gate.js +479 -0
- package/dist/gates/review-gate.js.map +1 -0
- package/dist/gates/tests-gate.js +5 -0
- package/dist/gates/tests-gate.js.map +1 -1
- package/dist/gates/types-gate.js +5 -0
- package/dist/gates/types-gate.js.map +1 -1
- package/dist/gates/visual-capture.d.ts +24 -0
- package/dist/gates/visual-capture.js +144 -0
- package/dist/gates/visual-capture.js.map +1 -0
- package/dist/gates/visual-gate.d.ts +12 -0
- package/dist/gates/visual-gate.js +92 -13
- package/dist/gates/visual-gate.js.map +1 -1
- package/dist/gates/visual-reviewer.d.ts +11 -0
- package/dist/gates/visual-reviewer.js +211 -0
- package/dist/gates/visual-reviewer.js.map +1 -0
- package/dist/go/verify-loop.js +2 -1
- package/dist/go/verify-loop.js.map +1 -1
- package/dist/types.d.ts +51 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +15 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Default viewports for multi-viewport capture: desktop, tablet, mobile */
|
|
4
|
+
export const DEFAULT_VIEWPORTS = [
|
|
5
|
+
{ name: "desktop", width: 1280, height: 800 },
|
|
6
|
+
{ name: "tablet", width: 768, height: 1024 },
|
|
7
|
+
{ name: "mobile", width: 390, height: 844 },
|
|
8
|
+
];
|
|
9
|
+
/**
|
|
10
|
+
* Sanitize a page path into a safe filename component.
|
|
11
|
+
* Replaces slashes with underscores and strips leading underscores.
|
|
12
|
+
* Falls back to "index" for root path.
|
|
13
|
+
*/
|
|
14
|
+
function sanitizePageName(pagePath) {
|
|
15
|
+
const sanitized = pagePath.replace(/\//g, "_").replace(/^_/, "");
|
|
16
|
+
return sanitized || "index";
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract DOM snapshot from the page via page.evaluate().
|
|
20
|
+
* The entire extraction logic runs inside the browser context so it must be
|
|
21
|
+
* self-contained -- no references to Node.js modules or TypeScript types.
|
|
22
|
+
*/
|
|
23
|
+
async function extractDOM(page) {
|
|
24
|
+
return await page.evaluate(() => {
|
|
25
|
+
function walkElement(el, isTopLevel) {
|
|
26
|
+
const style = window.getComputedStyle(el);
|
|
27
|
+
const visible = style.display !== "none" &&
|
|
28
|
+
style.visibility !== "hidden" &&
|
|
29
|
+
style.opacity !== "0";
|
|
30
|
+
const node = {
|
|
31
|
+
tag: el.tagName.toLowerCase(),
|
|
32
|
+
visible,
|
|
33
|
+
children: [],
|
|
34
|
+
};
|
|
35
|
+
if (el.id) {
|
|
36
|
+
node.id = el.id;
|
|
37
|
+
}
|
|
38
|
+
if (el.className &&
|
|
39
|
+
typeof el.className === "string" &&
|
|
40
|
+
el.className.trim()) {
|
|
41
|
+
node.className = el.className.trim();
|
|
42
|
+
}
|
|
43
|
+
// Only capture bounding rect for top-level elements to limit payload size
|
|
44
|
+
if (isTopLevel) {
|
|
45
|
+
const rect = el.getBoundingClientRect();
|
|
46
|
+
node.rect = {
|
|
47
|
+
x: Math.round(rect.x),
|
|
48
|
+
y: Math.round(rect.y),
|
|
49
|
+
width: Math.round(rect.width),
|
|
50
|
+
height: Math.round(rect.height),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const childElements = el.children;
|
|
54
|
+
for (let i = 0; i < childElements.length; i++) {
|
|
55
|
+
node.children.push(walkElement(childElements[i], false));
|
|
56
|
+
}
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
const body = document.body;
|
|
60
|
+
if (!body) {
|
|
61
|
+
return { tag: "body", visible: true, children: [] };
|
|
62
|
+
}
|
|
63
|
+
const snapshot = {
|
|
64
|
+
tag: "body",
|
|
65
|
+
visible: true,
|
|
66
|
+
children: [],
|
|
67
|
+
};
|
|
68
|
+
if (body.id) {
|
|
69
|
+
snapshot.id = body.id;
|
|
70
|
+
}
|
|
71
|
+
if (body.className &&
|
|
72
|
+
typeof body.className === "string" &&
|
|
73
|
+
body.className.trim()) {
|
|
74
|
+
snapshot.className = body.className.trim();
|
|
75
|
+
}
|
|
76
|
+
const childElements = body.children;
|
|
77
|
+
for (let i = 0; i < childElements.length; i++) {
|
|
78
|
+
snapshot.children.push(walkElement(childElements[i], true));
|
|
79
|
+
}
|
|
80
|
+
return snapshot;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Capture multi-viewport screenshots and extract DOM structure from a Playwright page.
|
|
85
|
+
*
|
|
86
|
+
* For each viewport in the configuration:
|
|
87
|
+
* 1. Resizes the viewport via page.setViewportSize()
|
|
88
|
+
* 2. Waits for layout to settle (500ms)
|
|
89
|
+
* 3. Takes a full-page screenshot saved as {safeName}_{viewportName}.png
|
|
90
|
+
* 4. Extracts the serialized DOM tree via page.evaluate()
|
|
91
|
+
*
|
|
92
|
+
* @param page - Playwright Page instance (already navigated to the target URL)
|
|
93
|
+
* @param options - Capture configuration including page path, screenshot dir, and optional viewports
|
|
94
|
+
* @returns VisualCaptureResult with screenshots array, DOM snapshots per viewport, and metadata
|
|
95
|
+
*/
|
|
96
|
+
export async function captureVisual(page, options) {
|
|
97
|
+
const start = Date.now();
|
|
98
|
+
const viewports = options.viewports ?? DEFAULT_VIEWPORTS;
|
|
99
|
+
const safeName = sanitizePageName(options.pagePath);
|
|
100
|
+
mkdirSync(options.screenshotDir, { recursive: true });
|
|
101
|
+
const screenshots = [];
|
|
102
|
+
const domSnapshots = {};
|
|
103
|
+
for (const viewport of viewports) {
|
|
104
|
+
try {
|
|
105
|
+
// Resize viewport
|
|
106
|
+
await page.setViewportSize({
|
|
107
|
+
width: viewport.width,
|
|
108
|
+
height: viewport.height,
|
|
109
|
+
});
|
|
110
|
+
// Wait for layout to settle after resize
|
|
111
|
+
await page.waitForTimeout(500);
|
|
112
|
+
// Take full-page screenshot
|
|
113
|
+
const screenshotPath = join(options.screenshotDir, `${safeName}_${viewport.name}.png`);
|
|
114
|
+
try {
|
|
115
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
116
|
+
screenshots.push({
|
|
117
|
+
page: options.pagePath,
|
|
118
|
+
viewport: viewport.name,
|
|
119
|
+
path: screenshotPath,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Screenshot failed for this viewport — skip it but continue
|
|
124
|
+
}
|
|
125
|
+
// Extract DOM snapshot
|
|
126
|
+
const domSnapshot = await extractDOM(page);
|
|
127
|
+
domSnapshots[viewport.name] = domSnapshot;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Viewport resize or other operation failed — skip this viewport entirely
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
screenshots,
|
|
135
|
+
domSnapshots,
|
|
136
|
+
metadata: {
|
|
137
|
+
viewports,
|
|
138
|
+
pagePath: options.pagePath,
|
|
139
|
+
capturedAt: new Date().toISOString(),
|
|
140
|
+
durationMs: Date.now() - start,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=visual-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-capture.js","sourceRoot":"","sources":["../../src/gates/visual-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { VisualResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Capture and store "before" snapshots for the given pages.
|
|
4
|
+
* Called at milestone start by the orchestrator so we have a baseline
|
|
5
|
+
* to compare against when `verifyVisual` runs later.
|
|
6
|
+
*/
|
|
7
|
+
export declare function captureBeforeSnapshots(projectDir: string, pages: string[], options?: {
|
|
8
|
+
devServerCommand?: string;
|
|
9
|
+
devServerPort?: number;
|
|
10
|
+
screenshotDir?: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
/** Clear all stored "before" snapshots (e.g. between milestones). */
|
|
13
|
+
export declare function clearBeforeSnapshots(): void;
|
|
2
14
|
export declare function verifyVisual(projectDir: string, pages: string[], options?: {
|
|
3
15
|
devServerCommand?: string;
|
|
4
16
|
devServerPort?: number;
|
|
@@ -1,15 +1,79 @@
|
|
|
1
1
|
import { getBrowser, startDevServer, stopDevServer, waitForServer, } from "../utils/browser.js";
|
|
2
|
+
import { captureVisual } from "./visual-capture.js";
|
|
3
|
+
import { reviewVisual } from "./visual-reviewer.js";
|
|
2
4
|
import { mkdirSync } from "node:fs";
|
|
3
5
|
import { join } from "node:path";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Before/after snapshot storage
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/** Module-level map storing "before" snapshots keyed by page path */
|
|
10
|
+
const beforeSnapshots = new Map();
|
|
11
|
+
/**
|
|
12
|
+
* Capture and store "before" snapshots for the given pages.
|
|
13
|
+
* Called at milestone start by the orchestrator so we have a baseline
|
|
14
|
+
* to compare against when `verifyVisual` runs later.
|
|
15
|
+
*/
|
|
16
|
+
export async function captureBeforeSnapshots(projectDir, pages, options) {
|
|
17
|
+
const resolvedPages = pages.length > 0 ? pages : ["/"];
|
|
18
|
+
const port = options?.devServerPort ?? 3000;
|
|
19
|
+
const screenshotDir = options?.screenshotDir ?? join(projectDir, ".forge", "screenshots");
|
|
20
|
+
const beforeDir = join(screenshotDir, "before");
|
|
21
|
+
mkdirSync(beforeDir, { recursive: true });
|
|
22
|
+
try {
|
|
23
|
+
await startDevServer(projectDir, options?.devServerCommand, port);
|
|
24
|
+
const ready = await waitForServer(port);
|
|
25
|
+
if (!ready) {
|
|
26
|
+
return; // Cannot capture baseline — verifyVisual will still work without it
|
|
27
|
+
}
|
|
28
|
+
const browser = await getBrowser();
|
|
29
|
+
const context = await browser.newContext();
|
|
30
|
+
try {
|
|
31
|
+
for (const pagePath of resolvedPages) {
|
|
32
|
+
const page = await context.newPage();
|
|
33
|
+
try {
|
|
34
|
+
await page.goto(`http://localhost:${port}${pagePath}`, {
|
|
35
|
+
waitUntil: "networkidle",
|
|
36
|
+
timeout: 30_000,
|
|
37
|
+
});
|
|
38
|
+
const result = await captureVisual(page, {
|
|
39
|
+
pagePath,
|
|
40
|
+
screenshotDir: beforeDir,
|
|
41
|
+
});
|
|
42
|
+
beforeSnapshots.set(pagePath, result);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Failed to capture baseline for this page — skip it
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await page.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await context.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await stopDevServer();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Clear all stored "before" snapshots (e.g. between milestones). */
|
|
61
|
+
export function clearBeforeSnapshots() {
|
|
62
|
+
beforeSnapshots.clear();
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Main visual gate
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
4
67
|
export async function verifyVisual(projectDir, pages, options) {
|
|
5
68
|
const start = Date.now();
|
|
6
69
|
const resolvedPages = pages.length > 0 ? pages : ["/"];
|
|
7
70
|
const port = options?.devServerPort ?? 3000;
|
|
8
71
|
const screenshotDir = options?.screenshotDir ?? join(projectDir, ".forge", "screenshots");
|
|
72
|
+
const afterDir = join(screenshotDir, "after");
|
|
9
73
|
const consoleErrors = [];
|
|
10
74
|
const warnings = [];
|
|
11
75
|
const screenshots = [];
|
|
12
|
-
mkdirSync(
|
|
76
|
+
mkdirSync(afterDir, { recursive: true });
|
|
13
77
|
try {
|
|
14
78
|
// Start dev server
|
|
15
79
|
try {
|
|
@@ -43,6 +107,7 @@ export async function verifyVisual(projectDir, pages, options) {
|
|
|
43
107
|
// Launch browser and create context
|
|
44
108
|
const browser = await getBrowser();
|
|
45
109
|
const context = await browser.newContext();
|
|
110
|
+
const reviewerErrors = [];
|
|
46
111
|
try {
|
|
47
112
|
for (const pagePath of resolvedPages) {
|
|
48
113
|
const page = await context.newPage();
|
|
@@ -68,16 +133,29 @@ export async function verifyVisual(projectDir, pages, options) {
|
|
|
68
133
|
await page.close();
|
|
69
134
|
continue;
|
|
70
135
|
}
|
|
71
|
-
//
|
|
136
|
+
// Capture "after" snapshot using the M1 capture module
|
|
72
137
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
138
|
+
const afterResult = await captureVisual(page, {
|
|
139
|
+
pagePath,
|
|
140
|
+
screenshotDir: afterDir,
|
|
141
|
+
});
|
|
142
|
+
// Map multi-viewport screenshots to the flat { page, path } format
|
|
143
|
+
for (const shot of afterResult.screenshots) {
|
|
144
|
+
screenshots.push({ page: shot.page, path: shot.path });
|
|
145
|
+
}
|
|
146
|
+
// Compare with "before" snapshot if one exists
|
|
147
|
+
const beforeResult = beforeSnapshots.get(pagePath);
|
|
148
|
+
if (beforeResult) {
|
|
149
|
+
const findings = reviewVisual(beforeResult, afterResult);
|
|
150
|
+
reviewerErrors.push(...findings);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
warnings.push(`No baseline snapshot for ${pagePath} — before/after comparison skipped. Call captureBeforeSnapshots() before verifyVisual() to enable.`);
|
|
154
|
+
}
|
|
77
155
|
}
|
|
78
156
|
catch (err) {
|
|
79
|
-
const message = err instanceof Error ? err.message : "
|
|
80
|
-
warnings.push(`
|
|
157
|
+
const message = err instanceof Error ? err.message : "Visual capture failed";
|
|
158
|
+
warnings.push(`Visual capture failed for ${pagePath}: ${message}`);
|
|
81
159
|
}
|
|
82
160
|
await page.close();
|
|
83
161
|
}
|
|
@@ -85,13 +163,14 @@ export async function verifyVisual(projectDir, pages, options) {
|
|
|
85
163
|
finally {
|
|
86
164
|
await context.close();
|
|
87
165
|
}
|
|
88
|
-
//
|
|
89
|
-
const errors =
|
|
90
|
-
message: msg,
|
|
91
|
-
|
|
166
|
+
// Combine console errors and reviewer errors into a single GateError list
|
|
167
|
+
const errors = [
|
|
168
|
+
...consoleErrors.map((msg) => ({ message: msg })),
|
|
169
|
+
...reviewerErrors,
|
|
170
|
+
];
|
|
92
171
|
return {
|
|
93
172
|
gate: "visual",
|
|
94
|
-
passed:
|
|
173
|
+
passed: errors.length === 0,
|
|
95
174
|
errors,
|
|
96
175
|
warnings,
|
|
97
176
|
duration_ms: Date.now() - start,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual-gate.js","sourceRoot":"","sources":["../../src/gates/visual-gate.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,UAAkB,EAClB,KAAe,EACf,OAIC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC;IAC5C,MAAM,aAAa,GACjB,OAAO,EAAE,aAAa,IAAI,IAAI,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IACtE,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,WAAW,GAA0C,EAAE,CAAC;IAE9D,SAAS,CAAC,
|
|
1
|
+
{"version":3,"file":"visual-gate.js","sourceRoot":"","sources":["../../src/gates/visual-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GateError, VisualCaptureResult } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compare before and after visual capture results using DOM structural
|
|
4
|
+
* analysis. Returns a list of `GateError` entries describing detected
|
|
5
|
+
* differences per viewport.
|
|
6
|
+
*
|
|
7
|
+
* This does NOT perform pixel-level comparison — it analyses the serialized
|
|
8
|
+
* DOM trees for element count changes, missing/added elements, visibility
|
|
9
|
+
* changes, and significant layout dimension shifts.
|
|
10
|
+
*/
|
|
11
|
+
export declare function reviewVisual(before: VisualCaptureResult, after: VisualCaptureResult): GateError[];
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/**
|
|
5
|
+
* Build a human-readable DOM path string from root to the given node.
|
|
6
|
+
* Example: "body > div#main > nav.sidebar"
|
|
7
|
+
*/
|
|
8
|
+
function domPath(node, ancestors) {
|
|
9
|
+
const parts = [];
|
|
10
|
+
for (const a of ancestors) {
|
|
11
|
+
parts.push(nodeLabel(a));
|
|
12
|
+
}
|
|
13
|
+
parts.push(nodeLabel(node));
|
|
14
|
+
return parts.join(" > ");
|
|
15
|
+
}
|
|
16
|
+
/** Produce a concise label for a single DOM node: tag#id.className */
|
|
17
|
+
function nodeLabel(node) {
|
|
18
|
+
let label = node.tag;
|
|
19
|
+
if (node.id) {
|
|
20
|
+
label += `#${node.id}`;
|
|
21
|
+
}
|
|
22
|
+
if (node.className) {
|
|
23
|
+
// Collapse whitespace and join with dots
|
|
24
|
+
label += "." + node.className.split(/\s+/).join(".");
|
|
25
|
+
}
|
|
26
|
+
return label;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build an identity key for a node so we can match elements across
|
|
30
|
+
* before/after snapshots. Nodes with an `id` get that as their key;
|
|
31
|
+
* otherwise we fall back to tag+className which is less unique but still
|
|
32
|
+
* useful.
|
|
33
|
+
*/
|
|
34
|
+
function nodeKey(node) {
|
|
35
|
+
if (node.id) {
|
|
36
|
+
return `id:${node.id}`;
|
|
37
|
+
}
|
|
38
|
+
return `tag:${node.tag}|class:${node.className ?? ""}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Recursively flatten a DOM tree into a list of FlatNode entries, each
|
|
42
|
+
* carrying its identity key and human-readable path.
|
|
43
|
+
*/
|
|
44
|
+
function flattenTree(root, ancestors = []) {
|
|
45
|
+
const result = [];
|
|
46
|
+
const currentPath = domPath(root, ancestors);
|
|
47
|
+
result.push({ node: root, path: currentPath, key: nodeKey(root) });
|
|
48
|
+
const nextAncestors = [...ancestors, root];
|
|
49
|
+
for (const child of root.children) {
|
|
50
|
+
result.push(...flattenTree(child, nextAncestors));
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
/** Count the total number of nodes in a DOM tree (including the root). */
|
|
55
|
+
function countNodes(root) {
|
|
56
|
+
let count = 1;
|
|
57
|
+
for (const child of root.children) {
|
|
58
|
+
count += countNodes(child);
|
|
59
|
+
}
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Comparison thresholds
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
/** If total element count changes by more than this ratio, flag it. */
|
|
66
|
+
const ELEMENT_COUNT_CHANGE_THRESHOLD = 0.20; // 20%
|
|
67
|
+
/** Bounding-rect shift (in px) above which we flag a layout change. */
|
|
68
|
+
const LAYOUT_SHIFT_PX = 50;
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Per-viewport comparison
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
function compareSnapshots(viewport, before, after) {
|
|
73
|
+
const errors = [];
|
|
74
|
+
// --- Element count change ------------------------------------------------
|
|
75
|
+
const beforeCount = countNodes(before);
|
|
76
|
+
const afterCount = countNodes(after);
|
|
77
|
+
if (beforeCount > 0) {
|
|
78
|
+
const ratio = Math.abs(afterCount - beforeCount) / beforeCount;
|
|
79
|
+
if (ratio > ELEMENT_COUNT_CHANGE_THRESHOLD) {
|
|
80
|
+
const direction = afterCount > beforeCount ? "increase" : "decrease";
|
|
81
|
+
errors.push({
|
|
82
|
+
message: `[${viewport}] Significant element count ${direction}: ${beforeCount} -> ${afterCount} (${Math.round(ratio * 100)}% change)`,
|
|
83
|
+
remediation: `Review the DOM on the "${viewport}" viewport — a large element count ${direction} may indicate unintended additions or removals.`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// --- Flatten both trees and index by key ---------------------------------
|
|
88
|
+
const beforeFlat = flattenTree(before);
|
|
89
|
+
const afterFlat = flattenTree(after);
|
|
90
|
+
const beforeByKey = new Map();
|
|
91
|
+
for (const entry of beforeFlat) {
|
|
92
|
+
const existing = beforeByKey.get(entry.key);
|
|
93
|
+
if (existing) {
|
|
94
|
+
existing.push(entry);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
beforeByKey.set(entry.key, [entry]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const afterByKey = new Map();
|
|
101
|
+
for (const entry of afterFlat) {
|
|
102
|
+
const existing = afterByKey.get(entry.key);
|
|
103
|
+
if (existing) {
|
|
104
|
+
existing.push(entry);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
afterByKey.set(entry.key, [entry]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// --- Missing elements (in before, not in after) --------------------------
|
|
111
|
+
for (const [key, beforeEntries] of beforeByKey) {
|
|
112
|
+
if (!afterByKey.has(key)) {
|
|
113
|
+
// All instances of this key are missing in `after`
|
|
114
|
+
for (const entry of beforeEntries) {
|
|
115
|
+
errors.push({
|
|
116
|
+
message: `[${viewport}] Missing element: ${entry.path}`,
|
|
117
|
+
remediation: `Element ${nodeLabel(entry.node)} missing on "${viewport}" viewport — check responsive CSS rules and conditional rendering logic.`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// --- Added elements (in after, not in before) — informational ------------
|
|
123
|
+
for (const [key, afterEntries] of afterByKey) {
|
|
124
|
+
if (!beforeByKey.has(key)) {
|
|
125
|
+
for (const entry of afterEntries) {
|
|
126
|
+
errors.push({
|
|
127
|
+
message: `[${viewport}] Added element: ${entry.path}`,
|
|
128
|
+
remediation: `New element ${nodeLabel(entry.node)} detected on "${viewport}" viewport — verify this addition is intentional.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// --- Visibility changes and layout shifts --------------------------------
|
|
134
|
+
// For elements present in both snapshots, compare visibility and rect.
|
|
135
|
+
for (const [key, beforeEntries] of beforeByKey) {
|
|
136
|
+
const afterEntries = afterByKey.get(key);
|
|
137
|
+
if (!afterEntries)
|
|
138
|
+
continue;
|
|
139
|
+
// Compare pairwise (first-to-first, etc.) up to the shorter list length
|
|
140
|
+
const pairCount = Math.min(beforeEntries.length, afterEntries.length);
|
|
141
|
+
for (let i = 0; i < pairCount; i++) {
|
|
142
|
+
const bEntry = beforeEntries[i];
|
|
143
|
+
const aEntry = afterEntries[i];
|
|
144
|
+
// Visibility change
|
|
145
|
+
if (bEntry.node.visible && !aEntry.node.visible) {
|
|
146
|
+
errors.push({
|
|
147
|
+
message: `[${viewport}] Element became hidden: ${bEntry.path}`,
|
|
148
|
+
remediation: `Element ${nodeLabel(bEntry.node)} changed from visible to hidden on "${viewport}" viewport — check CSS display/visibility/opacity rules.`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (!bEntry.node.visible && aEntry.node.visible) {
|
|
152
|
+
errors.push({
|
|
153
|
+
message: `[${viewport}] Element became visible: ${bEntry.path}`,
|
|
154
|
+
remediation: `Element ${nodeLabel(bEntry.node)} changed from hidden to visible on "${viewport}" viewport — verify this visibility change is intentional.`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
// Layout dimension shift (only when both have rect data)
|
|
158
|
+
if (bEntry.node.rect && aEntry.node.rect) {
|
|
159
|
+
const bRect = bEntry.node.rect;
|
|
160
|
+
const aRect = aEntry.node.rect;
|
|
161
|
+
const dx = Math.abs(aRect.x - bRect.x);
|
|
162
|
+
const dy = Math.abs(aRect.y - bRect.y);
|
|
163
|
+
const dw = Math.abs(aRect.width - bRect.width);
|
|
164
|
+
const dh = Math.abs(aRect.height - bRect.height);
|
|
165
|
+
const shifts = [];
|
|
166
|
+
if (dx > LAYOUT_SHIFT_PX)
|
|
167
|
+
shifts.push(`x shifted by ${dx}px`);
|
|
168
|
+
if (dy > LAYOUT_SHIFT_PX)
|
|
169
|
+
shifts.push(`y shifted by ${dy}px`);
|
|
170
|
+
if (dw > LAYOUT_SHIFT_PX)
|
|
171
|
+
shifts.push(`width changed by ${dw}px`);
|
|
172
|
+
if (dh > LAYOUT_SHIFT_PX)
|
|
173
|
+
shifts.push(`height changed by ${dh}px`);
|
|
174
|
+
if (shifts.length > 0) {
|
|
175
|
+
errors.push({
|
|
176
|
+
message: `[${viewport}] Layout shift on ${bEntry.path}: ${shifts.join(", ")}`,
|
|
177
|
+
remediation: `Element ${nodeLabel(bEntry.node)} dimensions shifted by >${LAYOUT_SHIFT_PX}px on "${viewport}" viewport — review layout changes.`,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return errors;
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Public API
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
/**
|
|
189
|
+
* Compare before and after visual capture results using DOM structural
|
|
190
|
+
* analysis. Returns a list of `GateError` entries describing detected
|
|
191
|
+
* differences per viewport.
|
|
192
|
+
*
|
|
193
|
+
* This does NOT perform pixel-level comparison — it analyses the serialized
|
|
194
|
+
* DOM trees for element count changes, missing/added elements, visibility
|
|
195
|
+
* changes, and significant layout dimension shifts.
|
|
196
|
+
*/
|
|
197
|
+
export function reviewVisual(before, after) {
|
|
198
|
+
const errors = [];
|
|
199
|
+
// Only compare viewports present in both snapshots
|
|
200
|
+
const beforeViewports = new Set(Object.keys(before.domSnapshots));
|
|
201
|
+
const afterViewports = new Set(Object.keys(after.domSnapshots));
|
|
202
|
+
for (const viewport of beforeViewports) {
|
|
203
|
+
if (!afterViewports.has(viewport))
|
|
204
|
+
continue;
|
|
205
|
+
const beforeSnap = before.domSnapshots[viewport];
|
|
206
|
+
const afterSnap = after.domSnapshots[viewport];
|
|
207
|
+
errors.push(...compareSnapshots(viewport, beforeSnap, afterSnap));
|
|
208
|
+
}
|
|
209
|
+
return errors;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=visual-reviewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-reviewer.js","sourceRoot":"","sources":["../../src/gates/visual-reviewer.ts"],"names":[],"mappings":"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"}
|
package/dist/go/verify-loop.js
CHANGED
|
@@ -150,7 +150,8 @@ export function formatErrorsForAgent(result) {
|
|
|
150
150
|
}
|
|
151
151
|
lines.push(`**Error:** ${err.message}`);
|
|
152
152
|
if (err.remediation) {
|
|
153
|
-
lines.push(
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push(`> **Remediation:** ${err.remediation}`);
|
|
154
155
|
}
|
|
155
156
|
lines.push("");
|
|
156
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-loop.js","sourceRoot":"","sources":["../../src/go/verify-loop.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"verify-loop.js","sourceRoot":"","sources":["../../src/go/verify-loop.ts"],"names":[],"mappings":"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"}
|