Skip to content

ci: a touched-components check runs visual, a11y and RTL for the components a pull request changes - #9178

Merged
cixzhang merged 10 commits into
mainfrom
ci/touched-components-check
Nov 1, 2026
Merged

cixzhang merged 10 commits into
mainfrom
ci/touched-components-check

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Maintainer impact

A pull request update runs no visual, accessibility or RTL check unless someone adds a ci:run-* label, so a component change usually reaches main with no browser evidence. The PR comment can't link a reviewer to the affected components' docsite and Storybook pages with a result beside each. A narrow default check gives every component pull request that evidence for a few standard-runner minutes per update, and leaves the full suites behind their labels.

Intended invariant

  • Every pull request runs a touched-components check, against the PR's Storybook build and within the 15-minute budget:
    • Visual: pr-visual's default mode compares the touched Core components (the only stable visual stories).
    • Accessibility and RTL: pr-touched-components audits the touched components in every package (core/…, lab/…, charts/…) on one standard arm runner.
  • It never widens to reverse dependents or a full suite. A wide change skips it: more than 5 touched components, or a change to shared styles (themes, tokens, or a .stylex.ts file in a package's shared theme, utils, hooks, internal or Layer folder). Behaviour-only shared code is not a wide change. Nothing starts a full suite on its own; ci:run-visual|a11y|rtl do.
  • It's advisory: no required context and no release-check join depends on it. The release check is unchanged.

CI impact (standard ubuntu-24.04-arm runners for both jobs; no non-standard runner):

  • Stable visual regression (pr-visual) now starts on every unlabelled pull request whose Storybook builds.
    • Comparing nothing (no touched Core component with a baseline, or a wide change, which writes its skipped verdict) costs about 0.7 job-minutes: 43 s on this pull request's own run.
    • Comparing 1–5 touched components costs about 0.9–1.1 job-minutes.
    • Before, it didn't start without ci:run-visual.
  • Touched components (visual, accessibility, RTL) (new) runs on every pull request whose Storybook builds, labelled or not.
    • With no component, or on a wide change, it skips in about 0.3 job-minutes (18 s here).
    • With 1–5 touched components it costs about 1.6–3.6 job-minutes (measured replay below).
    • It also runs beside ci:run-a11y and ci:run-rtl.
  • Per update: about 1.0 job-minute with no component, and 2.5–4.7 with 1–5 components.
  • Runner pool: the last 7 days had 372 pull-request CI runs, about 53 updates a day. That adds about 53 runner-minutes a day if no update touches a component, about 95 if half touch one (median), and at most about 250 if every update touches five. That's about 0.04 / 0.07 / 0.17 average concurrent standard jobs.
  • Time to green: unchanged for required checks, since both jobs are advisory and in no join. The advisory result lands about 3–5 minutes after the Storybook build.

Change

  • pr-visual's default mode (the visual part; Core only, since only Core has stable visual stories):
    • Without ci:run-visual, touched-components.mjs scope decides the touched set and wide change, keeping only components with an accepted baseline shot.
    • gate.mjs check --tiers component --no-scout compares them, with the PR head and base SHA and ASTRYX_VISUAL_SCOPE=touched, so the published verdict carries scope: touched.
    • A wide change writes a skipped verdict whose reason names ci:run-visual. A scope with nothing to compare leaves no verdict.
    • The full check runs only with ci:run-visual. pr-visual stays the one visual owner (FR12 guard).
  • New pr-touched-components job (accessibility and RTL, every package, one standard arm runner):
    • It uses the shared scope action for the changed-file list, then the PR's pr-analysis, skipped for spec-only, docsite-only and tooling-only changes.
    • It outputs wide_change and component_count as job outputs.
    • It runs the accessibility audit (a11y-pr-scope.mjs filters) and the RTL audit (--filter per package). RTL runs even after a failed accessibility audit.
    • It reads pr-visual's verdict, including after a failed pr-visual.
    • touched-components.mjs report writes touched-components.json: {headSha, wideChange, componentCount, components: [{key, package, name, results: {visual, a11y, rtl}}]}, with each result pass, fail, changed or not-run. A wide change writes wideChange: true, componentCount, foundations, and the components without results. The file is uploaded as the touched-components artifact, which the PR report reads (ci: the PR report reads the touched-components check's per-component results and wide-change flag, once #9178 writes them #9217).
  • Result rules:
    • Visual: a failed job without a verdict is fail, and a failed capture is matched to its component through the manifest. A component with no accepted baseline is not-run.
    • Accessibility: a new violation in an owner's own stories, or in its folder's umbrella story, fails it. An owner the audit checked outside the touched set, such as a member's pattern story, gets its own row, so no violation is dropped. An audit that failed before its report fails every owner.
    • RTL: automatic sections and curated cards count, and a partial curated card fails. A coverage gap fails, and an audit that failed before its report fails.
  • Multi-component folders: a changed folder such as Chat or Table expands to its members through changedFolderMembers in a11y-pr-scope.mjs, the accessibility scope's own helper. A folder counts as one component for the wide rule, as the threshold's data counted folders.
  • Why no reverse dependents: the analysis reports every component whose bundle reaches a change. In recent opted-in runs, touching Skeleton or Spinner reached most of Core (AlertDialog, AppShell, AspectRatio, Avatar, …), which pushed the visual check over its focused limit into the full plan. Dependents stay with the labelled owners.
  • Records:
    • spec:AST-030 FR12 covers the default check and the wide rule.
    • FR11 and DEC-4 cover the advisory job as the owners' own coverage, not a new lane.
    • FR13, DEC-5, DEC-6, DEC-7 and architecture:rtl-audit-evidence INV7 match.
    • CONTRIBUTING and the visual-gate README are updated.

Wide-change threshold: N = 5

The coordinator's analysis of 600 merged PRs since 10-17: 327 touched at least one component, with a median of 1, p90 of 2, p95 of 3 and a maximum of 21. Only 2.1% touched more than 5 and 0.9% more than 10.

My count over 908 merged PRs since 10-17 agrees. 454 touched at least one component, with a p90 of 3 and a p95 of 3. Examples above the line: #9055 (21 components), #8367 (20), #9071 (16).

Evidence

  • touched-components.test.mjs:

    • Scope is touched-only (never dependents) and includes non-Core packages.
    • 5 components run and 6 skip; foundation paths (themes, tokens, shared .stylex.ts) are wide while behaviour-only paths aren't.
    • The wide record and summary point to the labels.
    • Per-component visual (changed, added, failed, not-run), accessibility (new violations only) and RTL (any package report) results.
    • The record shape matches what the comment reads.
    • Routing: pr-touched-components runs on every PR after the Storybook build and pr-visual, on ubuntu-24.04-arm with a ≤15-minute timeout; exposes the outputs; is in no join; skips visibly; uploads.
    • pr-visual's default step runs gate.mjs check --tiers component, never the release plan, with the PR head and base and scope: touched.
  • ci-test-routing.test.mjs: the job asks the scope action for component-audit.

  • pr-status.test.mjs and publish-pr-report.test.mjs: the published verdict keeps only the touched scope marker, which the PR report (ci: the PR report reads the touched-components check's per-component results and wide-change flag, once #9178 writes them #9217) reads.

  • Suites: .github/scripts passes 1,727 tests. actionlint shows no new findings and pnpm check:knowledge is aligned.

  • Measured replay (a do-not-merge replay pull request, now closed, on this design; standard GitHub-hosted arm runners; wall-clock minutes per job):

    Scenario Touched components pr-touched-components pr-visual (default touched mode) Runner-minutes per update
    A 1 (Toast) 1.6 1.0 2.6
    C 4 (MultiSelector, TextArea, Toast, Lab MobileTokenizer) 3.6 1.1 4.7
    D 5 (C + Slider) 3.5 0.9 4.4
    • Each job's fixed cost is about 45 seconds (checkout, setup, Playwright install). Accessibility and RTL then add about 15–20 seconds per component, and the focused visual check takes 3–6 seconds. Both jobs stay well inside the 15-minute budget, so no sharding is needed.
    • Scenarios C and D surfaced two defects, both fixed in this head:
      • A coverage gap in a Lab component (no RTL route) made the RTL audit exit nonzero and skip the report. Audit findings are now the report's to judge, as in pr-rtl-shard.
      • The visual verdict download was skipped after that failure. It now always runs.
    • A component whose RTL checks are all not applicable now reports not-run, not pass.
    • The analysis decides the touched set, so it can differ from the files touched: a Selector edit that changed no bundle output was not counted. A wide change, above five, is covered by the unit tests; the replay's widest set was five.

Scope

  • No public API or product behavior change.
  • Public text contains no internal context.

Writing rubric results

R1 narration: none. FR11, FR12, FR13, DEC-4, DEC-6, DEC-7 and rtl-audit-evidence INV7 state the check, the wide rule and the advisory job in the present tense.
R2 research: none. The threshold data lives in this description; the record keeps only the rule.
R3 evidence: none changed.
R4 rejected alternatives: none added.
R5 change references: none.
R6 open questions: none.

Testing

pnpm vitest run --project node .github/scripts/; actionlint; pnpm check:knowledge.

@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 1, 2026 7:03am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 31, 2026
@astracat-bot

astracat-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Nov 1, 2026, 7:07 AM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · 1b51112

Storybook · Sandbox · built from this PR's head

Package size: no published package changed; no new dependencies.

API changes: core +2 · ~1 · −0 (report stale: run pnpm api:report)

3 API changes (advisory)
Package Where Signature Usage
+ core . · Types · interface SearchContext interface SearchContext<T extends SearchableItem = SearchableItem> import type {SearchContext} from '@astryxdesign/core'
+ core ./Typeahead · Also exported from an earlier entry point · SearchContext SearchContext (.) import {SearchContext} from '@astryxdesign/core/Typeahead'
~ core . · Types · interface SearchSource search: (query: string) => Promise<T[]> | T[] → search: (query: string, context?: SearchContext<T>) => Promise<T[]> | T[] import type {SearchSource} from '@astryxdesign/core'; const x: SearchSource = {search: …}

Not run on this PR: add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.

CI impact: +0.2 job-min per update · pool +0.3% · time to green +1.4 min · some runners chosen by event or unknown · 11 CI action or script files changed (not measured)
Before After Change
Job-minutes per PR update 78.6 78.9 +0.2
Runner pool, runner-minutes a day 36,679 36,792 +113 (+0.3%)
Average concurrent jobs 25.5 25.5 +0.1
Time to green (median → this run) 14.6 min 15.9 min +1.4 min
Runs a day 466 466 0

Changed jobs: Touched components added: standard · Stable visual regression changed: standard or non-standard by event (2-core-ubuntu-arm)

Not measured (the jobs that use these keep their recent minutes above): .github/scripts/a11y-pr-scope.mjs, .github/scripts/ci-heavy-lanes.test.mjs, .github/scripts/ci-test-routing.test.mjs, .github/scripts/run-labels.test.mjs, .github/scripts/touched-components.mjs, .github/scripts/touched-components.test.mjs, .github/scripts/visual-gate/README.md, .github/scripts/visual-gate/gate.mjs, .github/scripts/visual-gate/publish-pr-report.mjs, .github/scripts/visual-gate/publish-pr-report.test.mjs, and 1 more.

Assumptions: before is each job's average over the last 20 pull-request runs; after keeps that for unchanged jobs and uses this run's minutes for added or changed ones, weighted by how often each changed job ran in those runs; runs a day averages the seven whole days before today; time to green compares this run with the median of those runs that ran the same jobs.


Generated by PR Enrichment workflow | View full report

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 4aa2ed3

@cixzhang
cixzhang force-pushed the ci/touched-components-check branch from 4aa2ed3 to 07ccaba Compare October 31, 2026 18:05
@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
@cixzhang
cixzhang force-pushed the ci/touched-components-check branch from 07ccaba to 472d86c Compare October 31, 2026 18:09
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision. This pull request rewrites spec:AST-030 FR12 and DEC-7 to add a label-free pull-request check that the current FR12 and DEC-7 rule out, and no /approve-spec covers the current head. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA>, and should say whether DEC-6, DEC-4, FR12's single-owner sentence and the RTL audit evidence record's INV7 are amended to match. Whatever the decision, these need fixing:

  • The pr-analysis download has no if, so the check fails every spec-only, docsite-only, tooling-only and release-branch pull request.
  • A change inside a multi-component folder such as Table, Chat or DropdownMenu is reported as "No component changed" and nothing is audited.
  • A visual capture failure reads as pass and skips the accessibility and RTL steps, and a pull request that only adds a Core component makes the visual gate exit 1 on an empty plan.
  • The RTL loop stops at the first package with a finding, and curated failures or all-N/A results read as pass.
  • The visual step builds Core and every theme inside the 15-minute job because it does not download the built dists artifact the way pr-visual does.

[Automated review]

Comment thread .github/workflows/ci.yml
Comment on lines +2936 to +2939
- name: Download analysis artifact
uses: actions/download-artifact@v8
with:
name: pr-analysis

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This download has no if. For spec-only, docsite-only, tooling-only and release-branch pull requests build-storybook uploads no pr-analysis, so the job fails on pull requests with no component. Gate it on steps.scope.outputs.has_components == 'true', the way pr-a11y-guards does.

Comment thread .github/scripts/touched-components.mjs Outdated
Comment on lines +94 to +99
const touched = [
...new Set([
...strings(analysis.newComponentOwners),
...strings(analysis.modifiedComponentOwners),
]),
].filter(owner => owner.includes('/'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analyze-pr.js leaves multi-component folders (Table, Chat, DropdownMenu) in unresolvedComponentSources, so a change there gives an empty touched set and "No component changed". a11y-pr-scope.mjs already expands those sources; the touched set needs the same members.

Comment thread .github/scripts/touched-components.mjs Outdated
Comment on lines +158 to +163
const mine = entry =>
namesOwner(
typeof entry === 'string' ? shots[entry]?.component : entry?.component,
identity,
);
if ((verdict.failures ?? []).some(mine)) return {result: 'fail'};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gate writes failures as {key, error} and keeps failed shots out of the manifest, so mine never matches a failure and a failed visual check reads pass. Treat a failure with no manifest component as a failure for every audited Core component.

Comment thread .github/workflows/ci.yml
Comment on lines +3033 to +3042
for PACKAGE in "${PACKAGE_LIST[@]}"; do
FILTER=$(tr ',' '\n' <<< "$OWNERS" | grep "^$PACKAGE/" | paste -sd, -)
[ -n "$FILTER" ] || continue
node apps/storybook/rtl-audit/rtl-audit.mjs \
--storybook-dir apps/storybook/dist \
--output "rtl-reports/$PACKAGE.json" \
--packages "$PACKAGE" \
--concurrency 8 \
--filter "$FILTER"
done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rtl-audit.mjs exits 1 on any finding (that is why pr-rtl-shard is continue-on-error), so under bash -e the first package with a finding ends this loop and the remaining packages are never audited.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally changes spec:AST-030 FR12 and DEC-7 to add a label-free touched-components check that the current FR12 and DEC-7 rule out, and no /approve-spec covers the current head. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA>, and should settle whether FR12's retained label sentence, FR13, DEC-6, DEC-7's title, FR11/DEC-4 and the RTL audit evidence record's INV7 are amended to match. Whatever the decision, these need fixing:

  • pr-visual's default step sets no ASTRYX_PR_HEAD_SHA or ASTRYX_PR_BASE_SHA, so the PR comment rejects its verdict and the PR Comment status fails on unlabelled component pull requests.
  • The pr-analysis download in pr-touched-components has no if, so the check fails every spec-only, docsite-only and tooling-only pull request.
  • The default visual step exits 1 when no touched Core component has an accepted baseline shot, as in a pull request that only adds one, which turns Stable visual regression red.
  • RTL rows read pass for all-N/A components and curated failures, and the per-package loop stops at the first package with a finding.
  • A visual failure is recorded as not-run, and a change inside a multi-component folder such as Table or Chat is reported as "No component changed".

[Automated review]

Comment thread .github/workflows/ci.yml
- name: Visual check for the default touched components
if: steps.touched.outputs.has_components == 'true' && steps.touched.outputs.visual_components != ''
env:
COMPONENTS: ${{ steps.touched.outputs.visual_components }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step sets no ASTRYX_PR_HEAD_SHA or ASTRYX_PR_BASE_SHA (the labelled step does at L2410-L2411), so the verdict records null head and base and publish-pr-report.mjs refuses it; pr-comment.yml then reports the PR Comment status as failed. Pass the same two variables here.

Comment thread .github/workflows/ci.yml
set -e
case "$code" in
0|2) ;;
3) rm -f .visual-run/verdict.json ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gate exits 3 only when --dependents is passed (gate.mjs:243-253), so this branch never runs. When no touched Core component has an accepted baseline shot, as in a pull request that only adds one, the plan is empty and the gate exits 1 here, failing pr-visual.

Comment thread .github/workflows/ci.yml
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false

- name: Download analysis artifact

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This download has no if. Spec-only, docsite-only and tooling-only pull requests upload no pr-analysis, so the job fails on them. Gate it on steps.scope.outputs.has_components == 'true', as every other analysis download in this file is gated.

Comment thread .github/workflows/ci.yml Outdated
# The visual result comes from pr-visual, the one visual owner: its
# default touched check, or its full check under ci:run-visual.
- name: Download pr-visual's verdict
if: steps.touched.outputs.has_components == 'true' && needs.pr-visual.result == 'success'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A failed visual verdict makes pr-visual exit 1, so this never downloads a failure and every row reads visual not-run. With no always(), an RTL finding in the step above also skips it.

const mine = results.filter(result => namesOwner(result.component, identity));
if (mine.length === 0) return {result: 'not-run'};
const failed = mine.filter(
result => result.verdict !== 'pass' && result.verdict !== 'N-A',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N-A counts as passing and the curated section is never read, so an all-N/A component or a curated not-RTL card reads pass (rtl-audit-evidence INV3, INV4). report.coverage already carries each component's measured, verified-N/A or gap status.

Comment thread .github/scripts/touched-components.mjs Outdated
const touched = [
...new Set([
...strings(analysis.newComponentOwners),
...strings(analysis.modifiedComponentOwners),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analyze-pr.js leaves multi-component folders (Table, Chat, DropdownMenu) in unresolvedComponentSources, so a change there gives an empty touched set and "No component changed". resolvePrA11yComponents in a11y-pr-scope.mjs already expands those folders.

…the components a pull request changes

Every pull request without a ci:run-<suite> label runs one default check for
only the components it touches, in every package, against its Storybook
build. The visual part is pr-visual's new default mode, a focused check of
the touched Core components that never escalates to the release plan, so
pr-visual stays the one visual owner (AST-030 FR12). The accessibility and
RTL parts run in pr-touched-components on one standard arm runner, which
folds in pr-visual's verdict and writes touched-components.json: one row per
component with each suite's pass/fail/changed/not-run result, or the wide
flag and count. A wide change (more than five components, or shared styles:
themes, tokens, shared .stylex.ts) skips it, and nothing starts a full suite
on its own. AST-030 FR12 and DEC-7 record the check.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally changes spec:AST-030 FR12 and DEC-7 to add a label-free touched-components check that the current FR12 and DEC-7 rule out, and no /approve-spec covers the current head. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA>, and should settle whether FR12's retained label sentence, FR13, DEC-6, DEC-7's title, FR11/DEC-4 and the RTL audit evidence record's INV7 are amended to match. Whatever the decision, these need fixing:

  • pr-visual's default step sets no ASTRYX_PR_HEAD_SHA or ASTRYX_PR_BASE_SHA, so the PR comment rejects its verdict and the PR Comment status fails on unlabelled component pull requests.
  • The pr-analysis download in pr-touched-components has no if, so the check fails every spec-only, docsite-only and tooling-only pull request.
  • The default visual step exits 1 when no touched Core component has an accepted baseline shot, as in a pull request that only adds one.
  • A visual failure is recorded as not-run, a curated RTL failure as pass, and a change inside Chat or Table as "No component changed".
  • With pr-visual running on every pull request, the PR comment no longer tells anyone to add ci:run-visual, wide changes included.

[Automated review]

Comment thread .github/workflows/ci.yml
if: >-
(github.event_name != 'pull_request' || !github.event.pull_request.draft && (github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'ci:run'))) && (!cancelled() &&
(((github.event_name == 'pull_request' && needs.run-labels.outputs.visual == 'true') &&
((github.event_name == 'pull_request' &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the label clause gone, Stable visual regression now succeeds on every unlabelled pull request, including a wide change or one with no Core component where nothing is compared. On main, pr-status.js counts visual as run whenever this job is not skipped (suiteRunState), so the PR comment's "Not run on this PR" line stops naming ci:run-visual. Have the comment treat visual as not run when no visual verdict was published.

Comment thread .github/workflows/ci.yml Outdated
# The visual result comes from pr-visual, the one visual owner: its
# default touched check, or its full check under ci:run-visual.
- name: Download pr-visual's verdict
if: always() && steps.touched.outputs.has_components == 'true' && needs.pr-visual.result == 'success'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A failed verdict makes the gate exit 1 (gate.mjs:629), so pr-visual fails and this download never runs: the row reads not-run. Download when needs.pr-visual.result != 'skipped', and treat status failed as fail in visualResult, since the gate writes failures as {key, error} with no component.

Comment thread CONTRIBUTING.md Outdated
that suite from the next run. When the accessibility audit did not run, the
pull-request comment names `ci:run-a11y`.
- **Every pull request runs a touched-components check** (visual,
accessibility and RTL for only the components it changes) on one standard

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The visual part runs in pr-visual, on its own runner, before pr-touched-components starts, so the check uses two runners; "on one standard runner" describes only the accessibility and RTL job.

@cixzhang
cixzhang marked this pull request as ready for review November 1, 2026 00:14
pr-visual's default step sets the PR head and base and marks its verdict
scope: touched, so the PR comment accepts it and names ci:run-visual; a wide
change writes a skipped verdict that does the same. Only touched components
with an accepted baseline shot are compared. pr-touched-components skips the
analysis for spec-only, docsite-only and tooling-only changes, reads a failed
pr-visual's verdict (and reports a failed visual job as fail), counts curated
RTL cards, and expands a changed multi-component folder to its members,
counted as one component. AST-030 FR11/DEC-4 record the advisory job; FR12,
FR13, DEC-6, the DEC-7 title and rtl-audit-evidence INV7 match.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally rewrites spec:AST-030 FR11, FR12, FR13, DEC-4, DEC-6 and DEC-7 and the RTL audit evidence record's INV7 to add a label-free touched-components check that the current FR12 and DEC-7 rule out, and no /approve-spec covers the current head. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA>, and should say whether DEC-7's opening sentence and DEC-5's guard sentence are amended to match. Whatever the decision, these need fixing:

  • The branch conflicts with main, which deleted lib/visual-format.js; main's comment builder (lib/pr-status.js) would then mark every sized component visual ✓ after a touched-only check and stop naming ci:run-visual, so it needs to learn the touched scope.
  • The RTL audit step has no always(), so a failed accessibility audit skips it and both suites read not-run instead of fail.
  • CONTRIBUTING and DEC-7 say the whole check runs on one standard runner, but its visual part runs in pr-visual on a second runner.

[Automated review]

Comment thread .github/workflows/ci.yml Outdated
--components "$COMPONENTS"

- name: RTL audit for the touched components
if: steps.touched.outputs.has_components == 'true' && steps.touched.outputs.rtl_packages != ''

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without always() this step is skipped whenever the accessibility step fails (for example accessibility-audit.js throwing "No owned Storybook stories resolved"), and the report then records both accessibility and RTL as not-run. Prefix it with always() &&, and pass the accessibility step's outcome to report so a crashed audit reads fail.

Comment thread .github/scripts/lib/visual-format.js Outdated
verdict?.context?.scope === 'touched' &&
verdict.status !== 'skipped'
) {
return `${section}Only the touched components were checked; add \`ci:run-visual\` to run the full suite.\n\n`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main deleted this file (#9201) and now builds the comment in lib/pr-status.js, so this line never reaches a reader and the branch conflicts. On main, notRunLine stops naming ci:run-visual once pr-visual runs on every pull request, and componentRows marks every sized component visual ✓ after a touched-only pass; the touched scope has to be read there.

Comment thread CONTRIBUTING.md Outdated
that suite from the next run. When the accessibility audit did not run, the
pull-request comment names `ci:run-a11y`.
- **Every pull request runs a touched-components check** (visual,
accessibility and RTL for only the components it changes) on one standard

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The visual part runs in pr-visual, a separate job on its own runner, before pr-touched-components starts, so this is two standard runners, as FR12 already says.

Comment thread docs/specs/AST-030/spec.md Outdated
@@ -479,7 +507,10 @@ the same exact-commit `release-check` the release gate uses, and before a cut;
a pull request runs each only when it opts in with that suite's `ci:run-visual`, `ci:run-a11y` or `ci:run-rtl` label. Main-push checks are throttled: one runs at a time, a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This opening still says a pull request runs each suite only with its label, beside FR12's label-free pr-visual mode; it needs "at its opted-in scope" like FR12. DEC-5's "PR accessibility … retain[s] the … guards" sentence has the same gap for the touched job's guard-less audit.

…-check

# Conflicts:
#	.github/scripts/lib/visual-format.js
#	.github/scripts/lib/visual-format.test.mjs
…er a failed a11y audit

Main's status table (pr-status.js) replaced visual-format.js. A touched-only
visual verdict now gives a result only to the components it compared (its
published context carries them) and the table names ci:run-visual, with its
own line for a wide change. The touched job's RTL audit runs even when the
accessibility audit failed. CONTRIBUTING and AST-030 DEC-7 say where each
part runs; DEC-7's opening and DEC-5's guard sentence match the check.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally rewrites spec:AST-030 FR11, FR12, FR13 and DEC-4 to DEC-7 and the RTL audit evidence record's INV7 to add a label-free touched-components check, with its five-component and shared-style skip rule, that the current FR12 and DEC-7 rule out, and no /approve-spec covers the current head. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA>. Whatever the decision, these need fixing:

  • When pr-visual's default mode compares nothing, as for a behaviour-only hook change, the PR comment stops naming ci:run-visual and reads as if visual ran.
  • touched-components.json records a crashed accessibility audit, and an RTL coverage gap, as not-run ("no RTL check applies") instead of a failure or a gap.
  • expandFolderOwners copies the folder expansion in a11y-pr-scope.mjs and has already drifted from it; both should call one shared helper.
  • The description still says pr-touched-components runs the visual check in every package on one runner, but visual runs in pr-visual, for Core only.

[Automated review]

Comment thread .github/scripts/lib/pr-status.js Outdated
if (notRun.length === 0) return '';
// pr-visual's default check ran, but compared only the touched components
// (or nothing, for a wide change).
const touchedVisual =

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-visual now runs on every pull request, so suiteRunState counts visual as run even when the default mode published no verdict (a behaviour-only hook change, or no touched Core component with a baseline shot). Then neither this line nor the not-run list names ci:run-visual. Keep naming it when no visual verdict was published, or have the default step publish a touched-scope verdict that says nothing was compared.

Comment thread .github/scripts/touched-components.mjs Outdated

/** The accessibility result for one owner: new violations fail it. */
export function a11yResult(identity, report, newViolations) {
if (report == null) return {result: 'not-run'};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thrown accessibility-audit.js or a11y-pr-scope.mjs leaves no report, so a crashed audit reads not-run while the job goes red. Pass the accessibility step's outcome to report and read a failure with no report as fail, as visualResult already does for a failed visual job.

]);
const mine = results.filter(result => namesOwner(result.component, identity));
if (mine.length === 0) return {result: 'not-run'};
if (mine.every(result => result.verdict === 'N-A')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The report's coverage rollup already says whether an all-N/A component is verified-na or a coverage-gap (and rtl-audit.mjs exits 1 for a gap). Reading only the sections labels an unverified gap "no RTL check applies" (rtl-audit-evidence INV4, INV5). Read coverage.results[].status and keep this detail for verified-na.

* expands only the changed folder, as the accessibility scope does, without
* reading Storybook.
*/
export function expandFolderOwners(sources, repoRoot = process.cwd()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repeats resolvePrA11yComponents' folder expansion (a11y-pr-scope.mjs:108-157) and has already drifted: it accepts ./.. and skips a source the original rejects. Export the member enumeration from one module and call it from both; the accessibility scope adds its route check on top.

…s; one folder expansion

An accessibility or RTL audit that failed before writing its report now
fails its owners instead of reading not-run, and an RTL coverage gap fails
rather than reading "no RTL check applies". The PR status table says visual
did not run (and names ci:run-visual) when pr-visual's default mode compared
nothing. The changed-folder expansion is one helper, changedFolderMembers in
a11y-pr-scope.mjs, used by both the accessibility scope and this check.
cixzhang added a commit that referenced this pull request Nov 1, 2026
…results and wide-change flag, once #9178 writes them (#9217)

ci: the PR report reads the touched-components check's per-component results and wide-change flag, once #9178 writes them

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally rewrites spec:AST-030 FR11 to FR13 and DEC-4 to DEC-7 and the RTL audit evidence record's INV7 to add a label-free touched-components check, with its five-component and shared-style skip rule, which the current FR12 and DEC-7 rule out. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA> for this head. Whatever the decision, these need fixing:

  • The branch conflicts with #9217 in .github/scripts/lib/pr-status.js and its test, so CI has not run; merge main and keep #9217's comment reader as the one path, dropping this branch's overlapping touched-scope edits there.
  • The new suiteSucceeded clause tells a pull request that already has ci:run-visual to add it whenever its visual run published no verdict.
  • The description's Change and Evidence sections still say pr-touched-components runs the focused visual check; that is pr-visual's default step.

[Automated review]

Comment thread .github/scripts/lib/pr-status.js Outdated
Comment on lines +458 to +465
if (
suite.id === 'visual' &&
state === 'ran' &&
!visualVerdict &&
suiteSucceeded(suite, jobs)
) {
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a pull request that already carries ci:run-visual and whose visual run published no verdict (no stable story in scope), this returns true and the comment says "add ci:run-visual"; the base does not. This clause sits outside the conflict hunks with #9217, so a merge that takes main's side keeps it. Drop it with the rest of the touched-scope reading and rely on #9217's scout-based check.

…-check

# Conflicts:
#	.github/scripts/lib/pr-status.js
#	.github/scripts/lib/pr-status.test.mjs
Main's PR report (#9217) reads the touched-components record for per-component
results; this branch's own status-table edits were dropped in the merge, so
the verdict's compared-components list has no reader.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally rewrites spec:AST-030 FR11 to FR13 and DEC-4 to DEC-7 and the RTL audit evidence record's INV7 to add a label-free touched-components check, with its five-component and shared-style skip rule, which the current FR12 and DEC-7 rule out. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA> for this head. Whatever the decision, one fix is needed:

  • a11yResult in touched-components.mjs drops new accessibility violations the audit files under a multi-component folder's umbrella story (such as core/Chat) or a member's pattern story (such as ChatSendButton), so a Chat change with a new axe violation reads pass or not-run and the job stays green; take audited owners from auditedStories, count umbrella violations against the folder's touched members, and test with the real report shape.

[Automated review]

Comment thread .github/scripts/touched-components.mjs Outdated
}
: {result: 'not-run'};
}
const audited = Object.keys(report.components ?? {}).some(component =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

report.components is keyed by story-title segment (Chat, ChatComposer, Button Pattern), while new violations are named by the audit's owner (core/Chat for the umbrella story, core/ChatSendButton for its pattern story). Neither matches a touched member here, so a Chat change with a new axe violation reads pass/not-run and hasFailure stays false. Read audited owners from report.auditedStories[].owner, count a <package>/<Folder> umbrella's violations against that folder's touched members, and test with the real report shape.

…y violations

The audit files a multi-component folder's violations under its umbrella
story owner (core/Chat) or a member's pattern story owner
(core/ChatSendButton). Audited owners now come from the report's
auditedStories; an umbrella violation fails every touched member of its
folder, and an audited owner outside the touched set gets its own row, so a
new violation can never read pass or not-run. Tested with the audit's real
report shape through diffAgainstBaseline.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's spec decision: this pull request intentionally rewrites spec:AST-030 FR11 to FR13 and DEC-4 to DEC-7 and the RTL audit evidence record's INV7 to add a label-free touched-components check, with its five-component and shared-style skip rule, which the current FR12 and DEC-7 rule out. Any engineering owner, including the author, records that decision by commenting /approve-spec <full head SHA> for this head. The accessibility fix for multi-component folders is in and tested, so no other change is needed.

[Automated review]

@cixzhang

cixzhang commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 555e6f3

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 1, 2026
@cixzhang

cixzhang commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec ea20f24

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: the owner-approved spec rewrite and the CI impact section hold, but three small fixes remain, and the new commit will need a fresh /approve-spec <full head SHA> from an engineering owner (the author may record it).

  • rtlResult treats a curated RTL card whose rollup is partial (for example, prev and next ordered correctly in LTR but not mirrored in RTL) as N/A, so the component reads RTL pass or "no RTL check applies"; map partial to fail with a test.
  • .github/scripts/visual-gate/README.md still says a pull request runs pr-visual only with ci:run-visual and that PR accessibility always includes the modal-close, theme-var and story-play guards; update both sentences for the default touched check.
  • The new job was inserted between release-check: and its "Release-time projection only" comment in ci.yml; move that comment back above release-check.

[Automated review]

Comment thread .github/workflows/ci.yml
if: >-
(github.event_name != 'pull_request' || !github.event.pull_request.draft && (github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'ci:run'))) && (!cancelled() &&
(((github.event_name == 'pull_request' && needs.run-labels.outputs.visual == 'true') &&
((github.event_name == 'pull_request' &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the label clause this job starts on every unlabelled pull request, and its Storybook download and Playwright install (L2361, L2388-L2390) run whenever has_stable_visual is true: this pull request's own run took 43 s and compared nothing, where the base skipped the job. The description's CI impact: line (AST-030 FR19) should state that cost; gating those steps on the touched decision would also avoid most of it.

…ase-check comment match

A curated card whose rollup is partial (some dimensions hold, others do not)
now fails its owner instead of reading N/A. The visual-gate README says a
pull request runs pr-visual's full check with ci:run-visual and its default
touched check otherwise, and that the touched accessibility audit runs without
the guards. The release-check comment moves back above its job.
@cixzhang

cixzhang commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 1b51112

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval recommended: an engineering owner approved the rewritten spec:AST-030 and rtl-audit-evidence INV7 at this head, and the partial-RTL, visual-gate README and release-check comment fixes are in, the first with a test. Nothing further is needed from the contributor beyond required checks finishing green.

[Automated review]

@cixzhang
cixzhang merged commit 1315e78 into main Nov 1, 2026
52 checks passed
@github-actions
github-actions Bot deleted the ci/touched-components-check branch November 2, 2026 06:04

This branch was successfully deployed

1 active deployment
Preview — 1b51112b Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant