@godxjp/ui 32.14.0 → 33.0.0

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 (39) hide show
  1. package/README.md +4 -4
  2. package/agent/START-HERE.md +7 -7
  3. package/agent/components/HeatmapChart.json +97 -0
  4. package/agent/components/Tabs.json +1 -1
  5. package/agent/components/Toolbar.json +2 -9
  6. package/agent/components-index.json +5 -0
  7. package/agent/components.json +100 -10
  8. package/agent/index.json +7 -7
  9. package/agent/llms.txt +6 -6
  10. package/agent/tokens.json +114 -6
  11. package/dist/components/charts/chart-summary.d.ts +32 -1
  12. package/dist/components/charts/chart-summary.js +49 -0
  13. package/dist/components/charts/heatmap-chart.d.ts +19 -0
  14. package/dist/components/charts/heatmap-chart.js +293 -0
  15. package/dist/components/charts/index.d.ts +3 -0
  16. package/dist/components/charts/index.js +2 -0
  17. package/dist/components/feedback/tooltip.d.ts +9 -0
  18. package/dist/components/feedback/tooltip.js +4 -1
  19. package/dist/components/navigation/index.d.ts +1 -1
  20. package/dist/components/navigation/toolbar.d.ts +2 -2
  21. package/dist/components/navigation/toolbar.js +0 -3
  22. package/dist/contracts/measurement.json +1 -1
  23. package/dist/i18n/messages/en.json +8 -1
  24. package/dist/i18n/messages/ja.json +8 -1
  25. package/dist/i18n/messages/vi.json +8 -1
  26. package/dist/props/components/charts.prop.d.ts +58 -0
  27. package/dist/props/components/navigation.prop.d.ts +10 -12
  28. package/dist/props/registry.d.ts +37 -5
  29. package/dist/props/registry.js +36 -5
  30. package/dist/styles/chart-layout.css +161 -0
  31. package/dist/styles/layers.json +8 -1
  32. package/dist/styles/layout.css +0 -33
  33. package/dist/styles.css +1 -1
  34. package/dist/tokens/components/chart.css +27 -0
  35. package/dist/tokens/components/navigation.css +0 -2
  36. package/dist/tokens/foundation.css +12 -0
  37. package/docs/migrations/v33.md +60 -0
  38. package/package.json +6 -2
  39. package/scripts/ui-audit.mjs +13 -0
package/README.md CHANGED
@@ -18,7 +18,7 @@ npm i @godxjp/ui
18
18
 
19
19
  <!-- agent-catalog-counts:start -->
20
20
 
21
- The catalog — 162 components, 2210 tokens, 43 cardinal rules — is published in two forms from one
21
+ The catalog — 163 components, 2228 tokens, 43 cardinal rules — is published in two forms from one
22
22
  source. **Entry point for either: [`AGENTS.md`](AGENTS.md).**
23
23
  <!-- agent-catalog-counts:end -->
24
24
 
