Skip to content
Closed
63 changes: 41 additions & 22 deletions website/scripts/page-width-regression.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,41 @@ const frameSelector = [
'[class*="sectionInner"]', '[class*="_catalog_"]', '[class*="catalogHeader"]',
'[class*="recommendedInner"]', '[class*="ctaFrameInner"]',
'#resources [class*="Grid-module__Grid___"]',
'[class*="_wideFrame_"]',
].join(", ");
// Catalogs beside their filter rail. 6 per page never splits, so these also run
// at 50 per page, where the wide grid below the filters is a frame too.
const splitRoutes = new Set([
"agents/", "instructions/", "skills/", "plugins/", "extensions/", "learning-hub/",
]);
const browser = await chromium.launch();

async function checkFrames(page, colorScheme, width, route) {
const frames = await page.locator(frameSelector).evaluateAll(elements =>
elements.map(element => {
const box = element.getBoundingClientRect();
return { name: element.className, x: box.x, right: box.right, width: box.width };
}),
);
assert.ok(frames.length > 0, `${route}: content frames found`);
const header = await page.getByRole("navigation", { name: "Primary", exact: true }).boundingBox();
const expected = Math.min(1280, header.width);
for (const frame of frames) {
const label = `${colorScheme} ${width}px ${route || "homepage"} ${frame.name}`;
assert.ok(frame.width <= 1281, `${label}: maximum width is 1280px, got ${frame.width}`);
assert.ok(frame.x >= -1 && frame.right <= width + 1, `${label}: within viewport`);
if (width >= 768) {
assert.ok(Math.abs(frame.width - expected) <= 1, `${label}: matches header width ${expected}`);
assert.ok(Math.abs(frame.x - header.x) <= 1, `${label}: aligns with header`);
}
}
if (width >= 1600) {
assert.ok(Math.abs(header.width - 1280) <= 1, `${route}: desktop frame fills the 1280px cap`);
}
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false,
`${route}: no horizontal overflow`);
}

