Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-back-merge-loop.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/fix-back-merge-pre-json-conflict.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions .changeset/fix-js-yaml-override-conflict.md
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 29 additions & 0 deletions .changeset/pre.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"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": [
"fix-back-merge-loop",
"fix-back-merge-pre-json-conflict",
"fix-js-yaml-override-conflict"
]
}
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/stackwright-docs/stackwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 8 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/components/base/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -66,6 +69,7 @@ export function CodeBlock({ code, language, lineNumbers = false, background }: C
</div>
)}
<pre
tabIndex={0}
style={{
margin: 0,
padding: theme.spacing.md,
Expand Down Expand Up @@ -99,7 +103,7 @@ export function CodeBlock({ code, language, lineNumbers = false, background }: C
<span>
{lineTokens.length > 0
? lineTokens.map((t, j) => {
const color = getTokenColor(t.type, isDark);
const color = getTokenColor(t.type, isDarkSurface);
return color ? (
<span key={j} style={{ color }}>
{t.content}
Expand Down
138 changes: 92 additions & 46 deletions packages/core/src/components/base/Faq.tsx
Original file line number Diff line number Diff line change
@@ -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 <details>/<summary> implementation which overrode
* native disclosure widget behavior (listStyle: none + display: flex on
* <summary>) 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<string[]>([]);

const headingColor = resolveColor(
heading?.textColor ? heading.textColor : theme.colors.primary,
Expand All @@ -32,7 +46,12 @@ export function Faq({ heading, items, background }: FaqContent) {
{heading.text}
</h3>
)}
<div

{/* Accordion.Root renders as <div> — style it as the flex column container */}
<Accordion.Root
type="multiple"
value={openItems}
onValueChange={setOpenItems}
style={{
maxWidth: '768px',
margin: '0 auto',
Expand All @@ -41,54 +60,81 @@ export function Faq({ heading, items, background }: FaqContent) {
gap: theme.spacing.xs,
}}
>
{items.map((item, index) => (
<details
key={index}
style={{
backgroundColor: theme.colors.surface,
borderRadius: '8px',
overflow: 'hidden',
boxShadow: getThemeShadow(theme, 'sm'),
}}
>
<summary
style={{
padding: `${theme.spacing.md} ${theme.spacing.md}`,
cursor: 'pointer',
fontWeight: 600,
color: theme.colors.text,
listStyle: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{item.question}
<span
aria-hidden="true"
style={{
marginLeft: theme.spacing.md,
flexShrink: 0,
fontSize: '1.25rem',
lineHeight: 1,
}}
>
+
</span>
</summary>
<div
{items.map((item, index) => {
const value = `item-${index}`;
const isOpen = openItems.includes(value);

return (
<Accordion.Item
key={index}
value={value}
style={{
padding: `0 ${theme.spacing.md} ${theme.spacing.md} ${theme.spacing.md}`,
color: theme.colors.text,
opacity: 0.8,
lineHeight: 1.6,
backgroundColor: theme.colors.surface,
borderRadius: '8px',
overflow: 'hidden',
boxShadow: getThemeShadow(theme, 'sm'),
}}
>
{item.answer}
</div>
</details>
))}
</div>
{/*
* Accordion.Header defaults to <h3>. Using asChild + <div> 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.
*/}
<Accordion.Header asChild>
<div>
<Accordion.Trigger
style={{
width: '100%',
padding: `${theme.spacing.md} ${theme.spacing.md}`,
cursor: 'pointer',
fontWeight: 600,
color: theme.colors.text,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
background: 'none',
border: 'none',
textAlign: 'left',
fontFamily: 'inherit',
fontSize: 'inherit',
}}
>
{item.question}
<span
aria-hidden="true"
style={{
marginLeft: theme.spacing.md,
flexShrink: 0,
fontSize: '1.25rem',
lineHeight: 1,
}}
>
{isOpen ? '−' : '+'}
</span>
</Accordion.Trigger>
</div>
</Accordion.Header>

{/* Accordion.Content unmounts from DOM when closed (no forceMount) */}
<Accordion.Content>
<div
style={{
padding: `0 ${theme.spacing.md} ${theme.spacing.md} ${theme.spacing.md}`,
color: theme.colors.text,
opacity: 0.8,
lineHeight: 1.6,
}}
>
{item.answer}
</div>
</Accordion.Content>
</Accordion.Item>
);
})}
</Accordion.Root>
</section>
);
}
6 changes: 4 additions & 2 deletions packages/core/src/components/base/ThemedButton.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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';

Expand Down
Loading
Loading