@godxjp/ui 28.12.0 → 29.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 (178) hide show
  1. package/agent/START-HERE.md +29 -10
  2. package/agent/components/Anchor.json +6 -1
  3. package/agent/components/AppLauncher.json +10 -0
  4. package/agent/components/AppShell.json +1 -1
  5. package/agent/components/AreaChart.json +19 -1
  6. package/agent/components/Attachments.json +26 -1
  7. package/agent/components/BarChart.json +11 -1
  8. package/agent/components/BranchScopePicker.json +10 -0
  9. package/agent/components/Cascader.json +6 -1
  10. package/agent/components/Checkbox.json +6 -0
  11. package/agent/components/CompactBarTrend.json +1 -1
  12. package/agent/components/CredentialReveal.json +21 -0
  13. package/agent/components/DataState.json +1 -1
  14. package/agent/components/DataTable.json +4 -4
  15. package/agent/components/FormField.json +1 -1
  16. package/agent/components/InfiniteQueryState.json +1 -1
  17. package/agent/components/Input.json +1 -1
  18. package/agent/components/InputOTP.json +35 -0
  19. package/agent/components/LineChart.json +20 -2
  20. package/agent/components/ListRow.json +1 -1
  21. package/agent/components/Masonry.json +1 -1
  22. package/agent/components/MasterDetail.json +1 -1
  23. package/agent/components/PasswordStrength.json +1 -1
  24. package/agent/components/PermissionMatrix.json +6 -1
  25. package/agent/components/SearchInput.json +5 -0
  26. package/agent/components/Select.json +1 -0
  27. package/agent/components/ServiceRolePanel.json +5 -0
  28. package/agent/components/Sidebar.json +1 -1
  29. package/agent/components/Switch.json +6 -0
  30. package/agent/components/Table.json +8 -3
  31. package/agent/components/Tabs.json +10 -0
  32. package/agent/components/ThemeScope.json +49 -0
  33. package/agent/components/TimeRangePicker.json +5 -0
  34. package/agent/components/Topbar.json +1 -0
  35. package/agent/components/TopbarItem.json +2 -1
  36. package/agent/components/Transfer.json +6 -1
  37. package/agent/components/TreeSelect.json +1 -1
  38. package/agent/components/Upload.json +5 -0
  39. package/agent/components/UploadCropDialog.json +1 -1
  40. package/agent/components/formatDate.json +1 -1
  41. package/agent/components-index.json +5 -0
  42. package/agent/components.json +297 -31
  43. package/agent/index.json +19 -9
  44. package/agent/llms.txt +10 -10
  45. package/agent/patterns/tenant-brand-color.json +28 -0
  46. package/agent/patterns-index.json +27 -0
  47. package/agent/patterns.json +28 -0
  48. package/agent/rules.json +15 -0
  49. package/agent/tokens.json +4965 -970
  50. package/dist/app/index.d.ts +3 -0
  51. package/dist/app/index.js +3 -0
  52. package/dist/app/tenant-theme.d.ts +80 -0
  53. package/dist/app/tenant-theme.js +154 -0
  54. package/dist/app/theme-axes.d.ts +14 -1
  55. package/dist/app/theme-axes.js +24 -31
  56. package/dist/components/charts/chart-cartesian.d.ts +5 -1
  57. package/dist/components/charts/chart-cartesian.js +15 -8
  58. package/dist/components/data-display/badge.d.ts +1 -1
  59. package/dist/components/data-display/badge.js +20 -2
  60. package/dist/components/data-display/carousel.js +4 -4
  61. package/dist/components/data-display/data-table.js +13 -2
  62. package/dist/components/data-display/permission-matrix.js +1 -1
  63. package/dist/components/data-display/table.d.ts +11 -2
  64. package/dist/components/data-display/table.js +18 -2
  65. package/dist/components/data-entry/control-appearance.d.ts +12 -6
  66. package/dist/components/data-entry/control-appearance.js +1 -1
  67. package/dist/components/data-entry/select.js +4 -3
  68. package/dist/components/feedback/dialog.js +6 -3
  69. package/dist/components/feedback/overlay-header-tone.d.ts +7 -0
  70. package/dist/components/feedback/overlay-header-tone.js +4 -4
  71. package/dist/components/feedback/sheet.d.ts +1 -1
  72. package/dist/components/feedback/sheet.js +6 -9
  73. package/dist/components/feedback/sonner.js +16 -3
  74. package/dist/components/general/button.js +22 -5
  75. package/dist/components/layout/affix.js +15 -1
  76. package/dist/components/layout/sidebar.js +7 -1
  77. package/dist/components/navigation/anchor.d.ts +1 -1
  78. package/dist/components/navigation/anchor.js +5 -4
  79. package/dist/components/navigation/app-setting-picker.js +1 -1
  80. package/dist/components/navigation/pagination.js +1 -1
  81. package/dist/components/navigation/tabs.js +15 -2
  82. package/dist/components/query/infinite-query-state.d.ts +22 -6
  83. package/dist/contracts/measurement.json +1 -1
  84. package/dist/i18n/messages/en.json +0 -697
  85. package/dist/i18n/messages/ja.json +0 -691
  86. package/dist/i18n/messages/vi.json +0 -691
  87. package/dist/lib/control-styles.d.ts +31 -11
  88. package/dist/lib/control-styles.js +6 -6
  89. package/dist/lib/overlay-portal.d.ts +20 -0
  90. package/dist/lib/overlay-portal.js +93 -0
  91. package/dist/props/components/app.prop.d.ts +12 -0
  92. package/dist/props/components/charts.prop.d.ts +30 -0
  93. package/dist/props/components/index.d.ts +1 -1
  94. package/dist/props/components/navigation.prop.d.ts +21 -2
  95. package/dist/props/components/query.prop.d.ts +36 -2
  96. package/dist/props/registry.d.ts +46 -1
  97. package/dist/props/registry.js +38 -3
  98. package/dist/styles/alert-layout.css +34 -14
  99. package/dist/styles/badge-layout.css +10 -6
  100. package/dist/styles/base.css +14 -5
  101. package/dist/styles/card-layout.css +19 -8
  102. package/dist/styles/chart-layout.css +22 -3
  103. package/dist/styles/control.css +165 -59
  104. package/dist/styles/data-display-layout.css +129 -36
  105. package/dist/styles/data-entry-layout.css +23 -87
  106. package/dist/styles/dialog-layout.css +49 -19
  107. package/dist/styles/float-button-layout.css +5 -5
  108. package/dist/styles/focus-ring.css +9 -5
  109. package/dist/styles/layout.css +42 -15
  110. package/dist/styles/logo-layout.css +1 -1
  111. package/dist/styles/motion.css +1 -1
  112. package/dist/styles/navigation-layout.css +90 -29
  113. package/dist/styles/shell-layout.css +63 -40
  114. package/dist/styles/table-layout.css +56 -17
  115. package/dist/styles/text-layout.css +13 -4
  116. package/dist/styles/toggle.css +8 -2
  117. package/dist/tokens/components/actions.css +1 -1
  118. package/dist/tokens/components/attachments.css +4 -4
  119. package/dist/tokens/components/badge.css +4 -4
  120. package/dist/tokens/components/callout.css +1 -1
  121. package/dist/tokens/components/card.css +9 -4
  122. package/dist/tokens/components/chart.css +10 -1
  123. package/dist/tokens/components/chat-bubble.css +1 -1
  124. package/dist/tokens/components/control.css +28 -10
  125. package/dist/tokens/components/conversations.css +2 -1
  126. package/dist/tokens/components/data-display.css +12 -7
  127. package/dist/tokens/components/descriptions.css +1 -1
  128. package/dist/tokens/components/draggable-panel.css +1 -1
  129. package/dist/tokens/components/feedback.css +28 -8
  130. package/dist/tokens/components/float-button.css +1 -1
  131. package/dist/tokens/components/legal-document.css +1 -1
  132. package/dist/tokens/components/logo.css +1 -1
  133. package/dist/tokens/components/mega-menu.css +5 -3
  134. package/dist/tokens/components/navigation.css +21 -7
  135. package/dist/tokens/components/segmented.css +8 -3
  136. package/dist/tokens/components/shell.css +19 -5
  137. package/dist/tokens/components/table.css +9 -1
  138. package/dist/tokens/components/thought-chain.css +1 -1
  139. package/dist/tokens/components/toggle.css +2 -0
  140. package/dist/tokens/components/tree.css +3 -1
  141. package/dist/tokens/components/upload.css +6 -6
  142. package/dist/tokens/components/welcome.css +1 -1
  143. package/dist/tokens/foundation.css +28 -1
  144. package/docs/COMPOSITION-VS-COMPONENT.md +31 -0
  145. package/docs/CUSTOMER-THEMING.md +637 -1
  146. package/docs/DESIGN-AUTHORITY.md +13 -0
  147. package/docs/FRAME-COVERAGE-REPORT.md +3 -2
  148. package/docs/GLASSMORPHISM-STANDARD.md +196 -0
  149. package/docs/THEME-API-COVERAGE.md +538 -0
  150. package/docs/TOKEN-RESOLUTION.md +195 -0
  151. package/docs/TOKENS.md +63 -24
  152. package/docs/asset-modules.d.ts +7 -0
  153. package/docs/data-display/charts.tsx +80 -0
  154. package/docs/data-display/data-table/index.tsx +30 -0
  155. package/docs/data-display/popover.tsx +1 -1
  156. package/docs/data-display/table.tsx +52 -0
  157. package/docs/feedback/sheet.tsx +10 -10
  158. package/docs/foundation/density.tsx +4 -4
  159. package/docs/i18n/messages/en.json +1201 -0
  160. package/docs/i18n/messages/ja.json +1195 -0
  161. package/docs/i18n/messages/vi.json +1195 -0
  162. package/docs/layout/account-chip.tsx +2 -2
  163. package/docs/layout/responsive-grid.tsx +1 -1
  164. package/docs/navigation/toolbar.tsx +20 -12
  165. package/docs/providers/theme-scope.tsx +186 -0
  166. package/docs/showcase/caimono-price-comparison.tsx +911 -0
  167. package/docs/showcase/case4-login.tsx +2 -2
  168. package/docs/showcase/marketing-page.tsx +3 -2
  169. package/docs/showcase/permission-matrix.tsx +13 -5
  170. package/docs/showcase/table-pagination.tsx +2 -1
  171. package/docs/showcase/tenant-brand-color.tsx +338 -0
  172. package/docs/showcase/theme-customization.tsx +2 -1
  173. package/docs/showcase/theme-lab.tsx +2125 -0
  174. package/docs/themes/flat.css +462 -0
  175. package/docs/themes/glassmorphism.css +958 -0
  176. package/docs/themes/index.ts +200 -0
  177. package/package.json +4 -3
  178. package/scripts/explain-token.mjs +382 -0
