@nurix/etna 0.20.37 → 0.20.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/data/catalog/rules/frontend.md +5 -5
  2. package/data/catalog/rules/pnpm.md +2 -2
  3. package/data/harness-index.json +1 -1
  4. package/data/harnesses/code/harness.json +2 -2
  5. package/data/harnesses/code/hooks/approved-packages.md +1 -2
  6. package/data/harnesses/code/rules/frontend.md +5 -5
  7. package/data/harnesses/code/rules/pnpm.md +2 -2
  8. package/data/harnesses/code/skills/boundary/references/packs/build-config-runtime.md +1 -1
  9. package/data/harnesses/code/skills/design/components/charts.md +121 -43
  10. package/data/harnesses/code/skills/design/components/collections.md +209 -41
  11. package/data/harnesses/code/skills/design/components/feedback.md +86 -25
  12. package/data/harnesses/code/skills/design/components/hooks.md +198 -12
  13. package/data/harnesses/code/skills/design/components/index.md +93 -71
  14. package/data/harnesses/code/skills/design/components/inputs.md +249 -157
  15. package/data/harnesses/code/skills/design/components/layout.md +98 -38
  16. package/data/harnesses/code/skills/design/components/media.md +46 -25
  17. package/data/harnesses/code/skills/design/components/motion.md +56 -46
  18. package/data/harnesses/code/skills/design/components/navigation.md +54 -15
  19. package/data/harnesses/code/skills/design/components/overlays.md +51 -41
  20. package/data/harnesses/code/skills/design/components/setup.md +84 -5
  21. package/data/harnesses/code/skills/design/components/typography.md +72 -22
  22. package/data/harnesses/code/skills/design/components/utils.md +47 -13
  23. package/data/harnesses/code/skills/design/data/chrome/board-column.md +12 -12
  24. package/data/harnesses/code/skills/design/data/chrome/data-table.md +6 -6
  25. package/data/harnesses/code/skills/design/data/chrome/side-panel.md +15 -15
  26. package/data/harnesses/code/skills/design/data/chrome/sidebar.md +25 -25
  27. package/data/harnesses/code/skills/design/data/chrome/stat-cards.md +22 -22
  28. package/data/harnesses/code/skills/design/data/chrome/topbar.md +12 -12
  29. package/data/harnesses/code/skills/design/references/emission.md +6 -6
  30. package/data/harnesses/code/skills/design/scripts/README-validator.md +20 -8
  31. package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +15 -10
  32. package/data/harnesses/delivery/harness.json +1 -1
  33. package/data/harnesses/etna/harness.json +2 -2
  34. package/data/harnesses/etna/skills/boundary/references/packs/build-config-runtime.md +1 -1
  35. package/data/harnesses/hr/harness.json +1 -1
  36. package/package.json +2 -2
@@ -14,18 +14,18 @@ Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nu
14
14
 
15
15
  - **Build every UI through the `design` skill** — never hand-roll layouts; the page archetypes, the `design.md` identity system with its token artifact (`design.tokens.css`), and chrome wiring live in the skill.
16
16
  - **The brief's stated theme binds** — dark boots dark when the brief says so (recorded in the project's `design.md`); light is the default only when the brief is silent.
17
- - **`@nurix/components`** is the UI library — import by subpath (`@nurix/components/button`). **Never** `npx shadcn add`, no `components.json` scaffold — consume the prebuilt library (`.tsx`, not `.jsx`). *A recorded deviation from canonical shadcn, where primitives are vendored into `components/ui/` and edited in place: this workspace consumes a published library instead, and vendoring would fork it. Deliberate — the design skill does not "converge" it.*
17
+ - **`@nurix/components`** is the UI library — import by layer-prefixed subpath (`@nurix/components/element/button`). **Never** `npx shadcn add`, no `components.json` scaffold — consume the prebuilt library (`.tsx`, not `.jsx`). *A recorded deviation from canonical shadcn, where primitives are vendored into `components/ui/` and edited in place: this workspace consumes a published library instead, and vendoring would fork it. Deliberate — the design skill does not "converge" it.*
18
18
  - Vite: `@tailwindcss/vite` + `@vitejs/plugin-react-swc`. Pin `react` / `react-dom` / `react/jsx-runtime` / `react/jsx-dev-runtime` to the app's single copy via `resolve.alias` — `@nurix/*` ship nested react, and two copies means two hook dispatchers.
19
19
  - Tailwind v4 is CSS-only — no `tailwind.config.js`, no PostCSS.
20
20
  - **The CSS entry is whatever the manifest names**, not a fixed filename — `globals.css`, `index.css` and `styles.css` are each conventional in different framework guides, so resolve it rather than grep for one. Imports in order: `@import "./fonts.css"` (`@fontsource` registrations) → the project's `design.tokens.css` (derived by the design skill's validator from the `design.md` in the design folder — `packages/common/` at the base of a multi-package workspace, `src/common/` in a single-package repo — **never** hand-edited) → `@import "tailwindcss"` → the library's `styles.css` → the project's `personality.css` (the same validator's `--emit-personality`, compiling the `design.md` traits through `@nurix/components`' own compiler — **never** hand-edited) — then `@theme inline {…}` mapping vars to tokens.
21
21
  - **Dark mode is the `.dark` class on the root element**, bound as `@custom-variant dark (&:is(.dark *))` in the CSS entry — **never** a `data-theme` attribute. `@nurix/components` keys its `dark:` variants, its theme toggle, and its toaster on that class and a consumer cannot re-key them, so an app on the attribute renders mixed: its own tokens invert, the library's dark-only chrome stays light. The switcher is three parts — a pre-paint inline script in the HTML shell (a mounted provider runs too late and the page flashes), `color-scheme: light dark`, and the persisted-preference module.
