Status: Accepted (2026-06-04) · Repositioned 2026-06-04 (late): Storybook-first remains the canonical brownfield-mode primary onboarding path. Greenfield-mode primary onboarding is now ADR 0036 Foundation v1 default — the New project modal shows Foundation first/default, Storybook second. Both paths are equal-rank; selection depends on whether the user has an existing DS. Capture pipeline (this ADR's implementation) and Storybook ingestion remain valid and unchanged. Supersedes (in priority, not delete): none — repositions DOM-walk capture (ADR 0030) and capture-v3 fiber introspection as secondary tracks.
Context
Two parallel session-long efforts in late May / early June 2026:
- DOM-walk reassembly track (ADR 0030 / find-layouts.js + reconstructed-page.tsx). Captures a rendered DOM snapshot, replays it in a sandboxed iframe. Months of patches: width-drop fix, height-keep, top/right/bottom/left bake, position-offset 0px exemption, MAX_DEPTH 14→24, opacity:0 force-show experiments, sandbox flag iterations.
- Fiber introspection track (capture-v3, branch
feat/capture-v2commits4670680..b431c92and onward). Walks React fiber tree, clusters by typeId, renders nested labelled boxes with prop editor. CompositionRenderer with prop-overrides persistence (ported to main 2026-06-04).
Both tracks hit a structural ceiling on every measurement pass:
- DOM-walk gives a one-moment snapshot of a state-machine. Animations, hover states, dropdowns, modals, RSC-served sections are either captured mid-frame (overlap, duplication) or invisible at capture time (whole sections missing). Each fix is local; the architecture is lossy by design. Stripe 2-3/10, Vercel 2-3/10 fidelity; Mantine 9/10 only because docs sites are nearly static.
- Fiber introspection gives editable component identity but no rendering machinery.
<Button {...props}>from fiber → we can label and edit props, but we cannot render the real Button without its component code. Without source access via sourcemaps (stripped on Vercel/Stripe production builds), we get mangled module IDs.
The 2026-06-04 root-cause conversation surfaced the underlying mismatch: the target audience is a designer/PO at a large enterprise. They need a simple Figma-like tool but starting from their company's existing UI. The session enumerated their constraints:
- GitHub OAuth path won't fly. Enterprises sit on self-hosted GitLab / Bitbucket Server / Azure DevOps. CISO review of external SaaS-to-source connectors is multi-month. Designers/PMs don't have repo access by default; getting it is its own multi-month process. PCI-DSS / GDPR / banking regulations forbid source egress.
- Browser extensions are CISO-paranoid territory. Read/modify any page including internal apps = enterprise security policy red flag. Chrome.exe is often deployed under group policy with extension allowlist.
- DOM-walk fidelity ceiling cannot be raised by more patches — the architecture is lossy on dynamic state-machine pages.
- Source access (sourcemaps) is gated on Vercel/Stripe.
.mapfiles return 403 forbidden; only Sentry instances can fetch. Production marketing pages are mangled minified bundles with no recoverable component identity.
The probe of core-ds.github.io/core-components/master (Alfa-Bank's open core-ds) showed a different pattern that punches through every one of these walls:
- Storybook ships
index.json— a stable registry of every component + variant + import path. Public CORS-OK. - Storybook bundle preserves
displayNamefor every component (Storybook needs it for its Controls panel). Even minified bundles expose real React component names. - Standalone
iframe.html?id=<storyId>renders any story isolated, cross-origin loadable, captures real DOM + computed CSS + CSS variables. - Internal corporate Storybooks are SSO-gated but the designer already has SSO session in their browser. No new access grant needed; no CISO review of external connector required; the Storybook URL is an existing approved-internal resource.
Probed 30+ candidate Storybook URLs. Seven confirmed public with substantial catalogs (Primer React 203, Fluent UI React 241, Adobe Spectrum 161, IBM Carbon React 133, Alfa core-ds 154, VK UI 154, Nord Health 56). Several large DSs are docs-only (Polaris, Salesforce Lightning, Atlassian Atlaskit) or internal (Stripe Sail) — those are the residual gap.
Decision
ARNO's primary capture path is a Storybook URL pointed at the user's design system. ARNO ingests the Storybook's index.json registry, fetches each iframe.html?id=X render, harvests displayName + computed DOM + design tokens. The result is a real component library with stable identity, editable props, real visual rendering — exactly the editor model fiber introspection was reaching for, with the rendering machinery the company has already published.
When the user then captures an internal page through the browser extension, ARNO matches the captured DOM nodes against the loaded DS components by structural fingerprint. Where matched: linked to the real Storybook component (editable props, exportable code via the original import path). Where unmatched: falls back to DOM-walk leaf (today's behavior, edit via overrides only).
The three data sources surfaced in the New project modal (commit 1d4acdd):
- Storybook link (default) —
https://your-ds.example/storybookURL. - Browser capture — existing extension-driven DOM-walk capture, demoted from primary to secondary.
- GitHub repo — Phase 13 sync target, unchanged.
Storybook link is the default because:
- Solves the enterprise compliance gap (no CISO review needed — internal Storybook is an existing approved resource accessed via existing SSO session).
- Gives ARNO real component identity without source code access (displayName preserved in bundle, importPath in index.json).
- Matches the Penpot / UXPin Merge / Stripe Protodash paradigm — composition layer over a published DS, not generative scraping.
- Captures the user's design intent: they want to assemble from THEIR components, not clone someone else's pixel art.
Consequences
Positive
- Enterprise compliance story works without engineering buy-in. Designer/PO uses internal Storybook URL they already have access to. No code egress.
- Component identity, prop schema, and design tokens are first-class data, not heuristically reconstructed from runtime artifacts.
- Visual rendering uses the DS's own iframe → fidelity matches what the DS team shipped, no whack-a-mole DOM-walk patches.
- Export to clean TSX is tractable: ARNO knows
importPathfrom Storybook registry, so output uses real package imports (import { Button } from "@alfalab/core-components/button") instead of inlined HTML. - Page-specific compositions (CheckoutHeader, PromoCard) that aren't stories themselves still decompose recursively into DS atoms; the wrapper becomes an editable layout container.
- User-promoted patterns: when ARNO sees an unmatched fragment recur across captures, it offers to promote it to a project-local component. Path even for sites with partial DS adoption.
Negative / trade-offs
- Mass-product sites that don't have a public/internal Storybook (~30% of large enterprises that haven't invested in DS adoption, plus the Stripe-class production marketing pages with internal-only DS) fall back to today's DOM-walk capture with its known limits. ARNO becomes weaker on this segment, not stronger.
- DOM-walk patches lose strategic priority. TOC investigation, image proxy, animation handling — all become P2 work, only revisited if DS-first path runs into a use case it can't cover (rare for the primary audience).
- Storybook bundle fetch is heavy (Mantine 11 MB; Fluent UI likely similar). One-time per project + caching is fine, but cold start UX needs design.
- Cross-origin Storybook iframes obey same-origin policy in some browsers / with some CSPs. Need graceful fallback for that subset.
- Component fingerprint matching is heuristic. False positives (matching a captured div to the wrong story) need a confidence floor; false negatives stay unmatched (which is honest, not broken).
Neutral
- Capture-v3 fiber introspection stays in the codebase as a complementary signal for matching. When fiber tree IS available (page is a same-origin React app with devtools-style introspection), it disambiguates structural-similar candidates.
- DOM-walk reassembly stays as the visual reference for cases where component matching is partial (you can always look at "what the original looked like" while the DS-resolved tree drives editing).
Implementation order
- Done (2026-06-04): Storybook link UI in New project modal. URL persists to localStorage keyed by project id (
arno_storybook_url:<id>), backend ingest deferred. (1d4acdd(opens in a new tab)) - Done (2026-06-04): Prop overrides persistence — the fiber-track edit/save/reload loop. Foundation that the Storybook-ingest pipeline reuses (every component override is name-keyed, same shape). (
b9977ca(opens in a new tab)) - Next: Backend Storybook ingest. New endpoint
POST /api/v1/projects/:id/storybookingests anindex.jsonURL → fetches each story'siframe.html→ harvests displayName, importPath, computed DOM, CSS vars → persists as project-scoped component library. Probably new tableproject_componentwith(project_id, source, name, story_id, dom_fingerprint, import_path, props_schema). - Next: Capture-side matching. When extension captures a page in a project that has a loaded Storybook DS, match each leaf/container against the DS fingerprint set. Surface coverage stats in the workflow view ("312 nodes captured, 248 matched 28 stories, 64 unmatched").
- Later: Token harvest from rendered stories (CSS custom properties at
:rootin each iframe.html) → project-scope design tokens, editable via the same persistence layer as prop-overrides. - Later: Code export — generate TSX page using real DS imports and edited props, deployable to user's repo via Phase 13 sync.
DOM-walk improvements (TOC, opacity:0, image proxy) move to a permanent P2 queue. Touch them only when a specific customer use case demands and DS-first can't cover.
Sources surveyed
Open Storybooks confirmed accessible 2026-06-04:
- Mantine —
mantine.dev(160+ components, displayName preserved e.g.@mantine/core/Paper) - Alfa core-ds —
core-ds.github.io/core-components/master(154 components, 347 entries) - GitHub Primer React —
primer.style/react/storybook(203 components, 1004 entries) - Microsoft Fluent UI React —
react.fluentui.dev(241 components, 1083 entries) - Adobe Spectrum Web Components —
opensource.adobe.com/spectrum-web-components/storybook(161 components, 1241 entries) - IBM Carbon React —
react.carbondesignsystem.com(133 components, 604 entries) - VK UI —
vkcom.github.io/VKUI/playground(154 components, 247 entries) - Nord Health —
nordhealth.design/storybook(56 components, 545 entries)
Notable gaps (internal-only or docs-only): Shopify Polaris, Salesforce Lightning, Atlassian Atlaskit, Tinkoff Taiga, Yandex Gravity, GitLab Pajamas, Stripe Sail.