package/agent/index.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "counts": {
3
3
  "anti-ai-tells": 26,
4
- "components": 170,
5
- "patterns": 19,
6
- "rules": 47,
7
- "tokens": 1685,
4
+ "components": 171,
5
+ "patterns": 20,
6
+ "rules": 50,
7
+ "tokens": 2070,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "files": [
11
11
  {
12
12
  "file": "components-index.json",
13
- "note": "45 KB — name + group + tagline for all 170. FETCH THIS FIRST, then fetch only the components you chose.",
13
+ "note": "45 KB — name + group + tagline for all 171. FETCH THIS FIRST, then fetch only the components you chose.",
14
14
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json"
15
15
  },
16
16
  {
17
17
  "file": "components/<Name>.json",
18
- "note": "One file per component (1 KB–33 KB, median 6 KB), each carrying its importPath. This is the selective route: read the index, then fetch only what you need instead of the 1.1 MB blob.",
18
+ "note": "One file per component (1 KB–34 KB, median 6 KB), each carrying its importPath. This is the selective route: read the index, then fetch only what you need instead of the 1.2 MB blob.",
19
19
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/<Name>.json"
20
20
  },
21
21
  {
@@ -48,9 +48,19 @@
48
48
  "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.",
49
49
  "read": {
50
50
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v28.12.0/agent/index.json"
51
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v29.0.0/agent/index.json"
52
52
  },
53
- "source": "mcp/src/data — the same data @godxjp/ui-mcp serves",
53
+ "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
54
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
55
- "version": "28.12.0"
55
+ "tokenTiers": {
56
+ "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
57
+ "counts": {
58
+ "component": 1756,
59
+ "foundation": 211,
60
+ "semantic": 103
61
+ },
62
+ "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
+ "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
+ },
65
+ "version": "29.0.0"
56
66
  }
package/agent/llms.txt CHANGED
@@ -1,10 +1,10 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 170 components, 1685 design tokens,
4
- > 47 cardinal rules. This file is the entry point for AI agents. Catalog version 28.12.0.
3
+ > A Japanese-enterprise React design system: 171 components, 2070 design tokens,
4
+ > 50 cardinal rules. This file is the entry point for AI agents. Catalog version 29.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@28.12.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@29.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
@@ -14,19 +14,19 @@ that can only fetch URLs.
14
14
 
15
15
  ## Catalog
16
16
 
17
- - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 19 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.
18
- - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 45 KB — all 170 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
- - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–33 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.
20
- - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.1 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
- - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value and the reason it exists.
17
+ - [patterns-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/patterns-index.json): 20 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.
18
+ - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 45 KB — all 171 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
+ - [components/&lt;Name&gt;.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components/Select.json): one file per component (1 KB–34 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.
20
+ - [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.
21
+ - [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), 103 `semantic` roles, 1756 `component` knobs.
22
22
  - [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.
23
- - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 47 cardinal rules.
23
+ - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 50 cardinal rules.
24
24
  - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
25
25
 
26
26
  ## Pinning
27
27
 
28
28
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v28.12.0/agent/...`. A catalog that does not match the installed
29
+ the tag: `.../godx-jp/godxjp-ui/v29.0.0/agent/...`. A catalog that does not match the installed
30
30
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
31
 
32
32
  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.
@@ -0,0 +1,28 @@
1
+ {
2
+ "aliases": [
3
+ "tenanttheme",
4
+ "customer-brand-color",
5
+ "runtime-theme",
6
+ "brand-color"
7
+ ],
8
+ "code": "// ─────────────────────────────────────────────────────────────────────────\n// A CUSTOMER's brand colour, at runtime, on ONE region. gh#861 / gh#868.\n//\n// The colour arrives as DATA (a hex an admin picked in your settings screen),\n// not as a stylesheet you author. AppSettingPicker `brand` is an ENUM PRESET\n// (brand|crm|logistics|partner|slate|dxs) and cannot take an arbitrary hex.\n//\n// ❌ DON'T hand-roll the conversion or the label. Two consumer repos each wrote\n// their own 18-line hexToHslChannels and their own contrast rule; a wrong\n// label makes nothing red — the text is just hard to read, for end users only.\n// ❌ DON'T set --primary alone. It does not travel alone: --primary-foreground\n// does NOT follow it, and --ring is bound at :root where it FREEZES.\n// ✅ DO ask the package for the declarations.\n\nimport { tenantTheme, contrastRatio, hexToHsl } from \"@godxjp/ui/app\";\n\nconst brand = tenantTheme(tenant.primary_color); // \"#0071bd\" · 3- or 6-digit\n\n<div data-tenant={tenant.slug} style={brand.vars}> // a REGION, not <html>\n <Topbar … />\n <Button>Open app</Button> {/* rest, hover AND pressed follow */}\n</div>\n\n// brand.vars is exactly eight declarations:\n// --primary --primary-foreground --ring --primary-hover --primary-active\n// --text-link --text-brand --text-primary ← the brand as INK (gh#887)\n// An unusable hex yields EMPTY vars, so the region falls back to your own theme\n// instead of breaking the page (same contract ColorPicker already has).\n\n// THE LABEL IS CHOSEN BY CONTRAST, and the ratio is reported so you can check it.\n// WCAG 2.2 SC 1.4.3, normal text, 4.5:1. White/black pivot at relative luminance\n// 0.179; both candidates measure 4.58:1 there, so the winner ALWAYS clears AA.\n// #FFD400 → #000000 at 14.67:1 (\"always white\" would have been 1.29:1)\n// #0A1F44 → #ffffff at 16.25:1\nbrand.foreground // \"#ffffff\"\nbrand.contrast // 5.13\nbrand.meetsAA // true\n\n// ALREADY COMPUTED THE PAIR SERVER-SIDE? Pass it. It is used AS GIVEN — never\n// silently replaced — and the result still reports what it achieves.\nconst supplied = tenantTheme(hex, { foreground: tenant.theme_tokens.foreground });\nif (!supplied.meetsAA) {\n // supplied.contrast is the number. The API says NO instead of returning white.\n}\n\n// NESTED TENANTS on one page: call it per region. Each region carries its own\n// pair, so nothing freezes from the region above it.\n\n// THE BRAND AS INK IS ALSO FLOORED (gh#887) — it used not to be, and that is the\n// half of the contract a consumer used to have to write. --primary is guaranteed\n// against --primary-foreground: a FILL and its label. Nothing guaranteed the same\n// colour as TEXT on a page surface, and a sidebar active item, a NavList item, a\n// MegaMenu trigger, an Anchor link, `Text link` and `Button variant=\"link\"` all do\n// exactly that. Measured on /showcase/theme-lab: #FFD400 gave 1.18–2.06:1.\n// So the three inks come back as LITERALS, each walked away from the surface until\n// the painted pixel clears 4.5:1 and no further — a seed whose ramp step already\n// clears is untouched. No call site of yours changes; the roles were always read.\n// #FFD400 link ink 1.73:1 → 4.56:1 #E2564A 3.80:1 → 4.53:1\n// #7C3AED / #2563EB / #0A1F44 byte-identical, they already cleared\n\n// WHICH SURFACE? The DARKEST one the ink lands on — the default is --accent\n// (#ebe9e5), not --background, because an ink clamped on the canvas measures\n// 3.78:1 the moment the row under it is hovered. Pass your own when the region is\n// darker than the package light theme, and ALWAYS for a region inside a dark theme\n// (--accent there is #3c3a34) or the ink is walked the wrong way:\nconst darkRegion = tenantTheme(tenant.primary_color, { surface: \"#3c3a34\" });\n\n// Still your call whether the raw seed is usable as ink somewhere the library does\n// not paint — contrastRatio is the same arithmetic, exported:\nif ((contrastRatio(tenant.primary_color, \"#ffffff\") ?? 0) >= 4.5) {\n // …the seed ITSELF, unclamped, on a white page\n}\n\n// The raw conversion, if you need it on its own (the inverse of hslToHex):\nhexToHsl(\"#0071bd\"); // \"204.13 100% 37.06%\"\n\n// Worked screen: /showcase/tenant-brand-color · docs/CUSTOMER-THEMING.md\n// Why there is no <TenantTheme> component: docs/COMPOSITION-VS-COMPONENT.md §3.2\n",
9
+ "name": "tenant-brand-color",
10
+ "tagline": "Wear a CUSTOMER's own hex at runtime, scoped to one region — tenantTheme(hex) returns the token declarations plus the WCAG 2.2 AA label, so you never hand-roll hex→HSL or guess a foreground. There is no <TenantTheme> component and there should not be one.",
11
+ "tags": [
12
+ "theme",
13
+ "tenant",
14
+ "multi-tenant",
15
+ "brand",
16
+ "color",
17
+ "colour",
18
+ "contrast",
19
+ "wcag",
20
+ "a11y",
21
+ "runtime",
22
+ "scoped",
23
+ "hex",
24
+ "hsl",
25
+ "white-label",
26
+ "launcher"
27
+ ]
28
+ }
@@ -319,5 +319,32 @@
319
319
  "account",
320
320
  "chip"
321
321
  ]
322
+ },
323
+ {
324
+ "aliases": [
325
+ "tenanttheme",
326
+ "customer-brand-color",
327
+ "runtime-theme",
328
+ "brand-color"
329
+ ],
330
+ "name": "tenant-brand-color",
331
+ "tagline": "Wear a CUSTOMER's own hex at runtime, scoped to one region — tenantTheme(hex) returns the token declarations plus the WCAG 2.2 AA label, so you never hand-roll hex→HSL or guess a foreground. There is no <TenantTheme> component and there should not be one.",
332
+ "tags": [
333
+ "theme",
334
+ "tenant",
335
+ "multi-tenant",
336
+ "brand",
337
+ "color",
338
+ "colour",
339
+ "contrast",
340
+ "wcag",
341
+ "a11y",
342
+ "runtime",
343
+ "scoped",
344
+ "hex",
345
+ "hsl",
346
+ "white-label",
347
+ "launcher"
348
+ ]
322
349
  }
323
350
  ]
@@ -338,5 +338,33 @@
338
338
  "account",
339
339
  "chip"
340
340
  ]