22
- - Tokens: raw values in `:root:not(.dark)` and `:root.dark` — **both specificity (0,2,0), and that armor is load-bearing.** Tailwind compiles `@theme` into `@layer theme` and any unlayered declaration beats a layered one, so the library's unlayered `:root`/`.dark` defeats an app's `@theme` outright and *ties* a bare `:root`/`.dark`, leaving the winner to import order. Values are then consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`) through an `@theme inline` mapping layer. The library bundles `class-variance-authority`, `clsx`, `tailwind-merge` — don't re-add them.
22
+ - Tokens: raw values in `:root:not(.dark)` and `:root.dark` — **both specificity (0,2,0), and that armor is load-bearing.** Tailwind compiles `@theme` into `@layer theme` and any unlayered declaration beats a layered one, so the library's unlayered `:root`/`.dark` defeats an app's `@theme` outright and *ties* a bare `:root`/`.dark`, leaving the winner to import order. Values are then consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`) through an `@theme inline` mapping layer. The library ships `class-variance-authority` and `cn` (the class merger) as dependencies — don't re-add them.
23
23
  - **Never retune a Tailwind scale key `@nurix/components` consumes** — `--text-xs`, `--text-sm`, `--text-base`, `--text-lg`, `--text-xl`, `--text-2xl`. Its components carry raw scale utilities and its stylesheet declares no `font-size`, so those compile against *this app's* `@theme`: redefining `--text-sm` moves every button, menu item and table cell in the library at once, with nothing to report it. Change type by changing ramp tokens.
