Affected capture
Affected capture is StoryShelf’s answer to Chromatic’s TurboSnap: instead of rendering every story on every build, the server renders only the stories downstream of your change and inherits the rest unchanged from their baselines. It is on by default and fails open — anything it cannot prove unchanged gets rendered.
How it works
Section titled “How it works”flowchart LR
A[Create build] --> B[Server returns baselineSha]
B --> C[CLI: git diff baseline..HEAD]
C --> D[Trace via preview-stats.json]
D --> E[POST affected set]
E --> F[Render affected only]
F --> G[Inherit the rest unchanged]
- Ancestor.
POST /api/v1/projects/:slug/buildsreturnsbaselineSha: the latest prior revision on the same branch with a build (nullfor first builds). - Changed files. The CLI diffs
baselineSha..HEAD(plus staged and untracked files) to get the repo-relative change list. - Trace. The change list is traced through the bundler dependency graph (
preview-stats.jsonin the built Storybook) down to story files. New stories — files with no baseline — always render regardless of the trace. - Record. The CLI posts
{ baselineSha, changedFiles, affectedImportPaths }toPOST …/builds/:id/affectedbefore uploading the bundle, so the computation is auditable on the build row. - Partition. The orchestrator splits discovered stories into render vs inherit sets, renders only the affected ones, and creates
unchangedsnapshots for the rest that reuse the baseline screenshot (inherited: true). Counts include inherited snapshots;changedCountexcludes them. The build stores how many it inherited asinheritedCount.
Opting out
Section titled “Opting out”| Method | Effect |
|---|---|
storyshelf upload --full | Render every story for this upload |
STORYSHELF_FULL=1 | Same, via env (useful for release pipelines) |
"affectedOnly": false in .storybook/storyshelf.json | Disable for the project (see Configuration) |
Build identity: sha and branch without git
Section titled “Build identity: sha and branch without git”upload resolves each field independently — flags > env > local git > synthesized (local-<random> sha on the local branch). A project with no git at all uploads with zero identity flags; those builds always render everything (not-a-git-repo below). The full story — synthetic identity, environments as branches, checkout-less pipelines — lives in Uploading without git.
Full-render fallbacks
Section titled “Full-render fallbacks”Affected capture never fails an upload. When it cannot prove a story unchanged, it renders everything and prints the reason:
| Reason | Meaning |
|---|---|
shallow-clone | No git history (actions/checkout needs fetch-depth: 0) |
not-a-git-repo | No git repository — upload used a synthetic local identity |
dependency-graph-unavailable | No usable preview-stats.json in the built Storybook |
story-index-unreadable | No index.json/stories.json to map stories to files |
global-file-changed | A change touches preview.*, manager.*, .storybook/main.*, or a lockfile — these can affect every story |
too-many-changed-files | Change list exceeds 10,000 files (large merges, vendored drops) |
| First build | No baseline revision exists yet (baselineSha: null) |
Typical CLI output:
Affected capture: rendering 3 stories (baseline abc1234)# orAffected capture: no stories affected (baseline abc1234)# orFull capture (shallow-clone): rendering all storiesTuning the trace
Section titled “Tuning the trace”Untraced files. Files that appear in the graph but cannot meaningfully change rendering (generated clients, mock data, docs) can be excluded so touching them doesn’t re-render their dependents:
storyshelf upload --untraced "**/*.generated.ts" --untraced "**/mocks/**"For project-wide exclusions that apply to every upload, commit them once in .storybook/storyshelf.json instead of repeating flags (see Configuration):
{ "slug": "my-design-system", "untraced": ["**/*.generated.ts"] } }Config and flag globs are combined (union, deduplicated). Untraced files are dropped before tracing — stories reachable only through them are inherited. Stories reachable through another (traced) changed file still render.
Custom stats file. The graph is read from <buildDir>/preview-stats.json by default (emitted by the Vite builder). Override per run:
storyshelf upload --stats-file ./stats/preview-stats.jsonDiagnose. storyshelf doctor reports affected-capture readiness: shallow clones and missing stats produce warnings (not failures).
What you see in the UI
Section titled “What you see in the UI”- The build page shows an
affected capturebadge (orfull capturewhen opted out) plusN rendered, M inheritedcounts, and an Inherited stat next to the other counts. The numbers are the ones stored on the build (snapshotCount,inheritedCount), also served by the API. - The project’s Builds page shows Inherited from baseline: the average, over the project’s last 20 builds that hold snapshots, of each build’s
inheritedCount / snapshotCount. It is computed from the stored counts only, so it reads no snapshots. A high number means affected capture is skipping most of your Storybook; a number near zero means most changes reach most stories (a shared import, for example), and the trace is worth a look. - Inherited snapshots carry an
inheritedbadge andunchangedstatus — they need no review. - Capture attempt logs include an
affected capture partitionedline with{ rendered, inherited, total }.
Differences from TurboSnap
Section titled “Differences from TurboSnap”Same shape (ancestor → diff → graph trace → selective render), different economics and scope:
- No billed snapshots — skipping saves your server’s CPU, not money. There is no 0.2× copy charge because there is no billing at all.
- Vite stats only — the trace reads
preview-stats.json(plus tolerant Webpack-reasons shapes). Custom builders without stats fall back to full renders. - Server verifies — the orchestrator re-partitions from the stored payload and only inherits stories with a resolvable baseline, so a stale or forged list can only over-render, never under-test.
Related
Section titled “Related”- Uploading without git — synthetic identity and non-git workflows
- Capture & viewports — the render pipeline
- Builds & snapshots — the
inheritedflag - Configuration —
affectedOnlyand precedence - CI setup —
fetch-depth: 0and upload flags - Package:
@storyshelf/affected(dependency tracing library)