From 2508e1190073bcf0e4b8c3061859ee2306b80eeb Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 11:24:03 -0300 Subject: [PATCH 1/9] test(storybook): snapshot the design token stories The token stories were the only design system layer with no visual coverage at all, so a changed value in tokens.json was caught only indirectly, via whichever component story happened to consume it. Enables snapshots for the five stories that render specimens: semantic colours, the primitive palette, radius, elevation and motion. Left off: TokenReference (a text table of variable names, no visual signal, exists for MCP) and CategoricalPalette (hardcoded tag and project hexes, not tokens). Motion is safe to capture; its only animation is a CSS transition on click, unlike Loader's SVG animateTransform which Chromatic cannot pause. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/documentation/ColourPalette.stories.tsx | 2 +- frontend/documentation/ElevationTokens.stories.tsx | 2 +- frontend/documentation/MotionTokens.stories.tsx | 2 +- frontend/documentation/RadiusTokens.stories.tsx | 2 +- frontend/documentation/SemanticTokens.stories.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/documentation/ColourPalette.stories.tsx b/frontend/documentation/ColourPalette.stories.tsx index f6dd6c6b0e08..19ee0254ca0b 100644 --- a/frontend/documentation/ColourPalette.stories.tsx +++ b/frontend/documentation/ColourPalette.stories.tsx @@ -10,7 +10,7 @@ import type { Scale } from './components/ScaleRow' import primitivesSource from '!!raw-loader!../web/styles/_primitives.scss' const meta: Meta = { - parameters: { chromatic: { disableSnapshot: true }, layout: 'padded' }, + parameters: { layout: 'padded' }, title: 'Design System/Palette', } export default meta diff --git a/frontend/documentation/ElevationTokens.stories.tsx b/frontend/documentation/ElevationTokens.stories.tsx index c23dc989835b..d70968f0d278 100644 --- a/frontend/documentation/ElevationTokens.stories.tsx +++ b/frontend/documentation/ElevationTokens.stories.tsx @@ -6,7 +6,7 @@ import DocPage from './components/DocPage' import { shadow } from 'common/theme/tokens' const meta: Meta = { - parameters: { chromatic: { disableSnapshot: true }, layout: 'padded' }, + parameters: { layout: 'padded' }, title: 'Design System/Elevation', } export default meta diff --git a/frontend/documentation/MotionTokens.stories.tsx b/frontend/documentation/MotionTokens.stories.tsx index 6a6b5f260ecb..e8d33ccd6957 100644 --- a/frontend/documentation/MotionTokens.stories.tsx +++ b/frontend/documentation/MotionTokens.stories.tsx @@ -6,7 +6,7 @@ import DocPage from './components/DocPage' import { duration, easing } from 'common/theme/tokens' const meta: Meta = { - parameters: { chromatic: { disableSnapshot: true }, layout: 'padded' }, + parameters: { layout: 'padded' }, title: 'Design System/Motion', } export default meta diff --git a/frontend/documentation/RadiusTokens.stories.tsx b/frontend/documentation/RadiusTokens.stories.tsx index 610cffd4bf75..f017c26a8655 100644 --- a/frontend/documentation/RadiusTokens.stories.tsx +++ b/frontend/documentation/RadiusTokens.stories.tsx @@ -6,7 +6,7 @@ import DocPage from './components/DocPage' import { radius } from 'common/theme/tokens' const meta: Meta = { - parameters: { chromatic: { disableSnapshot: true }, layout: 'padded' }, + parameters: { layout: 'padded' }, title: 'Design System/Border Radius', } export default meta diff --git a/frontend/documentation/SemanticTokens.stories.tsx b/frontend/documentation/SemanticTokens.stories.tsx index d378dc4e52d1..f3ac90951537 100644 --- a/frontend/documentation/SemanticTokens.stories.tsx +++ b/frontend/documentation/SemanticTokens.stories.tsx @@ -7,7 +7,7 @@ import TokenGroup from './components/TokenGroup' import type { TokenGroupData } from './components/TokenGroup' const meta: Meta = { - parameters: { chromatic: { disableSnapshot: true }, layout: 'padded' }, + parameters: { layout: 'padded' }, title: 'Design System/Semantic Colour Tokens', } export default meta From fe9fb89ecf6e7045ab26e31f7ee1b6475c544e7f Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 11:24:21 -0300 Subject: [PATCH 2/9] chore(storybook): skip snapshots for the layout-only primitives Row, Column and Flex render a single div with flexbox classes. They carry no colour, radius or shadow, so no design token can change how they look and their light and dark captures are identical. With both modes enabled that is 18 snapshots per full build for a diff that cannot occur. BareButton was considered and kept: it does reference tokens, via a focus-visible ring using --color-border-action and --radius-sm. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/documentation/components/Column.stories.tsx | 8 +++++++- frontend/documentation/components/Flex.stories.tsx | 8 +++++++- frontend/documentation/components/Row.stories.tsx | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/frontend/documentation/components/Column.stories.tsx b/frontend/documentation/components/Column.stories.tsx index f6bb3234dcf8..e146951d1115 100644 --- a/frontend/documentation/components/Column.stories.tsx +++ b/frontend/documentation/components/Column.stories.tsx @@ -4,7 +4,13 @@ import type { Meta, StoryObj } from 'storybook' import Column from 'components/base/grid/Column' const meta: Meta = { - parameters: { layout: 'padded' }, + parameters: { + // Flexbox only, no colour, radius or shadow, so no design token can change + // how this renders and light/dark are identical. Snapshotting it costs + // budget for a diff that cannot happen. + chromatic: { disableSnapshot: true }, + layout: 'padded', + }, title: 'Components/Layout/Column', } export default meta diff --git a/frontend/documentation/components/Flex.stories.tsx b/frontend/documentation/components/Flex.stories.tsx index d51b6a3cb058..2c5fb3ee3e3c 100644 --- a/frontend/documentation/components/Flex.stories.tsx +++ b/frontend/documentation/components/Flex.stories.tsx @@ -4,7 +4,13 @@ import type { Meta, StoryObj } from 'storybook' import Flex from 'components/base/grid/Flex' const meta: Meta = { - parameters: { layout: 'padded' }, + parameters: { + // Flexbox only, no colour, radius or shadow, so no design token can change + // how this renders and light/dark are identical. Snapshotting it costs + // budget for a diff that cannot happen. + chromatic: { disableSnapshot: true }, + layout: 'padded', + }, title: 'Components/Layout/Flex', } export default meta diff --git a/frontend/documentation/components/Row.stories.tsx b/frontend/documentation/components/Row.stories.tsx index 9c9a7cb90320..9d2b384e6dd6 100644 --- a/frontend/documentation/components/Row.stories.tsx +++ b/frontend/documentation/components/Row.stories.tsx @@ -4,7 +4,13 @@ import type { Meta, StoryObj } from 'storybook' import Row from 'components/base/grid/Row' const meta: Meta = { - parameters: { layout: 'padded' }, + parameters: { + // Flexbox only, no colour, radius or shadow, so no design token can change + // how this renders and light/dark are identical. Snapshotting it costs + // budget for a diff that cannot happen. + chromatic: { disableSnapshot: true }, + layout: 'padded', + }, title: 'Components/Layout/Row', } export default meta From 055961259ded642079a21c3f3b438a7c59c346f8 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 11:24:23 -0300 Subject: [PATCH 3/9] ci(chromatic): only build for paths that can change a story The pull_request trigger matched frontend/**, so a change to an e2e spec, an RTK service or an API type started a Chromatic build that TurboSnap then found nothing to capture in. Aligns it with the narrower push path list, and adds package.json to both, since a dependency bump can change rendering and is also where TurboSnap bails to a full build. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index 1931f9e08ac6..8aa492c72d4d 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -1,6 +1,14 @@ name: Frontend Chromatic on: + # Keep the two path lists below identical. Anything outside them cannot change + # how a story renders, so a build there only burns snapshot quota. They are + # duplicated rather than shared via a YAML anchor because GitHub Actions does + # not support anchors in workflow files. + # + # package.json is included because a dependency bump (Storybook, React, + # Bootstrap) can change rendering, and it is also where TurboSnap bails to a + # full build, which is exactly when main's baselines need refreshing. push: branches: [main] paths: @@ -9,11 +17,17 @@ on: - frontend/common/theme/** - frontend/documentation/** - frontend/.storybook/** + - frontend/package.json - .github/workflows/frontend-chromatic.yml pull_request: types: [opened, synchronize, reopened, ready_for_review] paths: - - frontend/** + - frontend/web/components/** + - frontend/web/styles/** + - frontend/common/theme/** + - frontend/documentation/** + - frontend/.storybook/** + - frontend/package.json - .github/workflows/frontend-chromatic.yml permissions: From 5a8645155806fdfbb2ed1ab196b3fbfa37bdd472 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 15:59:59 -0300 Subject: [PATCH 4/9] ci(chromatic): snapshot only the stories a token change can break We are on the free 5,000-snapshot plan and demand is roughly 20,000 a month, so snapshots stop being captured part-way through every billing period. The full surface is 217 capturable stories, which makes any build that cannot use TurboSnap cost 434 snapshots; 8 July alone burned 2,766. Scopes captures to 17 story files via onlyStoryFiles: the five token specimen stories, plus the twelve components whose dark appearance comes from a hand-written `.dark` override rather than from a token, which are the only ones where a snapshot can catch drift. That is 45 story exports, or roughly 3,710 snapshots a month at the current build volume. onlyStoryFiles rather than onlyStoryNames because several story titles contain spaces and the action does not document how it splits list inputs. Unverified: the quota is exhausted until 17 August, so this cannot be tested against a real build until then. Check the first build's snapshot count before assuming the allowlist took effect. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index 8aa492c72d4d..f464d1fdfade 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -78,4 +78,37 @@ jobs: exitZeroOnChanges: true exitOnceUploaded: true onlyChanged: true + # Snapshot allowlist. We are on the free 5,000-snapshot plan, and the + # full surface is 217 capturable stories, so one build that cannot use + # TurboSnap costs 434 snapshots (x2 for the light and dark modes). A + # single bad day in July burned 2,766. These 17 files are the stories + # whose dark appearance comes from a hand-written `.dark` override + # rather than from a token, so they are the only ones a snapshot can + # catch drifting. Everything else inherits its baseline. + # + # Paths are relative to the Storybook project root (workingDir), and + # are file paths rather than story titles because several titles + # contain spaces ("Components/Data Display/Chip") and the action does + # not document how it splits list inputs. + # + # Widening this list costs ~62 snapshots/month per story added, at the + # current ~261 builds/month and 12% TurboSnap bail rate. + onlyStoryFiles: | + documentation/SemanticTokens.stories.tsx + documentation/ColourPalette.stories.tsx + documentation/RadiusTokens.stories.tsx + documentation/ElevationTokens.stories.tsx + documentation/MotionTokens.stories.tsx + documentation/components/Icons.stories.tsx + documentation/components/Button.stories.tsx + documentation/components/Input.stories.tsx + documentation/components/Switch.stories.tsx + documentation/components/Chip.stories.tsx + documentation/components/Tooltip.stories.tsx + documentation/components/Tabs.stories.tsx + documentation/components/Card.stories.tsx + documentation/components/Panel.stories.tsx + documentation/components/Modal.stories.tsx + documentation/components/ErrorMessage.stories.tsx + documentation/components/MultiSelect.stories.tsx autoAcceptChanges: main From 324df23a83a104a53cb024b7bf797970afe8e7a5 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 16:03:41 -0300 Subject: [PATCH 5/9] ci(chromatic): drop the token pages that a snapshot cannot judge Four of the five token documentation stories earn nothing: - MotionTokens: a static image cannot show duration or easing. - RadiusTokens, ElevationTokens: specimen sheets for tokens that Button, Card, Panel, Chip, Input and Modal already exercise, all of which are in the allowlist. - ColourPalette: renders _primitives.scss, the hand-maintained duplicate, not tokens.json. All four print token values as text, which the git diff of tokens.ts and _tokens.scss already shows whenever tokens.json changes. That is the same reason TokenReference is excluded. SemanticTokens stays. It renders live swatches read from the stylesheet, so it is the only place tokens no component exercises are visible: the nine code-* colours, surface-info, text-tertiary. Scope is now 13 story files, 41 exports, ~3,460 snapshots a month. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index f464d1fdfade..b2830bd4ff65 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -81,7 +81,7 @@ jobs: # Snapshot allowlist. We are on the free 5,000-snapshot plan, and the # full surface is 217 capturable stories, so one build that cannot use # TurboSnap costs 434 snapshots (x2 for the light and dark modes). A - # single bad day in July burned 2,766. These 17 files are the stories + # single bad day in July burned 2,766. These files are the stories # whose dark appearance comes from a hand-written `.dark` override # rather than from a token, so they are the only ones a snapshot can # catch drifting. Everything else inherits its baseline. @@ -93,12 +93,18 @@ jobs: # # Widening this list costs ~62 snapshots/month per story added, at the # current ~261 builds/month and 12% TurboSnap bail rate. + # + # Of the token documentation pages only SemanticTokens is here. It + # renders live swatches read from the stylesheet, so it is the only + # place tokens no component exercises are visible (the nine code-* + # colours, surface-info, text-tertiary). The others earn nothing: a + # static image cannot show duration or easing; radius and elevation + # specimens duplicate what Button, Card, Panel, Chip, Input and Modal + # already cover; and ColourPalette renders _primitives.scss, which is + # not the source of truth. All of them print token values as text, + # which the git diff of tokens.ts already shows for free. onlyStoryFiles: | documentation/SemanticTokens.stories.tsx - documentation/ColourPalette.stories.tsx - documentation/RadiusTokens.stories.tsx - documentation/ElevationTokens.stories.tsx - documentation/MotionTokens.stories.tsx documentation/components/Icons.stories.tsx documentation/components/Button.stories.tsx documentation/components/Input.stories.tsx From 962a7c1aa3571f2c949182bcae6b225620e9a691 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 16:05:43 -0300 Subject: [PATCH 6/9] chore(chromatic): trim the comments Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 41 ++++--------------- .../components/Column.stories.tsx | 5 +-- .../documentation/components/Flex.stories.tsx | 5 +-- .../documentation/components/Row.stories.tsx | 5 +-- 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index b2830bd4ff65..cea8f493e0c6 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -1,14 +1,8 @@ name: Frontend Chromatic on: - # Keep the two path lists below identical. Anything outside them cannot change - # how a story renders, so a build there only burns snapshot quota. They are - # duplicated rather than shared via a YAML anchor because GitHub Actions does - # not support anchors in workflow files. - # - # package.json is included because a dependency bump (Storybook, React, - # Bootstrap) can change rendering, and it is also where TurboSnap bails to a - # full build, which is exactly when main's baselines need refreshing. + # Keep both path lists identical. Nothing outside them can change how a story + # renders. Duplicated because GitHub Actions does not support YAML anchors. push: branches: [main] paths: @@ -78,31 +72,12 @@ jobs: exitZeroOnChanges: true exitOnceUploaded: true onlyChanged: true - # Snapshot allowlist. We are on the free 5,000-snapshot plan, and the - # full surface is 217 capturable stories, so one build that cannot use - # TurboSnap costs 434 snapshots (x2 for the light and dark modes). A - # single bad day in July burned 2,766. These files are the stories - # whose dark appearance comes from a hand-written `.dark` override - # rather than from a token, so they are the only ones a snapshot can - # catch drifting. Everything else inherits its baseline. - # - # Paths are relative to the Storybook project root (workingDir), and - # are file paths rather than story titles because several titles - # contain spaces ("Components/Data Display/Chip") and the action does - # not document how it splits list inputs. - # - # Widening this list costs ~62 snapshots/month per story added, at the - # current ~261 builds/month and 12% TurboSnap bail rate. - # - # Of the token documentation pages only SemanticTokens is here. It - # renders live swatches read from the stylesheet, so it is the only - # place tokens no component exercises are visible (the nine code-* - # colours, surface-info, text-tertiary). The others earn nothing: a - # static image cannot show duration or easing; radius and elevation - # specimens duplicate what Button, Card, Panel, Chip, Input and Modal - # already cover; and ColourPalette renders _primitives.scss, which is - # not the source of truth. All of them print token values as text, - # which the git diff of tokens.ts already shows for free. + # On the free plan a build that cannot use TurboSnap costs 434 + # snapshots, so capture only stories whose dark styling is a + # hand-written `.dark` override rather than a token. Everything else + # inherits its baseline. Widening this costs ~62 snapshots/month per + # story. Paths relative to workingDir, not story titles, because some + # titles contain spaces and the action does not document list parsing. onlyStoryFiles: | documentation/SemanticTokens.stories.tsx documentation/components/Icons.stories.tsx diff --git a/frontend/documentation/components/Column.stories.tsx b/frontend/documentation/components/Column.stories.tsx index e146951d1115..ceb7e4514e04 100644 --- a/frontend/documentation/components/Column.stories.tsx +++ b/frontend/documentation/components/Column.stories.tsx @@ -5,9 +5,8 @@ import Column from 'components/base/grid/Column' const meta: Meta = { parameters: { - // Flexbox only, no colour, radius or shadow, so no design token can change - // how this renders and light/dark are identical. Snapshotting it costs - // budget for a diff that cannot happen. + // Flexbox only. No token can change how this renders, and light/dark are + // identical, so there is no diff to catch. chromatic: { disableSnapshot: true }, layout: 'padded', }, diff --git a/frontend/documentation/components/Flex.stories.tsx b/frontend/documentation/components/Flex.stories.tsx index 2c5fb3ee3e3c..96ba645081ad 100644 --- a/frontend/documentation/components/Flex.stories.tsx +++ b/frontend/documentation/components/Flex.stories.tsx @@ -5,9 +5,8 @@ import Flex from 'components/base/grid/Flex' const meta: Meta = { parameters: { - // Flexbox only, no colour, radius or shadow, so no design token can change - // how this renders and light/dark are identical. Snapshotting it costs - // budget for a diff that cannot happen. + // Flexbox only. No token can change how this renders, and light/dark are + // identical, so there is no diff to catch. chromatic: { disableSnapshot: true }, layout: 'padded', }, diff --git a/frontend/documentation/components/Row.stories.tsx b/frontend/documentation/components/Row.stories.tsx index 9d2b384e6dd6..41b27195d20d 100644 --- a/frontend/documentation/components/Row.stories.tsx +++ b/frontend/documentation/components/Row.stories.tsx @@ -5,9 +5,8 @@ import Row from 'components/base/grid/Row' const meta: Meta = { parameters: { - // Flexbox only, no colour, radius or shadow, so no design token can change - // how this renders and light/dark are identical. Snapshotting it costs - // budget for a diff that cannot happen. + // Flexbox only. No token can change how this renders, and light/dark are + // identical, so there is no diff to catch. chromatic: { disableSnapshot: true }, layout: 'padded', }, From 2bc090402ad5b5cb44625c0f3a4bfbb5299e615f Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 17:25:17 -0300 Subject: [PATCH 7/9] fix(chromatic): scope snapshots with disableSnapshot, not onlyStoryFiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The action rejects the combination outright: ✖ You can only use one of --only-changed, --only-story-files And dropping onlyChanged instead is worse: without TurboSnap there is no baseline inheritance, so all 261 builds a month pay the full allowlist rather than only the ones that bail. disableSnapshot is a Storybook parameter rather than a CLI flag, so it composes with onlyChanged. Off by default in preview.js, opted into on the 13 story files worth capturing. TurboSnap keeps clean builds cheap, and a build that cannot use it captures 82 snapshots instead of 434. Also reverts the Row/Column/Flex opt-outs, now redundant under a default-off policy, and folds disableSnapshot into Modal's existing chromatic parameter rather than adding a duplicate key. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 20 ------------------- frontend/.storybook/preview.js | 3 +++ .../documentation/SemanticTokens.stories.tsx | 2 +- .../components/Button.stories.tsx | 2 +- .../documentation/components/Card.stories.tsx | 1 + .../documentation/components/Chip.stories.tsx | 1 + .../components/Column.stories.tsx | 7 +------ .../components/ErrorMessage.stories.tsx | 1 + .../documentation/components/Flex.stories.tsx | 7 +------ .../components/Icons.stories.tsx | 2 +- .../components/Input.stories.tsx | 2 +- .../components/Modal.stories.tsx | 2 +- .../components/MultiSelect.stories.tsx | 1 + .../components/Panel.stories.tsx | 1 + .../documentation/components/Row.stories.tsx | 7 +------ .../components/Switch.stories.tsx | 2 +- .../documentation/components/Tabs.stories.tsx | 1 + .../components/Tooltip.stories.tsx | 1 + 18 files changed, 19 insertions(+), 44 deletions(-) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index cea8f493e0c6..ef6f679f27ac 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -72,24 +72,4 @@ jobs: exitZeroOnChanges: true exitOnceUploaded: true onlyChanged: true - # On the free plan a build that cannot use TurboSnap costs 434 - # snapshots, so capture only stories whose dark styling is a - # hand-written `.dark` override rather than a token. Everything else - # inherits its baseline. Widening this costs ~62 snapshots/month per - # story. Paths relative to workingDir, not story titles, because some - # titles contain spaces and the action does not document list parsing. - onlyStoryFiles: | - documentation/SemanticTokens.stories.tsx - documentation/components/Icons.stories.tsx - documentation/components/Button.stories.tsx - documentation/components/Input.stories.tsx - documentation/components/Switch.stories.tsx - documentation/components/Chip.stories.tsx - documentation/components/Tooltip.stories.tsx - documentation/components/Tabs.stories.tsx - documentation/components/Card.stories.tsx - documentation/components/Panel.stories.tsx - documentation/components/Modal.stories.tsx - documentation/components/ErrorMessage.stories.tsx - documentation/components/MultiSelect.stories.tsx autoAcceptChanges: main diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 29065d615a96..7538d5f11707 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -106,6 +106,9 @@ const preview = { container: DocsContainer, }, chromatic: { + // Off by default; opt in per story with `disableSnapshot: false`. We only + // capture stories whose dark styling is a `.dark` override, not a token. + disableSnapshot: true, modes: { light: allModes.light, dark: allModes.dark, diff --git a/frontend/documentation/SemanticTokens.stories.tsx b/frontend/documentation/SemanticTokens.stories.tsx index f3ac90951537..f90391aa6831 100644 --- a/frontend/documentation/SemanticTokens.stories.tsx +++ b/frontend/documentation/SemanticTokens.stories.tsx @@ -7,7 +7,7 @@ import TokenGroup from './components/TokenGroup' import type { TokenGroupData } from './components/TokenGroup' const meta: Meta = { - parameters: { layout: 'padded' }, + parameters: { chromatic: { disableSnapshot: false }, layout: 'padded' }, title: 'Design System/Semantic Colour Tokens', } export default meta diff --git a/frontend/documentation/components/Button.stories.tsx b/frontend/documentation/components/Button.stories.tsx index 3e5533f5bbef..0f6fbd31b23f 100644 --- a/frontend/documentation/components/Button.stories.tsx +++ b/frontend/documentation/components/Button.stories.tsx @@ -46,7 +46,7 @@ const meta: Meta = { theme: 'primary', }, component: Button, - parameters: { layout: 'centered' }, + parameters: { chromatic: { disableSnapshot: false }, layout: 'centered' }, title: 'Components/Button', } diff --git a/frontend/documentation/components/Card.stories.tsx b/frontend/documentation/components/Card.stories.tsx index 450a7987ce99..5be92a9a3f0b 100644 --- a/frontend/documentation/components/Card.stories.tsx +++ b/frontend/documentation/components/Card.stories.tsx @@ -5,6 +5,7 @@ import Card from 'components/Card' const meta: Meta = { parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Chip.stories.tsx b/frontend/documentation/components/Chip.stories.tsx index 3b92bf36a299..c88fe9860935 100644 --- a/frontend/documentation/components/Chip.stories.tsx +++ b/frontend/documentation/components/Chip.stories.tsx @@ -7,6 +7,7 @@ const meta: Meta = { args: { children: 'Production' }, component: Chip, parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Column.stories.tsx b/frontend/documentation/components/Column.stories.tsx index ceb7e4514e04..f6bb3234dcf8 100644 --- a/frontend/documentation/components/Column.stories.tsx +++ b/frontend/documentation/components/Column.stories.tsx @@ -4,12 +4,7 @@ import type { Meta, StoryObj } from 'storybook' import Column from 'components/base/grid/Column' const meta: Meta = { - parameters: { - // Flexbox only. No token can change how this renders, and light/dark are - // identical, so there is no diff to catch. - chromatic: { disableSnapshot: true }, - layout: 'padded', - }, + parameters: { layout: 'padded' }, title: 'Components/Layout/Column', } export default meta diff --git a/frontend/documentation/components/ErrorMessage.stories.tsx b/frontend/documentation/components/ErrorMessage.stories.tsx index f6a634c98f17..f80f597ff0e4 100644 --- a/frontend/documentation/components/ErrorMessage.stories.tsx +++ b/frontend/documentation/components/ErrorMessage.stories.tsx @@ -5,6 +5,7 @@ import ErrorMessage from 'components/ErrorMessage' const meta: Meta = { parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Flex.stories.tsx b/frontend/documentation/components/Flex.stories.tsx index 96ba645081ad..d51b6a3cb058 100644 --- a/frontend/documentation/components/Flex.stories.tsx +++ b/frontend/documentation/components/Flex.stories.tsx @@ -4,12 +4,7 @@ import type { Meta, StoryObj } from 'storybook' import Flex from 'components/base/grid/Flex' const meta: Meta = { - parameters: { - // Flexbox only. No token can change how this renders, and light/dark are - // identical, so there is no diff to catch. - chromatic: { disableSnapshot: true }, - layout: 'padded', - }, + parameters: { layout: 'padded' }, title: 'Components/Layout/Flex', } export default meta diff --git a/frontend/documentation/components/Icons.stories.tsx b/frontend/documentation/components/Icons.stories.tsx index f6fdf7947c45..dff1c2ba7125 100644 --- a/frontend/documentation/components/Icons.stories.tsx +++ b/frontend/documentation/components/Icons.stories.tsx @@ -7,7 +7,7 @@ import type { IconName } from 'components/icons/Icon' import '../docs.scss' const meta: Meta = { - parameters: { layout: 'padded' }, + parameters: { chromatic: { disableSnapshot: false }, layout: 'padded' }, title: 'Components/Icons', } export default meta diff --git a/frontend/documentation/components/Input.stories.tsx b/frontend/documentation/components/Input.stories.tsx index 6a030aca7e8d..5d67d42e691e 100644 --- a/frontend/documentation/components/Input.stories.tsx +++ b/frontend/documentation/components/Input.stories.tsx @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from 'storybook' import Input from 'components/base/forms/Input' const meta: Meta = { - parameters: { layout: 'centered' }, + parameters: { chromatic: { disableSnapshot: false }, layout: 'centered' }, title: 'Components/Forms/Input', } export default meta diff --git a/frontend/documentation/components/Modal.stories.tsx b/frontend/documentation/components/Modal.stories.tsx index 04c3a784b689..97729dde4a74 100644 --- a/frontend/documentation/components/Modal.stories.tsx +++ b/frontend/documentation/components/Modal.stories.tsx @@ -5,7 +5,7 @@ import ModalDefault from 'components/modals/base/ModalDefault' const meta: Meta = { parameters: { - chromatic: { delay: 300 }, + chromatic: { delay: 300, disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/MultiSelect.stories.tsx b/frontend/documentation/components/MultiSelect.stories.tsx index 3a1fb16d8cad..318d47708ff3 100644 --- a/frontend/documentation/components/MultiSelect.stories.tsx +++ b/frontend/documentation/components/MultiSelect.stories.tsx @@ -18,6 +18,7 @@ const FRUITS: MultiSelectOption[] = [ const meta: Meta = { component: MultiSelect, parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Panel.stories.tsx b/frontend/documentation/components/Panel.stories.tsx index af08bcc43be4..be0d2712461d 100644 --- a/frontend/documentation/components/Panel.stories.tsx +++ b/frontend/documentation/components/Panel.stories.tsx @@ -5,6 +5,7 @@ import Panel from 'components/base/grid/Panel' const meta: Meta = { parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Row.stories.tsx b/frontend/documentation/components/Row.stories.tsx index 41b27195d20d..9c9a7cb90320 100644 --- a/frontend/documentation/components/Row.stories.tsx +++ b/frontend/documentation/components/Row.stories.tsx @@ -4,12 +4,7 @@ import type { Meta, StoryObj } from 'storybook' import Row from 'components/base/grid/Row' const meta: Meta = { - parameters: { - // Flexbox only. No token can change how this renders, and light/dark are - // identical, so there is no diff to catch. - chromatic: { disableSnapshot: true }, - layout: 'padded', - }, + parameters: { layout: 'padded' }, title: 'Components/Layout/Row', } export default meta diff --git a/frontend/documentation/components/Switch.stories.tsx b/frontend/documentation/components/Switch.stories.tsx index b77d4f804c11..84896f833669 100644 --- a/frontend/documentation/components/Switch.stories.tsx +++ b/frontend/documentation/components/Switch.stories.tsx @@ -20,7 +20,7 @@ const meta: Meta = { disabled: false, }, component: Switch, - parameters: { layout: 'centered' }, + parameters: { chromatic: { disableSnapshot: false }, layout: 'centered' }, title: 'Components/Forms/Switch', } diff --git a/frontend/documentation/components/Tabs.stories.tsx b/frontend/documentation/components/Tabs.stories.tsx index 4ac40fa35993..8c22fa140603 100644 --- a/frontend/documentation/components/Tabs.stories.tsx +++ b/frontend/documentation/components/Tabs.stories.tsx @@ -28,6 +28,7 @@ const meta: Meta = { }, decorators: [withRouter], parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: diff --git a/frontend/documentation/components/Tooltip.stories.tsx b/frontend/documentation/components/Tooltip.stories.tsx index 91ab5f4f7384..153bf494f368 100644 --- a/frontend/documentation/components/Tooltip.stories.tsx +++ b/frontend/documentation/components/Tooltip.stories.tsx @@ -6,6 +6,7 @@ import Tooltip from 'components/Tooltip' const meta: Meta = { parameters: { + chromatic: { disableSnapshot: false }, docs: { description: { component: From 87129be5bfd964f9293483e51cd2f287baab3fa8 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 17:48:08 -0300 Subject: [PATCH 8/9] ci(chromatic): use a YAML anchor for the paths, add the lockfile Both from CodeRabbit on #8208. GitHub Actions has supported YAML anchors since September 2025, so the duplicated path lists collapse into one, and the comment claiming otherwise was wrong. package-lock.json joins package.json: the job runs `npm ci`, which installs from the lockfile, so a transitive bump can change rendering without package.json moving. Missing it also left main's baseline stale after a dependency update, which is what makes TurboSnap bail. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/frontend-chromatic.yml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/frontend-chromatic.yml b/.github/workflows/frontend-chromatic.yml index ef6f679f27ac..b3336e2a85d8 100644 --- a/.github/workflows/frontend-chromatic.yml +++ b/.github/workflows/frontend-chromatic.yml @@ -1,28 +1,23 @@ name: Frontend Chromatic on: - # Keep both path lists identical. Nothing outside them can change how a story - # renders. Duplicated because GitHub Actions does not support YAML anchors. + # Nothing outside these paths can change how a story renders. The lockfile is + # included alongside package.json because `npm ci` installs from it, so a + # transitive bump can change rendering without package.json moving. push: branches: [main] - paths: + paths: &story_paths - frontend/web/components/** - frontend/web/styles/** - frontend/common/theme/** - frontend/documentation/** - frontend/.storybook/** - frontend/package.json + - frontend/package-lock.json - .github/workflows/frontend-chromatic.yml pull_request: types: [opened, synchronize, reopened, ready_for_review] - paths: - - frontend/web/components/** - - frontend/web/styles/** - - frontend/common/theme/** - - frontend/documentation/** - - frontend/.storybook/** - - frontend/package.json - - .github/workflows/frontend-chromatic.yml + paths: *story_paths permissions: contents: read From 0d1653f2393d4d0d4c3a54e9f3941c96451a65e9 Mon Sep 17 00:00:00 2001 From: Talisson Costa Date: Mon, 3 Aug 2026 17:54:37 -0300 Subject: [PATCH 9/9] docs(storybook): describe both reasons a story opts into snapshots The comment named only the `.dark` override criterion, but SemanticTokens opts in and has no `.dark` override at all. From CodeRabbit on #8208. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/.storybook/preview.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 7538d5f11707..90c66e996cfb 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -106,8 +106,9 @@ const preview = { container: DocsContainer, }, chromatic: { - // Off by default; opt in per story with `disableSnapshot: false`. We only - // capture stories whose dark styling is a `.dark` override, not a token. + // Off by default; opt in per story with `disableSnapshot: false`. Opted + // in: components still styling dark mode with hand-written `.dark` blocks, + // plus SemanticTokens for token values no component exercises. disableSnapshot: true, modes: { light: allModes.light,