Skip to content

feat(storybook): add on-demand performance metrics scripts - #5229

Open
sauldom102 wants to merge 2 commits into
mainfrom
claude/perf-metrics-tooling
Open

feat(storybook): add on-demand performance metrics scripts#5229
sauldom102 wants to merge 2 commits into
mainfrom
claude/perf-metrics-tooling

Conversation

@sauldom102

@sauldom102 sauldom102 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #5223, which added the Storybook performance panel. That panel only shows its numbers to someone with Storybook open; this exposes the same measurements as JSON on demand, so an agent or a developer can check a component while building or changing it. Nothing here runs in CI or gates anything.

pnpm perf-metrics F0Button --snapshot
pnpm perf-metrics components-button-button--variants | jq '.stories[0].deterministic'

Not included

The workflow wiring. agentic-checks.yaml runs agents but only writes to the job summary, while the PR-comment action lives in storybook-tests.yaml; joining the two is the remaining piece. The prompt is here so that change is small when it lands.

Notes for reviewers

Why the output is split into deterministic and timing

Measuring the same story five times on an idle machine:

  • deterministic — identical every run (mounts 1, renders 4, cascades 1, domElements 33). These count work done, not time taken, so they survive a move to different hardware and can be compared between two runs or two branches.
  • timing — moves even when nothing changes (totalBlockingTime ranged 42–47ms) and is contaminated by the rest of the page: longTasks in Storybook is frequently axe-core, not the component. Reported, but never used to raise a highlight, and the agent prompt tells the model not to call anything "slow" based on it.

The split is also why collection is cheap: the React counts are complete as soon as the story mounts, because the Profiler records renders as they happen regardless of when collection starts.

Highlight thresholds come from the measured distribution, not from intuition

The obvious rule — "flag any render cascade" — turns out to be useless here. 102 of 102 snapshot stories record at least one cascade (median 3), because Storybook's own decorators and providers render around the story. A rule that fires on 100% of stories highlights nothing; the first version of this flagged 40 out of 40 measured stories.

Thresholds are now set at roughly p95 of the measured distribution across those 102 stories:

metric p50 p90 p95 max threshold
cascades 3 6 8 18 8
updates 6 10 13 26 13
domElements 89 678 884 5250 900

forcedReflows > 0 and slowUpdates > 0 stay absolute — their measured base rates are 0/40 and 2/40. On a real changed-set this now highlights 4 stories out of 40.

Layout shift is highlighted by CLS score, never by shift count

Shift count is not reproducible. The same five F0Card stories measured three times gave three different sets:

story run A run B run C
card--with-children 1 1 1
card--compact 1 0 0
card--with-actions-and-link 0 1 0

A shift is only recorded when the browser happens to paint between the two layouts. Highlighting on count would mean the same PR gets a different comment on every re-run.

The score is stable once it clears the noise floor — across three runs ApplicationFrame scored 0.1943 / 0.1932 / 0.1936 and AnalyticsDashboard scored 0.0472 all three times, while every story that flickered scored ~0.0001. So the rule is cls >= 0.01, an order of magnitude above that floor and below Core Web Vitals' 0.1 "needs improvement" line. Across the library it selects 4 of 102 snapshot stories:

story CLS
patterns-app-shell-applicationframe--snapshot 0.198
patterns-analyticsdashboard--snapshot 0.047
patterns-resource-header--snapshot 0.023
patterns-navigation-sidebar-chatlist--snapshot 0.017

ApplicationFrame is worth a look independently of this PR — 0.198 is approaching the 0.25 that Core Web Vitals calls "poor".

Two traps worth keeping in mind when editing these scripts
  • stdout is the JSON payload. Progress goes through a note() helper that writes to stderr, because consola.info/consola.success write to stdout — routing progress through them corrupts the payload and | jq fails with "Invalid numeric literal".
  • git pathspecs are cwd-relative. perf-changed.ts anchors its globs with :(top); without it, running from packages/react (where pnpm --filter puts you) matches nothing and the script cheerfully reports "no story files changed" on a PR that changed plenty.

Update: component changes are now measured

Detection keyed off changed *.stories.tsx files, so a PR that changed a component without touching its story measured nothing — the common shape of a fix. It now diffs every source file under packages/react/src and maps each to the stories that render it.

How a source file finds its stories

The file's directory is walked upwards until one is found with stories beneath it, so nested layouts attribute correctly (F0Button/internal/helpers.tsF0Button, whose stories may sit at the root or under __stories__/).

The walk stops at src/<zone>/<Name> depth. Without that floor, src/lib/utils.ts would walk up to src/lib and drag in every story in the tree.

Story files keep their exact-path matching, and are processed first so a precise attribution is never overwritten by the vaguer directory one. Results are deduplicated by story id, since a PR that changes both a component and its story reaches the same stories twice.

Test and docs files are excluded — this one bit

fix(F0Chat) (bf41fa6e4) changed two components and two __tests__/ files. The test files alone attributed 38 stories, so a PR that only adjusted assertions would have triggered a full performance comment about code whose behaviour never moved.