341
+ },
342
+ {
343
+ "aliases": [
344
+ "tenanttheme",
345
+ "customer-brand-color",
346
+ "runtime-theme",
347
+ "brand-color"
348
+ ],
349
+ "code": "// ─────────────────────────────────────────────────────────────────────────\n// A CUSTOMER's brand colour, at runtime, on ONE region. gh#861 / gh#868.\n//\n// The colour arrives as DATA (a hex an admin picked in your settings screen),\n// not as a stylesheet you author. AppSettingPicker `brand` is an ENUM PRESET\n// (brand|crm|logistics|partner|slate|dxs) and cannot take an arbitrary hex.\n//\n// ❌ DON'T hand-roll the conversion or the label. Two consumer repos each wrote\n// their own 18-line hexToHslChannels and their own contrast rule; a wrong\n// label makes nothing red — the text is just hard to read, for end users only.\n// ❌ DON'T set --primary alone. It does not travel alone: --primary-foreground\n// does NOT follow it, and --ring is bound at :root where it FREEZES.\n// ✅ DO ask the package for the declarations.\n\nimport { tenantTheme, contrastRatio, hexToHsl } from \"@godxjp/ui/app\";\n\nconst brand = tenantTheme(tenant.primary_color); // \"#0071bd\" · 3- or 6-digit\n\n<div data-tenant={tenant.slug} style={brand.vars}> // a REGION, not <html>\n <Topbar … />\n <Button>Open app</Button> {/* rest, hover AND pressed follow */}\n</div>\n\n// brand.vars is exactly eight declarations:\n// --primary --primary-foreground --ring --primary-hover --primary-active\n// --text-link --text-brand --text-primary ← the brand as INK (gh#887)\n// An unusable hex yields EMPTY vars, so the region falls back to your own theme\n// instead of breaking the page (same contract ColorPicker already has).\n\n// THE LABEL IS CHOSEN BY CONTRAST, and the ratio is reported so you can check it.\n// WCAG 2.2 SC 1.4.3, normal text, 4.5:1. White/black pivot at relative luminance\n// 0.179; both candidates measure 4.58:1 there, so the winner ALWAYS clears AA.\n// #FFD400 → #000000 at 14.67:1 (\"always white\" would have been 1.29:1)\n// #0A1F44 → #ffffff at 16.25:1\nbrand.foreground // \"#ffffff\"\nbrand.contrast // 5.13\nbrand.meetsAA // true\n\n// ALREADY COMPUTED THE PAIR SERVER-SIDE? Pass it. It is used AS GIVEN — never\n// silently replaced — and the result still reports what it achieves.\nconst supplied = tenantTheme(hex, { foreground: tenant.theme_tokens.foreground });\nif (!supplied.meetsAA) {\n // supplied.contrast is the number. The API says NO instead of returning white.\n}\n\n// NESTED TENANTS on one page: call it per region. Each region carries its own\n// pair, so nothing freezes from the region above it.\n\n// THE BRAND AS INK IS ALSO FLOORED (gh#887) — it used not to be, and that is the\n// half of the contract a consumer used to have to write. --primary is guaranteed\n// against --primary-foreground: a FILL and its label. Nothing guaranteed the same\n// colour as TEXT on a page surface, and a sidebar active item, a NavList item, a\n// MegaMenu trigger, an Anchor link, `Text link` and `Button variant=\"link\"` all do\n// exactly that. Measured on /showcase/theme-lab: #FFD400 gave 1.18–2.06:1.\n// So the three inks come back as LITERALS, each walked away from the surface until\n// the painted pixel clears 4.5:1 and no further — a seed whose ramp step already\n// clears is untouched. No call site of yours changes; the roles were always read.\n// #FFD400 link ink 1.73:1 → 4.56:1 #E2564A 3.80:1 → 4.53:1\n// #7C3AED / #2563EB / #0A1F44 byte-identical, they already cleared\n\n// WHICH SURFACE? The DARKEST one the ink lands on — the default is --accent\n// (#ebe9e5), not --background, because an ink clamped on the canvas measures\n// 3.78:1 the moment the row under it is hovered. Pass your own when the region is\n// darker than the package light theme, and ALWAYS for a region inside a dark theme\n// (--accent there is #3c3a34) or the ink is walked the wrong way:\nconst darkRegion = tenantTheme(tenant.primary_color, { surface: \"#3c3a34\" });\n\n// Still your call whether the raw seed is usable as ink somewhere the library does\n// not paint — contrastRatio is the same arithmetic, exported:\nif ((contrastRatio(tenant.primary_color, \"#ffffff\") ?? 0) >= 4.5) {\n // …the seed ITSELF, unclamped, on a white page\n}\n\n// The raw conversion, if you need it on its own (the inverse of hslToHex):\nhexToHsl(\"#0071bd\"); // \"204.13 100% 37.06%\"\n\n// Worked screen: /showcase/tenant-brand-color · docs/CUSTOMER-THEMING.md\n// Why there is no <TenantTheme> component: docs/COMPOSITION-VS-COMPONENT.md §3.2\n",
350
+ "name": "tenant-brand-color",
351
+ "tagline": "Wear a CUSTOMER's own hex at runtime, scoped to one region — tenantTheme(hex) returns the token declarations plus the WCAG 2.2 AA label, so you never hand-roll hex→HSL or guess a foreground. There is no <TenantTheme> component and there should not be one.",
352
+ "tags": [
353
+ "theme",
354
+ "tenant",
355
+ "multi-tenant",
356
+ "brand",
357
+ "color",
358
+ "colour",
359
+ "contrast",
360
+ "wcag",
361
+ "a11y",
362
+ "runtime",
363
+ "scoped",
364
+ "hex",
365
+ "hsl",
366
+ "white-label",
367
+ "launcher"
368
+ ]
341
369
  }
