- Date: 2026-06-07
- Status: Proposed (design spec)
- Phase / Feature: Foundation v2+ maintenance loop
- Builds on: ADR 0036 (Foundation v1), ADR 0039 (Foundation upgrade path), ADR 0049 (Arno Sorgente)
Context
Arno Sorgente (ADR 0049) is the self-hosting project for the product UI of ARNO — the river. Foundation is something else: it's the design-system substrate every ARNO consumer (including ARNO itself) inherits from. When I want to add a new component to Foundation, evolve a token semantic, ship 200 more icons (ADR 0046 §2), the round-trip today is the same code-edit-deploy loop I rejected for Sorgente. Wrong shape for the same reason: Foundation IS a customer of ARNO too, and should evolve through ARNO's own primitives.
This ADR introduces a sibling self-hosting project — Arno Foundation — that mirrors Sorgente's mechanics but targets the Foundation package instead of the product app. Two self-hosting projects total: Sorgente owns ARNO's look; Foundation owns the substrate every ARNO project (and every external designer) starts from.
Decision
§1 — Project: Arno Foundation
Reserved id: prj-arno-foundation. Route: /app/foundation. Boolean
flag on project: is_arno_foundation = true. Same single-row-per-env
constraint as Sorgente. Same maintainer-only gate. Same naming chord
(Italian/Renaissance: foundation — fondazione, the foundation
stone in Brunelleschi's Florentine architectural vocabulary; the term
was already canon in the codebase from Foundation v1, so we keep it).
§1a — Foundation has two faces, on purpose
This trips up the naming, so worth pinning explicitly:
-
For the user of ARNO, Foundation is not a project at all. It's the default state of the Design System tab in any greenfield project they create (data source = ARNO Foundation). They see Foundation through every project they start. They customize it via their own project's brand-import / token-override surfaces (ADR 0038), and the result lives inside their project, not inside Foundation.
-
For me (the maintainer), Foundation is
prj-arno-foundation— a special maintainer-only edit surface where I evolve the substrate itself. My edits → Publish → semver bump → users see updated baseline in new greenfield projects immediately, existing projects via ADR 0039 opt-in upgrade ritual.
Only one substrate exists at any time. Users don't have separate
"Foundation projects"; they have their own projects whose Design
System tab renders from the current published Foundation. Nothing about
Foundation self-hosting changes that — it just gives me a real
in-product surface to evolve the substrate instead of editing
packages/foundation/ files by hand.
§2 — What lives where (Sorgente vs Foundation)
The line between the two self-hosting projects is the line between product and substrate.
-
Arno Sorgente owns:
- ARNO's brand seeds, tokens, fonts, icons specific to ARNO's look
- ARNO's pages — Library, Workflow, Brand panel, settings, etc
- ARNO's component compositions (how Cards, Tabs, Buttons get arranged on ARNO's own screens)
-
Arno Foundation owns:
- The base token system (color ramps, spacing scale, typography primitives, motion, elevation) — what every greenfield project starts from
- The 50 Foundation components — Button, Card, Tabs, Input, Select, DatePicker, etc — their default visual + accessibility contract
- The 108 (eventually 300) Foundation icons (ADR 0046)
- Foundation's variant metas (ADR 0044) — what props each component exposes to designers
- Foundation v1 → v2 migration paths (ADR 0039)
When ARNO consumes Foundation, Sorgente sits on top of Foundation as a brand-import layer (semantic rebinds + overrides). When a greenfield user project consumes Foundation, that user's brand-import layer sits on top — Sorgente isn't visible to them, but Foundation is.
§3 — Three-layer cascade per consumer
For ARNO itself (the product):
Foundation defaults → Sorgente overrides → runtime user prefs
(Arno Foundation) (Arno Sorgente) (theme toggle etc)For a greenfield user project:
Foundation defaults → user project overrides → runtime user prefs
(Arno Foundation) (their brand-import) (their theme)For an existing user project on a captured DS (brownfield):
captured DS → user project overrides → runtime user prefs
(their tweaks) (their theme)Foundation appears in two of three cascades. Sorgente only in ARNO's own cascade.
§4 — Mechanics mirror Sorgente
Everything Sorgente specifies in ADR 0049 §1-§11 reapplies one-to-one for Foundation. Only difference: export target.
| Concern | Sorgente target | Foundation target |
|---|---|---|
| Layer 1 (tokens) | packages/foundation/dist/arno-sorgente.css (ARNO-only overlay) | packages/foundation/tokens/*.json (baseline) + packages/foundation/dist/tokens.css (everyone) |
| Layer 2 (variant defaults) | packages/foundation/dist/arno-sorgente-variants.ts (ARNO resolver) | packages/foundation/src/components/*.meta.ts (every component's default props) |
| Layer 3 (structure) | apps/web/src/app/* (ARNO pages) | packages/foundation/src/components/*.tsx (Foundation components) |
Foundation's Layer 3 is components, not pages. A composition change
in the Foundation project is "drag the chevron from the right side of
the DatePicker to the inside of the input." Codegen writes the new TSX
into packages/foundation/src/components/DatePicker.tsx + regenerates
the meta + commits + bumps packages/foundation/package.json semver.
§5 — Versioning is mandatory for Foundation (unlike Sorgente)
Sorgente publishes onto a single moving target — the live ARNO web app. Foundation publishes a package consumed by other projects with already-pinned versions. So every Foundation publish must:
- Run the integrity gates (typecheck on consumers, story snapshots, WCAG AA on the resolved palette, size-limit budgets per ADR 0036 §13.4).
- Compute semver bump from the diff (token rename → major; token add → minor; token value change → minor; non-visual code → patch). Diff classifier is a tool, not a guess.
- Write to
packages/foundation/package.jsonand emit a changelog entry intopackages/foundation/CHANGELOG.md. - Tag the release. Consumer projects (Sorgente + user projects on greenfield) opt into the new version through ADR 0039 §2's migration ritual; they don't auto-upgrade.
The hourly snapshot timeline from ADR 0049 §11.5 still applies as audit
- revert history, just on top of Foundation's own table set.
§6 — Anti-patterns explicit
- Do not edit Foundation tokens directly through Sorgente's override paths. Sorgente overrides ride on top of Foundation; if you want to change a Foundation default, you go through Foundation's own project. The two projects don't have write access to each other's source-of-truth tables.
- Do not auto-bump consumer projects when Foundation publishes. ADR 0039 §2 explicitly makes upgrade an opt-in ritual; that doesn't change. Consumers see a "Foundation 1.3 available — review changes" banner.
- Do not merge the two self-hosting projects into one mega-project "Arno Self." The product / substrate split exists for a reason: different audience (ARNO maintainer vs all ARNO users), different publish gates (live deploy vs semver-tagged package release), different blast radius (one app vs every consumer).
§7 — Phases (delivery order)
Foundation evolution can't start until Sorgente is shipped, because it reuses ~80% of Sorgente's machinery (mirror middleware, snapshot table, codegen pipeline). Phases proposed after Sorgente Phases A-J land:
- Phase K — fork the Sorgente machinery (~1 day). Rename internal
modules to take a
(projectId, kind)parameter where Sorgente currently hardcodesprj-arno-sorgente. - Phase L — Foundation Layer 1 (~1 day).
prj-arno-foundationcreation + mirror middleware writes to Foundation tokens path. - Phase M — Foundation Layer 2 (~1-2 days). Variant defaults
export to
packages/foundation/src/components/*.meta.ts. - Phase N — Foundation Layer 3 + semver (~3-4 days). Component codegen + diff classifier + semver bumper + CHANGELOG generation.
- Phase O — Consumer migration UX (~2 days). The "Foundation 1.3 available" banner + side-by-side diff + opt-in upgrade flow per ADR 0039 §2.
Total ~9-10 days after Sorgente, sequenced so Phase K's refactor benefits both projects going forward.
Open questions / parking
- Foundation breaking changes vs Sorgente's pin. If Foundation v2 ships a renamed semantic role and Sorgente has overrides on the old name, what does Sorgente see? Default: Sorgente's overrides stay pinned to Foundation v1 until I explicitly opt-in to v2 in Sorgente too. ADR 0039 §3 ritual handles this.
- Third self-hosting? If we ever do Arno Storybook or Arno Icons as a separate publishable surface, the same shape extends. Lock-step: never more than 5-6 of these total — design discipline.