Theme Model — Future Extensibility

Updated: 2026-08-29 · Status: proposal — base model superseded (AB#2609); most of this doc no longer applies

Reconciled 2026-08-29 (AB#2609). This entire doc extends the sparse-override cascade model, which was never built — the shipped theme model is a flat, independent-row design with Site/Page-level assignment and no cascade (see Theme Data Structure — Schema Reference). Most of the extension points below (§1, §4, §5, §7) are specific to the cascade and no longer apply to anything real. A few (§2 token aliasing, §3 token registry, §6 caching, §8 schema migration) describe extension points that are still generically relevant to the real, flat token model — those are marked below. This doc is kept for historical context; treat it as design exploration, not a live roadmap.
TL;DRThe sparse-override inheritance model this doc extends was never built (see warning above). This doc goes one level further than that proposal: extension points that would have been worth designing room for within it. Most no longer apply; see the per-section notes below for which ones still have relevance to the real, flat theme model.

Contents

1. Treat scopeType as an open enum Moot — no scopeType shipped

Moot: there is no scopeType/chain in the real model — Site and Page each carry a plain activeThemeId. Kept for historical context only.

The model doesn't hardcode chain depth — a node just has a parent. That means a new tier can be inserted later (a regional grouping above MEC, an intermediate tier below Page) without restructuring anything, as long as resolution code never assumes a fixed chain length or a fixed set of scopeType values. Worth stating as an explicit constraint on any resolver implementation, not just an incidental property of the schema. Note this now only applies to Site/Page — Container and Item were deliberately simplified out of the scopeType chain entirely; see Container and Item: inline overrides, not cascading nodes.

2. Token aliasing / references

Tokens are flat scalars today. A common next need is a token defined in terms of another token, not just a value that happens to match:

{
  "Action/Primary": "{Surface/Brand}"
}

Without this, an Admin rebranding a Site by changing one core color has to hunt down every token that was coincidentally set to the same value. This is how Style Dictionary and Figma Variables handle it, and it still composes cleanly with the real, flat theme model: resolve a theme's tokens dictionary as-is (no cascade to walk), then resolve any {...} references against that resolved set as a second pass. Still a relevant idea for the shipped model.

3. A real token schema/registry

Per canonical token key, track a type (color / dimension / fontFamily / …), and optionally lockable and deprecated flags. This isn't invented from nothing — design-tokens.html already documents richer per-token metadata today (description, paired On-* token, CSS variable name, per-namespace grouping), just as prose rather than structured, queryable data. This point is really "formalize what already exists as Tokens fields" (see Tokens structure), not a new concept. Doing so buys three things at once:

This registry has to be enforced uniformly — including against the Container/Item *Token fields and the free-form tokenOverrides JSON field (see Container / Item token references), which have no dedicated theme-management screen of their own to gate input the way a Site/Page theme editor would. This point is generically relevant to the real, shipped token model, not just the cascade proposal — the Admin API's POST /theme/validate-tokens and GET /theme/token-metadata endpoints (admin-portal.html) already cover roughly this ground server-side.

4. Orthogonal axes beyond the tree Moot — no scopeChain to extend

Moot: this extension point assumed a scopeChain to append a context object to. There is no chain in the real model.

ALPA Mobile already has Canadian MECs — locale-based theming is a plausible near-future ask, and platform (iOS/Android/future web) is another. Neither fits naturally as a node in the Site → Page chain (or as a key in a Container/Item override field); they're independent dimensions that cut across the whole model.

If this need shows up, don't force it into parentThemeId. Model resolution as taking a context object — { scopeChain, locale, platform } — and composing an ordered list of applicable override sets. Decide now whether this is in scope, even if the context object isn't populated with anything beyond scopeChain yet; adding the parameter later is easy, but retrofitting call sites that assumed a single-axis resolver is not.

5. Draft/Publish across the whole cascade Solved differently — real system already handles this

This section's own concern turned out to be real and was solved — just not by extending this proposal's per-node status flag. The problem identified here (a single-row-per-scope model can't distinguish "the last published value" from "an in-progress edit") is exactly what the shipped Admin API's snapshot mechanism solves: a theme's workflowState (Draft/Published/Unpublished/Archived) plus activeSnapshotId and hasDraftChanges mean the live token rows (GET /theme/{key}) and the last-published snapshot (GET /theme/{key}/published, immutable) coexist as two separate reads of the same theme — an Admin can be mid-edit on a theme's draft tokens while the Dynamic Feed keeps serving whatever was last published, with no "grandparent fallback" ambiguity. See admin-portal.html — Themes section. The specific cascade-node mechanics below no longer apply.

6. Cache the resolved token set

Still relevant to the shipped model, in simplified form. There is no cascade to walk, so "resolving" a theme is just fetching its ThemeDto.tokens dictionary directly — but the client-side caching question this section raised is still real: cache theme fetches by themeId (see theme-endpoint-contract.html §8) rather than re-fetching a Site's active theme on every screen. The invalidation concern is simpler than originally framed here too — there is no "which Pages does this Site change affect" traversal to design, since there is no cascade; a changed theme is just re-fetched by whichever Sites/Pages reference its id.

7. Per-node version history Moot — no per-node model

Moot as originally framed (there's no per-node ThemeNode to keep history for), but the underlying need — "what did this theme change from, and who changed it" — is only partly covered by the real system: ThemeDto.updatedAt answers "when," and the Admin API's GET /theme/{key}/versions + POST /theme/{key}/publish snapshot flow gives a version-numbered publish history, but there's no documented per-token diff or arbitrary undo beyond reverting to a prior published snapshot. Kept as an open question against the real model, not the cascade.

8. A migration story for the token schema itself Still relevant — count corrected

The canonical set is 95 tokens across 15 categories (not the "45 fixed keys" originally stated here — see the token-count warning at the top of theme-endpoint-contract.html). The underlying point stands regardless of the exact count: when a token is renamed or removed later, existing theme rows referencing the old key shouldn't silently break. A small tokenAliases map (old key → new key), or a tokenSchemaVersion stamped on each theme, lets the canonical set evolve without a flag-day migration across every existing theme. Nothing in Vlad Oprica's docs suggests this has been built — still a fair, generically relevant open item.

Suggested priority

Table below is superseded. It ranked levers against the never-built cascade model (items #1, #4, #5, #7 no longer apply as scoped). Kept for historical reference; see the per-section notes above for what's still relevant.
#LeverDesign now / build now?
5Draft/Publish single-row gapSolved differently — the shipped Admin API's workflowState/activeSnapshotId/published-snapshot mechanism addresses this without a per-node status flag. See §5.
1Open scopeType enumMoot — no scopeType shipped.
3Token registry + lockable tokensStill relevant — the real Admin API's token-metadata/validate-tokens endpoints cover validation; token-locking/governance is not documented as built.
2Token aliasingStill relevant — not documented as built; would still be useful against the real flat token model.
4Orthogonal axes (locale/platform)Moot as scoped (assumed a scopeChain to extend) — would need to be re-designed against Site/Page assignment if this need arises.
6Resolved-token cachingStill relevant, simplified — client-side cache-by-themeId, no invalidation traversal needed. See §6.
7Per-node version historyMoot as scoped — partially covered by the real publish/version-snapshot flow. See §7.
8Token schema migration storyStill relevant — defer until the first real token rename/removal is needed.

See also