342
370
  ]
package/agent/rules.json CHANGED
@@ -233,5 +233,20 @@
233
233
  "body": "Every rule this package ships is inside a cascade layer, and layer order beats specificity outright. Two consequences. (1) INSIDE the package: `@layer components` is EARLIER than Tailwind's `utilities`, so a utility a component emits on its own element (`<table class=\"text-sm\">`) silently outranks the component rule meant to own that property — no selector can win. A responsive re-point that must beat such a utility goes in `@layer godxjp-ui-responsive`, declared after Tailwind in `styles/base.css` and therefore LAST; it is reserved for `@container`/`@media` re-points, never static rules.",
234
234
  "number": 47,
235
235
  "title": "The layer contract — cascade layers, not specificity"
236
+ },
237
+ {
238
+ "body": "A colour token holds EITHER bare HSL components (`--card: 60 33% 99%`, read as `hsl(var(--card))`) OR a complete CSS colour (`--card-tint: hsl(var(--primary) / 4%)`, read as `var(--card-tint)`). The NAME does not tell you which, and neither does the tier: `--menu-item-hover-background` / `--tree-node-hover-background` / `--table-row-hover-background` take a complete colour while `--segmented-item-hover-background` / `--topbar-item-hover-background` take components — five knobs for the same hover fill, all defaulting to `--accent`. Measured: 346 published tokens hold a colour, 96 components / 249 complete, and 3 say which. SET THE WRONG FORM AND NOTHING SAYS SO: the declaration is invalid at computed-value time, the property takes its own INITIAL value (`transparent` for a background) and the call-site fallback is NOT used — so the surface shows what is behind it and reads exactly like a knob that does not work (a table header measured 1.03:1 this way). The tell in DevTools: the custom property holds what you wrote and the painted property is `rgba(0, 0, 0, 0)`. To check a knob: `grep -rho 'hsl(var(--NAME\\|var(--NAME' node_modules/@godxjp/ui/dist` — `hsl(var(--NAME` means components, bare `var(--NAME` means complete. Grep `dist` whole: some knobs are read from a component's JS, not its CSS. SUB-TRAP: a components role may carry an alpha (`--card: 0 0% 100% / 42%` is how a glass theme works) but 83 declarations across 15 roles apply their OWN alpha (`hsl(var(--accent) / 0.7)`), and a second `/` is a parse error → transparent. `--primary`, `--muted`, `--destructive`, `--warning`, `--success`, `--info`, `--accent`, `--foreground`, `--muted-foreground`, `--table-row-tone-color`, `--background`, `--destructive-foreground`, `--secondary`, `--accent-foreground`, `--ring` all have at least one such reader; `--card` and `--popover` have none. docs/CUSTOMER-THEMING.md \"Building a COMPLEX theme\" §1.",
239
+ "number": 48,
240
+ "title": "Two token value forms — read the call site before you set a colour"
241
+ },
242
+ {
243
+ "body": "Hover, active, selected, checked, pressed and focus are their own tokens: 95 published (`node -e \"const t=require('./node_modules/@godxjp/ui/agent/tokens.json'); console.log(t.filter(x => /-(hover|active|selected|checked|pressed|focus)(-|$)/.test(x.name)).length)\"`), 62 of which paint a colour, an edge or an elevation. A theme that re-materialises a surface and sets only its RESTING fill ships every other state broken, and each defect is reported from a screenshot rather than by a gate: a sidebar row at dark-violet-on-violet (`--sidebar-item-active-foreground` defaults to the live `--primary-active`, src/styles/shell-layout.css:2289-2291), a Segmented hover with a dark fill under dark text (`--segmented-item-hover-background` set without `--segmented-item-hover-color`), a Topbar hover block. `--focus-ring-color` is on that list and a ring that fails 3:1 on its new ground is a WCAG 2.2 SC 1.4.11 defect, not a polish item. THE METHOD: for every component you re-theme, list its tokens (`get_component`, or `grep -- \"--<component>-\" node_modules/@godxjp/ui/dist/tokens/components/`) and set the state rows at the same time as the resting row. A state knob left at its default resolves against the LIVE role at the painting element — the correct default, and exactly why it can be wrong for you once `--accent` is no longer pale.",
244
+ "number": 49,
245
+ "title": "Interaction states are 95 separate tokens — resting state is not one of them"
246
+ },
247
+ {
248
+ "body": "`src/styles/base.css:261` sets `color: hsl(var(--foreground))` on `body`, which is above every scope a consumer can make, so that `var()` substitutes against the ROOT's `--foreground` exactly once and every element below inherits the already-resolved colour. A plain `<div>` carrying a theme's tokens therefore retints every SURFACE and no TEXT: measured, a title whose own computed `--foreground` was the theme's near-white still painted `rgb(36, 35, 30)`, 1.34:1 on real pixels, with every element reporting it was inside the scope (gh#881). It is the §3 freeze rule applied to a PROPERTY instead of a token, and worse there — a token can be given a knob and `color` on `body` cannot. FIX, React: wrap the region in `ThemeScope` (`@godxjp/ui/app`), which re-states `color` on its own element (src/lib/overlay-portal.tsx:270, `display: contents` so the box goes and the inheritance stays) AND on the body-level host it creates for portalled overlays (:203) — the other half, since a Dialog portals to `document.body` and would otherwise wear the root's theme. FIX, stylesheet-only scope: declare `color: hsl(var(--foreground))` on your own selector alongside the tokens; that is your element, not a selector into this package, so cardinal rule \"a consumer sets tokens, never selectors\" permits it — but it does NOT reach portalled overlays. docs/TOKEN-RESOLUTION.md §5 rule 7.",
249
+ "number": 50,
250
+ "title": "A scoped theme must state its own `color`"
236
251
  }
237
252
  ]