The concise, current front door. Everything operational points here. The why behind past decisions lives in the historical archive (
decisionlog.md·roadmap/) — you rarely need it; this doc + the code are the source of truth.
Nuri is a mobile-first design system for Expo · React Native. RN is the production target; the web side is a prototyping + documentation surface, not a co-equal platform. The system is built so that one source of data drives every target — you author a token/axis/component once, and each target's output is generated from it.
Data lives once, in
@nuri/spec. Logic and generated output live in the projection that runs them.
@nuri/spec is pure data — no code, no dependencies, no generated artifacts. Every consumer is a
projection that resolves spec's data into the form it needs and owns that resolution.
The design-system repository is the slow shared core. Fast learning normally lives in the consumer
repo that owns the product problem; the DS playground/prototype may host bounded DS-owned
non-contract experiments, but there is no fast lane for public DS growth. Public/observable
shared-core changes follow the design-system admission policy
before Project Ready/implementation: the requester/consumer provides need, gap, workaround, and
available edge evidence, then a named human DS architect independent from the requester/consumer
records Admit, Reject, Request more evidence, or Keep consumer-owned in an authenticated
canonical issue comment or architect-owned linked ADR. A contract is only admitted after that
independent decision; the requester/consumer cannot self-admit, and overlapping requester/architect
roles require a second named human architectural reviewer. The admitting architect may implement
later after independent admission, but independent verification remains required. Before Project
Ready, the item needs the linked architect-owned decision record, architect-accepted contract,
bounded execution scope, and downstream verification plan. Actual Expo/device/runtime/compatibility
proof is collected after implementation before Verified/Done. Consumer demand, form completion,
branch creation, PRs, Expo demos, implementing agents, labels, or green gates do not confer admission
or Ready. Gates prove behavior/drift only — not architecture, ownership, recurrence, or whether a
concept belongs in the shared core.
@nuri/spec PURE DATA — the source of truth
tokens/ dimensions · colours · typography (the token SoTs)
axes/ resolve-map · palette-surface · interactive-effects ·
typography-axis · property-spelling · interaction
components/ button · icon-avatar · icon-button · modal-panel ·
tab-bar · tab-bar-item · topbar · schema (descriptors)
icons/ *.svg (the icon SoT)
@nuri/rn PRODUCTION — the RN projection
generated/ the resolved contract, flattened from spec at build · committed
data/ the six data tables (tokens · palette · recipes · icons ·
interaction · token-paths)
components/ the per-descriptor API adapters (Path C)
contract.ts imports ./generated/data/*
runtime/ the INTERNAL engine: resolve · renderer · theme-payload builder
primitives/ the open primitive layer (View/Text/Pressable/Screen/Scroll · NuriIcon)
theme.tsx NuriThemeProvider / NuriScope / the resolved theme payload
toast.tsx ToastProvider / useToast · imperative overlay tenant
@nuri/prototype the WEB projection (prototyping + the doc surface's components)
generated/ the token CSS · descriptor .js twins · token-vars · icon registry
factory · recipes · primitives
@nuri/doc the documentation site (just-the-docs → GitHub Pages, live off main).
Generates component/foundation Markdown from spec DATA + the projections.
@nuri/playground the local bench (demo.html · factory-parity)
scripts/ the codegen — reads spec's TS SoTs, writes each projection's generated/ output
- Build-time resolves everything resolvable; runtime selects only the context-variant. Box, stack, typography, interactive are 100% static. Only colour varies by context.
- Colour = layered substitution, not a cross-product.
accentis upstream (it fills the accent-portion of the palette);mode(light/dark) is downstream (resolves the variant across the composed palette). chrome is stored once (theme-only{light,dark}); each accent is one object (roles flat where mode-invariant,{light,dark}where they adapt). The provider composes(accent, mode)at selection time — there is no materialized(accent × theme)table. - The RN provider is a contract, opt-in. Default = a static resolved slice (zero runtime, max
perf). Opt-in = runtime
(accent, mode)switching (Unistyles is the intended backing, behind the contract — never leaked).accentis a runtime-capable selection axis (latent: semantic accents, e.g. acrypto/fiattoggle). - Two projections, one SoT: web composes via the CSS cascade (
[data-accent]×[data-theme], the browser resolves); RN composes via the provider (refs flattened to hex at build).
- Codegen:
node scripts/tokens-parser.js(rootnpm run build) — reads the spec SoTs, writes@nuri/rn/generated/+@nuri/prototype/generated/. The generated output is committed. - Drift guards:
node --test scripts/*.test.js(rootnpm test). - CI — 5 gates (
.github/workflows/gates.yml, all required):spec(codegen + drift + re-emit ≡ committed) ·prototype·doc·rn(render-smoke + tsc) ·expo-demo(tsc). Thernrender-smoke imports the contract and renders it, so a contract change that breaks RN fails CI by construction. - Discipline: generated output is never hand-edited — change the SoT and regenerate; the re-emit
gate fails a stale commit. Every change ships on a branch via PR into protected
main.
- Use the right DS front door: use the lightweight maintenance form for Corrections, internal
maintenance, and bounded non-contract Experiments; use the contract-admission request form for
Contractions, Extensions, or Mixed public/observable contract changes. The request form does not
admit the contract: an independent named human DS architect records the outcome separately, and
Project
Ready, not a form field or label, is the manual-pilot authorization boundary. - Add an accent: a ramp + one accent object + one
ACCENTSentry intokens/colours.ts. Data only. - Add/edit a token: edit the SoT in
tokens/(dimensions · colours · typography), regenerate. - Add a component: author a descriptor in
components/(a composition of the axis namespaces in semantic names · the factory resolves it on RN, CSS on web). This is the product work (the catalog). - An axis (box/stack/palette/interactive/typography) is a data table in
axes/.
The infrastructure refactor is complete (#105–#126) — @nuri/spec is pure data, the two
projections own their generated output, the colour system is data-driven end-to-end, the
component-API arc landed (#114–#119 · each descriptor declares its public API and codegen emits
exact per-component adapters), and the debt register closed with every entry resolved. The
current work is the product: the catalog — descriptors for the components still living in
@nuri/prototype/legacy/, built on that clean foundation, on the principle that everything is
axis composition.
decisionlog.md and roadmap/ are the immutable historical
record — the why behind how things got here. Settled design docs and closed registers (the debt
register · the target designs · the consumer feedback · the 2026-06 risk snapshot) live in
docs/archive/. They are archived: do not cite them operationally (this doc +
the code are current). docs/RISKS.md is the live risk register.