Skip to content

Commit 6ae59a6

Browse files
committed
refactor: consolidate responsive variant switching into shared ResponsiveDiagram
1 parent be84fa9 commit 6ae59a6

63 files changed

Lines changed: 1505 additions & 878 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const fs = require('node:fs');
2+
const path = require('node:path');
3+
4+
const root = path.resolve(__dirname, '..', 'website', 'src', 'components', 'VisualElements');
5+
const names = [
6+
'AgentWorkStabilityDiagram',
7+
'ContextMechanismMap',
8+
'ContextWindowAnatomyDiagram',
9+
'ErrorReasonComparisonDiagram',
10+
'ExecutionPortfolioDiagram',
11+
'GroundingDistillationDiagram',
12+
'HarnessContextLoop',
13+
'HarnessDeltaExplorer',
14+
'HarnessLoopDiagram',
15+
'InstructionLayerAuthority',
16+
'InteractiveHarnessWorkbench',
17+
'LivingContextWindowStream',
18+
'LocalChoicesGlobalCoherenceDiagram',
19+
'LongContextBenchmarkExplorer',
20+
'ModelEncodingAtlas',
21+
'OperatorCycleDiagram',
22+
'OperatorTransformationDiagram',
23+
'OwnershipBoundaryDiagram',
24+
'PlanningContractCheckpointDiagram',
25+
'PostTrainingTuningBoard',
26+
'ProbabilityIsNotLogicDiagram',
27+
'SpecExecutionRunsDiagram',
28+
'SpeedAccuracyTradeoff',
29+
'StructuredControlPlaneWorkbench',
30+
'SubAgentFanoutDiagram',
31+
'TokenPredictionDiagram',
32+
'UShapeAttentionCurve',
33+
'ValidationClaimBenchDiagram',
34+
'ValidationEvidenceLifecycle',
35+
];
36+
const variantSelector =
37+
/\.(?:desktopDiagram|mobileDiagram|desktop|mobile|operatorDesktop|operatorMobile|validationDesktop|validationMobile|desktopChart|mobileChart)\b/;
38+
const failures = [];
39+
40+
for (const name of names) {
41+
const tsxPath = path.join(root, `${name}.tsx`);
42+
const cssPath = path.join(root, `${name}.module.css`);
43+
const tsx = fs.readFileSync(tsxPath, 'utf8');
44+
const css = fs.readFileSync(cssPath, 'utf8');
45+
46+
if (!tsx.includes('ResponsiveDiagram')) {
47+
failures.push(`${name}: paired diagram does not use ResponsiveDiagram`);
48+
}
49+
50+
for (const match of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
51+
const selector = match[1];
52+
const declarations = match[2];
53+
if (variantSelector.test(selector) && /\bdisplay\s*:/.test(declarations)) {
54+
failures.push(`${name}.module.css: variant selector owns display (${selector.trim()})`);
55+
}
56+
if (/^\s*\.diagram\s*$/.test(selector) && /\bdisplay\s*:/.test(declarations)) {
57+
failures.push(`${name}.module.css: generic diagram selector owns display`);
58+
}
59+
}
60+
}
61+
62+
const sharedCss = fs.readFileSync(
63+
path.join(root, 'ResponsiveDiagram.module.css'),
64+
'utf8'
65+
);
66+
for (const required of [
67+
'.container[data-responsive-breakpoint=',
68+
'.desktopVariant',
69+
'.mobileVariant',
70+
'data-responsive-mode',
71+
]) {
72+
if (!sharedCss.includes(required)) failures.push(`shared contract missing ${required}`);
73+
}
74+
75+
if (failures.length) {
76+
console.error(failures.map((failure) => `- ${failure}`).join('\n'));
77+
process.exitCode = 1;
78+
} else {
79+
console.log(`responsive diagram audit passed (${names.length} paired diagrams)`);
80+
}
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
const { spawn, spawnSync } = require("node:child_process");
2+
const fs = require("node:fs");
3+
const http = require("node:http");
4+
const net = require("node:net");
5+
const os = require("node:os");
6+
const path = require("node:path");
7+
8+
const puppeteer = require(
9+
path.resolve(__dirname, "..", "website", "node_modules", "puppeteer"),
10+
);
11+
12+
const websiteDir = path.resolve(__dirname, "..", "website");
13+
const tempRoot = fs.mkdtempSync(
14+
path.join(os.tmpdir(), "agenticoding-responsive-diagrams-"),
15+
);
16+
const buildDir = path.join(tempRoot, "build");
17+
const docusaurusCache = path.join(websiteDir, ".docusaurus");
18+
const hadDocusaurusCache = fs.existsSync(docusaurusCache);
19+
20+
let server;
21+
let browser;
22+
23+
function fail(message) {
24+
throw new Error(message);
25+
}
26+
27+
function runBuild() {
28+
const result = spawnSync(
29+
"npm",
30+
["run", "build", "--", "--out-dir", buildDir],
31+
{ cwd: websiteDir, stdio: "inherit" },
32+
);
33+
if (result.error) throw result.error;
34+
if (result.status !== 0)
35+
fail(`optimized build exited with status ${result.status}`);
36+
}
37+
38+
function reservePort() {
39+
return new Promise((resolve, reject) => {
40+
const socket = net.createServer();
41+
socket.once("error", reject);
42+
socket.listen(0, "127.0.0.1", () => {
43+
const address = socket.address();
44+
socket.close(() => resolve(address.port));
45+
});
46+
});
47+
}
48+
49+
function serveArgs(port) {
50+
return [
51+
"run",
52+
"serve",
53+
"--",
54+
"--dir",
55+
buildDir,
56+
"--port",
57+
String(port),
58+
"--host",
59+
"127.0.0.1",
60+
"--no-open",
61+
];
62+
}
63+
64+
function startServer(port) {
65+
const output = [];
66+
server = spawn("npm", serveArgs(port), {
67+
cwd: websiteDir,
68+
stdio: ["ignore", "pipe", "pipe"],
69+
});
70+
for (const stream of [server.stdout, server.stderr]) {
71+
stream.on("data", (chunk) => {
72+
output.push(String(chunk));
73+
if (output.length > 20) output.shift();
74+
});
75+
}
76+
server._recentOutput = output;
77+
server._port = port;
78+
}
79+
80+
function checkServer(port) {
81+
return new Promise((resolve) => {
82+
const request = http.get(
83+
{ host: "127.0.0.1", port, path: "/", timeout: 1000 },
84+
(response) => {
85+
response.resume();
86+
resolve(response.statusCode < 500);
87+
},
88+
);
89+
request.on("error", () => resolve(false));
90+
request.on("timeout", () => request.destroy());
91+
});
92+
}
93+
94+
async function waitForServer(port) {
95+
for (let attempt = 0; attempt < 120; attempt += 1) {
96+
if (server.exitCode !== null) {
97+
fail(
98+
`static server exited with status ${server.exitCode}\n${server._recentOutput.join("")}`,
99+
);
100+
}
101+
if (await checkServer(port)) return;
102+
await new Promise((resolve) => setTimeout(resolve, 250));
103+
}
104+
fail(`static server did not start\n${server._recentOutput.join("")}`);
105+
}
106+
107+
function routesFromSitemap() {
108+
const sitemap = fs.readFileSync(path.join(buildDir, "sitemap.xml"), "utf8");
109+
const routes = new Set();
110+
for (const [, location] of sitemap.matchAll(/<loc>([^<]+)<\/loc>/g)) {
111+
routes.add(new URL(location).pathname);
112+
}
113+
if (!routes.size) fail("sitemap contains no routes");
114+
return [...routes].sort();
115+
}
116+
117+
function variantState() {
118+
return [...document.querySelectorAll("[data-responsive-breakpoint]")].map(
119+
(container) => {
120+
const variants = [...container.children]
121+
.filter((element) =>
122+
[...element.classList].some((name) =>
123+
/^(desktopVariant|mobileVariant)_/.test(name),
124+
),
125+
)
126+
.map((element) => {
127+
const style = getComputedStyle(element);
128+
const rects = element.getClientRects();
129+
const child = element.firstElementChild;
130+
const wrapperRect = element.getBoundingClientRect();
131+
const childRect = child?.getBoundingClientRect();
132+
return {
133+
name: [...element.classList].find((name) =>
134+
/^(desktopVariant|mobileVariant)_/.test(name),
135+
),
136+
display: style.display,
137+
visibility: style.visibility,
138+
rectCount: rects.length,
139+
childTag: child?.tagName.toLowerCase() || null,
140+
childCenterDelta: childRect
141+
? Math.abs(
142+
childRect.left +
143+
childRect.width / 2 -
144+
(wrapperRect.left + wrapperRect.width / 2),
145+
)
146+
: null,
147+
};
148+
});
149+
return {
150+
breakpoint: container.dataset.responsiveBreakpoint,
151+
fallback: container.dataset.responsiveFallback || null,
152+
mode: container.dataset.responsiveMode,
153+
variants,
154+
};
155+
},
156+
);
157+
}
158+
159+
function breakpointPixels(value) {
160+
if (value.endsWith("px")) return Number.parseFloat(value);
161+
if (value.endsWith("rem")) return Number.parseFloat(value) * 16;
162+
fail(`unsupported responsive breakpoint unit: ${value}`);
163+
}
164+
165+
function visibleVariants(state) {
166+
return state.variants.filter(
167+
(variant) =>
168+
variant.display !== "none" &&
169+
variant.visibility !== "hidden" &&
170+
variant.visibility !== "collapse" &&
171+
variant.rectCount > 0,
172+
);
173+
}
174+
175+
async function waitForPaint(page) {
176+
await page.evaluate(async () => {
177+
if (document.fonts) await document.fonts.ready;
178+
await new Promise((resolve) =>
179+
requestAnimationFrame(() => requestAnimationFrame(resolve)),
180+
);
181+
});
182+
}
183+
184+
function assertVariantState(state, route, width) {
185+
if (state.variants.length !== 2) {
186+
fail(
187+
`${route} at ${width}px has ${state.variants.length} responsive variants for ${state.breakpoint}`,
188+
);
189+
}
190+
const visible = visibleVariants(state);
191+
if (visible.length !== 1) {
192+
fail(
193+
`${route} at ${width}px has ${visible.length} visible variants for ${state.breakpoint} (${state.mode}): ${JSON.stringify(state.variants)}`,
194+
);
195+
}
196+
if (
197+
visible[0].childTag === "svg" &&
198+
visible[0].childCenterDelta !== null &&
199+
visible[0].childCenterDelta > 1
200+
) {
201+
fail(
202+
`${route} at ${width}px left-aligns its visible SVG by ${visible[0].childCenterDelta}px for ${state.breakpoint}`,
203+
);
204+
}
205+
if (state.mode !== "viewport") return;
206+
const expected =
207+
width <= breakpointPixels(state.breakpoint)
208+
? "mobileVariant"
209+
: "desktopVariant";
210+
if (!visible[0].name.startsWith(`${expected}_`)) {
211+
fail(
212+
`${route} at ${width}px selected ${visible[0].name}; expected ${expected} for viewport breakpoint ${state.breakpoint}`,
213+
);
214+
}
215+
}
216+
217+
async function inspectRoute(page, route, width) {
218+
await page.setViewport({ width, height: 900, deviceScaleFactor: 1 });
219+
const response = await page.goto(`http://127.0.0.1:${server._port}${route}`, {
220+
waitUntil: "domcontentloaded",
221+
});
222+
if (!response || response.status() >= 400) {
223+
fail(`${route} at ${width}px returned ${response && response.status()}`);
224+
}
225+
await waitForPaint(page);
226+
const states = await page.evaluate(variantState);
227+
states.forEach((state) => assertVariantState(state, route, width));
228+
return states.length;
229+
}
230+
231+
async function inspectRoutes(page, routes) {
232+
let responsiveContainers = 0;
233+
for (const route of routes) {
234+
for (const width of [1440, 390]) {
235+
responsiveContainers += await inspectRoute(page, route, width);
236+
}
237+
}
238+
return responsiveContainers;
239+
}
240+
241+
async function main() {
242+
runBuild();
243+
const routes = routesFromSitemap();
244+
const port = await reservePort();
245+
startServer(port);
246+
await waitForServer(port);
247+
248+
browser = await puppeteer.launch({ headless: true });
249+
const page = await browser.newPage();
250+
const responsiveContainers = await inspectRoutes(page, routes);
251+
if (!responsiveContainers)
252+
fail("no responsive diagrams found in generated routes");
253+
console.log(
254+
`responsive diagram browser regression passed (${routes.length} routes × 2 viewports; ${responsiveContainers} checks)`,
255+
);
256+
}
257+
258+
async function cleanup() {
259+
if (browser) await browser.close().catch(() => {});
260+
if (server && server.exitCode === null) {
261+
server.kill("SIGTERM");
262+
await new Promise((resolve) => server.once("close", resolve));
263+
}
264+
fs.rmSync(tempRoot, { recursive: true, force: true });
265+
if (!hadDocusaurusCache) {
266+
fs.rmSync(docusaurusCache, { recursive: true, force: true });
267+
}
268+
}
269+
270+
main()
271+
.catch((error) => {
272+
console.error(error.stack || error);
273+
process.exitCode = 1;
274+
})
275+
.finally(cleanup);