try {
for (const colorScheme of ["light", "dark"]) {
const page = await browser.newPage({ colorScheme, reducedMotion: "reduce" });
Expand All @@ -28,29 +60,16 @@ try {
assert.equal(response.status(), 200, route);
await page.waitForFunction(() => !document.querySelector("astro-island[ssr]"));
await page.addStyleTag({ content: "html { scrollbar-gutter: stable; }" });
const frames = await page.locator(frameSelector).evaluateAll(elements =>
elements.map(element => {
const box = element.getBoundingClientRect();
return { name: element.className, x: box.x, right: box.right, width: box.width };
}),
);
assert.ok(frames.length > 0, `${route}: content frames found`);
const header = await page.getByRole("navigation", { name: "Primary", exact: true }).boundingBox();
const expected = Math.min(1280, header.width);
for (const frame of frames) {
const label = `${colorScheme} ${width}px ${route || "homepage"} ${frame.name}`;
assert.ok(frame.width <= 1281, `${label}: maximum width is 1280px, got ${frame.width}`);
assert.ok(frame.x >= -1 && frame.right <= width + 1, `${label}: within viewport`);
if (width >= 768) {
assert.ok(Math.abs(frame.width - expected) <= 1, `${label}: matches header width ${expected}`);
assert.ok(Math.abs(frame.x - header.x) <= 1, `${label}: aligns with header`);
}
}
if (width >= 1600) {
assert.ok(Math.abs(header.width - 1280) <= 1, `${route}: desktop frame fills the 1280px cap`);
await checkFrames(page, colorScheme, width, route);
// The rail stacks above the cards below 1012px, so nothing splits there.
if (width >= 1024 && splitRoutes.has(route)) {
const trigger = page.locator('summary[aria-label^="Resource page size"]');
await trigger.click();
await page.locator("details", { has: trigger })
.getByRole("menuitemradio", { name: "50", exact: true }).click();
await page.locator('[class*="_wideFrame_"]').waitFor();
await checkFrames(page, colorScheme, width, `${route} at 50 per page`);
}
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false,
`${route}: no horizontal overflow`);
}
console.log(`PASS ${colorScheme} ${width}px: ${routes.length} page layouts share the 1280px cap`);
}
Expand Down
156 changes: 96 additions & 60 deletions website/src/components/brand/AgentsCatalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { daysSince, toggleValue, updatedBuckets, updatedBucketOf } from "./catal
import { pageHref } from "./pageHref";
import { downloadFile } from "./resourceActions";
import { useCatalogPageFocus } from "./useCatalogPageFocus";
import { useCatalogSplit } from "./useCatalogSplit";
import type { SearchItem } from "./searchIndex";
import styles from "./styles/agents.module.css";

Expand Down Expand Up @@ -201,6 +202,69 @@ export function AgentsCatalog({
const page = Math.min(currentPage, pageCount);
const visibleAgents = sortedAgents.slice((page - 1) * pageSize, page * pageSize);

const { filterNavRef, sideGridRef, wideGridRef, beside, below } =
useCatalogSplit(visibleAgents, pageSize);

const renderAgentCard = (agent: AgentItem) => (
<Box className={clsx(styles.item, styles.itemHover)}>
<Card
href={agentDetailHref(agent)}
fullWidth
ctaVariant="none"
backgroundColor="none"
className={styles.card}
>
<Card.Heading as="h2">{agent.title}</Card.Heading>
<Card.Description>
<span className={styles.cardDescText}>
{agent.description}
</span>
</Card.Description>
</Card>
<div className={styles.cardActions}>
<ActionMenu mode="split-button" menuAlignment="start">
<ActionMenu.Button
as="a"
href={installUrl(agent)}
variant="primary"
>
Install
</ActionMenu.Button>
<ActionMenu.Overlay
aria-label={`Install ${agent.title} to your editor`}
>
<ActionMenu.Item as="a" href={installUrl(agent, false)}>
VS Code
</ActionMenu.Item>
<ActionMenu.Item as="a" href={installUrl(agent, true)}>
VS Code Insiders
</ActionMenu.Item>
</ActionMenu.Overlay>
</ActionMenu>
<Button
as="button"
variant="secondary"
onClick={() =>
void downloadFile(downloadUrl(agent), agent.filename)
}
aria-label={`Download ${agent.title} agent file`}
className={styles.iconButton}
>
<DownloadIcon />
</Button>
<Button
as="a"
href={agentSourceUrl(agent)}
variant="secondary"
aria-label={`View ${agent.title} agent on GitHub`}
className={styles.iconButton}
>
<MarkGithubIcon />
</Button>
</div>
</Box>
);

return (
<PageShell
styles={styles}
Expand Down Expand Up @@ -230,7 +294,11 @@ export function AgentsCatalog({
paddingBlockEnd="none"
>
<Box className={styles.catalog}>
<aside className={styles.filterNav} aria-label="Filter agents">
<aside
ref={filterNavRef}
className={styles.filterNav}
aria-label="Filter agents"
>
<button
type="button"
className={styles.filterToggle}
Expand Down Expand Up @@ -337,74 +405,22 @@ export function AgentsCatalog({
<Box className={styles.gridFrame} data-mode={colorMode}>
<Box className={styles.gridContent}>
<Grid
className={styles.threeUp}
ref={sideGridRef}
className={clsx(
styles.threeUp,
below.length > 0 && styles.threeUpSplit,
)}
columnGap="none"
rowGap="none"
enableGutters={false}
>
{visibleAgents.map((agent) => (
{beside.map((agent) => (
<Grid.Column
key={agent.id}
span={{ xsmall: 12, medium: 6, large: 6 }}
className={styles.col}
>
<Box className={clsx(styles.item, styles.itemHover)}>
<Card
href={agentDetailHref(agent)}
fullWidth
ctaVariant="none"
backgroundColor="none"
className={styles.card}
>
<Card.Heading as="h2">{agent.title}</Card.Heading>
<Card.Description>
<span className={styles.cardDescText}>
{agent.description}
</span>
</Card.Description>
</Card>
<div className={styles.cardActions}>
<ActionMenu mode="split-button" menuAlignment="start">
<ActionMenu.Button
as="a"
href={installUrl(agent)}
variant="primary"
>
Install
</ActionMenu.Button>
<ActionMenu.Overlay
aria-label={`Install ${agent.title} to your editor`}
>
<ActionMenu.Item as="a" href={installUrl(agent, false)}>
VS Code
</ActionMenu.Item>
<ActionMenu.Item as="a" href={installUrl(agent, true)}>
VS Code Insiders
</ActionMenu.Item>
</ActionMenu.Overlay>
</ActionMenu>
<Button
as="button"
variant="secondary"
onClick={() =>
void downloadFile(downloadUrl(agent), agent.filename)
}
aria-label={`Download ${agent.title} agent file`}
className={styles.iconButton}
>
<DownloadIcon />
</Button>
<Button
as="a"
href={agentSourceUrl(agent)}
variant="secondary"
aria-label={`View ${agent.title} agent on GitHub`}
className={styles.iconButton}
>
<MarkGithubIcon />
</Button>
</div>
</Box>
{renderAgentCard(agent)}
</Grid.Column>
))}
</Grid>
Expand All @@ -420,6 +436,26 @@ export function AgentsCatalog({
) : null}
</Box>
</Box>
{below.length > 0 ? (
<Box
className={clsx(styles.gridFrame, styles.wideFrame)}
data-mode={colorMode}
>
<div
ref={wideGridRef}
className={clsx(
styles.wideGrid,
page < pageCount && styles.wideGridCentered,
)}
>
{below.map((agent) => (
<div key={agent.id} className={styles.col}>
{renderAgentCard(agent)}
</div>
))}
</div>
</Box>
) : null}
</Section>

<div className={styles.rule} aria-hidden="true" />
Expand Down
Loading
Loading