@godxjp/ui 33.22.2 → 33.25.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 (130) hide show
  1. package/DESIGN.md +1 -1
  2. package/README.md +16 -5
  3. package/agent/DESIGN.md +1 -1
  4. package/agent/START-HERE.md +8 -8
  5. package/agent/components/AgentBadge.json +43 -0
  6. package/agent/components/AppShell.json +6 -0
  7. package/agent/components/Avatar.json +7 -0
  8. package/agent/components/CatalogDetail.json +7 -3
  9. package/agent/components/DatePicker.json +1 -1
  10. package/agent/components/DropdownMenu.json +1 -0
  11. package/agent/components/EditorialCard.json +2 -1
  12. package/agent/components/Image.json +6 -1
  13. package/agent/components/PageContainer.json +5 -0
  14. package/agent/components/PageErrorBoundary.json +48 -0
  15. package/agent/components/Pagination.json +5 -0
  16. package/agent/components/Popover.json +1 -0
  17. package/agent/components/Select.json +2 -1
  18. package/agent/components/Toaster.json +2 -2
  19. package/agent/components/Toolbar.json +1 -1
  20. package/agent/components/Topbar.json +1 -1
  21. package/agent/components-index.json +10 -0
  22. package/agent/components.json +138 -11
  23. package/agent/index.json +8 -8
  24. package/agent/llms.txt +7 -7
  25. package/agent/patterns/notice-bar.json +16 -0
  26. package/agent/patterns-index.json +15 -0
  27. package/agent/patterns.json +16 -0
  28. package/agent/rules.json +2 -2
  29. package/agent/tokens.json +121 -7
  30. package/dist/components/charts/chart-cartesian.js +5 -1
  31. package/dist/components/charts/chart-summary.d.ts +1 -1
  32. package/dist/components/charts/chart-summary.js +17 -6
  33. package/dist/components/charts/compact-bar-trend.js +4 -3
  34. package/dist/components/charts/pie-chart.js +2 -1
  35. package/dist/components/data-display/avatar.d.ts +1 -1
  36. package/dist/components/data-display/avatar.js +7 -1
  37. package/dist/components/data-display/badge.d.ts +11 -0
  38. package/dist/components/data-display/badge.js +20 -0
  39. package/dist/components/data-display/image.d.ts +1 -0
  40. package/dist/components/data-display/image.js +10 -2
  41. package/dist/components/data-display/index.d.ts +2 -2
  42. package/dist/components/data-display/index.js +2 -1
  43. package/dist/components/data-display/popover.js +11 -0
  44. package/dist/components/data-entry/calendar.js +1 -1
  45. package/dist/components/data-entry/date-picker.js +22 -5
  46. package/dist/components/data-entry/record-picker.js +122 -98
  47. package/dist/components/data-entry/select.js +13 -3
  48. package/dist/components/general/button.js +2 -2
  49. package/dist/components/layout/affix.js +1 -1
  50. package/dist/components/layout/app-shell.d.ts +1 -1
  51. package/dist/components/layout/app-shell.js +3 -1
  52. package/dist/components/layout/editorial-catalog.d.ts +1 -1
  53. package/dist/components/layout/editorial-catalog.js +53 -15
  54. package/dist/components/layout/index.d.ts +2 -0
  55. package/dist/components/layout/index.js +2 -0
  56. package/dist/components/layout/page-container.d.ts +1 -1
  57. package/dist/components/layout/page-container.js +6 -3
  58. package/dist/components/layout/page-error-boundary.d.ts +32 -0
  59. package/dist/components/layout/page-error-boundary.js +57 -0
  60. package/dist/components/layout/sidebar.d.ts +1 -1
  61. package/dist/components/layout/sidebar.js +100 -49
  62. package/dist/components/layout/topbar.js +108 -21
  63. package/dist/components/navigation/dropdown-menu.d.ts +13 -3
  64. package/dist/components/navigation/dropdown-menu.js +36 -6
  65. package/dist/components/navigation/pagination.d.ts +1 -1
  66. package/dist/components/navigation/pagination.js +13 -1
  67. package/dist/components/navigation/toolbar.js +2 -1
  68. package/dist/contracts/measurement.json +1 -1
  69. package/dist/i18n/messages/en.json +7 -0
  70. package/dist/i18n/messages/ja.json +7 -0
  71. package/dist/i18n/messages/vi.json +7 -0
  72. package/dist/props/components/data-display.prop.d.ts +29 -0
  73. package/dist/props/components/layout.prop.d.ts +52 -2
  74. package/dist/props/components/navigation.prop.d.ts +15 -1
  75. package/dist/props/registry.d.ts +65 -5
  76. package/dist/props/registry.js +90 -4
  77. package/dist/styles/alert-layout.css +4 -0
  78. package/dist/styles/application.css +24 -0
  79. package/dist/styles/badge-layout.css +14 -0
  80. package/dist/styles/base.css +11 -0
  81. package/dist/styles/control.css +32 -19
  82. package/dist/styles/core.css +1 -0
  83. package/dist/styles/data-display-layout.css +51 -0
  84. package/dist/styles/dialog-layout.css +9 -0
  85. package/dist/styles/editorial-layout.css +2076 -0
  86. package/dist/styles/editorial.css +3 -0
  87. package/dist/styles/icon-layout.css +3 -0
  88. package/dist/styles/index.css +1 -0
  89. package/dist/styles/layers.json +60 -9
  90. package/dist/styles/layout.css +9 -1985
  91. package/dist/styles/precompiled/alert-layout.css +2 -0
  92. package/dist/styles/precompiled/badge-layout.css +2 -0
  93. package/dist/styles/precompiled/base.css +2 -0
  94. package/dist/styles/precompiled/card-layout.css +2 -0
  95. package/dist/styles/precompiled/chart-layout.css +2 -0
  96. package/dist/styles/precompiled/control.css +2 -0
  97. package/dist/styles/precompiled/data-display-layout.css +2 -0
  98. package/dist/styles/precompiled/data-entry-layout.css +2 -0
  99. package/dist/styles/precompiled/dialog-layout.css +2 -0
  100. package/dist/styles/precompiled/editorial-account-layout.css +2 -0
  101. package/dist/styles/precompiled/editorial-layout.css +2 -0
  102. package/dist/styles/precompiled/float-button-layout.css +2 -0
  103. package/dist/styles/precompiled/form-layout.css +2 -0
  104. package/dist/styles/precompiled/icon-layout.css +2 -0
  105. package/dist/styles/precompiled/layout.css +2 -0
  106. package/dist/styles/precompiled/logo-layout.css +2 -0
  107. package/dist/styles/precompiled/motion.css +2 -0
  108. package/dist/styles/precompiled/navigation-layout.css +2 -0
  109. package/dist/styles/precompiled/shell-layout.css +2 -0
  110. package/dist/styles/precompiled/table-layout.css +2 -0
  111. package/dist/styles/precompiled/text-layout.css +2 -0
  112. package/dist/styles/precompiled/toggle.css +2 -0
  113. package/dist/styles/precompiled/vendor-day-picker.css +2 -0
  114. package/dist/styles/precompiled/vendor-sonner.css +2 -0
  115. package/dist/styles/shell-layout.css +17 -1
  116. package/dist/styles/table-layout.css +1 -1
  117. package/dist/styles.css +1 -1
  118. package/dist/tokens/components/badge.css +3 -0
  119. package/dist/tokens/components/control.css +3 -0
  120. package/dist/tokens/components/data-display.css +9 -0
  121. package/dist/tokens/components/editorial.css +9 -6
  122. package/dist/tokens/components/shell.css +2 -0
  123. package/dist/tokens/foundation.css +8 -0
  124. package/docs/CONSUMER-RULES.md +36 -1
  125. package/package.json +4 -2
  126. package/scripts/consumer-rule.md +1 -1
  127. package/scripts/prune-css.mjs +56 -8
  128. package/scripts/ui-audit.mjs +36 -0
  129. package/skills/godxjp-ui-navigation/SKILL.md +14 -9
  130. package/skills/godxjp-ui-navigation/references/principles.md +2 -2
