ADRs
ADR 0050 — Arno Foundation evolution via its own self-hosting project
  • 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: foundationfondazione, 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.

ConcernSorgente targetFoundation 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:

  1. Run the integrity gates (typecheck on consumers, story snapshots, WCAG AA on the resolved palette, size-limit budgets per ADR 0036 §13.4).
  2. 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.
  3. Write to packages/foundation/package.json and emit a changelog entry into packages/foundation/CHANGELOG.md.
  4. 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:

  1. Phase K — fork the Sorgente machinery (~1 day). Rename internal modules to take a (projectId, kind) parameter where Sorgente currently hardcodes prj-arno-sorgente.
  2. Phase L — Foundation Layer 1 (~1 day). prj-arno-foundation creation + mirror middleware writes to Foundation tokens path.
  3. Phase M — Foundation Layer 2 (~1-2 days). Variant defaults export to packages/foundation/src/components/*.meta.ts.
  4. Phase N — Foundation Layer 3 + semver (~3-4 days). Component codegen + diff classifier + semver bumper + CHANGELOG generation.
  5. 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.