@@ -53,10 +53,10 @@ static files straight from this public repo — no hosting, no deploy step:
53
53
  | [`agent/llms.txt`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/llms.txt) | 3 KB | the [llms.txt](https://llmstxt.org/) entry point |
54
54
  | [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 17 KB | the visual spec: tokens, colour scarcity, admin screen anatomy, do/don't |
55
55
  | [`agent/index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json) | 3 KB | manifest: version, counts, every file's URL |
56
- | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 48 KB | all 162 as name + group + tagline — **fetch this first** |
56
+ | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 48 KB | all 163 as name + group + tagline — **fetch this first** |
57
57
  | [`agent/components/<Name>.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json) | 1 KB–37 KB | one file per component, each with its `importPath` — **this is the route to take** |
58
- | [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.2 MB | all 162 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
59
- | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 851 KB | all 2210 design tokens, their values and why they exist |
58
+ | [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.2 MB | all 163 entries in one file — most URL fetchers truncate this silently; prefer the per-component files |
59
+ | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 857 KB | all 2228 design tokens, their values and why they exist |
60
60
  | [`agent/vocabulary.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json) | 7 KB | the controlled prop vocabulary |
61
61
  | [`agent/rules.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json) | 21 KB | the 43 cardinal rules |
62
62
  <!-- prettier-ignore-end -->
@@ -3,7 +3,7 @@
3
3
  You are about to write code against a design system you did not author. This file is the whole
4
4
  contract. Read it before you write JSX.
5
5
 
6
- **This catalog describes `@godxjp/ui` 32.14.0.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 33.0.0.** If the project you are editing has a different
7
7
  version in its `package.json`, read the pinned catalog for THAT version instead
8
8
  (`…/v<their-version>/agent/…`). A catalog newer than the installed package describes props that do
9
9
  not exist yet; older, and it hides props that do. Neither failure announces itself.
@@ -52,7 +52,7 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
52
52
  task is a task** — "build a settings page", "confirm a destructive delete", "a list page with
53
53
  filters" — start HERE, not at the components. Then fetch `patterns/<name>.json` for complete,
54
54
  copy-paste-ready code. A component index answers "does X exist"; it cannot answer "build Y".
55
- 1. `components-index.json` — 48 KB, all 162 components as name + group +
55
+ 1. `components-index.json` — 48 KB, all 163 components as name + group +
56
56
  tagline. Read this when you already know the SHAPE you need. Each entry may carry `absorbed`:
57
57
  names that **do not exist** and map to it — `Combobox`, `Autocomplete`, `CountrySelect` and
58
58
  `SearchSelect` are all `Select`. If you are about to hand-roll something, search this field
@@ -61,10 +61,10 @@ screen anatomy, the visual target the rules below do not give you. Then fetch in
61
61
  its `importPath`, and its examples. Fetch only the handful you picked in step 1.
62
62
  3. `rules.json` — 43 cardinal rules. The ones about raw HTML and hardcoded colour are not
63
63
  style advice.
64
- 4. `tokens.json` — 2210 design tokens, each tagged with its `tier`. **If you were handed a
65
- brand, read the 211 `foundation` entries first** — `--primary`, `--background`,
64
+ 4. `tokens.json` — 2228 design tokens, each tagged with its `tier`. **If you were handed a
65
+ brand, read the 216 `foundation` entries first** — `--primary`, `--background`,
66
66
  `--radius`, `--font-size-base` are the handful everything else derives from. The
67
- 1886 `component` entries are per-part knobs; reach for one only when a role is
67
+ 1899 `component` entries are per-part knobs; reach for one only when a role is
68
68
  right everywhere except one component.
69
69
  5. `anti-ai-tells.json` — 35 shapes that make generated UI look generated, each with the
70
70
  fix. Read before you reach for a gradient hero or a wall of coloured chips.
@@ -148,9 +148,9 @@ has stopped following the brand.
148
148
 
149
149
  | `tier` | count | what it is | set it? |
150
150
  |---|---|---|---|
151
- | `foundation` | 211 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** The default `--primary` is a neutral placeholder: set it (`:root { --primary: <H> <S>% <L>%; }`, HSL components, no `hsl()` wrapper) or import a theme preset such as `@godxjp/ui/themes/godx.css` |
151
+ | `foundation` | 216 | the seeds — `--primary`, `--background`, `--foreground`, `--radius`, `--font-size-base`, `--shadow-color`. Everything below is derived from these | **yes — this is the main road.** The default `--primary` is a neutral placeholder: set it (`:root { --primary: <H> <S>% <L>%; }`, HSL components, no `hsl()` wrapper) or import a theme preset such as `@godxjp/ui/themes/godx.css` |
152
152
  | `semantic` | 113 | named roles that follow the seeds — `--ring`, `--text-link`, `--primary-hover`, `--overlay-background` | only when the seed is right and ONE role must differ. That role then stops following a later brand change |
153
- | `component` | 1886 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
153
+ | `component` | 1899 | per-part knobs, `--{component}-{part}-{property}` | rarely. Most are declared `initial` with the real default at the call site — deliberate, so a scoped override re-resolves instead of freezing at `:root` |
154
154
 
155
155
  A token whose `value` is `initial` is not empty and not broken: `initial` is the guaranteed-invalid
156
156
  value, so the real default is computed where the element paints it. Set it and yours wins.
@@ -0,0 +1,97 @@
1
+ {
2
+ "example": "import { HeatmapChart } from \"@godxjp/ui/charts/heatmap-chart\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\nconst { t, locale } = useTranslation();\nconst weekday = new Intl.DateTimeFormat(locale, { weekday: \"short\", timeZone: \"UTC\" });\nconst rows = [\"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\", \"sun\"].map((key, day) => ({\n key,\n label: weekday.format(new Date(Date.UTC(2024, 0, 1 + day))), // 2024-01-01 is a Monday\n}));\nconst columns = Array.from({ length: 24 }, (_, hour) => ({ key: hour, label: new Intl.NumberFormat(locale).format(hour) }));\n\n<HeatmapChart\n label={t(\"dashboard.revenueByHour\")}\n rows={rows}\n columns={columns}\n data={slots /* [{ row: \"fri\", column: 19, value: 48000 }, …] — omit closed hours */}\n numberFormat={{ style: \"currency\", currency: \"JPY\" }}\n topN={3}\n/>",
3
+ "group": "data-display",
4
+ "importPath": "@godxjp/ui/charts/heatmap-chart",
5
+ "name": "HeatmapChart",
6
+ "props": [
7
+ {
8
+ "description": "Row categories, top to bottom (the y axis — weekdays). `label` is already translated.",
9
+ "name": "rows",
10
+ "required": true,
11
+ "type": "{ key: string | number; label: string }[]"
12
+ },
13
+ {
14
+ "description": "Column categories, start to end (the x axis — hours). `label` is already translated.",
15
+ "name": "columns",
16
+ "required": true,
17
+ "type": "{ key: string | number; label: string }[]"
18
+ },
19
+ {
20
+ "description": "Cell values keyed by row + column key. A cell that is absent, null, undefined or NaN is EMPTY ('no data') — never painted as a zero.",
21
+ "name": "data",
22
+ "required": true,
23
+ "type": "{ row: string | number; column: string | number; value: number | null | undefined }[]"
24
+ },
25
+ {
26
+ "description": "Accessible name of the figure and its grid + visible caption.",
27
+ "name": "label",
28
+ "required": true,
29
+ "type": "string"
30
+ },
31
+ {
32
+ "defaultValue": "true",
33
+ "description": "Paint `label` as a visible caption. Set false under a CardTitle that already says it — the caption stays sr-only so the grid keeps its name.",
34
+ "name": "showCaption",
35
+ "type": "boolean"
36
+ },
37
+ {
38
+ "description": "Extra context prepended to the screen-reader summary.",
39
+ "name": "description",
40
+ "type": "string"
41
+ },
42
+ {
43
+ "defaultValue": "\"md\"",
44
+ "description": "CELL size tier (--chart-heatmap-cell-size-*: 24/28/32/40px) — not a canvas height. A cell never shrinks below it; when the columns do not fit, the matrix scrolls inside its own box with the row header pinned.",
45
+ "name": "size",
46
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
47
+ },
48
+ {
49
+ "description": "Locale-aware formatting for cell values, the tooltip, the legend ranges and the summary (e.g. `{ style: 'currency', currency: 'JPY' }`).",
50
+ "name": "numberFormat",
51
+ "type": "Intl.NumberFormatOptions"
52
+ },
53
+ {
54
+ "description": "Emphasize the N highest cells with an outline ring plus a 'Top k' note in the tooltip, the cell text and the legend — never colour alone. Ties keep `data` order.",
55
+ "name": "topN",
56
+ "type": "number"
57
+ },
58
+ {
59
+ "defaultValue": "true",
60
+ "description": "Colour-scale legend (min … max, one readable range per swatch) plus the 'no data' and 'top N' keys when they apply.",
61
+ "name": "showLegend",
62
+ "type": "boolean"
63
+ },
64
+ {
65
+ "description": "Shown when there are no rows, no columns or no cells with a value.",
66
+ "name": "emptyMessage",
67
+ "type": "string"
68
+ },
69
+ {
70
+ "description": "Forwarded to the <figure> element.",
71
+ "name": "ref",
72
+ "type": "React.Ref<HTMLElement>"
73
+ }
74
+ ],
75
+ "related": [
76
+ "CompactBarTrend — one row of values; the other dependency-free chart.",
77
+ "BarChart — exact comparison across a single category axis (recharts peer).",
78
+ "DataTable — when the reader needs to sort, filter or read exact numbers first."
79
+ ],
80
+ "rules": [],
81
+ "storyPath": "charts/heatmap-chart.tsx",
82
+ "tagline": "DEPENDENCY-FREE rows × columns heatmap — weekday × hour revenue / order counts, cell intensity from the sequential CVD-safe --chart-heat-1..5 ramp. Empty ≠ zero (dashed outline + 'no data' text), optional top-N ring, per-cell tooltip, APG grid keyboard, the table itself is the text alternative. Needs NO recharts. Matrix / punch card / activity heatmap / busy-hours chart.",
83
+ "usage": [
84
+ "DO import from the isolated entry: `import { HeatmapChart } from \"@godxjp/ui/charts/heatmap-chart\";` — it never links the optional `recharts` peer.",
85
+ "DO derive axis labels with Intl in the active locale — weekdays via `Intl.DateTimeFormat(locale, { weekday: 'short' })`, hours via `Intl.NumberFormat(locale)` — and pass money through `numberFormat`, never a hand-built '¥' string.",
86
+ "DO leave a slot OUT of `data` (or pass `value: null`) when there is no measurement — a closed hour, a day before the store opened. Pass `0` only for a real zero. The two render differently (dashed outline vs filled) and read differently ('No data' vs '¥0').",
87
+ "DO use `topN` to call out the peak slots; the ring is a shape and the rank is text, so the emphasis survives greyscale and colour-vision deficiency.",
88
+ "DO retheme through the tokens: the ramp is the foundation `--chart-heat-1..5` (light + dark), geometry and cues are `--chart-heatmap-*`. NEVER tint cells from the app, add page CSS, or fake the matrix with a styled Table.",
89
+ "DON'T add a separate sr-only table: the matrix IS a native data table with `role=\"grid\"` and header cells, so the visible chart and its text alternative cannot drift apart.",
90
+ "DON'T use it for a continuous series over time (LineChart / AreaChart) or one row of values (CompactBarTrend)."
91
+ ],
92
+ "useCases": [
93
+ "Restaurant / retail HQ: revenue or order count by weekday × hour across the chain (POS busy-hours).",
94
+ "Support desk: ticket volume by weekday × hour, to plan staffing.",
95
+ "Any categorical matrix where intensity matters more than the exact figure: store × week, region × product."
96
+ ]
97
+ }
@@ -91,7 +91,7 @@
91
91
  },
92
92
  {
93
93
  "defaultValue": "\"scroll\"",
94
- "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Toolbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
94
+ "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Topbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
95
95
  "name": "overflow",
96
96
  "type": "\"scroll\" | \"menu\""
97
97
  },
@@ -28,12 +28,6 @@
28
28
  "name": "sticky",
29
29
  "type": "boolean"
30
30
  },
31
- {
32
- "defaultValue": "'wrap'",
33
- "description": "Responsive overflow strategy. 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) — use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll.",
34
- "name": "overflow",
35
- "type": "'wrap' | 'scroll'"
36
- },
37
31
  {
38
32
  "description": "Typed model — canonical search slot rendered FIRST in the strip with the token-owned consistent width (--filter-bar-search-width, full-width below 640px). Controlled triad value/defaultValue/onValueChange plus SearchInput's debounced onSearch. ANY model prop (search/filters/chips/onChipRemove/actions/resultCount/loading/disabled/error) switches the bar to the canonical model layout; with none of them the bar stays the plain children-composition toolbar unchanged.",
39
33
  "name": "search",
@@ -127,14 +121,13 @@
127
121
  "DO drive the clear-all button with `hasActiveFilters` + `onClear` — it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.",
128
122
  "DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.",
129
123
  "DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
130
- "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
124
+ "DO let the strip WRAP — it stacks below 640px and wraps onto extra rows above; it never scrolls sideways (the `overflow` prop and its `scroll` strategy were removed in 33.0.0: a horizontal strip hid filters off-screen and clipped the first label at its edge). When a list page has more filters than fit one or two rows, keep the few common ones in the bar and put the rest behind ONE 'More filters' Button that opens a Sheet (or Popover) holding them — a composition of Button + Sheet + FilterBarGroup; there is no dedicated component. Never re-implement the geometry in the page: no page-local flex/grid/width/overflow rules on a filter strip.",
131
125
  "DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
132
126
  "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y.",
133
127
  "Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
134
128
  "Compose real controls as children when the model doesn't fit; filter state and URL synchronization remain consumer-owned. Children also render INSIDE the model layout (after the typed filters) for one-off custom controls like a date-range picker.",
135
129
  "Give each FilterBarGroup a `controlId` matching its single control's `id` so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader. Typed `filters` wire this automatically.",
136
- "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
137
- "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap/scroll (above), token-owned."
130
+ "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap (above), token-owned."
138
131
  ],
139
132
  "useCases": [
140
133
  "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
@@ -712,6 +712,11 @@
712
712
  "name": "CompactBarTrend",
713
713
  "tagline": "DEPENDENCY-FREE compact vertical bar trend for dashboard summary cards — N category/value pairs, muted marks plus ONE emphasized 'current' bar, all geometry from --chart-trend-* tokens. Needs NO recharts. Sparkline / micro-chart / activity pulse / KPI trend strip."
714
714
  },
715
+ {
716
+ "group": "data-display",
717
+ "name": "HeatmapChart",
718
+ "tagline": "DEPENDENCY-FREE rows × columns heatmap — weekday × hour revenue / order counts, cell intensity from the sequential CVD-safe --chart-heat-1..5 ramp. Empty ≠ zero (dashed outline + 'no data' text), optional top-N ring, per-cell tooltip, APG grid keyboard, the table itself is the text alternative. Needs NO recharts. Matrix / punch card / activity heatmap / busy-hours chart."
719
+ },
715
720
  {
716
721
  "group": "data-display",
717
722
  "name": "AreaChart",
@@ -8134,7 +8134,7 @@
8134
8134
  },
8135
8135
  {
8136
8136
  "defaultValue": "\"scroll\"",
8137
- "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Toolbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
8137
+ "description": "What the trigger strip does when there are more tabs than fit — Ant Design's `more`, mapped onto the `overflow` vocabulary Topbar already uses for the same question rather than re-spelled. `scroll` (default, and what every strip does today) keeps one bounded row that scrolls its own inline overflow, with the active (or, under manual activation, focused) trigger re-pinned into view. `menu` keeps all of that AND puts a real named button beside the strip listing the tabs currently outside the scrollport; choosing one selects it. DIVERGES FROM ANTD DELIBERATELY: antd REMOVES the overflowing tabs from the bar, but the WAI-ARIA APG tab pattern requires the tablist to own every tab and a `display: none` tab cannot take roving focus — so here every tab stays in the strip and the menu is an ADDITIONAL pointer route, not a relocation. The default is not `menu` because switching it would change the rendered bar for every existing consumer at once.",
8138
8138
  "name": "overflow",
8139
8139
  "type": "\"scroll\" | \"menu\""
8140
8140
  },
@@ -8584,12 +8584,6 @@
8584
8584
  "name": "sticky",
8585
8585
  "type": "boolean"
8586
8586
  },
8587
- {
8588
- "defaultValue": "'wrap'",
8589
- "description": "Responsive overflow strategy. 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) — use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll.",
8590
- "name": "overflow",
8591
- "type": "'wrap' | 'scroll'"
8592
- },
8593
8587
  {
8594
8588
  "description": "Typed model — canonical search slot rendered FIRST in the strip with the token-owned consistent width (--filter-bar-search-width, full-width below 640px). Controlled triad value/defaultValue/onValueChange plus SearchInput's debounced onSearch. ANY model prop (search/filters/chips/onChipRemove/actions/resultCount/loading/disabled/error) switches the bar to the canonical model layout; with none of them the bar stays the plain children-composition toolbar unchanged.",
8595
8589
  "name": "search",
@@ -8683,14 +8677,13 @@
8683
8677
  "DO drive the clear-all button with `hasActiveFilters` + `onClear` — it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.",
8684
8678
  "DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.",
8685
8679
  "DO pass `controlId` on a ToolbarGroup that wraps ONE control and give that control the same `id` — the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.",
8686
- "DO switch to `overflow='scroll'` when a list page carries more filters than fit one row — one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.",
8680
+ "DO let the strip WRAP — it stacks below 640px and wraps onto extra rows above; it never scrolls sideways (the `overflow` prop and its `scroll` strategy were removed in 33.0.0: a horizontal strip hid filters off-screen and clipped the first label at its edge). When a list page has more filters than fit one or two rows, keep the few common ones in the bar and put the rest behind ONE 'More filters' Button that opens a Sheet (or Popover) holding them — a composition of Button + Sheet + FilterBarGroup; there is no dedicated component. Never re-implement the geometry in the page: no page-local flex/grid/width/overflow rules on a filter strip.",
8687
8681
  "DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.",
8688
8682
  "DON'T hand-roll a debounced search box or a raw `<select>` — compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y.",
8689
8683
  "Prefer the typed model for a canonical list page: pass `search` + `filters` + `chips` + `resultCount` and the bar owns the layout, widths, chip lifecycle, keyboard order (search → filters → children → reset → actions → chip removes) and responsive stacking through --filter-bar-* tokens. Everything stays consumer data — the bar renders state, never owns it.",
8690
8684
  "Compose real controls as children when the model doesn't fit; filter state and URL synchronization remain consumer-owned. Children also render INSIDE the model layout (after the typed filters) for one-off custom controls like a date-range picker.",
8691
8685
  "Give each FilterBarGroup a `controlId` matching its single control's `id` so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader. Typed `filters` wire this automatically.",
8692
- "Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold.",
8693
- "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap/scroll (above), token-owned."
8686
+ "A mobile sheet presentation is a COMPOSITION, not a prop: put the same typed FilterBar inside a Sheet triggered from a compact toolbar when a screen wants drawer-style filters. The bar's own responsive behavior is stack (below 640px) + wrap (above), token-owned."
8694
8687
  ],
8695
8688
  "useCases": [
8696
8689
  "Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.",
@@ -13247,6 +13240,103 @@
13247
13240
  "A dependency-constrained app (no recharts) that still needs a token-driven chart."
13248
13241
  ]
13249
13242
  },
13243
+ {
13244
+ "example": "import { HeatmapChart } from \"@godxjp/ui/charts/heatmap-chart\";\nimport { useTranslation } from \"@godxjp/ui/i18n\";\n\nconst { t, locale } = useTranslation();\nconst weekday = new Intl.DateTimeFormat(locale, { weekday: \"short\", timeZone: \"UTC\" });\nconst rows = [\"mon\", \"tue\", \"wed\", \"thu\", \"fri\", \"sat\", \"sun\"].map((key, day) => ({\n key,\n label: weekday.format(new Date(Date.UTC(2024, 0, 1 + day))), // 2024-01-01 is a Monday\n}));\nconst columns = Array.from({ length: 24 }, (_, hour) => ({ key: hour, label: new Intl.NumberFormat(locale).format(hour) }));\n\n<HeatmapChart\n label={t(\"dashboard.revenueByHour\")}\n rows={rows}\n columns={columns}\n data={slots /* [{ row: \"fri\", column: 19, value: 48000 }, …] — omit closed hours */}\n numberFormat={{ style: \"currency\", currency: \"JPY\" }}\n topN={3}\n/>",
13245
+ "group": "data-display",
13246
+ "importPath": "@godxjp/ui/charts/heatmap-chart",
13247
+ "name": "HeatmapChart",
13248
+ "props": [
13249
+ {
13250
+ "description": "Row categories, top to bottom (the y axis — weekdays). `label` is already translated.",
13251
+ "name": "rows",
13252
+ "required": true,
13253
+ "type": "{ key: string | number; label: string }[]"
13254
+ },
13255
+ {
13256
+ "description": "Column categories, start to end (the x axis — hours). `label` is already translated.",
13257
+ "name": "columns",
13258
+ "required": true,
13259
+ "type": "{ key: string | number; label: string }[]"
13260
+ },
13261
+ {
13262
+ "description": "Cell values keyed by row + column key. A cell that is absent, null, undefined or NaN is EMPTY ('no data') — never painted as a zero.",
13263
+ "name": "data",
13264
+ "required": true,
13265
+ "type": "{ row: string | number; column: string | number; value: number | null | undefined }[]"
13266
+ },
13267
+ {
13268
+ "description": "Accessible name of the figure and its grid + visible caption.",
13269
+ "name": "label",
13270
+ "required": true,
13271
+ "type": "string"
13272
+ },
13273
+ {
13274
+ "defaultValue": "true",
13275
+ "description": "Paint `label` as a visible caption. Set false under a CardTitle that already says it — the caption stays sr-only so the grid keeps its name.",
13276
+ "name": "showCaption",
13277
+ "type": "boolean"
13278
+ },
13279
+ {
13280
+ "description": "Extra context prepended to the screen-reader summary.",
13281
+ "name": "description",
13282
+ "type": "string"
13283
+ },
13284
+ {
13285
+ "defaultValue": "\"md\"",
13286
+ "description": "CELL size tier (--chart-heatmap-cell-size-*: 24/28/32/40px) — not a canvas height. A cell never shrinks below it; when the columns do not fit, the matrix scrolls inside its own box with the row header pinned.",
13287
+ "name": "size",
13288
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
13289
+ },
13290
+ {
13291
+ "description": "Locale-aware formatting for cell values, the tooltip, the legend ranges and the summary (e.g. `{ style: 'currency', currency: 'JPY' }`).",
13292
+ "name": "numberFormat",
13293
+ "type": "Intl.NumberFormatOptions"
13294
+ },
13295
+ {
13296
+ "description": "Emphasize the N highest cells with an outline ring plus a 'Top k' note in the tooltip, the cell text and the legend — never colour alone. Ties keep `data` order.",
13297
+ "name": "topN",
13298
+ "type": "number"
13299
+ },
13300
+ {
13301
+ "defaultValue": "true",
13302
+ "description": "Colour-scale legend (min … max, one readable range per swatch) plus the 'no data' and 'top N' keys when they apply.",
13303
+ "name": "showLegend",
13304
+ "type": "boolean"
13305
+ },
13306
+ {
13307
+ "description": "Shown when there are no rows, no columns or no cells with a value.",
13308
+ "name": "emptyMessage",
13309
+ "type": "string"
13310
+ },
13311
+ {
13312
+ "description": "Forwarded to the <figure> element.",
13313
+ "name": "ref",
13314
+ "type": "React.Ref<HTMLElement>"
13315
+ }
13316
+ ],
13317
+ "related": [
13318
+ "CompactBarTrend — one row of values; the other dependency-free chart.",
13319
+ "BarChart — exact comparison across a single category axis (recharts peer).",
13320
+ "DataTable — when the reader needs to sort, filter or read exact numbers first."
13321
+ ],
13322
+ "rules": [],
13323
+ "storyPath": "charts/heatmap-chart.tsx",
13324
+ "tagline": "DEPENDENCY-FREE rows × columns heatmap — weekday × hour revenue / order counts, cell intensity from the sequential CVD-safe --chart-heat-1..5 ramp. Empty ≠ zero (dashed outline + 'no data' text), optional top-N ring, per-cell tooltip, APG grid keyboard, the table itself is the text alternative. Needs NO recharts. Matrix / punch card / activity heatmap / busy-hours chart.",
13325
+ "usage": [
13326
+ "DO import from the isolated entry: `import { HeatmapChart } from \"@godxjp/ui/charts/heatmap-chart\";` — it never links the optional `recharts` peer.",
13327
+ "DO derive axis labels with Intl in the active locale — weekdays via `Intl.DateTimeFormat(locale, { weekday: 'short' })`, hours via `Intl.NumberFormat(locale)` — and pass money through `numberFormat`, never a hand-built '¥' string.",
13328
+ "DO leave a slot OUT of `data` (or pass `value: null`) when there is no measurement — a closed hour, a day before the store opened. Pass `0` only for a real zero. The two render differently (dashed outline vs filled) and read differently ('No data' vs '¥0').",
13329
+ "DO use `topN` to call out the peak slots; the ring is a shape and the rank is text, so the emphasis survives greyscale and colour-vision deficiency.",
13330
+ "DO retheme through the tokens: the ramp is the foundation `--chart-heat-1..5` (light + dark), geometry and cues are `--chart-heatmap-*`. NEVER tint cells from the app, add page CSS, or fake the matrix with a styled Table.",
13331
+ "DON'T add a separate sr-only table: the matrix IS a native data table with `role=\"grid\"` and header cells, so the visible chart and its text alternative cannot drift apart.",
13332
+ "DON'T use it for a continuous series over time (LineChart / AreaChart) or one row of values (CompactBarTrend)."
13333
+ ],
13334
+ "useCases": [
13335
+ "Restaurant / retail HQ: revenue or order count by weekday × hour across the chain (POS busy-hours).",
13336
+ "Support desk: ticket volume by weekday × hour, to plan staffing.",
13337
+ "Any categorical matrix where intensity matters more than the exact figure: store × week, region × product."
13338
+ ]
13339
+ },
13250
13340
  {
13251
13341
  "example": "import { AreaChart } from \"@godxjp/ui/charts\";\n\n<AreaChart\n label={t(\"dashboard.trafficByChannel\")}\n data={data}\n categoryKey=\"day\"\n series={[\n { dataKey: \"organic\", label: t(\"channel.organic\") },\n { dataKey: \"paid\", label: t(\"channel.paid\") },\n ]}\n stacked\n/>",
13252
13342
  "group": "data-display",
package/agent/index.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "counts": {
3
3
  "anti-ai-tells": 35,
4
- "components": 162,
4
+ "components": 163,
5
5
  "patterns": 24,
6
6
  "rules": 43,
7
- "tokens": 2210,
7
+ "tokens": 2228,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  {
18
18
  "file": "components-index.json",
19
- "note": "48 KB — name + group + tagline for all 162. FETCH THIS FIRST, then fetch only the components you chose.",
19
+ "note": "48 KB — name + group + tagline for all 163. FETCH THIS FIRST, then fetch only the components you chose.",
20
20
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
21
21
  },
22
22
  {
@@ -54,19 +54,19 @@
54
54
  "note": "Pin to the tag that matches the @godxjp/ui version you installed. A catalog newer than your package describes props you do not have; older, and it hides props you do.",
55
55
  "read": {
56
56
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
57
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.14.0/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.0.0/agent/index.json"
58
58
  },
59
59
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
60
60
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
61
61
  "tokenTiers": {
62
62
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
63
63
  "counts": {
64
- "component": 1886,
65
- "foundation": 211,
64
+ "component": 1899,
65
+ "foundation": 216,
66
66
  "semantic": 113
67
67
  },
68
68
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
69
69
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
70
70
  },
71
- "version": "32.14.0"
71
+ "version": "33.0.0"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 162 components, 2210 design tokens,
4
- > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.14.0.
3
+ > A Japanese-enterprise React design system: 163 components, 2228 design tokens,
4
+ > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 33.0.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@32.14.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.0.0`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
@@ -16,10 +16,10 @@ that can only fetch URLs.
16
16
  ## Catalog
17
17
 
18
18
  - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 24 whole-task patterns (name, tagline, tags). Start here when the task is a TASK — "build a settings page" — then fetch `patterns/<name>.json` for complete code.
19
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 162 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 48 KB — all 163 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
20
20
  - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–37 KB, median 6 KB). Read the index, then fetch only the ones you chose — this is the selective route, and the reason you do not need the blob.
21
21
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
22
- - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1886 `component` knobs.
22
+ - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1899 `component` knobs.
23
23
  - [vocabulary.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/vocabulary.json): the controlled prop vocabulary — which prop name means what, across every component.
24
24
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 43 cardinal rules.
25
25
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 35 shapes that make generated UI look generated, each with its fix.
@@ -27,7 +27,7 @@ that can only fetch URLs.
27
27
  ## Pinning
28
28
 
29
29
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
30
- the tag: `.../godx-jp/godxjp-ui/v32.14.0/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.0.0/agent/...`. A catalog that does not match the installed
31
31
  package describes props that are absent, or hides props that are present, and says nothing either way.
32
32
 
33
33
  Pinned catalogs only exist for releases whose tag actually contains `agent/`. If `…/v<version>/agent/index.json` returns 404, that release predates this catalog: read `…/main/…` instead and compare `index.json` → `version` against the package you have, so you at least know which way it drifted.
package/agent/tokens.json CHANGED
@@ -443,6 +443,36 @@
443
443
  "tier": "foundation",
444
444
  "value": "#eb6101"
445
445
  },
446
+ {
447
+ "description": "Sequential heat ramp — HeatmapChart (gh#1330). ONE hue (the --chart-1 blue, 204°) stepped by lightness alone, so the order of the steps survives every colour-vision deficiency: CVD shifts hue, and a single-hue ramp carries its meaning in luminance, which protan/deutan/tritan vision all keep. Relative luminance falls monotonically 0.805 → 0.541 → 0.330 → 0.173 → 0.064; against the light --card that is 1.21 / 1.74 / 2.72 / 4.62 / 9.03:1, each step ≥1.45:1 from the last. Step 1 is deliberately quiet (it is \"low\", not \"absent\"): an EMPTY cell is told apart by its dashed outline and its text, never by this fill. A service retunes the whole ramp here.",
448
+ "name": "--chart-heat-1",
449
+ "tier": "foundation",
450
+ "value": "#d6ebf8"
451
+ },
452
+ {
453
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
454
+ "name": "--chart-heat-2",
455
+ "tier": "foundation",
456
+ "value": "#94c9ec"
457
+ },
458
+ {
459
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
460
+ "name": "--chart-heat-3",
461
+ "tier": "foundation",
462
+ "value": "#4ea3dc"
463
+ },
464
+ {
465
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
466
+ "name": "--chart-heat-4",
467
+ "tier": "foundation",
468
+ "value": "#0077c7"
469
+ },
470
+ {
471
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
472
+ "name": "--chart-heat-5",
473
+ "tier": "foundation",
474
+ "value": "#004a7f"
475
+ },
446
476
  {
447
477
  "description": "Disabled affordance opacity — single knob (was referenced but undefined → silent failure).",
448
478
  "name": "--disabled-opacity",
@@ -2855,6 +2885,90 @@
2855
2885
  "tier": "component",
2856
2886
  "value": "3 3"
2857
2887
  },
2888
+ {
2889
+ "description": "Minimum cell edge per tier. `xs` is still 24px — a cell is a hover/focus target (WCAG 2.5.8).",
2890
+ "name": "--chart-heatmap-cell-size-xs",
2891
+ "tier": "component",
2892
+ "value": "1.5rem"
2893
+ },
2894
+ {
2895
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2896
+ "name": "--chart-heatmap-cell-size-sm",
2897
+ "tier": "component",
2898
+ "value": "1.75rem"
2899
+ },
2900
+ {
2901
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2902
+ "name": "--chart-heatmap-cell-size-md",
2903
+ "tier": "component",
2904
+ "value": "2rem"
2905
+ },
2906
+ {
2907
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2908
+ "name": "--chart-heatmap-cell-size-lg",
2909
+ "tier": "component",
2910
+ "value": "2.5rem"
2911
+ },
2912
+ {
2913
+ "description": "Active tier — chart-layout.css re-points this per `data-size`.",
2914
+ "name": "--chart-heatmap-cell-size",
2915
+ "tier": "component",
2916
+ "value": "var(--chart-heatmap-cell-size-md)"
2917
+ },
2918
+ {
2919
+ "description": "Surface gap between neighbouring cells (the table's border-spacing).",
2920
+ "name": "--chart-heatmap-cell-gap",
2921
+ "tier": "component",
2922
+ "value": "var(--stroke-md)"
2923
+ },
2924
+ {
2925
+ "description": "default = calc(var(--radius) / var(--radius-ratio) / var(--radius-ratio)), resolved at the call site",
2926
+ "name": "--chart-heatmap-cell-radius",
2927
+ "tier": "component",
2928
+ "value": "initial"
2929
+ },
2930
+ {
2931
+ "description": "EMPTY cell outline — the non-colour cue that tells \"no data\" from a zero (WCAG 1.4.1). Role-mirror knob, `initial` at :root; default at the call site = `var(--stroke-hairline) dashed hsl(var(--muted-foreground))`.",
2932
+ "name": "--chart-heatmap-empty-border",
2933
+ "tier": "component",
2934
+ "value": "initial"
2935
+ },
2936
+ {
2937
+ "description": "Top-N emphasis ring — a SHAPE cue, never colour alone. Colour is a role-mirror knob (default `hsl(var(--foreground))` at the call site); width is a plain constant.",
2938
+ "name": "--chart-heatmap-emphasis-border",
2939
+ "tier": "component",
2940
+ "value": "initial"
2941
+ },
2942
+ {
2943
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2944
+ "name": "--chart-heatmap-emphasis-width",
2945
+ "tier": "component",
2946
+ "value": "var(--stroke-md)"
2947
+ },
2948
+ {
2949
+ "description": "The pinned row-header column paints over cells scrolled beneath it, so it needs a ground. Role-mirror knob; default = `hsl(var(--card))` at the call site.",
2950
+ "name": "--chart-heatmap-row-header-background",
2951
+ "tier": "component",
2952
+ "value": "initial"
2953
+ },
2954
+ {
2955
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2956
+ "name": "--chart-heatmap-tick-font-size",
2957
+ "tier": "component",
2958
+ "value": "var( --font-size-xs, calc(var(--font-size-base) / var(--font-size-ratio)) )"
2959
+ },
2960
+ {
2961
+ "description": "Legend rhythm: the swatch edge and the gap between legend items / plot ↔ legend.",
2962
+ "name": "--chart-heatmap-legend-swatch-size",
2963
+ "tier": "component",
2964
+ "value": "var(--space-3)"
2965
+ },
2966
+ {
2967
+ "description": "Chart component tokens — the dependency-free `CompactBarTrend` primitive (`@godxjp/ui/charts`). Every piece of its geometry and every fill is a public knob so a service theme can match its own design grid WITHOUT page-local CSS or inline heights (cardinal rules #44/#45). The recharts-backed charts draw their own SVG from the foundation `--chart-1..6` palette, plus the cartesian series knobs at the bottom of this file.",
2968
+ "name": "--chart-heatmap-legend-gap",
2969
+ "tier": "component",
2970
+ "value": "var(--space-2)"
2971
+ },
2858
2972
  {
2859
2973
  "description": "Bubble fill. `initial` so the --muted default re-resolves at the CALL SITE under a scoped theme; a :root binding to a role var freezes at :root (docs/TOKENS.md — the :root freeze rule). Documented default = hsl(var(--muted)).",
2860
2974
  "name": "--chat-bubble-background",
@@ -9383,12 +9497,6 @@
9383
9497
  "tier": "component",
9384
9498
  "value": "initial"
9385
9499
  },
9386
- {
9387
- "description": "Scrollbar gutter reserved under a `overflow=\"scroll\"` filter strip (#216) so the inline scrollbar never overlaps the controls. Rule #45 — a service theme retunes it to its grid (0 on an overlay-scrollbar platform).",
9388
- "name": "--filter-bar-scroll-padding-y",
9389
- "tier": "component",
9390
- "value": "var(--space-1)"
9391
- },
9392
9500
  {
9393
9501
  "description": "FilterBar typed model (gh#258) — token-owned geometry so every list page gets the SAME search width, filter width, chip rhythm and section stacking without page-local CSS. All are rule-#45 knobs: a service theme retunes them once, props/className override per instance. Below 640px the strip stacks (search/filters full-width); the widths apply from the 640px breakpoint up, always clamped by min(100%, …) so a narrow container never overflows at 390px with long JA/EN/VI labels.",
9394
9502
  "name": "--filter-bar-search-width",