package/agent/tokens.json CHANGED
@@ -185,6 +185,24 @@
185
185
  "tier": "foundation",
186
186
  "value": "48 8% 13%"
187
187
  },
188
+ {
189
+ "description": "AGENT role — marks an AI agent (Avatar kind=\"agent\", AgentBadge) so it is never mistaken for a person. Violet, chosen apart from every status hue so \"AI\" never reads as a state. FILL #7c3aed under white ink measures 5.70:1; TEXT --text-agent #6d28d9 on white measures 7.10:1 (AA). #7c3aed",
190
+ "name": "--agent",
191
+ "tier": "foundation",
192
+ "value": "262 83% 58%"
193
+ },
194
+ {
195
+ "description": "Foundation tokens: color, typography, raw spacing, ratio, radius, shadow.",
196
+ "name": "--agent-foreground",
197
+ "tier": "foundation",
198
+ "value": "0 0% 100%"
199
+ },
200
+ {
201
+ "description": "#6d28d9 — AA violet text on white",
202
+ "name": "--text-agent",
203
+ "tier": "foundation",
204
+ "value": "263 70% 50%"
205
+ },
188
206
  {
189
207
  "description": "IDENTITY role — the PRODUCT MARK colour (Logo `tone=\"success\"`, its wordmark, the email brand capsule). Neutral ink by default since v32 (gh#1220); the GoDX preset (`themes/godx.css`) sets it back to the violet of identity v2.3. It is deliberately distinct from two neighbours it keeps being confused with: · from --primary — a service re-theming its action colour must never recolour the mark. They hold the SAME VALUE by default and are still separate declarations; `brand-identity-role.test.ts` asserts on the DECLARATION, not the value, that this one never reads `var(--primary)` (gh#250). · from --success — 若竹 #68be8d is the STATUS green (badges, progress, \"saved\" text) and stays exactly as it is. The mark borrowed it once, rendering the identity at #69bf8e — ΔE76 ≈ 17.5 from the identity of the day, a plainly wrong brand colour (gh#250/#214). If the identity moves again, THIS COMMENT MOVES WITH IT. There is no Tailwind `bg-brand` utility on purpose: `--color-brand` is already taken by --text-brand, so identity surfaces read `hsl(var(--brand))` directly in component CSS. neutral ink. Same value as --primary by default, NOT an alias of it: re-theming the action colour must still leave the identity alone, which is what brand-identity-role.test.ts checks on the DECLARATION rather than the value.",
190
208
  "name": "--brand",
@@ -2309,6 +2327,18 @@
2309
2327
  "tier": "component",
2310
2328
  "value": "initial"
2311
2329
  },