24
24
  - Spacing in 2/4/6/8; `object-cover`; `clip` / `clip-<n>` / `clip-fade` (the library's — they carry `min-width: 0` and `overflow-wrap: anywhere`; **never** `truncate` / `line-clamp-*`, which leave the flex-child overflow bug open); color through tokens — **never** hardcoded hex in components, and **never a literal font size**. The type ramp and the family rules are the design skill's [`references/type-system.md`](../skills/design/references/type-system.md); `text-sm` is a scale step and is fine, `text-[13px]` is a component overruling the system.
25
25
  - **Feel comes from the traits, never from literals on a control or collection surface.** `personality.css` compiles `density` / `pace` / `edge` / `depth` into `--density-*`, `--motion-duration-*`, `--radius-scale`, `--elevation-*`, and every library component reads those. App code stays inside the same reach: scanned surfaces pad and gap through the tokens (`p-(--density-pad-md)`, `gap-(--density-gap-md)`), a `transition-*` utility carries **no** `duration-*` class (the library's default transition duration retimes it from `pace`), a repeated-row container carries `data-mode="inline"`, and app animation reads `var(--motion-duration-base)`. A `p-3`, a `duration-200`, or a `rounded-lg` on such a surface silently opts out of the product's feel.
26
- - **Every rendered time is `<Timestamp>`** (`@nurix/components/element/timestamp`, `format="auto"`), and a live label ticks through `useNow` (`@nurix/components/lib/time`) — one shared interval, never a `setInterval` per row or a hand-rolled relative formatter.
27
- - **Status colour is the role.** `success`, `warning`, `info` are declared in `design.md` in both themes; the app's `@theme inline` maps its utilities onto the same roles (`--color-success: var(--success)`), and a status dot is `<Chip prefix="dot" color="var(--tone-success)">` — one hue per meaning on every surface.
28
- - **`cn()` keeps only one of a type role and a colour merged together** — its tailwind-merge tables class `text-h1` / `text-body` / `text-dense` as colours, so `cn("text-h1", "text-foreground")` and `cn("text-h1 text-foreground")` are both `text-foreground`. Keep a role and a colour in a `className` string that never goes through `cn`, or put the colour on the parent.
26
+ - **Every rendered time is `<Timestamp>`** (`@nurix/components/element/timestamp`, which picks relative or absolute per render), and a live label ticks through `useNow` (`@nurix/components/lib/time`) — one shared interval, never a `setInterval` per row or a hand-rolled relative formatter.
27
+ - **Status colour is the role.** `success`, `warning`, `info` are declared in `design.md` in both themes; the app's `@theme inline` maps its utilities onto the same roles (`--color-success: var(--success)`), and a status dot is `<Chip prefix="dot" intent="success">` (`intent` paints from the `--intent-*` tokens; `color` is for stored data colours, never status) — one hue per meaning on every surface.
28
+ - **`cn()` keeps only one of a type role and a colour merged together** — its merge tables class `text-h1` / `text-body` / `text-dense` as colours, so `cn("text-h1", "text-foreground")` and `cn("text-h1 text-foreground")` are both `text-foreground`. Keep a role and a colour in a `className` string that never goes through `cn`, or put the colour on the parent.
29
29
 
30
30
  ## Component Search Order
31
31
 
@@ -61,6 +61,6 @@ Every workspace package is scoped **`@nurix/`**; the dot vs. hyphen after the sc
61
61
  //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
62
62
  ```
63
63
  The token needs only `Read` on the `nurix` scope. A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). Attempt the install directly — the token authenticates ambiently; don't pre-empt or second-guess it. If it fails, flag it to the user — **never** reach for alternatives, a substitute package, or `npm login` swaps.
64
- - **Import by subpath, never the barrel** — subpaths tree-shake reliably; the available subpaths are the package's `exports` keys. `import { Button } from "@nurix/components/button";`
65
- - **Styles** — import a CSS-shipping package's stylesheet **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. The shipped stylesheet is precompiled — no Tailwind `@source` directive unless consuming source via a workspace link.
64
+ - **Import by subpath, never the barrel** — subpaths tree-shake reliably; the available subpaths are the package's `exports` keys. `import { Button } from "@nurix/components/element/button";`
65
+ - **Styles** — `@import` a CSS-shipping package's stylesheet **once** in the app's CSS entry, after `@import "tailwindcss"`: `@import "@nurix/components/styles.css";` (the full import order is the `frontend` rule's). Unstyled components are almost always a missing styles import. The stylesheet is **not** precompiled — it omits `@import "tailwindcss"` and carries its own `@source` over the package's compiled JS, so the app's Tailwind build generates its utilities: add no `@source` for it, and never `import` it from JS, which compiles it standalone with no theme loaded and fails the build.
66
66
  - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "899bed2c91e6",
2
+ "fingerprint": "666a16c2c2da",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.20.7",
4
+ "version": "3.20.8",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -29,5 +29,5 @@
29
29
  "origin": "https://cdn.nustack.io",
30
30
  "track": "latest"
31
31
  },
32
- "digest": "068ff439e1f9"
32
+ "digest": "903fbdf14bc8"
33
33
  }
@@ -7,7 +7,7 @@ Read by the PreToolUse install gate (`compliance.mjs`) — an off-table install
7
7
  | `react` | ^19.2.3 | UI components | — |
8
8
  | `vite` | ^8.0.0 | Build tool, dev server | `webpack`, CRA |
9
9
  | `tailwindcss` | ^4.3.3 | Styling | `styled-components` |
10
- | `@nurix/components` | ^0.15.12 | The UI component library — import by subpath | `npx shadcn add`, a `components.json` scaffold |
10
+ | `@nurix/components` | ^2.0.0 | The UI component library — import by subpath; toasts come from its `compound/toast` subpath | `npx shadcn add`, a `components.json` scaffold, `sonner`, `react-toastify` |
11
11
  | `@tailwindcss/vite` | ^4.3.3 | Tailwind v4 Vite plugin — 4.3.x is the vite-8 peer line; 4.1.x tops out at vite 7 | `postcss`, `tailwind.config.js` |
12
12
  | `@vitejs/plugin-react-swc` | ^4.2.2 | JSX compilation | `@vitejs/plugin-react` |
13
13
  | `react-router-dom` | ^7.11.0 | Routing | — |
@@ -19,7 +19,6 @@ Read by the PreToolUse install gate (`compliance.mjs`) — an off-table install
19
19
  | `@dnd-kit/core` | ^6.3.1 | Drag and drop | `react-beautiful-dnd` |
20
20
  | `@xyflow/react` | ^12.11.0 | Node-based flow charts | `reactflow`, `react-flow-renderer` |
21
21
  | `motion` | ^12.23.26 | Animations | `react-spring` |
22
- | `sonner` | ^2.0.7 | Toast notifications | `react-toastify` |
23
22
  | `react-window` | ^2.2.3 | List virtualization | `react-virtualized` |
24
23
  | `nanoid` | ^6.0.0 | ID generation | `uuid` — exempt: UUIDv5/v7 database ids per `database.md` |
25
24
  | `express` | ^5.2.1 | HTTP server | `fastify`, `koa` |
@@ -14,18 +14,18 @@ Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nu
14
14
 
15
15
  - **Build every UI through the `design` skill** — never hand-roll layouts; the page archetypes, the `design.md` identity system with its token artifact (`design.tokens.css`), and chrome wiring live in the skill.
16
16
  - **The brief's stated theme binds** — dark boots dark when the brief says so (recorded in the project's `design.md`); light is the default only when the brief is silent.
17
- - **`@nurix/components`** is the UI library — import by subpath (`@nurix/components/button`). **Never** `npx shadcn add`, no `components.json` scaffold — consume the prebuilt library (`.tsx`, not `.jsx`). *A recorded deviation from canonical shadcn, where primitives are vendored into `components/ui/` and edited in place: this workspace consumes a published library instead, and vendoring would fork it. Deliberate — the design skill does not "converge" it.*
17
+ - **`@nurix/components`** is the UI library — import by layer-prefixed subpath (`@nurix/components/element/button`). **Never** `npx shadcn add`, no `components.json` scaffold — consume the prebuilt library (`.tsx`, not `.jsx`). *A recorded deviation from canonical shadcn, where primitives are vendored into `components/ui/` and edited in place: this workspace consumes a published library instead, and vendoring would fork it. Deliberate — the design skill does not "converge" it.*
18
18
  - Vite: `@tailwindcss/vite` + `@vitejs/plugin-react-swc`. Pin `react` / `react-dom` / `react/jsx-runtime` / `react/jsx-dev-runtime` to the app's single copy via `resolve.alias` — `@nurix/*` ship nested react, and two copies means two hook dispatchers.
19
19
  - Tailwind v4 is CSS-only — no `tailwind.config.js`, no PostCSS.
20
20
  - **The CSS entry is whatever the manifest names**, not a fixed filename — `globals.css`, `index.css` and `styles.css` are each conventional in different framework guides, so resolve it rather than grep for one. Imports in order: `@import "./fonts.css"` (`@fontsource` registrations) → the project's `design.tokens.css` (derived by the design skill's validator from the `design.md` in the design folder — `packages/common/` at the base of a multi-package workspace, `src/common/` in a single-package repo — **never** hand-edited) → `@import "tailwindcss"` → the library's `styles.css` → the project's `personality.css` (the same validator's `--emit-personality`, compiling the `design.md` traits through `@nurix/components`' own compiler — **never** hand-edited) — then `@theme inline {…}` mapping vars to tokens.
21
21
  - **Dark mode is the `.dark` class on the root element**, bound as `@custom-variant dark (&:is(.dark *))` in the CSS entry — **never** a `data-theme` attribute. `@nurix/components` keys its `dark:` variants, its theme toggle, and its toaster on that class and a consumer cannot re-key them, so an app on the attribute renders mixed: its own tokens invert, the library's dark-only chrome stays light. The switcher is three parts — a pre-paint inline script in the HTML shell (a mounted provider runs too late and the page flashes), `color-scheme: light dark`, and the persisted-preference module.
22
- - Tokens: raw values in `:root:not(.dark)` and `:root.dark` — **both specificity (0,2,0), and that armor is load-bearing.** Tailwind compiles `@theme` into `@layer theme` and any unlayered declaration beats a layered one, so the library's unlayered `:root`/`.dark` defeats an app's `@theme` outright and *ties* a bare `:root`/`.dark`, leaving the winner to import order. Values are then consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`) through an `@theme inline` mapping layer. The library bundles `class-variance-authority`, `clsx`, `tailwind-merge` — don't re-add them.
22
+ - Tokens: raw values in `:root:not(.dark)` and `:root.dark` — **both specificity (0,2,0), and that armor is load-bearing.** Tailwind compiles `@theme` into `@layer theme` and any unlayered declaration beats a layered one, so the library's unlayered `:root`/`.dark` defeats an app's `@theme` outright and *ties* a bare `:root`/`.dark`, leaving the winner to import order. Values are then consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`) through an `@theme inline` mapping layer. The library ships `class-variance-authority` and `cn` (the class merger) as dependencies — don't re-add them.
23
23
  - **Never retune a Tailwind scale key `@nurix/components` consumes** — `--text-xs`, `--text-sm`, `--text-base`, `--text-lg`, `--text-xl`, `--text-2xl`. Its components carry raw scale utilities and its stylesheet declares no `font-size`, so those compile against *this app's* `@theme`: redefining `--text-sm` moves every button, menu item and table cell in the library at once, with nothing to report it. Change type by changing ramp tokens.
24
24
  - Spacing in 2/4/6/8; `object-cover`; `clip` / `clip-<n>` / `clip-fade` (the library's — they carry `min-width: 0` and `overflow-wrap: anywhere`; **never** `truncate` / `line-clamp-*`, which leave the flex-child overflow bug open); color through tokens — **never** hardcoded hex in components, and **never a literal font size**. The type ramp and the family rules are the design skill's [`references/type-system.md`](../skills/design/references/type-system.md); `text-sm` is a scale step and is fine, `text-[13px]` is a component overruling the system.
25
25
  - **Feel comes from the traits, never from literals on a control or collection surface.** `personality.css` compiles `density` / `pace` / `edge` / `depth` into `--density-*`, `--motion-duration-*`, `--radius-scale`, `--elevation-*`, and every library component reads those. App code stays inside the same reach: scanned surfaces pad and gap through the tokens (`p-(--density-pad-md)`, `gap-(--density-gap-md)`), a `transition-*` utility carries **no** `duration-*` class (the library's default transition duration retimes it from `pace`), a repeated-row container carries `data-mode="inline"`, and app animation reads `var(--motion-duration-base)`. A `p-3`, a `duration-200`, or a `rounded-lg` on such a surface silently opts out of the product's feel.
26
- - **Every rendered time is `<Timestamp>`** (`@nurix/components/element/timestamp`, `format="auto"`), and a live label ticks through `useNow` (`@nurix/components/lib/time`) — one shared interval, never a `setInterval` per row or a hand-rolled relative formatter.
27
- - **Status colour is the role.** `success`, `warning`, `info` are declared in `design.md` in both themes; the app's `@theme inline` maps its utilities onto the same roles (`--color-success: var(--success)`), and a status dot is `<Chip prefix="dot" color="var(--tone-success)">` — one hue per meaning on every surface.
28
- - **`cn()` keeps only one of a type role and a colour merged together** — its tailwind-merge tables class `text-h1` / `text-body` / `text-dense` as colours, so `cn("text-h1", "text-foreground")` and `cn("text-h1 text-foreground")` are both `text-foreground`. Keep a role and a colour in a `className` string that never goes through `cn`, or put the colour on the parent.
26
+ - **Every rendered time is `<Timestamp>`** (`@nurix/components/element/timestamp`, which picks relative or absolute per render), and a live label ticks through `useNow` (`@nurix/components/lib/time`) — one shared interval, never a `setInterval` per row or a hand-rolled relative formatter.
27
+ - **Status colour is the role.** `success`, `warning`, `info` are declared in `design.md` in both themes; the app's `@theme inline` maps its utilities onto the same roles (`--color-success: var(--success)`), and a status dot is `<Chip prefix="dot" intent="success">` (`intent` paints from the `--intent-*` tokens; `color` is for stored data colours, never status) — one hue per meaning on every surface.
28
+ - **`cn()` keeps only one of a type role and a colour merged together** — its merge tables class `text-h1` / `text-body` / `text-dense` as colours, so `cn("text-h1", "text-foreground")` and `cn("text-h1 text-foreground")` are both `text-foreground`. Keep a role and a colour in a `className` string that never goes through `cn`, or put the colour on the parent.
29
29
 
30
30
  ## Component Search Order
31
31
 
@@ -61,6 +61,6 @@ Every workspace package is scoped **`@nurix/`**; the dot vs. hyphen after the sc
61
61
  //registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
62
62
  ```
63
63
  The token needs only `Read` on the `nurix` scope. A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). Attempt the install directly — the token authenticates ambiently; don't pre-empt or second-guess it. If it fails, flag it to the user — **never** reach for alternatives, a substitute package, or `npm login` swaps.
64
- - **Import by subpath, never the barrel** — subpaths tree-shake reliably; the available subpaths are the package's `exports` keys. `import { Button } from "@nurix/components/button";`
65
- - **Styles** — import a CSS-shipping package's stylesheet **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. The shipped stylesheet is precompiled — no Tailwind `@source` directive unless consuming source via a workspace link.
64
+ - **Import by subpath, never the barrel** — subpaths tree-shake reliably; the available subpaths are the package's `exports` keys. `import { Button } from "@nurix/components/element/button";`
65
+ - **Styles** — `@import` a CSS-shipping package's stylesheet **once** in the app's CSS entry, after `@import "tailwindcss"`: `@import "@nurix/components/styles.css";` (the full import order is the `frontend` rule's). Unstyled components are almost always a missing styles import. The stylesheet is **not** precompiled — it omits `@import "tailwindcss"` and carries its own `@source` over the package's compiled JS, so the app's Tailwind build generates its utilities: add no `@source` for it, and never `import` it from JS, which compiles it standalone with no theme loaded and fails the build.
66
66
  - **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
@@ -27,7 +27,7 @@ Load when: the touch list exhibits `package.json` (deps, `exports`, scripts, `pa
27
27
  **Safe change:** Declare every imported package in the importer's own `package.json` (workspace siblings as `"@nurix/x": "workspace:*"`); never widen `public-hoist-pattern` to fix a missing-module error — add the dep; verify with a fresh `pnpm install` on a clean clone.
28
28
 
29
29
  ## package-exports-subpath-map — HIGH · rendezvous-string
30
- **Contract:** Consumers' import specifiers (`@nurix/components/button`) must match literal keys in the package's `exports` map; the `files` allowlist and `types` conditions must cover the same paths.
30
+ **Contract:** Consumers' import specifiers (`@nurix/components/element/button`) must match literal keys in the package's `exports` map; the `files` allowlist and `types` conditions must cover the same paths.
31
31
  **Detect:** `"exports"` and `"files"` in `package.json`, `from ['"]@nurix/[^'"]+/`, `"moduleResolution"` in `tsconfig.json`
32
32
  **Ships green, breaks:** Any subpath not mapped in `exports` throws `ERR_PACKAGE_PATH_NOT_EXPORTED` at runtime — and tsc under `moduleResolution: "node10"` ignores `exports` entirely, so types resolve while Node throws (use `"bundler"`/`"nodenext"` to make tsc see the real contract); adding a new component file without an `exports` entry publishes fine and breaks every consumer import; a built `dist/` path referenced by `exports` but missing from the `files` allowlist installs cleanly and fails only on first import of the published tarball.
33
33
  **Safe change:** Add the `exports` subpath (with `types` condition first in each condition block) in the same change as the new file; confirm `files` includes everything `exports` references; check the shipped surface with `npm pack --dry-run`; treat removing/renaming an exports key as a breaking change for all consumers.
@@ -2,75 +2,153 @@
2
2
 
3
3
  > Generated reference (regen-owned; see [index.md](index.md)). Binds only under `primitives: nurix`.
4
4
 
5
- Three tiers, all consuming the canonical `ChartSeries[]` / `ChartPoint` data contract (re-exported from `@nurix/components/charts`) and resolving color through tokens — no chart takes hex literals as its primary color path.
5
+ Components: Spark, Chart, ChartErrorCard, StatusTimeline, plus the chart kernel helpers
6
6
 
7
- - **Tier 0 — marks only:** `spark`
8
- - **Tier 1 — inline charts:** `bar-graph`, `line-graph`, `pie-graph`, `progress-bar`, `sparkline`, plus the standalone families `funnel`, `gauge`, `heatmap`, `radar`, `ring`; shared chrome in `chart-primitives`
9
- - **Tier 2 — composable dashboard shells:** the `charts` subpath (visx-based)
7
+ Four entry points, all resolving colour through the `--chart-*` tokens — no chart takes a hex literal as its colour path:
10
8
 
11
- > The old `chart` subpath (a dead recharts wrapper) was removed with the 2026-07 chart adoption — it no longer exists; the `charts` family below replaced it.
9
+ - **Tier 0 — marks only:** `Spark` (`element/spark`) — an axis-less inline trend.
10
+ - **Tier 1 — spec-native charts:** `Chart` (`block/chart`) — one component rendering fourteen families from a serializable `{ config, theme?, data? }` spec.
11
+ - **Status strip:** `StatusTimeline` (`compound/status-timeline`) — one stacked bar per day, no axis; not a bar chart.
12
+ - **Kernel:** `lib/chart-kernel` (+ `/arc`, `/colors`, `/line`) — the data contract, colour policy, formatters and geometry, for a bespoke mark neither `Chart` nor `Spark` draws.
12
13
 
13
14
  ## Spark
14
15
 
15
- Tier-0 inline mark — no axes, no chrome. Import from `@nurix/components/element/spark`.
16
+ Tier-0 trend mark for a stat card or a caption — no axes, no legend, no tooltip, never a tab stop. Import from `@nurix/components/element/spark`. Install the `spark` peer family first: `@mantine/charts @mantine/core recharts`, plus `@mantine/hooks` (which `@mantine/core` peers on); no `MantineProvider` or Mantine stylesheet is needed.
16
17
 
17
- **Props:** `series` (`ChartSeries[]` or plain `number[]`), `type` [line|bar|pie], `filled`, `fillOpacity`, `strokeWidth` (line), `gap` (bar), `width`, `height`, `useTokens` (default false: self-contained oklch; true: `--chart-N` tokens)
18
+ **Props:** `values` (`(number | null)[]` — a `null` is a gap, never a zero), `family` [line|area|bar|pie] (default `line`), `width` / `height` (default `100`×`30` px; a class such as `w-full` overrides the width), `color` (any CSS colour, default `currentColor`), `strokeWidth` (line and area, default `1.5`), `gap` (bar, default `2`), `colors` (pie, per slice; default the `--chart-N` palette), `animate` (default `false`), `label`
19
+
20
+ - The props are a union on `family`: `strokeWidth` on bar, `gap` on line or area, `colors` off pie, and `width` or `color` on pie are type errors. A pie is a `height`×`height` square.
21
+ - It paints in `currentColor`, so a muted caption gives a muted spark; pass `color="var(--chart-1)"` for a token colour.
22
+ - It is `aria-hidden` until `label` names it (then `role="img"`) — a trend beside its number is decorative; a spark standing alone wants a label.
23
+ - A table's sparkline column is `block/table`'s `TableSparkline`, never a hand-embedded `Spark`.
18
24
 
19
25
  ```tsx
20
- <Spark series={[4, 6, 5, 9, 7, 12]} type="line" filled width={96} height={24} />
26
+ <Spark values={[4, 6, null, 9, 7, 12]} family="area" color="var(--chart-1)" className="w-full" />
21
27
  ```
22
28
 
23
- ## Inline charts
29
+ ## Chart
30
+
31
+ The spec-native chart. Import `Chart` from `@nurix/components/block/chart`. Install the `charts` and `animated-number` peer families first: `@visx/curve @visx/event @visx/grid @visx/group @visx/heatmap @visx/pattern @visx/responsive @visx/scale @visx/shape d3-array @number-flow/react`.
32
+
33
+ **Props:** `config` (what the chart means — `version`, `family`, `title`, `format`, `overlays`, `encode`, and `x` on cartesian families), `theme` (how it is presented — `context`, `motion`, `color`, `chrome`, `interaction`, `labels`), `data` (typed by `config.family`), `loading`, `onHoverChange`, `onRangeChange`, `pinnedIndex` / `defaultPinnedIndex` / `onPinnedIndexChange`, `onValidationIssue`, `replayNonce` (bump to replay enter motion), plus `ref`, `className` and any div prop, landing on the one root in every state.
34
+
35
+ - **Validation is internal and always on** — `withDefaultVersion` → `migrateSpec` → `validateChartSpec` → `diagnoseChart` → `normalize` on every render. An invalid spec never throws and never renders blank: it renders `ChartErrorCard` (`errors`, `labels`) naming the first violation's JSON Pointer. Warnings render the chart and report through `onValidationIssue`, once per distinct set.
36
+ - **`version` is `CURRENT_VERSION` (2).** A stored spec carries its own `version`; only the JSX boundary defaults a missing one. `migrateSpec` walks a spec forward only, so a spec newer than the installed renderer is rejected, never downgraded.
37
+ - **Every colour field takes a token slot, never a literal:** `chart-1`…`chart-5` (categorical, wrapping after five), `chart-positive`, `chart-negative`, `chart-diverging-mid`, `chart-scale-01`…`chart-scale-05`. `theme.color.palette` reorders the five categorical slots; an explicit series `color` wins over it.
38
+ - **Size comes from the parent.** `Chart` sets only `w-full`. The `cell`/`card` backend stretches into its parent's box; the `dashboard` backend renders nothing until it measures a non-zero ancestor — an unsized parent is a silently empty chart either way.
39
+ - **Emit-loop pre-flight:** `validateChartSpec(spec)` returns `{ ok, value | errors }`; `diagnoseChart(spec)` returns `{ errors, warnings }` over the data values (log scale over non-positive values, a palette wrap, date-like x on a category axis) — treat both as blocking before storing an artifact. `explainSpec(spec)` reports which default supplied each resolved value. `normalize` and `withDefaultVersion` are exported beside them.
40
+
41
+ ### Context
42
+
43
+ `theme.context` is `cell`, `card` or `dashboard`; it picks density defaults, the capability envelope and the backend. An omitted context resolves from the host — `cell` inside a `data-mode="inline"` or `data-density="compact"` ancestor, otherwise `card`. **A host never implies `dashboard`**, so a dashboard-only family needs `theme: { context: "dashboard" }` or it fails validation with the recorded reason.
24
44
 
25
- Import via `@nurix/components/<subpath>`; each ships a matching skeleton.
45
+ | Context | Chrome | Interaction | Motion (`auto`) | Families admitted |
46
+ | --- | --- | --- | --- | --- |
47
+ | `cell` | none | hover off unless turned on | none, whatever the request | line, area, bar, pie, progress, ring, gauge, stat |
48
+ | `card` | value labels and legend, off by default | hover on, pin off | enter | line, area, bar, pie, progress, ring, gauge, stat |
49
+ | `dashboard` | grid horizontal and both axes on; value labels and legend admissible | hover with a crosshair; `range` opt-in | full | line, area, bar, composed, scatter, funnel, heatmap-calendar, heatmap-matrix, radar |
26
50
 
27
- - `bar-graph` — `BarGraph`, `BarGraphSkeleton`
28
- - `line-graph` — `LineGraph`, `LineGraphSkeleton` (re-exports `ChartSeries`)
29
- - `pie-graph` — `PieGraph`, `PieGraphSkeleton` (variants pie|half-pie|doughnut|half-doughnut; doughnut variants take a `center` prop that auto-fits content in the hole)
30
- - `progress-bar` — `ProgressBar`, `ProgressBarSkeleton` (segmented multi-series bars; distinct from the `progress` subpath's shadcn Progress primitive)
31
- - `sparkline` — `Sparkline` (legacy; prefer `Spark` for new work)
51
+ ### Families and their data
32
52
 
33
- ## Standalone chart families
53
+ `config.family` is a closed enum, and each family takes exactly one data class. The cartesian families (`line`, `area`, `bar`, `composed`) require `x: { type: "time" | "category" | "linear", grain? }`; `scatter` requires `x: { type: "linear" | "log" | "time", grain? }`. `grain` is `hour`, `day`, `week`, `month`, `quarter` or `year`. `encode` is the one family-shaped key; an unknown key is a rejection naming the legal keys. Shared knobs keep one shape: `sort` is `"none" | "asc" | "desc"` or `{ by, order }`, `stack` is `"none" | "stacked" | "percent"`, `diverging` is `{ baseline?, positiveColor?, negativeColor? }`.
34
54
 
35
- Each on its own subpath with tooltip/legend/skeleton companions and full types:
55
+ Series data is `{ series: [{ id, label, points: [{ x, y, size? }], color? }] }` — `y` may be `null`, `id` is unique and stable, series order is stacking and draw order.
36
56
 
37
- - `funnel` — `Funnel` (+ `FunnelTooltip`, `FunnelLegend`, `FunnelSkeleton`): ordered stage bands. **Props:** `data: FunnelData`, `orientation`, `variant` (default `halo`), `sort` (`asc` renders a pyramid), `showPercentage`/`showValues`/`showLabels`, `showDrop` (inter-stage drop-off annotation), `showTooltip`, `percentBasis` (default `first`), `color`, `layers`
38
- - `gauge` — `Gauge`, `Bullet`, `GaugeSkeleton`: value on a [min,max] scale. **Props:** `orientation` (arc or horizontal notch track), `value` (null renders the empty state), `min`/`max`, `zones` (threshold color bands), `target` (tick marker), `ticks`, `showTicks`/`showValues`, `valueFormat`, `centerContent` (arc only), `totalNotches`
39
- - `heatmap` — `Heatmap`, `HeatmapSkeleton`, `HeatmapLegend`, `HeatmapTooltip`, `HeatmapXAxis`/`HeatmapYAxis`/`HeatmapSeparator`: calendar contribution grid. **Props:** `data: HeatmapColumn[]`, `xDomain`, `layout` [fluid|fill], `binSize`, `gap`, `cornerRadius`, `levelColors` (defaults to the `--chart-scale-*` tokens)
40
- - `heatmap` (same subpath) — `MatrixHeatmap`, `MatrixHeatmapSkeleton`, `MatrixHeatmapLegend`, `MatrixHeatmapTooltip`: x-by-y cell matrix (`MatrixHeatmapData` dense or cell list), quantized onto the token ramp
41
- - `radar` — `Radar`, `RadarSkeleton`: series polygons over spider axes. **Props:** `data` (`RadarSeries[]` or `ChartSeries[]`), `metrics` (ordered axes; derived when omitted), `levels`, `scaling` [independent|shared] (default `independent` — honest mixed-unit rendering), `maxValue`, `nullMode` [zero|gap], `colors`, `size`
42
- - `ring` — `Ring`, `RingSkeleton`: concentric segmented progress. **Props:** `data: RingDatum[]` (innermost first), `strokeWidth`, `ringGap`, `startAngle`/`endAngle`, `lineCap`, `trackColor`, `animated`, `interactive`, `size`
57
+ - **`line`** — multi-series lines. `encode`: `curve` [linear|smooth|step|stepAfter|stepBefore], `nulls` [gap|bridge] (default `gap`), `markers`, `diverging`, `endpointLabels`.
58
+ - **`area`** — filled series. `encode`: `curve`, `nulls` [gap|bridge], `markers`, `diverging`, `stack`, `band: { lower, upper }` (series ids drawing a range band).
59
+ - **`bar`** — grouped or stacked bars. `encode`: `mode` [categorical|histogram|pareto], `stack`, `horizontal`, `sort` (by `value`, `label` or a series id), `diverging`, `bins`, `binWidth`. **Histogram:** `mode: "histogram"` bins each point's raw `y` samples, so already-binned counts are binned again; `stack` and `sort` are rejected. **Pareto:** fixed descending with a cumulative-percent line; `sort` is rejected.
60
+ - **`composed`** (dashboard) — mixed marks over one shared scale pair. `encode.series` maps each series id to `{ mark: "line" | "area" | "bar", axis?: "left" | "right" }`, at most two y axes.
61
+ - **`scatter`** (dashboard) — x/y point field. `encode`: `yScale` [linear|log], `bubble` (reads `size`), `colorByValue`, `trend` (least-squares line), `quadrant: { x?, y?, shade? }`.
62
+ - **`radar`** (dashboard) — series polygons over spider axes; spokes are the points' categorical `x`. `encode`: `scaling` [independent|shared], `maxValue`, `nulls` [gap|zero] (default `gap`).
63
+ - **`pie`** (cell, card) — data `{ parts: [{ label, value, color? }] }`. `encode`: `donut`, `half`, `sort`, `maxSlices`, `otherThreshold`, `otherLabel` (Other sorts last), `minAngle`, `center` [total|none].
64
+ - **`progress`** (cell, card) — stacked horizontal segments from `parts`, left to right; no `encode` keys; the goal tick is a `target` overlay.
65
+ - **`funnel`** (dashboard) — ordered stage bands from `parts`, top-down. `encode`: `variant` [halo|solid] (`halo` draws concentric rings at ramping opacity), `colorMode` [single|ramp|categorical], `sort` (`asc` renders a pyramid), `showValue`, `showPercent`, `showDrop`.
66
+ - **`gauge`** (cell, card) — data `{ value, min, max }`, the domain required. `encode`: `shape` [arc|linear|bullet], `fill` [solid|gradient|scale]. Threshold bands are `zone` overlays and the goal tick a `target` overlay — `shape: "bullet"` with zones and a target is the bullet chart. Hover is unavailable (one always-visible scalar).
67
+ - **`ring`** (cell, card) — concentric value-vs-max rings. Data `{ rings: [{ label, value, max, color? }] }`, innermost first. `encode`: `endAngle` (narrow it for an arc-gauge look).
68
+ - **`heatmap-calendar`** (dashboard) — contribution grid. Data `{ cells: [{ date, value }] }` with ISO dates. `encode`: `weekStartDay` (0–6), `levels`, `hideGhostCells`.
69
+ - **`heatmap-matrix`** (dashboard) — x-by-y cell matrix on the token ramp. Data `{ cells: [{ x, y, value }] }`. `encode`: `colorScaleType` [sequential|diverging], `divergingMidpoint`, `nulls` [empty|zero|hatch] (default `empty`), `xOrder` / `yOrder` (`none`, `asc`, `desc`, `{ by, order }` or an explicit list), `aggregate` [sum|mean|max|min|count], `highlight` [none|cell|cross], `showValues`.
70
+ - **`stat`** (cell, card) — a KPI. Data `{ value, delta?, history? }` (`history` is points). `encode.visual: { family: "line" | "bar" | "pie" | "gauge" | "ring" | "progress" }` draws a cell-scale mini-chart from stat's own data. A KPI laid out as a page unit is `Stat` (`element/stat`, see [layout](layout.md)).
43
71
 
44
- ## Chart chrome primitives
72
+ ```tsx
73
+ import { Chart } from "@nurix/components/block/chart";
74
+
75
+ <Chart
76
+ config={{
77
+ version: 2,
78
+ family: "line",
79
+ title: "Revenue",
80
+ x: { type: "time", grain: "month" },
81
+ format: { y: { kind: "currency", currency: "USD", compact: true } },
82
+ encode: { curve: "smooth" },
83
+ overlays: [{ type: "line", y: 50000, label: "Target", color: "chart-negative" }],
84
+ }}
85
+ theme={{ context: "dashboard", chrome: { legend: "bottom" } }}
86
+ data={{ series: [{ id: "rev", label: "Revenue", points: [{ x: "2026-01-01", y: 42000 }, { x: "2026-02-01", y: 51000 }] }] }}
87
+ />
88
+
89
+ <Chart
90
+ config={{
91
+ version: 2,
92
+ family: "gauge",
93
+ title: "Quota",
94
+ encode: { shape: "bullet" },
95
+ overlays: [{ type: "zone", from: 0, to: 60, color: "chart-scale-01" }, { type: "target", value: 80 }],
96
+ }}
97
+ data={{ value: 72, min: 0, max: 100 }}
98
+ />
99
+ ```
100
+
101
+ ### Chrome, interaction, overlays and labels
45
102
 
46
- `@nurix/components/chart-primitives` — shared tier-1 chrome: `ChartEmpty` (empty state), `ChartHoverCard` (tooltip surface; `ChartHoverCardRow`), `ChartLegend` (`ChartLegendItem`, `ChartLegendPosition`).
103
+ The shared chart chrome — legend, tooltip, empty and error states — is set by the spec, never composed as children.
47
104
 
48
- ## Dashboard charts (`charts` subpath)
105
+ - **`theme.chrome`:** `grid` [none|horizontal|both], `xAxis` / `yAxis` / `yAxisRight` (`{ ticks, tickMode, format, largeNumbers, orientation }` or `false`), `legend` [none|right|bottom|inline], `valueLabels` (`true` or `{ position: "inside" | "outside", endpointOnly, total }`), `background` (a plot-area pattern). `legend` and `valueLabels` are illegal at `cell`; grid, axes and `background` are dashboard-only.
106
+ - **`theme.interaction`:** `hover`, `pin`, `range` [none|xband|box]. `pin` is legal on bar, pie, scatter, funnel and both heatmaps; `xband` on line, area, bar and composed; `box` on scatter only. Not every handler reaches the host from every renderer — dashboard hover reaches `onHoverChange` only from the two heatmaps, pin reaches `onPinnedIndexChange` only from `funnel`, and no renderer emits `onRangeChange`.
107
+ - **`config.overlays`:** `zone` (`from`, `to`, `color`, `label`), `target` (`value`), `line` (`x` or `y`), `dot` (`{ x, y }` or `{ resolve: "max" | "min" | "average", seriesId }`), `annotation` (`x`, `text`, `variant` [point|event]), `projection` (`seriesId`, `horizon`). Each is legal only on the families its envelope lists — `zone` on the cartesian families, scatter and gauge; `target` on line, area, bar, composed, gauge, progress and ring; `projection` on line and area.
108
+ - **`config.format`:** `x`, `y`, `value` channels, each `{ kind: "compact" | "int" | "percent" | "currency" | "unit" | "date", … }` with optional `prefix` / `suffix`.
109
+ - **`theme.motion`:** [none|enter|full|auto] (default `auto`, resolved by context).
110
+ - **`theme.labels`:** every string the chart writes — `empty` ("No data"), `invalid`, `invalidName`, `more`, `didYouMean`, `total`, `loading`, `retry`.
49
111
 
50
- Composable visx-based shells — a shell provides scales/context, marks draw data ink, chrome and overlays compose as children. All from `@nurix/components/charts`.
112
+ ## StatusTimeline
51
113
 
52
- **Shells:** `AreaChart`, `BarChart` (`BarOrientation`), `LineChart`, `ScatterChart`, `ComposedChart` — plus standalone loading shells `AreaChartLoading`, `BarChartLoading`, `LineChartLoading`
114
+ An uptime/status strip: one stacked bar per day with a shared day card on hover, focus or pin. No axis — a quantitative time series is `Chart`, a single percentage is `element/progress`. Import `StatusTimeline` and `formatDateRange` from `@nurix/components/compound/status-timeline`.
53
115
 
54
- **Recipes & transforms:** `Bullet`, `Histogram` (+ `computeHistogramBins`, `binsToRows`), `ParetoLine` (+ `computeParetoRows`), `BarSort`, `BarStackMode`
116
+ **Props:** `data: StatusTimelineData[]`, `title` (a muted caption that also names the strip), `height` (px, default `50`), `spacing` [none|sm|md|lg] (default `sm`), `roundedCorners` [none|top|all] (default `top`), `isNormalized` (default `true`), `hasTrack`, `animate` (default `false`), `locale`, `labels`, `loading`, `emptyState`, `renderBar(segment, index, { day, total })`, `renderCard`, `renderEvent`, `render`
55
117
 
56
- **Marks:** `Area`, `RangeArea`, `Bar`, `Line` (`DivergingConfig`), `Scatter`, `SeriesBar`, `SeriesMarkers`, `ValueLabels`; curve factories `curveLinear`, `curveMonotoneX`, `curveNatural`, `curveStep`, `curveStepAfter`, `curveStepBefore` (pass to a mark's `curve` prop — never import `@visx/curve` directly)
118
+ - One record per day: `{ day, summary?, bar: [{ intent, height }], card: [{ intent, value }], events: [{ id, name, type, from, to }] }`. `day` is an ISO date read in local time; `summary` is the card's muted header line; event `type` is `incident`, `report` or `maintenance`.
119
+ - `isNormalized` reads `bar[].height` as a percentage of 100 per day — pass raw counts with `isNormalized={false}`, or every bar mis-fills.
120
+ - Colour comes from `intent` (`neutral`, `info`, `success`, `warning`, `danger`) and the `--intent-*` tokens; retint by redeclaring the tokens, never a per-strip colour map.
121
+ - `loading` renders the skeleton; there is no separate skeleton member.
122
+ - `formatDateRange(from?, to?, { locale, labels })` formats an event's range, collapsing a same-day range.
57
123
 
58
- **Chrome:** `XAxis`, `YAxis`, `BarXAxis`, `BarYAxis`, `Grid`, `Background` (pattern presets), `PatternArea`, `PatternCircles`/`PatternHexagons`/`PatternLines`/`PatternWaves`
124
+ ```tsx
125
+ <StatusTimeline
126
+ title="API uptime"
127
+ data={[{
128
+ day: "2026-08-07",
129
+ summary: "1,204 requests",
130
+ bar: [{ intent: "success", height: 92 }, { intent: "warning", height: 8 }],
131
+ card: [{ intent: "success", value: "92%" }, { intent: "warning", value: "8%" }],
132
+ events: [],
133
+ }]}
134
+ />
135
+ ```
59
136
 
60
- **Tooltip & legend:** composable subsystems re-exported whole (`./tooltip`, `./legend` internals)
137
+ ## Chart kernel
61
138
 
62
- **Overlays:** `ReferenceLine`, `ReferenceArea`, `ReferenceDot`, `Annotation` (`AnnotationVariant`), `ProjectionLine`, `ProfitLossLine` + `ProfitLossLegend`, `ChartBrush`
139
+ The coherence-bearing subset of the chart core, for bespoke visualization code that must match the `Chart` system. Reach for it only for a mark neither `Chart` nor `Spark` draws; needing decimation, scatter geometry, thresholds or interaction scheduling is the signal to use a chart family — those stay unpublished.
63
140
 
64
- **Data contract:** `ChartPoint`, `ChartSeries`, `isChartSeriesArray`, `seriesToRows`
141
+ - **`lib/chart-kernel`:** the data contract types `ChartSeries` (`{ id, label, points, color? }`), `ChartPoint` (`{ x, y }`), `ChartSlice`, `ColorableSeries`; the colour policy `resolveSeriesColors(series, { useTokens })` / `resolveSeriesColor(index, explicitColor?, { useTokens })`; `buildColorScale(min, max, kind)` (`sequential` by default over `--chart-scale-01..05`, or `diverging` over `--chart-negative` / `--chart-diverging-mid` / `--chart-positive`); the formatters `formatCompactNumber`, `formatPercent(ratio, precision)`, `intFmt`, `shortDateFmt`, `weekdayDateFmt`, `monthLongFmt`, `monthShortFmt`, `weekdayLongFmt`, `hmsTimeFmt`; and `isChartDataEmpty(series)`.
142
+ - **`lib/chart-kernel/colors`:** `resolveSeriesColors`, `resolveSeriesColor` at a narrower entry.
143
+ - **`lib/chart-kernel/arc`:** `computeSliceAngles(values, startAngle, totalAngle)`, `arcCentroid`, `buildArcPath({ cx, cy, innerRadius, outerRadius, startAngle, endAngle, padAngle?, cornerRadius? })` (wedge and ring paths), `isFullArc(startAngle, endAngle)`.
144
+ - **`lib/chart-kernel/line`:** `resolveLineDomain(values)`, `createLineScales(config)`, `buildPolylinePoints`, `buildAreaPoints`, `buildLinePath(points, scales, curve, connectNulls)`, `buildAreaPath`, `buildBandAreaPath` — `curve` is `linear`, `smooth`, `step`, `stepAfter` or `stepBefore`.
145
+ - Colour resolves explicit override → `var(--chart-1..5)` → self-contained `oklch` fallbacks; pass `{ useTokens: false }` only for a shell with no theme wiring, or the marks stop re-branding with the host stylesheet.
146
+ - The formatter singletons are fixed to `en-US` at module scope and never follow the runtime locale — a localized axis builds its own `Intl` instance.
65
147
 
66
148
  ```tsx
67
- import { LineChart, Line, XAxis, YAxis, Grid, ReferenceLine } from "@nurix/components/charts";
68
-
69
- <LineChart series={revenue}>
70
- <Grid />
71
- <XAxis />
72
- <YAxis />
73
- <Line curve={curveMonotoneX} />
74
- <ReferenceLine y={target} label="Target" />
75
- </LineChart>
149
+ import { resolveSeriesColors } from "@nurix/components/lib/chart-kernel";
150
+ import { computeSliceAngles, buildArcPath } from "@nurix/components/lib/chart-kernel/arc";
151
+
152
+ const colors = resolveSeriesColors(slices);
153
+ const angles = computeSliceAngles(slices.map((s) => s.value), 0, Math.PI * 2);
76
154
  ```