website/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"build": "docusaurus build",
99
"test": "find src -name '*.test.ts' -print0 | xargs -0 node --test",
1010
"audit:breakpoints": "node ../scripts/audit-breakpoints.cjs",
11+
"audit:responsive-diagrams": "node ../scripts/audit-responsive-diagrams.cjs",
12+
"test:responsive-diagrams:browser": "node ../scripts/test-responsive-diagrams.cjs",
1113
"swizzle": "docusaurus swizzle",
1214
"deploy": "docusaurus deploy",
1315
"clear": "docusaurus clear",

website/src/components/VisualElements/AgentWorkStabilityDiagram.module.css

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@
88

99
.desktop,
1010
.mobile {
11-
display: block;
1211
width: 100%;
1312
height: auto;
1413
margin-inline: auto;
1514
font-feature-settings: var(--font-mono-features);
1615
}
1716

1817
.mobile {
19-
display: none;
2018
max-width: 340px;
2119
}
2220

@@ -202,13 +200,3 @@ marker path[data-tone='warning'] {
202200
display: none;
203201
}
204202
}
205-
206-
@container (max-width: 36rem) {
207-
.desktop {
208-
display: none;
209-
}
210-
211-
.mobile {
212-
display: block;
213-
}
214-
}

0 commit comments

Comments
 (0)