2330
+ {
2331
+ "description": "AgentBadge — the violet outline and ink of the \"AI agent\" label. Role-mirrors, `initial`: hsl(var(--agent)) border and hsl(var(--text-agent)) ink at the call site.",
2332
+ "name": "--badge-agent-border",
2333
+ "tier": "component",
2334
+ "value": "initial"
2335
+ },
2336
+ {
2337
+ "description": "Badge component tokens.",
2338
+ "name": "--badge-agent-foreground",
2339
+ "tier": "component",
2340
+ "value": "initial"
2341
+ },
2312
2342
  {
2313
2343
  "description": "Leading/trailing glyph inside the pill. It was a bare `0.75rem` in badge-layout.css with no token at all, so a service could only resize it with `!important` or a forked stylesheet — the two routes the icon axis's tier 2 exists to abolish (gh#326). Same step, so nothing moves. Matches --badge-font-size's step by design: the glyph reads as a character in the label's run, so the two retune together. Deliberately NOT --scaling-multiplied — the literal it replaces did not track density.",
2314
2344
  "name": "--badge-icon-size",
@@ -4439,6 +4469,18 @@
4439
4469
  "tier": "component",
4440
4470
  "value": "24rem"
4441
4471
  },
4472
+ {
4473
+ "description": "The listbox hugs its longest row up to this, then wraps rows; the same cap as the search panel so both Select modes size alike. The inset keeps it off the viewport edges.",
4474
+ "name": "--select-content-max-width",
4475
+ "tier": "component",
4476
+ "value": "32rem"
4477
+ },
4478
+ {
4479
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes.",
4480
+ "name": "--select-content-viewport-inset",
4481
+ "tier": "component",
4482
+ "value": "var(--space-6)"
4483
+ },
4442
4484
  {
4443
4485
  "description": "Seat for a NUMERIC `popupMatchSelectWidth` on the plain listbox. `auto` is the inert default.",
4444
4486
  "name": "--select-content-inline-size",
@@ -4692,7 +4734,7 @@
4692
4734
  "value": "var(--font-size-sm, var(--font-size-base))"
4693
4735
  },
4694
4736
  {
4695
- "description": "gh#888 — φ tier freezes at :root; default = calc(var(--radius) / var(--radius-ratio))",
4737
+ "description": "Scoped at the reader: interactive days fall back to --button-radius; read-only cells to calc(var(--radius) / var(--radius-ratio)).",
4696
4738
  "name": "--calendar-day-radius",
4697
4739
  "tier": "component",
4698
4740
  "value": "initial"
@@ -5939,6 +5981,54 @@
5939
5981
  "tier": "component",
5940
5982
  "value": "initial"
5941
5983
  },
5984
+ {
5985
+ "description": "Avatar `kind=\"agent\"` — the \"AI\" tag pinned to the block-start/inline-end corner (presence owns block-end). The tag's text is aria-hidden; a localized sr-only \"AI agent\" carries the meaning, so the colour is never the only signal (WCAG 1.4.1). --avatar-agent-background / -foreground / -ring-color are ROLE-MIRROR knobs, `initial` here so the --agent / --agent-foreground / --background defaults resolve at the call site under a scoped [data-tenant] / .dark theme. Defaults = 2xs bold label · pill · violet --agent fill, white ink · a 2px --background separator ring · spilling --space-1 past the corner. default = var(--font-size-2xs) at the call site",
5986
+ "name": "--avatar-agent-font-size",
5987
+ "tier": "component",
5988
+ "value": "initial"
5989
+ },
5990
+ {
5991
+ "description": "default = var(--font-weight-bold) at the call site",
5992
+ "name": "--avatar-agent-font-weight",
5993
+ "tier": "component",
5994
+ "value": "initial"
5995
+ },
5996
+ {
5997
+ "description": "default = var(--space-1) at the call site",
5998
+ "name": "--avatar-agent-padding-inline",
5999
+ "tier": "component",
6000
+ "value": "initial"
6001
+ },
6002
+ {
6003
+ "description": "default = calc(var(--space-1) * -1) at the call site: the tag spills slightly past the mark",
6004
+ "name": "--avatar-agent-inset",
6005
+ "tier": "component",
6006
+ "value": "initial"
6007
+ },
6008
+ {
6009
+ "description": "default = var(--stroke-md) at the call site",
6010
+ "name": "--avatar-agent-ring-width",
6011
+ "tier": "component",
6012
+ "value": "initial"
6013
+ },
6014
+ {
6015
+ "description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
6016
+ "name": "--avatar-agent-background",
6017
+ "tier": "component",
6018
+ "value": "initial"
6019
+ },
6020
+ {
6021
+ "description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
6022
+ "name": "--avatar-agent-foreground",
6023
+ "tier": "component",
6024
+ "value": "initial"
6025
+ },
6026
+ {
6027
+ "description": "Data-display component tokens — small-by-design text knobs (rule #45/#46).",
6028
+ "name": "--avatar-agent-ring-color",
6029
+ "tier": "component",
6030
+ "value": "initial"
6031
+ },
5942
6032
  {
5943
6033
  "description": "Progress track + fill — `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, the default (`primary`) fill reads the brand mark; a service re-tones once. Defaults = hsl(var(--secondary)) track · hsl(var(--mark-primary, var(--primary))) fill (gh#1308 — it was the success mark; status tones are opt-in and do not read this knob).",
5944
6034
  "name": "--progress-track-background",
@@ -9693,7 +9783,7 @@
9693
9783
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
9694
9784
  "name": "--editorial-search-shadow",
9695
9785
  "tier": "component",
9696
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
9786
+ "value": "initial"
9697
9787
  },