__tests__/, __snapshots__/, *.test.*, *.spec.*, *.md and *.mdx are now filtered out. Verified: that commit drops from 4 changed files to the 2 real component files and still reports; a docs-only commit reports nothing.

New fields and flag
  • measuredBecause"story" if the story's own file changed, "source" if only its component did. The agent prompt uses it to attribute findings to the component rather than implying the author edited a story they never opened.
  • changedFile replaces storyFile, since the file that pulled a story in is often not a story file.
  • storiesFromStoryChanges / storiesFromSourceChanges in the summary.
  • --head <ref> so a range other than …...HEAD can be previewed locally — "what would this commit have reported?".
A real finding from testing this

Measuring F0AiChatTextArea (pulled in by the F0Chat commit) shows it well outside the library norm: updates p50 of 21 against a library p50 of 6, 18 forced reflows where the measured base rate elsewhere was 0 of 40, and CLS up to 0.72 — nearly 3× the 0.25 that Core Web Vitals calls "poor".

Not addressed here; noting it because it is the kind of thing this tooling exists to surface.

Expose the Storybook performance panel's numbers as JSON, so agents and
humans can check a component while building or changing it:

  pnpm perf-metrics F0Button --snapshot

perf-changed.ts measures the stories a PR adds or changes and reduces
them to the few facts worth attention, for a future PR comment.

Highlight thresholds are set from the measured distribution across the
library, not by feel: 102 of 102 snapshot stories record at least one
render cascade, so "any cascade" would highlight nothing. Layout shift
is highlighted by CLS score rather than shift count because the count is
not reproducible between runs, while the score is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sauldom102
sauldom102 requested a review from a team as a code owner August 25, 2026 10:31
@github-actions github-actions Bot added feat react Changes affect packages/react labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Review policy: Feature

The PR title starts with feat, so this is a feature: it needs one approval from f0-devs AND one from f0-designers (rule 3).

Required approvals

Team Why Status
@factorialco/f0-devs Features need a dev approval ⏳ pending
@factorialco/f0-designers Features need a design approval ⏳ pending
How this was decided
  • PRs touching only sds/ modules require their owners and nothing else.
  • Otherwise, docs-only changes (*.md, *.mdx, *.stories.tsx, anything in __stories__/) → one f0-general approval.
  • Otherwise, feat: titles → one f0-devs and one f0-designers approval. Not a feature? Fix the title prefix.
  • Anything else → one f0-devs approval.
  • Add the needs-design-review label to also request a design approval on any PR.
  • Creating a new sds/ module (new package.yml) additionally requires an f0-general approval.

Policy source: ownership/review-policy.ts · Team members: ownership/teams.yml

@github-actions

Copy link
Copy Markdown
Contributor

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-5229 to install the package

Use pnpm i github:factorialco/f0#61dbc01b3df5c3061659ce65610b7dcf94f61b08 to install this specific commit

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

🔍 Visual review for your branch is published 🔍

Here are the links to:

@github-actions

Copy link
Copy Markdown
Contributor

✅ No breaking public API changes

No public exports were removed, renamed, or had existing props/types changed in a breaking way compared to main.

Comparing f0, experimental and ai against main. Adding components, types, or optional props is safe. This check is non-blocking.

⚠️ Could not analyze component-status (no-base) — a build may have failed; results may be incomplete.

@github-actions

Copy link
Copy Markdown
Contributor

♿ Accessibility (axe) — components changed in this PR

✅ No a11y issues in the stories this PR changed.

Scope: only stories in the files/component folders this PR changed. It can't yet flag downstream ripple from shared-code/token changes, or diff against main (planned: base-vs-head delta).

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 69.17% 29318 / 42381
🔵 Statements 68.13% 31019 / 45526
🔵 Functions 62.03% 6993 / 11273
🔵 Branches 61.88% 22021 / 35584
File CoverageNo changed files found.
Generated in workflow #17604 for commit 4dff963 by the Vitest Coverage Report Action

perf-changed.ts keyed off changed *.stories.tsx files, so a PR that
changed a component without touching its story measured nothing at all —
the common shape of a fix. It now diffs every source file under
packages/react/src and maps each to the stories that render it, walking
up to the owning component directory so nested files attribute correctly.

The walk stops at src/<zone>/<Name> depth so a shared utility cannot be
attributed to half the library, and test, snapshot and docs files are
excluded outright: fix(F0Chat) (bf41fa6) changed two components and two
__tests__/ files, and the test files alone attributed 38 stories.

Each measured story records `measuredBecause` ("story" or "source") so
the comment can attribute a finding to the component rather than imply
the author edited a story they never opened. Adds --head so a range other
than "…...HEAD" can be previewed locally.

Verified against real history: the component-only commit reports 39
stories affected where it previously reported none, and a docs-only
commit still reports nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ No untranslated copy added

Every user-visible string in this PR comes from the i18n layer. Codebase total unchanged at 133.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

✅ Storybook docs — no pages lost

Every page reachable on main is still reachable here.

Links point at this PR's Storybook build — browse the full Storybook.

Snapshot of the Storybook index (docs pages + stories) compared against main. Non-blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants