From cd0167136dcf37daac13e6b679e15faef2117aee Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Fri, 15 May 2026 13:46:10 -0400 Subject: [PATCH 1/8] fix(ci): resolve js-yaml override conflict breaking changeset pre exit --- .changeset/fix-js-yaml-override-conflict.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-js-yaml-override-conflict.md diff --git a/.changeset/fix-js-yaml-override-conflict.md b/.changeset/fix-js-yaml-override-conflict.md new file mode 100644 index 00000000..c5edcbf0 --- /dev/null +++ b/.changeset/fix-js-yaml-override-conflict.md @@ -0,0 +1,5 @@ +--- +"@stackwright/cli": patch +--- + +Fix js-yaml version override conflict in pnpm overrides that caused `yaml.safeLoad is removed` errors when running `changeset pre exit` in CI. The global `js-yaml: >=4.1.1` override was stomping the scoped `read-yaml-file>js-yaml: ^3` override, forcing js-yaml v4 into read-yaml-file which doesn't support it. From 381987191f042e4402a7b1a13e7a56738f2c367a Mon Sep 17 00:00:00 2001 From: Charles Gibson <46542971+perasperaactual@users.noreply.github.com> Date: Fri, 15 May 2026 15:00:52 -0400 Subject: [PATCH 2/8] fix(ci): handle modify/delete conflict on pre.json during back-merge rebase (#426) Co-authored-by: Stackwright Bot --- .changeset/fix-back-merge-pre-json-conflict.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-back-merge-pre-json-conflict.md diff --git a/.changeset/fix-back-merge-pre-json-conflict.md b/.changeset/fix-back-merge-pre-json-conflict.md new file mode 100644 index 00000000..5bb72d97 --- /dev/null +++ b/.changeset/fix-back-merge-pre-json-conflict.md @@ -0,0 +1,5 @@ +--- +"@stackwright/cli": patch +--- + +Fix back-merge into dev failing with a modify/delete conflict on `.changeset/pre.json` during rebase. The release workflow deletes this file via `changeset pre exit`, but dev's alpha-bump commits still reference it. The rebase now explicitly resolves the conflict by accepting main's deletion and continuing. From 669aeee4025b2da15fc0243aa54d887339bcd613 Mon Sep 17 00:00:00 2001 From: Charles Gibson <46542971+perasperaactual@users.noreply.github.com> Date: Sat, 16 May 2026 09:47:20 -0400 Subject: [PATCH 3/8] fix(ci): loop over pre.json conflicts during back-merge rebase (#428) Co-authored-by: Stackwright Bot --- .changeset/fix-back-merge-loop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-back-merge-loop.md diff --git a/.changeset/fix-back-merge-loop.md b/.changeset/fix-back-merge-loop.md new file mode 100644 index 00000000..b2d83090 --- /dev/null +++ b/.changeset/fix-back-merge-loop.md @@ -0,0 +1,5 @@ +--- +"@stackwright/cli": patch +--- + +Upgrade the back-merge rebase conflict handler from a one-shot block to a loop. Dev can accumulate multiple alpha-bump commits that all modified `.changeset/pre.json` — the previous one-shot `||{ }` only resolved the first conflict. The loop now runs until all pre.json conflicts are resolved or an unexpected conflict is encountered. From 9c900693f5c393156e1e40ca6d7ced8f242da671 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 16 May 2026 14:10:24 +0000 Subject: [PATCH 4/8] chore: re-enter prerelease mode after back-merge --- .changeset/pre.json | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..b6a266c0 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,25 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "stackwright-docs": "0.1.6", + "@stackwright/build-scripts": "0.7.2", + "@stackwright/cli": "0.8.5", + "@stackwright/collections": "0.1.1", + "@stackwright/core": "0.8.4", + "@stackwright/e2e": "0.3.0", + "@stackwright/hooks-registry": "0.1.1", + "@stackwright/icons": "0.5.2", + "launch-stackwright": "0.2.5", + "@stackwright/maplibre": "2.0.4", + "@stackwright/mcp": "0.4.5", + "@stackwright/nextjs": "0.5.3", + "@stackwright/otters": "0.2.1", + "@stackwright/sbom-generator": "0.2.1", + "@stackwright/scaffold-core": "0.3.1", + "@stackwright/themes": "0.5.3", + "@stackwright/types": "1.5.0", + "@stackwright/ui-shadcn": "0.1.3" + }, + "changesets": [] +} From 303bd1bbffc044bb5c7f8b06ecdddb104c552185 Mon Sep 17 00:00:00 2001 From: Charles Gibson <46542971+perasperaactual@users.noreply.github.com> Date: Sat, 16 May 2026 11:32:44 -0400 Subject: [PATCH 5/8] fix(a11y): tabindex on code blocks, skip link, fix timeline heading level (#430) Co-authored-by: Stackwright Bot --- .../core/src/components/base/CodeBlock.tsx | 1 + .../src/components/narrative/Timeline.tsx | 4 +- .../structural/DefaultPageLayout.tsx | 48 ++++++++++++++++++ .../src/components/structural/PageLayout.tsx | 49 ++++++++++++++++++- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/base/CodeBlock.tsx b/packages/core/src/components/base/CodeBlock.tsx index df879b57..1d350210 100644 --- a/packages/core/src/components/base/CodeBlock.tsx +++ b/packages/core/src/components/base/CodeBlock.tsx @@ -66,6 +66,7 @@ export function CodeBlock({ code, language, lineNumbers = false, background }: C )}
-                

{item.year} -

+

{item.event}

diff --git a/packages/core/src/components/structural/DefaultPageLayout.tsx b/packages/core/src/components/structural/DefaultPageLayout.tsx index b22bca98..dfe9e0d4 100644 --- a/packages/core/src/components/structural/DefaultPageLayout.tsx +++ b/packages/core/src/components/structural/DefaultPageLayout.tsx @@ -54,6 +54,53 @@ export default function DefaultPageLayout(pageContent: PageContent) { return (
+ { + Object.assign(e.currentTarget.style, { + left: '50%', + transform: 'translateX(-50%)', + top: '1rem', + width: 'auto', + height: 'auto', + overflow: 'visible', + padding: '0.5rem 1.25rem', + backgroundColor: '#000', + color: '#fff', + borderRadius: '4px', + textDecoration: 'none', + fontWeight: 'bold', + fontSize: '0.875rem', + }); + }} + onBlur={(e) => { + Object.assign(e.currentTarget.style, { + left: '-9999px', + top: 'auto', + width: '1px', + height: '1px', + overflow: 'hidden', + transform: '', + padding: '', + backgroundColor: '', + color: '', + borderRadius: '', + fontWeight: '', + fontSize: '', + }); + }} + > + Skip to main content + {resolvedSidebar && ( )}
+ { + Object.assign(e.currentTarget.style, { + left: '50%', + transform: 'translateX(-50%)', + top: '1rem', + width: 'auto', + height: 'auto', + overflow: 'visible', + padding: '0.5rem 1.25rem', + backgroundColor: '#000', + color: '#fff', + borderRadius: '4px', + textDecoration: 'none', + fontWeight: 'bold', + fontSize: '0.875rem', + }); + }} + onBlur={(e) => { + Object.assign(e.currentTarget.style, { + left: '-9999px', + top: 'auto', + width: '1px', + height: '1px', + overflow: 'hidden', + transform: '', + padding: '', + backgroundColor: '', + color: '', + borderRadius: '', + fontWeight: '', + fontSize: '', + }); + }} + > + Skip to main content + )} -
+
{renderContent(pageContent, { contentItemsOnly: true })}
From 886dc8a443facd8f83b3d2b30c4c9c9d144e7db1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 16 May 2026 15:33:41 +0000 Subject: [PATCH 6/8] chore: bump prerelease versions --- .changeset/pre.json | 6 +++++- packages/cli/CHANGELOG.md | 8 ++++++++ packages/cli/package.json | 2 +- packages/launch-stackwright/CHANGELOG.md | 9 +++++++++ packages/launch-stackwright/package.json | 2 +- packages/mcp/CHANGELOG.md | 9 +++++++++ packages/mcp/package.json | 2 +- 7 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index b6a266c0..32022c0b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -21,5 +21,9 @@ "@stackwright/types": "1.5.0", "@stackwright/ui-shadcn": "0.1.3" }, - "changesets": [] + "changesets": [ + "fix-back-merge-loop", + "fix-back-merge-pre-json-conflict", + "fix-js-yaml-override-conflict" + ] } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 72a4990c..1b3ee598 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @stackwright/cli +## 0.8.6-alpha.0 + +### Patch Changes + +- 669aeee: Upgrade the back-merge rebase conflict handler from a one-shot block to a loop. Dev can accumulate multiple alpha-bump commits that all modified `.changeset/pre.json` — the previous one-shot `||{ }` only resolved the first conflict. The loop now runs until all pre.json conflicts are resolved or an unexpected conflict is encountered. +- 3819871: Fix back-merge into dev failing with a modify/delete conflict on `.changeset/pre.json` during rebase. The release workflow deletes this file via `changeset pre exit`, but dev's alpha-bump commits still reference it. The rebase now explicitly resolves the conflict by accepting main's deletion and continuing. +- cd01671: Fix js-yaml version override conflict in pnpm overrides that caused `yaml.safeLoad is removed` errors when running `changeset pre exit` in CI. The global `js-yaml: >=4.1.1` override was stomping the scoped `read-yaml-file>js-yaml: ^3` override, forcing js-yaml v4 into read-yaml-file which doesn't support it. + ## 0.8.5 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 6435aefd..cb3bca5f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@stackwright/cli", - "version": "0.8.5", + "version": "0.8.6-alpha.0", "description": "CLI for Stackwright framework", "license": "MIT", "repository": { diff --git a/packages/launch-stackwright/CHANGELOG.md b/packages/launch-stackwright/CHANGELOG.md index 258612c2..e1ccb7fd 100644 --- a/packages/launch-stackwright/CHANGELOG.md +++ b/packages/launch-stackwright/CHANGELOG.md @@ -1,5 +1,14 @@ # launch-stackwright +## 0.2.6-alpha.0 + +### Patch Changes + +- Updated dependencies [669aeee] +- Updated dependencies [3819871] +- Updated dependencies [cd01671] + - @stackwright/cli@0.8.6-alpha.0 + ## 0.2.5 ### Patch Changes diff --git a/packages/launch-stackwright/package.json b/packages/launch-stackwright/package.json index 3ee4f425..576329ce 100644 --- a/packages/launch-stackwright/package.json +++ b/packages/launch-stackwright/package.json @@ -1,6 +1,6 @@ { "name": "launch-stackwright", - "version": "0.2.5", + "version": "0.2.6-alpha.0", "description": "Launch a new Stackwright project with the otter raft ready to build", "license": "MIT", "repository": { diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 2ba78c4a..682e4cbb 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,14 @@ # @stackwright/mcp +## 0.4.6-alpha.0 + +### Patch Changes + +- Updated dependencies [669aeee] +- Updated dependencies [3819871] +- Updated dependencies [cd01671] + - @stackwright/cli@0.8.6-alpha.0 + ## 0.4.5 ### Patch Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 905cd0e6..abe506dc 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@stackwright/mcp", - "version": "0.4.5", + "version": "0.4.6-alpha.0", "description": "MCP server for Stackwright — exposes content types, page management, and validation as agent tools", "license": "MIT", "repository": { From c344e9e225aed599686913bd76796c7db71c490d Mon Sep 17 00:00:00 2001 From: Charles Gibson <46542971+perasperaactual@users.noreply.github.com> Date: Sat, 16 May 2026 11:54:14 -0400 Subject: [PATCH 7/8] fix(a11y): use surface luminance for syntax highlighting, fix dark mode + button contrast (#431) Co-authored-by: Stackwright Bot --- examples/stackwright-docs/stackwright.yml | 2 +- packages/core/src/components/base/CodeBlock.tsx | 7 +++++-- packages/core/src/components/base/ThemedButton.tsx | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/stackwright-docs/stackwright.yml b/examples/stackwright-docs/stackwright.yml index 1dc764be..c7ae782d 100644 --- a/examples/stackwright-docs/stackwright.yml +++ b/examples/stackwright-docs/stackwright.yml @@ -77,7 +77,7 @@ customTheme: text: "#FFFFFF" textSecondary: "#B0BEC5" darkColors: - primary: "#D97706" # Amber 600 - darker for dark mode + primary: "#92400e" # Amber 800 — WCAG AA compliant on light dark-mode backgrounds (~5.8:1 on #F5F5F5) secondary: "#0288D1" accent: "#F59E0B" # Amber 500 background: "#FDFDFD" diff --git a/packages/core/src/components/base/CodeBlock.tsx b/packages/core/src/components/base/CodeBlock.tsx index 1d350210..c288021b 100644 --- a/packages/core/src/components/base/CodeBlock.tsx +++ b/packages/core/src/components/base/CodeBlock.tsx @@ -3,6 +3,7 @@ import { CodeBlockContent } from '@stackwright/types'; import { useSafeTheme, useSafeColorMode } from '../../hooks/useSafeTheme'; import { resolveBackground } from '../../utils/resolveBackground'; import { highlightCode, getTokenColor, HighlightToken } from '../../utils/prismHighlighter'; +import { hexToRgb, getLuminance } from '../../utils/colorUtils'; /** * Split a flat token list into per-line groups so each line can be @@ -25,7 +26,9 @@ function splitTokensByLine(tokens: HighlightToken[]): HighlightToken[][] { export function CodeBlock({ code, language, lineNumbers = false, background }: CodeBlockContent) { const theme = useSafeTheme(); const resolvedColorMode = useSafeColorMode(); - const isDark = resolvedColorMode === 'dark'; + const surfaceRgb = hexToRgb(theme.colors.surface); + const surfaceLuminance = surfaceRgb ? getLuminance(surfaceRgb.r, surfaceRgb.g, surfaceRgb.b) : 0; + const isDarkSurface = surfaceLuminance < 0.179; const tokens = highlightCode(code.trimEnd(), language); const tokenLines = splitTokensByLine(tokens); @@ -100,7 +103,7 @@ export function CodeBlock({ code, language, lineNumbers = false, background }: C {lineTokens.length > 0 ? lineTokens.map((t, j) => { - const color = getTokenColor(t.type, isDark); + const color = getTokenColor(t.type, isDarkSurface); return color ? ( {t.content} diff --git a/packages/core/src/components/base/ThemedButton.tsx b/packages/core/src/components/base/ThemedButton.tsx index b786c613..ef692693 100644 --- a/packages/core/src/components/base/ThemedButton.tsx +++ b/packages/core/src/components/base/ThemedButton.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ButtonContent } from '@stackwright/types'; import { useSafeTheme } from '../../hooks/useSafeTheme'; -import { getHoverColor, resolveColor } from '../../utils/colorUtils'; +import { getHoverColor, resolveColor, getBetterTextColor } from '../../utils/colorUtils'; import { Media } from '../media/Media'; interface ThemedButtonProps { @@ -40,7 +40,9 @@ export function ThemedButton({ button, className }: ThemedButtonProps) { : theme.colors.primary; const buttonTextColor = button.textColor ? resolveColor(button.textColor, theme.colors) - : theme.colors.text; + : button.variant === undefined || button.variant === 'contained' + ? getBetterTextColor('#FFFFFF', '#1A1A1A', buttonColor) + : theme.colors.text; const hoverColor = getHoverColor(buttonColor); const buttonSize = button.variantSize || 'medium'; From 6ae796c0a11cd90e14ffea24d41f0bdfc7c63ed9 Mon Sep 17 00:00:00 2001 From: Charles Gibson <46542971+perasperaactual@users.noreply.github.com> Date: Sun, 17 May 2026 11:05:01 -0400 Subject: [PATCH 8/8] fix(a11y): FAQ keyboard trap, video accessible name, test false positive guards (#432) * docs(a11y): document intentional Chromium-only CI scope for accessibility tests * fix(a11y): migrate FAQ to Radix Accordion, video accessible name, fix trap/carousel test guards * chore: update lockfile for @radix-ui/react-accordion in @stackwright/core * test(core): update FAQ test for Radix Accordion refactor The FAQ component was refactored to use Radix UI Accordion in this PR. Update the stale test to click the trigger before asserting answer visibility, and rename it to accurately reflect the new DOM structure. --------- Co-authored-by: Stackwright Bot --- CONTRIBUTING.md | 4 + packages/core/package.json | 1 + packages/core/src/components/base/Faq.tsx | 138 ++++++---- packages/core/src/components/media/Media.tsx | 78 +++--- .../test/components/content-types.test.tsx | 12 +- packages/e2e/TESTING_INFRASTRUCTURE.md | 17 ++ .../tests/a11y/keyboard-navigation.spec.ts | 237 ++++++++++-------- pnpm-lock.yaml | 3 + 8 files changed, 306 insertions(+), 184 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c1928b3..ad7f0ca4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -811,6 +811,8 @@ Common issues and fixes: See `packages/e2e/TESTING_INFRASTRUCTURE.md` for detailed accessibility guide. +> **CI browser scope:** In CI, the accessibility workflow runs **Chromium only** (Firefox/WebKit binaries are not installed in that job, by design). `Executable doesn't exist` errors for Firefox/WebKit in the a11y job logs are expected — not an infra gap. The job is non-blocking (`|| true`) so a11y issues are visible without blocking merges. To run a11y tests against all browsers locally, first run `pnpm --filter @stackwright/e2e exec playwright install` to install all binaries. + --- ## Cross-Browser Testing @@ -827,6 +829,8 @@ E2E tests run on multiple browsers and viewports in CI. **Total**: 6 test runs per PR (3 browsers × 2 viewports) +> **Note:** This matrix is for the main E2E suite. The **accessibility tests** (`tests/a11y/`) run **Chromium only** in CI — see below. + ### Running Cross-Browser Tests Locally ```bash diff --git a/packages/core/package.json b/packages/core/package.json index 098549ef..6614b3ea 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,6 +39,7 @@ "prepublishOnly": "npm run build" }, "dependencies": { + "@radix-ui/react-accordion": "^1.2.11", "@stackwright/themes": "workspace:*", "@stackwright/types": "workspace:*", "fuse.js": "^7.1.0", diff --git a/packages/core/src/components/base/Faq.tsx b/packages/core/src/components/base/Faq.tsx index 6189136a..c8332073 100644 --- a/packages/core/src/components/base/Faq.tsx +++ b/packages/core/src/components/base/Faq.tsx @@ -1,13 +1,27 @@ import React from 'react'; +import * as Accordion from '@radix-ui/react-accordion'; import { FaqContent } from '@stackwright/types'; import { useSafeColorMode, useSafeTheme } from '../../hooks/useSafeTheme'; import { resolveColor } from '../../utils/colorUtils'; import { resolveBackground } from '../../utils/resolveBackground'; import { getThemeShadow } from '../../utils/shadowUtils'; +/** + * FAQ accordion component built on @radix-ui/react-accordion. + * + * Replaces the previous
/ implementation which overrode + * native disclosure widget behavior (listStyle: none + display: flex on + * ) and caused a keyboard trap in Chromium. Radix Accordion handles + * all keyboard interactions correctly: Enter/Space to toggle, Tab to move + * between items, no traps. (WCAG 2.1.1, 2.1.2) + * + * type="multiple" lets users keep several answers open at once — friendlier + * for scanning a docs page than forcing single-open. + */ export function Faq({ heading, items, background }: FaqContent) { const theme = useSafeTheme(); const resolvedColorMode = useSafeColorMode(); + const [openItems, setOpenItems] = React.useState([]); const headingColor = resolveColor( heading?.textColor ? heading.textColor : theme.colors.primary, @@ -32,7 +46,12 @@ export function Faq({ heading, items, background }: FaqContent) { {heading.text} )} -
— style it as the flex column container */} + - {items.map((item, index) => ( -
- - {item.question} - - -
{ + const value = `item-${index}`; + const isOpen = openItems.includes(value); + + return ( + - {item.answer} -
-
- ))} -
+ {/* + * Accordion.Header defaults to

. Using asChild +
to + * avoid stacking multiple h3s alongside the section heading above — + * the WAI-ARIA accordion pattern requires a button inside a heading, + * but heading level depends on page context. Omitting the heading + * element here keeps Radix's ARIA button management while leaving + * heading hierarchy to the page author. + */} + +
+ + {item.question} + + +
+
+ + {/* Accordion.Content unmounts from DOM when closed (no forceMount) */} + +
+ {item.answer} +
+
+ + ); + })} + ); } diff --git a/packages/core/src/components/media/Media.tsx b/packages/core/src/components/media/Media.tsx index f917946a..dd7191cf 100644 --- a/packages/core/src/components/media/Media.tsx +++ b/packages/core/src/components/media/Media.tsx @@ -129,47 +129,65 @@ const renderImageMedia = (content: MediaItem) => { }; const renderVideoMedia = (content: VideoMediaItem) => { + const accessibleLabel = content.alt || content.label || 'Video'; return ( - + +
); }; /** Renders a basic