9698
9788
  {
9699
9789
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
@@ -9813,7 +9903,7 @@
9813
9903
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
9814
9904
  "name": "--editorial-search-popup-shadow",
9815
9905
  "tier": "component",
9816
- "value": "0 12px 32px rgb(var(--shadow-color, 0 0 0) / 0.12)"
9906
+ "value": "initial"
9817
9907
  },
9818
9908
  {
9819
9909
  "description": "Default = hsl(var(--border)).",
@@ -10257,7 +10347,7 @@
10257
10347
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10258
10348
  "name": "--editorial-showcase-caption-shadow",
10259
10349
  "tier": "component",
10260
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
10350
+ "value": "initial"
10261
10351
  },
10262
10352
  {
10263
10353
  "description": "scale-exempt: measured editorial geometry; retain the existing size between icon steps",
@@ -10485,7 +10575,7 @@
10485
10575
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10486
10576
  "name": "--editorial-catalog-card-hover-shadow",
10487
10577
  "tier": "component",
10488
- "value": "0 10px 28px rgb(var(--shadow-color, 0 0 0) / 0.1)"
10578
+ "value": "initial"
10489
10579
  },
10490
10580
  {
10491
10581
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
@@ -10911,7 +11001,7 @@
10911
11001
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
10912
11002
  "name": "--editorial-spotlight-hover-shadow",
10913
11003
  "tier": "component",
10914
- "value": "0 4px 16px rgb(var(--shadow-color, 0 0 0) / 0.06)"
11004
+ "value": "initial"
10915
11005
  },
10916
11006
  {
10917
11007
  "description": "── EditorialCard variant=\"journal\" ──────────────────────────────────────────────────────",
@@ -11073,7 +11163,7 @@
11073
11163
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11074
11164
  "name": "--editorial-journal-hover-shadow",
11075
11165
  "tier": "component",
11076
- "value": "0 12px 28px rgb(var(--shadow-color, 0 0 0) / 0.08)"
11166
+ "value": "initial"
11077
11167
  },
11078
11168
  {
11079
11169
  "description": "── EditorialMasthead ────────────────────────────────────────────────────────────────────",
@@ -11603,6 +11693,24 @@
11603
11693
  "tier": "component",
11604
11694
  "value": "var(--space-10)"
11605
11695
  },
11696
+ {
11697
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11698
+ "name": "--editorial-catalog-detail-loading-background",
11699
+ "tier": "component",
11700
+ "value": "initial"
11701
+ },
11702
+ {
11703
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11704
+ "name": "--editorial-catalog-detail-loading-duration",
11705
+ "tier": "component",
11706
+ "value": "var(--duration-loop)"
11707
+ },
11708
+ {
11709
+ "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11710
+ "name": "--editorial-catalog-detail-loading-alpha",
11711
+ "tier": "component",
11712
+ "value": "0.5"
11713
+ },
11606
11714
  {
11607
11715
  "description": "Editorial family tokens (gh#1435) — the public, magazine-like pages of a site built on `CenteredShell preset=\"public-landing\" flow=\"sections\"`: full-width sections, a hero with a product showcase, the ruled kicker, a section head, the inset site search, a pill navigation band, catalog / editorial cards and the brand banner. Every geometry value a design states lives here, so a theme retunes it once — including at its OWN breakpoints, by re-declaring the token under a width query in its theme file (the shapes here never hard-code a breakpoint of their own). Colour knobs are role mirrors: `initial`, with the documented default resolved at the call site, so a scoped [data-tenant] / dark override still reaches them.",
11608
11716
  "name": "--editorial-catalog-detail-art-radius",
@@ -16247,6 +16355,12 @@
16247
16355
  "tier": "component",
16248
16356
  "value": "var(--space-3)"
16249
16357
  },
16358
+ {
16359
+ "description": "The gap between the brand and the custom bar inside `.app-topbar-rail` (gh#1481). It was a literal `--space-3`, so a theme that tightens the bar for a 320px / 200%-text viewport could reach every other gap in the row but this one.",
16360
+ "name": "--app-shell-rail-gap",
16361
+ "tier": "component",
16362
+ "value": "var(--space-3)"
16363
+ },
16250
16364
  {
16251
16365
  "description": "HOW MUCH OF THE BAR THE BRAND MAY KEEP (gh#728). A wide bar is not the problem, so the cap is `none` there; below the `sm` step the brand keeps at most a square of the row it sits in, and everything past that is cropped from the inline-end — which is the half a lockup can lose, because a lockup leads with its mark in both directions. A theme that wants the wordmark to survive on a phone raises the compact cap; a theme with a very wide lockup lowers the other. Choosing WHAT the narrow bar shows, rather than how much of it, is `AppShell logoCompact`: `viewBox` is an attribute and no stylesheet can set one.",
16252
16366
  "name": "--app-shell-brand-max-inline-size",
@@ -54,6 +54,7 @@ function CartesianChart({
54
54
  assertRechartsPeer();
55
55
  const { t, locale } = useTranslation();
56
56
  const fmt = useChartNumberFormat(numberFormat);
57
+ const counts = useChartNumberFormat();
57
58
  const list = React.useMemo(() => listFormat(locale, { style: "narrow", type: "unit" }), [locale]);
58
59
  const points = series.flatMap(
59
60
  (s, seriesIndex) => data.flatMap(
@@ -127,7 +128,10 @@ function CartesianChart({
127
128
  categoryKey,
128
129
  fmt,
129
130
  list,
130
- (c) => t("chart.summaryCartesian", { series: fmt.format(c.series), points: fmt.format(c.points) })
131
+ (c) => t("chart.summaryCartesian", {
132
+ series: counts.format(c.series),
133
+ points: counts.format(c.points)
134
+ })
131
135
  );
132
136
  const resolvedHeight = chartHeight(size, height);
133
137
  const tickFormatter = (value) => fmt.format(value);
@@ -33,7 +33,7 @@ export declare function buildTrendSummary(data: ChartDatum[], categoryKey: strin
33
33
  * Negative values plot at the baseline (a compact trend has no negative axis) but
34
34
  * still read their true figure in the text alternative.
35
35
  */
36
- export declare function trendRatios(data: ChartDatum[], valueKey: string): number[];
36
+ export declare function trendRatios(data: ChartDatum[], valueKey: string): (number | null)[];
37
37
  /**
38
38
  * Resolve `emphasizedIndex` against a row count. Negative counts from the end (`-1` = the latest
39
39
  * datum).
@@ -1,11 +1,16 @@
1
+ import { translateCurrent as t } from "../../i18n/translate.js";
1
2
  function numeric(value) {
2
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
3
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
4
+ }
5
+ function formatValue(value, fmt) {
6
+ const number = numeric(value);
7
+ return number === null ? t("chart.noData") : fmt.format(number);
3
8
  }
4
9
  function buildCartesianSummary(data, series, categoryKey, fmt, list, summaryLine) {
5
10
  const rows = data.map((datum) => {
6
11
  const category = String(datum[categoryKey] ?? "");
7
12
  const parts = series.map(
8
- (s) => `${s.label ?? s.dataKey}: ${fmt.format(numeric(datum[s.dataKey]))}`
13
+ (s) => `${s.label ?? s.dataKey}: ${formatValue(datum[s.dataKey], fmt)}`
9
14
  );
10
15
  return `${category} \u2014 ${list.format(parts)}`;
11
16
  });
@@ -13,7 +18,7 @@ function buildCartesianSummary(data, series, categoryKey, fmt, list, summaryLine
13
18
  }
14
19
  function buildSeriesRows(data, categoryKey, valueKey, fmt, annotate) {
15
20
  return data.map((datum, index) => {
16
- const row = `${String(datum[categoryKey] ?? "")}: ${fmt.format(numeric(datum[valueKey]))}`;
21
+ const row = `${String(datum[categoryKey] ?? "")}: ${formatValue(datum[valueKey], fmt)}`;
17
22
  const note = annotate?.(index);
18
23
  return note ? `${row} (${note})` : row;
19
24
  });
@@ -31,9 +36,15 @@ function buildTrendSummary(data, categoryKey, valueKey, fmt, summaryLine, annota
31
36
  };
32
37
  }
33
38
  function trendRatios(data, valueKey) {
34
- const values = data.map((datum) => Math.max(0, numeric(datum[valueKey])));
35
- const max = values.reduce((acc, value) => value > acc ? value : acc, 0);
36
- return values.map((value) => max > 0 ? value / max : 0);
39
+ const values = data.map((datum) => {
40
+ const value = numeric(datum[valueKey]);
41
+ return value === null ? null : Math.max(0, value);
42
+ });
43
+ const max = values.reduce(
44
+ (acc, value) => value !== null && value > acc ? value : acc,
45
+ 0
46
+ );
47
+ return values.map((value) => value === null ? null : max > 0 ? value / max : 0);
37
48
  }
38
49
  function resolveEmphasizedIndex(index, length) {
39
50
  if (index === void 0 || !Number.isInteger(index) || length === 0) return -1;
@@ -25,6 +25,7 @@ function CompactBarTrend({
25
25
  }) {
26
26
  const { t } = useTranslation();
27
27
  const fmt = useChartNumberFormat(numberFormat);
28
+ const countsFormat = useChartNumberFormat();
28
29
  const hasData = data.length > 0;
29
30
  const emphasized = resolveEmphasizedIndex(emphasizedIndex, data.length);
30
31
  const ratios = React.useMemo(() => trendRatios(data, valueKey), [data, valueKey]);
@@ -33,7 +34,7 @@ function CompactBarTrend({
33
34
  categoryKey,
34
35
  valueKey,
35
36
  fmt,
36
- (counts) => t("chart.summaryTrend", { points: fmt.format(counts.points) }),
37
+ (counts) => t("chart.summaryTrend", { points: countsFormat.format(counts.points) }),
37
38
  (index) => index === emphasized ? t("chart.trendCurrent") : void 0
38
39
  );
39
40
  return /* @__PURE__ */ jsx(
@@ -51,14 +52,14 @@ function CompactBarTrend({
51
52
  className: cn("ui-chart-trend", className),
52
53
  id,
53
54
  children: hasData ? /* @__PURE__ */ jsxs("div", { className: "ui-chart-trend-body", children: [
54
- /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-plot", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-column", children: /* @__PURE__ */ jsx(
55
+ /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-plot", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-column", children: ratios[index] !== null ? /* @__PURE__ */ jsx(
55
56
  "div",
56
57
  {
57
58
  className: "ui-chart-trend-bar",
58
59
  "data-emphasized": index === emphasized ? "true" : void 0,
59
60
  style: { "--chart-trend-bar-value": ratios[index] }
60
61
  }
61
- ) }, `${String(datum[categoryKey])}-${index}`)) }),
62
+ ) : null }, `${String(datum[categoryKey])}-${index}`)) }),
62
63
  showCategoryLabels ? /* @__PURE__ */ jsx("div", { className: "ui-chart-trend-ticks", "aria-hidden": "true", children: data.map((datum, index) => /* @__PURE__ */ jsx(
63
64
  "span",
64
65
  {
@@ -37,6 +37,7 @@ function PieChart({
37
37
  assertRechartsPeer();
38
38
  const { t } = useTranslation();
39
39
  const fmt = useChartNumberFormat(numberFormat);
40
+ const counts = useChartNumberFormat();
40
41
  const points = data.flatMap(
41
42
  (datum, index) => typeof datum[dataKey] === "number" && Number.isFinite(datum[dataKey]) && Number(datum[dataKey]) > 0 ? [{ index, series: 0 }] : []
42
43
  );
@@ -71,7 +72,7 @@ function PieChart({
71
72
  dataKey,
72
73
  nameKey,
73
74
  fmt,
74
- (c) => t("chart.summaryPie", { slices: fmt.format(c.slices) })
75
+ (c) => t("chart.summaryPie", { slices: counts.format(c.slices) })
75
76
  );
76
77
  const resolvedHeight = chartHeight(size, height);
77
78
  return /* @__PURE__ */ jsx(
@@ -3,7 +3,7 @@ import { type ImageLoadingStatus } from "../../lib/image-loading-status.js";
3
3
  import type { AvatarImageFitProp, AvatarProp } from "../../props/components/data-display.prop.js";
4
4
  export declare const Avatar: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & {
5
5
  asChild?: boolean;
6
- } & Pick<AvatarProp, "appearance" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
6
+ } & Pick<AvatarProp, "appearance" | "kind" | "presence" | "presenceLabel" | "shape" | "size"> & React.RefAttributes<HTMLSpanElement>>;
7
7
  export declare const AvatarImage: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "ref"> & {
8
8
  asChild?: boolean;
9
9
  onLoadingStatusChange?: (status: ImageLoadingStatus) => void;
@@ -22,6 +22,7 @@ const Avatar = React.forwardRef(
22
22
  appearance = "default",
23
23
  presence,
24
24
  presenceLabel,
25
+ kind = "person",
25
26
  asChild = false,
26
27
  children,
27
28
  ...props
@@ -42,11 +43,16 @@ const Avatar = React.forwardRef(
42
43
  "data-size": size === "md" ? void 0 : size,
43
44
  "data-appearance": appearance === "tinted" ? "tinted" : void 0,
44
45
  "data-presence": presence,
46
+ "data-kind": kind === "agent" ? "agent" : void 0,
45
47
  className: cn("ui-avatar", className),
46
48
  ...props,
47
49
  children: [
48
50
  children,
49
- presence !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-presence", "data-presence": presence, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: presenceLabel ?? t(`dataDisplay.avatar.presence.${presence}`) }) }) : null
51
+ presence !== void 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "avatar-presence", "data-presence": presence, children: /* @__PURE__ */ jsx("span", { className: "sr-only", children: presenceLabel ?? t(`dataDisplay.avatar.presence.${presence}`) }) }) : null,
52
+ kind === "agent" ? /* @__PURE__ */ jsxs("span", { "data-slot": "avatar-agent", children: [
53
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "AI" }),
54
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("dataDisplay.avatar.agent") })
55
+ ] }) : null
50
56
  ]
51
57
  }
52
58
  ) });
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
+ import type { AgentBadgeProp } from "../../props/components/data-display.prop.js";
3
4
  import type { ShapeProp, ToneProp } from "../../props/vocabulary/index.js";
4
5
  export type BadgeVariant = "default" | "secondary" | "outline" | "dashed";
5
6
  /**
@@ -100,4 +101,14 @@ export declare function Badge({ as: Element, className, variant, shape, tone, co
100
101
  * general-purpose primitive.
101
102
  */
102
103
  export declare const StatusBadge: typeof Badge;
104
+ /** @see AgentBadgeProp */
105
+ export type AgentBadgeProps = AgentBadgeProp;
106
+ /**
107
+ * The "AI agent" label beside a NAME — the text-row half of telling an AI agent apart from a
108
+ * person; `<Avatar kind="agent">` is the mark half. A list that shows names uses both; a place with
109
+ * only a mark (an assignee icon column, a dense cell) uses the Avatar alone. Violet outline on a
110
+ * transparent ground (`--badge-agent-*`, reading the `--agent` role), pill, never wraps. The label
111
+ * is real text, so it is announced as written and never colour-only.
112
+ */
113
+ export declare function AgentBadge({ as: Element, className, children, ...props }: AgentBadgeProps): React.JSX.Element;
103
114
  export {};
@@ -193,7 +193,27 @@ function Badge({
193
193
  ] });
194
194
  }
195
195
  const StatusBadge = Badge;
196
+ function AgentBadge({
197
+ as: Element = "span",
198
+ className,
199
+ children,
200
+ ...props
201
+ }) {
202
+ const { t } = useTranslation();
203
+ return /* @__PURE__ */ jsx(
204
+ Element,
205
+ {
206
+ "data-slot": "badge",
207
+ "data-agent": "",
208
+ "data-shape": "pill",
209
+ className: cn(badgeVariants({ variant: "tinted", shape: "pill" }), className),
210
+ ...props,
211
+ children: /* @__PURE__ */ jsx("span", { "data-slot": "badge-label", children: children ?? t("dataDisplay.avatar.agent") })
212
+ }
213
+ );
214
+ }
196
215
  export {
216
+ AgentBadge,
197
217
  Badge,
198
218
  StatusBadge
199
219
  };
@@ -14,6 +14,7 @@ export declare const Image: React.ForwardRefExoticComponent<Omit<React.ImgHTMLAt
14
14
  alt: string;
15
15
  fallback?: string;
16
16
  placeholder?: React.ReactNode | boolean;
17
+ onStatusChange?: (status: "loading" | "loaded" | "error") => void;
17
18
  preview?: boolean | ImagePreviewConfigProp;
18
19
  width?: number | string;
19
20
  height?: number | string;
@@ -420,6 +420,7 @@ const ImageRoot = React.forwardRef(function Image({
420
420
  alt,
421
421
  fallback,
422
422
  placeholder,
423
+ onStatusChange,
423
424
  preview = true,
424
425
  width,
425
426
  height,
@@ -444,6 +445,11 @@ const ImageRoot = React.forwardRef(function Image({
444
445
  status: "loading"
445
446
  });
446
447
  const status = load.src === src ? load.status : "loading";
448
+ const statusChangeRef = React.useRef(onStatusChange);
449
+ statusChangeRef.current = onStatusChange;
450
+ React.useEffect(() => {
451
+ statusChangeRef.current?.(status);
452
+ }, [src, status]);
447
453
  const setStatus = React.useCallback(
448
454
  (next) => setLoad(
449
455
  (prev) => prev.src === src && prev.status === "error" ? prev : { src, status: next }
@@ -454,7 +460,7 @@ const ImageRoot = React.forwardRef(function Image({
454
460
  const setImgRef = React.useCallback(
455
461
  (node) => {
456
462
  imgRef.current = node;
457
- if (node?.complete && node.naturalWidth > 0) setStatus("loaded");
463
+ if (node?.complete) setStatus(node.naturalWidth > 0 ? "loaded" : "error");
458
464
  if (typeof ref === "function") ref(node);
459
465
  else if (ref) ref.current = node;
460
466
  },
@@ -471,7 +477,9 @@ const ImageRoot = React.forwardRef(function Image({
471
477
  }, [inGroup, group, id, previewSrc, alt]);
472
478
  const [visible, setVisible] = useMergedState(config.visible, false, config.onVisibleChange);
473
479
  const shownSrc = isError && fallback ? fallback : src;
474
- const showPlaceholder = placeholder != null && placeholder !== false && status === "loading";
480
+ const [mounted, setMounted] = React.useState(false);
481
+ React.useEffect(() => setMounted(true), []);
482
+ const showPlaceholder = mounted && placeholder != null && placeholder !== false && status === "loading";
475
483
  const intrinsic = fit === "intrinsic";
476
484
  const sized = intrinsic || size != null || width != null || height != null;
477
485
  const frameFit = fit ?? (size != null || height != null ? "cover" : void 0);
@@ -1,7 +1,7 @@
1
1
  export { Persona } from "./persona.js";
2
2
  export type { PersonaProp, PersonaProps } from "./persona.js";
3
- export { Badge, StatusBadge } from "./badge.js";
4
- export type { BadgeProps, BadgeTone } from "./badge.js";
3
+ export { AgentBadge, Badge, StatusBadge } from "./badge.js";
4
+ export type { AgentBadgeProps, BadgeProps, BadgeTone } from "./badge.js";
5
5
  export { Legend } from "./legend.js";
6
6
  export type { LegendItemProp, LegendProp, LegendProps } from "./legend.js";
7
7
  export { Swatch } from "./swatch.js";
@@ -1,5 +1,5 @@
1
1
  import { Persona } from "./persona.js";
2
- import { Badge, StatusBadge } from "./badge.js";
2
+ import { AgentBadge, Badge, StatusBadge } from "./badge.js";
3
3
  import { Legend } from "./legend.js";
4
4
  import { Swatch } from "./swatch.js";
5
5
  import { FeatureList } from "./feature-list.js";
@@ -60,6 +60,7 @@ export {
60
60
  AccordionContent,
61
61
  AccordionItem,
62
62
  AccordionTrigger,
63
+ AgentBadge,
63
64
  Avatar,
64
65
  AvatarFallback,
65
66
  AvatarImage,
@@ -243,6 +243,16 @@ function ClickContent({
243
243
  }) {
244
244
  const root = usePopoverRoot("PopoverContent");
245
245
  const contentRef = React.useRef(null);
246
+ const [viewportInlineSize, setViewportInlineSize] = React.useState();
247
+ React.useLayoutEffect(() => {
248
+ if (!root.open) return;
249
+ const doc = root.triggerRef.current?.ownerDocument ?? document;
250
+ const update = () => setViewportInlineSize(doc.documentElement.clientWidth);
251
+ update();
252
+ const observer = new ResizeObserver(update);
253
+ observer.observe(doc.documentElement);
254
+ return () => observer.disconnect();
255
+ }, [root.open, root.triggerRef]);
246
256
  const openAutoFocusRef = React.useRef(onOpenAutoFocus);
247
257
  openAutoFocusRef.current = onOpenAutoFocus;
248
258
  useOverlayCloseFocus(root.open, onCloseAutoFocus, contentRef);
@@ -350,6 +360,7 @@ function ClickContent({
350
360
  style: {
351
361
  ...racStyle,
352
362
  "--radix-popover-trigger-width": "var(--trigger-width)",
363
+ ...viewportInlineSize == null ? null : { "--popover-viewport-inline-size": `${viewportInlineSize}px` },
353
364
  "--radix-popover-content-transform-origin": "var(--trigger-anchor-point)",
354
365
  ...availableHeight == null ? null : {
355
366
  "--radix-popover-content-available-height": typeof availableHeight === "number" ? `${availableHeight}px` : availableHeight
@@ -161,7 +161,7 @@ function Calendar({
161
161
  day: cn("ui-calendar-day", classNames?.day),
162
162
  day_button: cn(
163
163
  buttonVariants({ variant: "ghost" }),
164
- "ui-calendar-day-button",
164
+ "ui-calendar-day-button rounded-[var(--calendar-day-radius,var(--button-radius))]",
165
165
  classNames?.day_button
166
166
  ),
167
167
  range_start: cn("day-range-start", classNames?.range_start),
@@ -131,7 +131,7 @@ function DatePicker(props) {
131
131
  defaultValue
132
132
  );
133
133
  const value = isControlled ? valueProp : internalValue;
134
- const working = hasPending ? pending : value;
134
+ const working = open && hasPending ? pending : value;
135
135
  const rangeValue = range ? working : void 0;
136
136
  const selectedDate = range ? rangeValue?.from : Array.isArray(working) ? working[0] : working;
137
137
  const emit = (next) => {
@@ -156,6 +156,16 @@ function DatePicker(props) {
156
156
  setText(displayAll(committedSingle));
157
157
  }
158
158
  }, [range, value, committedFrom, committedTo, format, locale, showTime]);
159
+ React.useEffect(() => {
160
+ if (!open) {
161
+ setHasPending(false);
162
+ setPending(void 0);
163
+ if (range) {
164
+ setFromText(display(committedFrom));
165
+ setToText(display(committedTo));
166
+ }
167
+ }
168
+ }, [open]);
159
169
  const valueAnchor = range ? committedRange?.from : Array.isArray(committedSingle) ? committedSingle[0] : committedSingle;
160
170
  const [internalView, setInternalView] = React.useState(
161
171
  () => defaultPickerValue ?? valueAnchor
@@ -182,7 +192,7 @@ function DatePicker(props) {
182
192
  const validRange = (next) => Boolean(
183
193
  next && (next.from ? allowed(next.from) : allowEmpty[0]) && (next.to ? allowed(next.to) : allowEmpty[1])
184
194
  );
185
- const choose = (input) => {
195
+ const choose = (input, typed = false) => {
186
196
  if (range) {
187
197
  let next = input;
188
198
  if (anyDisabled && next)
@@ -196,7 +206,14 @@ function DatePicker(props) {
196
206
  if (needConfirm) {
197
207
  setPending(next);
198
208
  setHasPending(true);
199
- } else emit(next);
209
+ } else if (open && !typed && next && !(next.from && next.to)) {
210
+ setPending(next);
211
+ setHasPending(true);
212
+ } else {
213
+ setPending(void 0);
214
+ setHasPending(false);
215
+ emit(next);
216
+ }
200
217
  return;
201
218
  }
202
219
  let date = input;
@@ -253,7 +270,7 @@ function DatePicker(props) {
253
270
  if (!trimmed && !allowEmpty[edge === "from" ? 0 : 1]) return;
254
271
  const base = working;
255
272
  const next = { from: base?.from, to: base?.to, [edge]: parsed && periodDate(parsed) };
256
- choose(next.from || next.to ? next : void 0);
273
+ choose(next.from || next.to ? next : void 0, true);
257
274
  };
258
275
  const keyHandlers = (onEnter) => ({
259
276
  onKeyDown: (event) => {
@@ -458,7 +475,7 @@ function DatePicker(props) {
458
475
  {
459
476
  mode: "multiple",
460
477
  selected: Array.isArray(working) ? working : [],
461
- onSelect: choose,
478
+ onSelect: (days) => choose(days),
462
479
  disabled: (day) => !allowed(day),
463
480
  ...calendarShared
464
481
  }