@godxjp/ui 33.9.1 → 33.11.2

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 (43) hide show
  1. package/DESIGN.md +10 -7
  2. package/README.md +3 -3
  3. package/agent/DESIGN.md +10 -7
  4. package/agent/START-HERE.md +4 -4
  5. package/agent/components/Cascader.json +1 -0
  6. package/agent/components/DatePicker.json +1 -0
  7. package/agent/components/Input.json +1 -0
  8. package/agent/components/NumberInput.json +1 -0
  9. package/agent/components/PasswordInput.json +1 -0
  10. package/agent/components/SearchInput.json +1 -0
  11. package/agent/components/Select.json +1 -0
  12. package/agent/components/TimePicker.json +1 -0
  13. package/agent/components/Topbar.json +8 -2
  14. package/agent/components/TopbarItem.json +7 -1
  15. package/agent/components/TreeSelect.json +1 -0
  16. package/agent/components.json +24 -3
  17. package/agent/index.json +5 -5
  18. package/agent/llms.txt +6 -6
  19. package/agent/tokens.json +54 -36
  20. package/dist/components/data-entry/input.js +16 -1
  21. package/dist/components/data-entry/search-input.js +1 -1
  22. package/dist/components/layout/topbar-item.d.ts +1 -0
  23. package/dist/components/layout/topbar-item.js +9 -4
  24. package/dist/components/layout/topbar.d.ts +1 -1
  25. package/dist/components/layout/topbar.js +25 -3
  26. package/dist/contracts/measurement.json +1 -1
  27. package/dist/props/components/layout.prop.d.ts +13 -0
  28. package/dist/props/registry.d.ts +9 -1
  29. package/dist/props/registry.js +10 -0
  30. package/dist/styles/control.css +131 -3
  31. package/dist/styles/layers.json +1 -1
  32. package/dist/styles/shell-layout.css +38 -0
  33. package/dist/styles.css +1 -1
  34. package/dist/tokens/components/card.css +3 -3
  35. package/dist/tokens/components/control.css +8 -5
  36. package/dist/tokens/components/form.css +1 -1
  37. package/dist/tokens/components/shell.css +3 -0
  38. package/dist/tokens/components/table.css +1 -1
  39. package/dist/tokens/foundation.css +11 -11
  40. package/dist/tokens/semantic/layout.css +3 -3
  41. package/docs/TOKENS.md +73 -1
  42. package/package.json +2 -2
  43. package/scripts/visual-audit.mjs +33 -3
package/DESIGN.md CHANGED
@@ -14,7 +14,7 @@ colors:
14
14
  muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)
15
15
  accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)
16
16
  hairline: "#eeedec" # --border (dividers, card edges, table rules)
17
- control-edge: "#90877f" # --input (the edge of an input; 3:1 against canvas)
17
+ control-edge: "#8b8279" # --input (the edge of an input; 3:1 against canvas)
18
18
  success: "#68be8d" # --success 若竹
19
19
  warning: "#f8b500" # --warning 山吹
20
20
  info: "#4c6cb3" # --info 群青
@@ -28,9 +28,9 @@ colors:
28
28
  typography:
29
29
  family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"
30
30
  base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)
31
- page-title: { size: "~20px (--heading-h1)", weight: 500, lineHeight: 1.25 }
31
+ page-title: { size: "28px (--page-title-font-size); 21px compact", weight: 500, lineHeight: 1.25 }
32
32
  section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }
33
- card-title: { size: "14px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
33
+ card-title: { size: "~15.7px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
34
34
  body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body
35
35
  caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }
36
36
  weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600
@@ -46,13 +46,13 @@ spacing:
46
46
  scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"
47
47
  stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap
48
48
  inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap
49
- page: { x: 24px, y: 24px } # --space-page-x / -y
49
+ page: { x: 32px, y: 32px } # --space-page-x / -y
50
50
  section: 16px # --space-section
51
51
 
52
52
  density:
53
53
  axis: "AppProvider density / PageContainer density / data-density"
54
54
  compact: "--scaling 0.92 (data-heavy tables)"
55
- default: "--scaling 1 (control and row height 32px, --band-height-md)"
55
+ default: "--scaling 1 (control and row height 36px, --band-height-lg)"
56
56
  comfortable: "--scaling 1.08 (login, public, touch)"
57
57
  touch-floor: 44px # --band-height-xl
58
58
 
@@ -75,7 +75,7 @@ components:
75
75
  {
76
76
  background: "{colors.canvas}",
77
77
  border: "1px {colors.hairline}",
78
- shadow: "--shadow-sm",
78
+ shadow: "none (--card-shadow)",
79
79
  radius: "{rounded.card}",
80
80
  padding: "--card-space-inset",
81
81
  }
@@ -117,7 +117,7 @@ Colour is scarce. Most of the screen is canvas, ink and hairline.
117
117
 
118
118
  ## Elevation
119
119
 
120
- - Flat. A card is canvas with a 1px `--border` hairline and a near-invisible `--shadow-sm`; nothing heavier at rest.
120
+ - Flat. A card is canvas with a 1px `--border` hairline and no resting shadow; elevation is reserved for floating surfaces.
121
121
  - `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.
122
122
  - No hover lift on data cards. Depth never signals importance.
123
123
 
@@ -195,6 +195,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
195
195
  - Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.
196
196
  - Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.
197
197
  - Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.
198
+ - Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.
198
199
  - Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
199
200
  - A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.
200
201
 
@@ -217,3 +218,5 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
217
218
  - Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.
218
219
  - From 640px up, `BlockEditor` text does not share the keyline of the `Input` above it: measured at 768/1024/1280, input text 13px inside its frame, editor text 57px inside (Δ44px), because the ⋮⋮/+ handles (two 24px targets) sit in a start gutter inside the frame. Aligning the text would put the handles outside the field border, where a content column 24px from the viewport edge has no room. Below 640px the gutter collapses (C05). `--block-editor-gutter-inline-size` sets the gutter width; it cannot close the gap, because the handles live inside it. Accepted, not a defect to fix in the app.
219
220
  - On narrow screens the `PageContainer` title steps down (≈17.6px) while body h2 stays ≈15.7px — one step apart. A document page not built on `LegalDocumentShell` keeps only that step on phones; there is no intermediate page-title size yet.
221
+
222
+ Modern refresh: field rows use 20px; form blocks 24px. Keyboard focus uses a 2px ring and 2px offset (navigation keeps its existing inset). Overlay scrim uses 40% black. Compact page gutters remain 16px. See docs/TOKENS.md for complete restoration.
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 — 169 components, 2235 tokens, 44 cardinal rules — is published in two forms from one
21
+ The catalog — 169 components, 2238 tokens, 44 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
 
@@ -51,12 +51,12 @@ static files straight from this public repo — no hosting, no deploy step:
51
51
  | --- | --- | --- |
52
52
  | [`agent/START-HERE.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md) | 11 KB | **read first** — self-contained: the four rules, the token override model, a page that passes review |
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
- | [`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 |
54
+ | [`agent/DESIGN.md`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md) | 18 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
56
  | [`agent/components-index.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json) | 52 KB | all 169 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
58
  | [`agent/components.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json) | 1.3 MB | all 169 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) | 859 KB | all 2235 design tokens, their values and why they exist |
59
+ | [`agent/tokens.json`](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json) | 858 KB | all 2238 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) | 23 KB | the 44 cardinal rules |
62
62
  <!-- prettier-ignore-end -->
package/agent/DESIGN.md CHANGED
@@ -14,7 +14,7 @@ colors:
14
14
  muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)
15
15
  accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)
16
16
  hairline: "#eeedec" # --border (dividers, card edges, table rules)
17
- control-edge: "#90877f" # --input (the edge of an input; 3:1 against canvas)
17
+ control-edge: "#8b8279" # --input (the edge of an input; 3:1 against canvas)
18
18
  success: "#68be8d" # --success 若竹
19
19
  warning: "#f8b500" # --warning 山吹
20
20
  info: "#4c6cb3" # --info 群青
@@ -28,9 +28,9 @@ colors:
28
28
  typography:
29
29
  family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"
30
30
  base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)
31
- page-title: { size: "~20px (--heading-h1)", weight: 500, lineHeight: 1.25 }
31
+ page-title: { size: "28px (--page-title-font-size); 21px compact", weight: 500, lineHeight: 1.25 }
32
32
  section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }
33
- card-title: { size: "14px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
33
+ card-title: { size: "~15.7px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }
34
34
  body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body
35
35
  caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }
36
36
  weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600
@@ -46,13 +46,13 @@ spacing:
46
46
  scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"
47
47
  stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap
48
48
  inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap
49
- page: { x: 24px, y: 24px } # --space-page-x / -y
49
+ page: { x: 32px, y: 32px } # --space-page-x / -y
50
50
  section: 16px # --space-section
51
51
 
52
52
  density:
53
53
  axis: "AppProvider density / PageContainer density / data-density"
54
54
  compact: "--scaling 0.92 (data-heavy tables)"
55
- default: "--scaling 1 (control and row height 32px, --band-height-md)"
55
+ default: "--scaling 1 (control and row height 36px, --band-height-lg)"
56
56
  comfortable: "--scaling 1.08 (login, public, touch)"
57
57
  touch-floor: 44px # --band-height-xl
58
58
 
@@ -75,7 +75,7 @@ components:
75
75
  {
76
76
  background: "{colors.canvas}",
77
77
  border: "1px {colors.hairline}",
78
- shadow: "--shadow-sm",
78
+ shadow: "none (--card-shadow)",
79
79
  radius: "{rounded.card}",
80
80
  padding: "--card-space-inset",
81
81
  }
@@ -117,7 +117,7 @@ Colour is scarce. Most of the screen is canvas, ink and hairline.
117
117
 
118
118
  ## Elevation
119
119
 
120
- - Flat. A card is canvas with a 1px `--border` hairline and a near-invisible `--shadow-sm`; nothing heavier at rest.
120
+ - Flat. A card is canvas with a 1px `--border` hairline and no resting shadow; elevation is reserved for floating surfaces.
121
121
  - `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.
122
122
  - No hover lift on data cards. Depth never signals importance.
123
123
 
@@ -195,6 +195,7 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
195
195
  - Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.
196
196
  - Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.
197
197
  - Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.
198
+ - Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.
198
199
  - Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu's `DropdownMenuLabel`, so a phone shows the avatar only.
199
200
  - A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.
200
201
 
@@ -217,3 +218,5 @@ A list whose rows are decisions. Show requester, target, reason, submitted time;
217
218
  - Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.
218
219
  - From 640px up, `BlockEditor` text does not share the keyline of the `Input` above it: measured at 768/1024/1280, input text 13px inside its frame, editor text 57px inside (Δ44px), because the ⋮⋮/+ handles (two 24px targets) sit in a start gutter inside the frame. Aligning the text would put the handles outside the field border, where a content column 24px from the viewport edge has no room. Below 640px the gutter collapses (C05). `--block-editor-gutter-inline-size` sets the gutter width; it cannot close the gap, because the handles live inside it. Accepted, not a defect to fix in the app.
219
220
  - On narrow screens the `PageContainer` title steps down (≈17.6px) while body h2 stays ≈15.7px — one step apart. A document page not built on `LegalDocumentShell` keeps only that step on phones; there is no intermediate page-title size yet.
221
+
222
+ Modern refresh: field rows use 20px; form blocks 24px. Keyboard focus uses a 2px ring and 2px offset (navigation keeps its existing inset). Overlay scrim uses 40% black. Compact page gutters remain 16px. See docs/TOKENS.md for complete restoration.
@@ -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` 33.9.1.** If the project you are editing has a different
6
+ **This catalog describes `@godxjp/ui` 33.11.2.** 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.
@@ -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` — 44 cardinal rules. The ones about raw HTML and hardcoded colour are not
63
63
  style advice.
64
- 4. `tokens.json` — 2235 design tokens, each tagged with its `tier`. **If you were handed a
64
+ 4. `tokens.json` — 2238 design tokens, each tagged with its `tier`. **If you were handed a
65
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
- 1906 `component` entries are per-part knobs; reach for one only when a role is
67
+ 1909 `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.
@@ -150,7 +150,7 @@ has stopped following the brand.
150
150
  |---|---|---|---|
151
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` | 1906 | 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` | 1909 | 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.
@@ -196,6 +196,7 @@
196
196
  "storyPath": "data-entry/cascader.tsx",
197
197
  "tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
198
198
  "usage": [
199
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
199
200
  "DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
200
201
  "DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both — providing value without onChange makes the field read-only (the internal state won't update).",
201
202
  "DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.",
@@ -264,6 +264,7 @@
264
264
  "storyPath": "data-entry/date-picker.tsx",
265
265
  "tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
266
266
  "usage": [
267
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
267
268
  "ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
268
269
  "DO use `name` to make the field form-submittable — it emits ISO-8601 at the picker's own precision (`2026-03` for a month, `2026` for a year, `2026-03-01` for a day). No hidden input of your own is needed.",
269
270
  "DO wrap it in FormField like every other labelled control; FormField injects the id/aria wiring onto the input.",
@@ -105,6 +105,7 @@
105
105
  "storyPath": "data-entry/input.tsx",
106
106
  "tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
107
107
  "usage": [
108
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The trailing slot is measured, so adjacent actions and counters reserve their combined width; allowClear still replaces trailingIcon/suffix.",
108
109
  "DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
109
110
  "DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
110
111
  "DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
@@ -140,6 +140,7 @@
140
140
  "storyPath": "data-entry/number-input.tsx",
141
141
  "tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
142
142
  "usage": [
143
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The increment/decrement actions sit side by side on touch screens so each owns a non-overlapping target.",
143
144
  "DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
144
145
  "DO drive it controlled with `value` + `onValueChange` carrying `number | null` (the vocabulary triad — NOT `onChange`). `null` means the field is empty; never substitute 0 for empty.",
145
146
  "DON'T pass `value` without `onValueChange` — like every controlled @godxjp/ui input it would freeze. Omit both for uncontrolled (use `defaultValue`).",
@@ -67,6 +67,7 @@
67
67
  "storyPath": "data-entry/password-input.tsx",
68
68
  "tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
69
69
  "usage": [
70
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
70
71
  "DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
71
72
  "DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
72
73
  "DON'T add your own eye button — it's built in (and excluded from the tab order)."
@@ -92,6 +92,7 @@
92
92
  "storyPath": "data-entry/search-input.tsx",
93
93
  "tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
94
94
  "usage": [
95
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
95
96
  "DON'T drop a bare SearchInput into a Topbar slot — use `TopbarSearch` (layout), which passes every SearchInput prop through and folds to a search cell below 40rem instead of shrinking under 8ch (ADR C02).",
96
97
  "DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
97
98
  "DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
@@ -398,6 +398,7 @@
398
398
  ],
399
399
  "tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
400
400
  "usage": [
401
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
401
402
  "FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
402
403
  "DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
403
404
  "DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
@@ -181,6 +181,7 @@
181
181
  "storyPath": "data-entry/time-picker.tsx",
182
182
  "tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
183
183
  "usage": [
184
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
184
185
  "One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
185
186
  "DO give it a name in native forms. It submits HH:mm or HH:mm:ss, regardless of display format; the component manages any hidden canonical field.",
186
187
  "DO use the controlled pattern (`value` + `onValueChange`) in React-managed forms (e.g. useForm). For simple HTML forms without React state, omit `value` and use `defaultValue` for the uncontrolled pattern.",
@@ -4,6 +4,11 @@
4
4
  "importPath": "@godxjp/ui/layout",
5
5
  "name": "Topbar",
6
6
  "props": [
7
+ {
8
+ "description": "Opt-in pinned shared-device identity: one TopbarItem truncate with Avatar in icon and a string name. Capped at --topbar-identity-max-inline-size (40%); --topbar-identity-min-text-inline-size reserves 3ch. Stays outside scroll/menu while primary cells retain their minima. Only other clusters follow overflow; children overrides all slots. Do not combine identity with hideBelow, labelHideBelow or iconHideFrom. At extreme widths choose overflow=\"menu\" to keep other controls reachable.",
9
+ "name": "identity",
10
+ "type": "ReactNode"
11
+ },
7
12
  {
8
13
  "description": "Instance inset using logical token steps.",
9
14
  "name": "pad",
@@ -36,11 +41,11 @@
36
41
  "defaultValue": "\"scroll\"",
37
42
  "description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
38
43
  "name": "overflow",
39
- "type": "\"scroll\" | \"clip\""
44
+ "type": "\"scroll\" | \"clip\" | \"menu\""
40
45
  },
41
46
  {
42
47
  "defaultValue": "undefined",
43
- "description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored.",
48
+ "description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end`/`identity` are ignored.",
44
49
  "name": "children",
45
50
  "type": "ReactNode"
46
51
  },
@@ -65,6 +70,7 @@
65
70
  "storyPath": "layout/topbar.tsx",
66
71
  "tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
67
72
  "usage": [
73
+ "For shared devices use identity={<TopbarItem truncate icon={<Avatar aria-hidden=\"true\"><AvatarFallback>DB</AvatarFallback></Avatar>}>{user.name}</TopbarItem>}, optionally wrapped in DropdownMenuTrigger asChild. Keep the locale picker icon-only with appearance=\"bar\" in end. The name stays visible and bounded; full string children become title and accessible text. AccountChip is a separate avatar/name/action row, not a full-height menu cell.",
68
74
  "DO use `TopbarSearch`, not a bare `SearchInput`, for the bar's search (ADR C02): a SearchInput in a slot shrinks with the cluster — measured at 390px in a consumer bar its text area was 0ch. TopbarSearch keeps at least 8ch from 40rem up and folds to a named search cell below it.",
69
75
  "DO align the bar's search with the content column (ADR C11) by composition: keep AppShell's default `topbarSpan=\"content\"` and put `TopbarSearch` FIRST in `start` — the bar then starts on the rail edge with the same inset as PageContainer, measured 0px apart at 1024/1280/1440. Under `topbarSpan=\"full\"` the bar starts at the window edge after the brand, so no keyline is shared; there, put the search in `end` deliberately rather than near a column it cannot meet.",
70
76
  "DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
@@ -4,6 +4,12 @@
4
4
  "importPath": "@godxjp/ui/layout",
5
5
  "name": "TopbarItem",
6
6
  "props": [
7
+ {
8
+ "defaultValue": "false",
9
+ "description": "Same vocabulary as Text.truncate: allow the cell to shrink to its control minimum and ellipse only its label. Keeps the icon; overrides labelHideBelow. Full text remains accessible; string children supply title automatically (including asChild). Pass title explicitly for rich children. Use in Topbar identity for a bounded always-visible shared-device name; default phone accounts still use labelHideBelow=\"sm\".",
10
+ "name": "truncate",
11
+ "type": "boolean"
12
+ },
7
13
  {
8
14
  "description": "The cell's leading glyph, placed in a slot the CELL sizes (--topbar-icon-size). Prefer it over passing the glyph as a child whenever the glyph is wrapped: `.ui-topbar-item > svg` only reaches a DIRECT child, so a glyph inside a `<Flex hideBelow>` renders at lucide's intrinsic 24px — measured at 390px on an org-switcher cell, 1.5x the cell's step (gh#712). Decorative by convention (the cell's name comes from its own aria-label); pass <Icon as={…} size=… label=… /> here when the glyph needs a name or a different step. Rendered with asChild too (gh#726): the sized slot is inserted INSIDE the child element, ahead of that element's own children, and the child element (e.g. an <a>) stays the rendered root with every merged prop.",
9
15
  "name": "icon",
@@ -65,7 +71,7 @@
65
71
  "storyPath": "layout/topbar-item.tsx",
66
72
  "tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
67
73
  "usage": [
68
- "DO collapse the account cell to its avatar on a phone (ADR C04): `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
74
+ "By default collapse the account cell to its avatar on a phone (ADR C04); shared devices opt into Topbar identity + TopbarItem truncate instead: `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
69
75
  "DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
70
76
  "DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
71
77
  "DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
@@ -214,6 +214,7 @@
214
214
  "storyPath": "data-entry/tree-select.tsx",
215
215
  "tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
216
216
  "usage": [
217
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
217
218
  "DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
218
219
  "DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
219
220
  "DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.",
@@ -1689,6 +1689,11 @@
1689
1689
  "importPath": "@godxjp/ui/layout",
1690
1690
  "name": "Topbar",
1691
1691
  "props": [
1692
+ {
1693
+ "description": "Opt-in pinned shared-device identity: one TopbarItem truncate with Avatar in icon and a string name. Capped at --topbar-identity-max-inline-size (40%); --topbar-identity-min-text-inline-size reserves 3ch. Stays outside scroll/menu while primary cells retain their minima. Only other clusters follow overflow; children overrides all slots. Do not combine identity with hideBelow, labelHideBelow or iconHideFrom. At extreme widths choose overflow=\"menu\" to keep other controls reachable.",
1694
+ "name": "identity",
1695
+ "type": "ReactNode"
1696
+ },
1692
1697
  {
1693
1698
  "description": "Instance inset using logical token steps.",
1694
1699
  "name": "pad",
@@ -1721,11 +1726,11 @@
1721
1726
  "defaultValue": "\"scroll\"",
1722
1727
  "description": "What a bar that does not fit does with the cells that do not fit (gh#728). scroll (default) lets the BAR scroll on the inline axis while the start and end clusters keep their cells whole — every cell stays in the DOM and in the accessibility tree, Tab reaches each one in order, and the browser scrolls a focused cell into view; the scrollbar is suppressed (a horizontal bar inside a 48px chrome row would eat the height that caused the deficit), so the pointer affordance is drag/wheel. clip is the pre-gh#728 behaviour: a cell past the cluster's edge is simply not painted — still focusable, still announced, invisible and unreachable by pointer. Independent of TopbarItem hideBelow, which REMOVES a cell at a step; this is only about what happens once there is no budget left.",
1723
1728
  "name": "overflow",
1724
- "type": "\"scroll\" | \"clip\""
1729
+ "type": "\"scroll\" | \"clip\" | \"menu\""
1725
1730
  },
1726
1731
  {
1727
1732
  "defaultValue": "undefined",
1728
- "description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored.",
1733
+ "description": "Escape hatch — render fully custom bar content instead of the three slots. When set, `start`/`center`/`end`/`identity` are ignored.",
1729
1734
  "name": "children",
1730
1735
  "type": "ReactNode"
1731
1736
  },
@@ -1750,6 +1755,7 @@
1750
1755
  "storyPath": "layout/topbar.tsx",
1751
1756
  "tagline": "A PURE SLOT bar for the app shell — positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",
1752
1757
  "usage": [
1758
+ "For shared devices use identity={<TopbarItem truncate icon={<Avatar aria-hidden=\"true\"><AvatarFallback>DB</AvatarFallback></Avatar>}>{user.name}</TopbarItem>}, optionally wrapped in DropdownMenuTrigger asChild. Keep the locale picker icon-only with appearance=\"bar\" in end. The name stays visible and bounded; full string children become title and accessible text. AccountChip is a separate avatar/name/action row, not a full-height menu cell.",
1753
1759
  "DO use `TopbarSearch`, not a bare `SearchInput`, for the bar's search (ADR C02): a SearchInput in a slot shrinks with the cluster — measured at 390px in a consumer bar its text area was 0ch. TopbarSearch keeps at least 8ch from 40rem up and folds to a named search cell below it.",
1754
1760
  "DO align the bar's search with the content column (ADR C11) by composition: keep AppShell's default `topbarSpan=\"content\"` and put `TopbarSearch` FIRST in `start` — the bar then starts on the rail edge with the same inset as PageContainer, measured 0px apart at 1024/1280/1440. Under `topbarSpan=\"full\"` the bar starts at the window edge after the brand, so no keyline is shared; there, put the search in `end` deliberately rather than near a column it cannot meet.",
1755
1761
  "DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",
@@ -1775,6 +1781,12 @@
1775
1781
  "importPath": "@godxjp/ui/layout",
1776
1782
  "name": "TopbarItem",
1777
1783
  "props": [
1784
+ {
1785
+ "defaultValue": "false",
1786
+ "description": "Same vocabulary as Text.truncate: allow the cell to shrink to its control minimum and ellipse only its label. Keeps the icon; overrides labelHideBelow. Full text remains accessible; string children supply title automatically (including asChild). Pass title explicitly for rich children. Use in Topbar identity for a bounded always-visible shared-device name; default phone accounts still use labelHideBelow=\"sm\".",
1787
+ "name": "truncate",
1788
+ "type": "boolean"
1789
+ },
1778
1790
  {
1779
1791
  "description": "The cell's leading glyph, placed in a slot the CELL sizes (--topbar-icon-size). Prefer it over passing the glyph as a child whenever the glyph is wrapped: `.ui-topbar-item > svg` only reaches a DIRECT child, so a glyph inside a `<Flex hideBelow>` renders at lucide's intrinsic 24px — measured at 390px on an org-switcher cell, 1.5x the cell's step (gh#712). Decorative by convention (the cell's name comes from its own aria-label); pass <Icon as={…} size=… label=… /> here when the glyph needs a name or a different step. Rendered with asChild too (gh#726): the sized slot is inserted INSIDE the child element, ahead of that element's own children, and the child element (e.g. an <a>) stays the rendered root with every merged prop.",
1780
1792
  "name": "icon",
@@ -1836,7 +1848,7 @@
1836
1848
  "storyPath": "layout/topbar-item.tsx",
1837
1849
  "tagline": "ONE interactive cell of a Topbar slot — the account button, a settings or notifications trigger. Full bar height, the bar's own hover surface, and the focus mark hosted INSIDE the cell. Use it INSTEAD OF a Button in a Topbar slot: a Button there is a --control-height pill floating in a taller bar, with its own hover fill and a ring drawn around the pill.",
1838
1850
  "usage": [
1839
- "DO collapse the account cell to its avatar on a phone (ADR C04): `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
1851
+ "By default collapse the account cell to its avatar on a phone (ADR C04); shared devices opt into Topbar identity + TopbarItem truncate instead: `<DropdownMenuTrigger asChild><TopbarItem aria-label={t(\"account\")} icon={<Avatar>…</Avatar>} labelHideBelow=\"sm\">{name}</TopbarItem></DropdownMenuTrigger>` and repeat the name (and email) in the menu's `DropdownMenuLabel`. Measured at 390px: without `labelHideBelow` the name took 74px of a 390px bar; with it the cell is 48x47, named, and the name is read in the open menu.",
1840
1852
  "DO put bar triggers in a Topbar slot as TopbarItem, not as Button — that is the difference between chrome and a control that landed in the chrome.",
1841
1853
  "DO wrap it in a DropdownMenuTrigger asChild for a user menu; the open state lights the cell via [data-state=open].",
1842
1854
  "DON'T set a height: the cell stretches to whatever the bar is (AppShell's grid row, --topbar-height, or the coarse-pointer bar), which is why there is no height knob.",
@@ -6221,6 +6233,7 @@
6221
6233
  "storyPath": "data-entry/input.tsx",
6222
6234
  "tagline": "Styled wrapper around native <input>; accepts all HTML input attributes. Pair with FormField for labelled fields.",
6223
6235
  "usage": [
6236
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The trailing slot is measured, so adjacent actions and counters reserve their combined width; allowClear still replaces trailingIcon/suffix.",
6224
6237
  "DO always wrap Input in FormField when the field needs a label, helper text, or validation error — FormField injects aria-describedby and aria-invalid onto Input automatically; never wire these attributes by hand.",
6225
6238
  "DO match the `id` prop on Input to the `id` prop on its parent FormField so that `htmlFor` linkage and the generated helper/error ids are consistent.",
6226
6239
  "DO use Input in controlled mode (`value` + `onChange`) for forms driven by Inertia's `useForm` or React state; uncontrolled usage (no `value`) is only acceptable for fire-and-forget inline edits where form state is not needed.",
@@ -6378,6 +6391,7 @@
6378
6391
  "storyPath": "data-entry/number-input.tsx",
6379
6392
  "tagline": "WAI-ARIA spinbutton for localized numeric entry — composes the real Input (role=spinbutton, inputMode=decimal) with stacked increment/decrement step Buttons. Type freely, Arrow/Shift-Arrow step, value commits clamped to min/max + rounded to precision.",
6380
6393
  "usage": [
6394
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged. The increment/decrement actions sit side by side on touch screens so each owns a non-overlapping target.",
6381
6395
  "DO use NumberInput (not `<Input type='number'>`) whenever numeric entry wants steppers, min/max clamping, precision rounding, or a ¥/% affix — it is the canonical numeric primitive. Plain Input has no stepper and no clamp.",
6382
6396
  "DO drive it controlled with `value` + `onValueChange` carrying `number | null` (the vocabulary triad — NOT `onChange`). `null` means the field is empty; never substitute 0 for empty.",
6383
6397
  "DON'T pass `value` without `onValueChange` — like every controlled @godxjp/ui input it would freeze. Omit both for uncontrolled (use `defaultValue`).",
@@ -6488,6 +6502,7 @@
6488
6502
  "storyPath": "data-entry/search-input.tsx",
6489
6503
  "tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
6490
6504
  "usage": [
6505
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
6491
6506
  "DON'T drop a bare SearchInput into a Topbar slot — use `TopbarSearch` (layout), which passes every SearchInput prop through and folds to a search cell below 40rem instead of shrinking under 8ch (ADR C02).",
6492
6507
  "DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
6493
6508
  "DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
@@ -7028,6 +7043,7 @@
7028
7043
  ],
7029
7044
  "tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
7030
7045
  "usage": [
7046
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
7031
7047
  "FREE-TEXT TAGS WITH NO LIST (keywords, email recipients, labels typed from scratch): `<Select mode=\"tags\" open={false} value={tags} onValueChange={setTags} />` — the inline chip field. Use plain `mode=\"tags\"` (popup) only when there is a list of existing tags to pick from. There is no `TagInput` component.",
7032
7048
  "DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
7033
7049
  "DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
@@ -7995,6 +8011,7 @@
7995
8011
  "storyPath": "data-entry/date-picker.tsx",
7996
8012
  "tagline": "ONE date control: `picker` sets the granularity (day · week · month · quarter · year), `range` makes it a two-endpoint field, `multiple` a set. A real typeable input holds the value and submits ISO-8601 at the picker's own precision; the panel is the visual-only affordance.",
7997
8013
  "usage": [
8014
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
7998
8015
  "ONE component covers every date-shaped field. `picker` is the granularity axis, `range` and `multiple` are the cardinality axis, and they compose: `<DatePicker range picker=\"month\" />` is a month range.",
7999
8016
  "DO use `name` to make the field form-submittable — it emits ISO-8601 at the picker's own precision (`2026-03` for a month, `2026` for a year, `2026-03-01` for a day). No hidden input of your own is needed.",
8000
8017
  "DO wrap it in FormField like every other labelled control; FormField injects the id/aria wiring onto the input.",
@@ -9653,6 +9670,7 @@
9653
9670
  "storyPath": "data-entry/time-picker.tsx",
9654
9671
  "tagline": "Time combobox with hours, minutes, optional seconds, confirmation and 12-hour formatting. Native submission stays canonical 24-hour time.",
9655
9672
  "usage": [
9673
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
9656
9674
  "One trailing icon: empty shows calendar/clock; filled and clearable shows only ×; allowClear=false retains the picker icon. Click the input or ArrowDown to open a filled picker.",
9657
9675
  "DO give it a name in native forms. It submits HH:mm or HH:mm:ss, regardless of display format; the component manages any hidden canonical field.",
9658
9676
  "DO use the controlled pattern (`value` + `onValueChange`) in React-managed forms (e.g. useForm). For simple HTML forms without React state, omit `value` and use `defaultValue` for the uncontrolled pattern.",
@@ -9868,6 +9886,7 @@
9868
9886
  "storyPath": "data-entry/cascader.tsx",
9869
9887
  "tagline": "Multi-level hierarchical path picker (Popover + cascading columns); value is always a string[] path, never a flat ID — passing a bare string breaks it.",
9870
9888
  "usage": [
9889
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
9871
9890
  "DO pass a string[] path as value in single mode (e.g. ['country','region','city']). DON'T pass a flat string ID — the component treats value as an ordered path array and will render nothing if you pass a bare string.",
9872
9891
  "DO use value + onChange together for controlled mode, or defaultValue alone for uncontrolled. DON'T mix both — providing value without onChange makes the field read-only (the internal state won't update).",
9873
9892
  "DO set multiple={true} and pass value as string[][] (array of paths) for multi-selection. onChange receives string[][] in that mode. Mixing single-mode shape with multiple={true} silently produces no selection.",
@@ -10100,6 +10119,7 @@
10100
10119
  "storyPath": "data-entry/tree-select.tsx",
10101
10120
  "tagline": "Hierarchical tree picker in a Popover (single or multi-select with checkboxes) — `onValueChange` receives `string` in single mode and `string[]` in multi/checkable mode; never use a raw `<select>` for tree-structured data.",
10102
10121
  "usage": [
10122
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
10103
10123
  "DO pair with a `<label htmlFor={id}>` and pass the matching `id` prop so screen readers announce the control correctly. The underlying trigger is a `<Button role='combobox'>` — not a native `<select>` — so an explicit label is required.",
10104
10124
  "DO use `treeCheckable` (+ optionally `showCheckedStrategy`) for selecting multiple nodes with parent–child cascade; use `multiple` only when you want multi-select WITHOUT the checkbox cascade behaviour.",
10105
10125
  "DO use the static constants `TreeSelect.SHOW_CHILD`, `TreeSelect.SHOW_PARENT`, `TreeSelect.SHOW_ALL` (or the named exports `SHOW_CHILD`/`SHOW_PARENT`/`SHOW_ALL` from the same import path) instead of raw string literals for `showCheckedStrategy`.",
@@ -12756,6 +12776,7 @@
12756
12776
  "storyPath": "data-entry/password-input.tsx",
12757
12777
  "tagline": "Input for passwords with a built-in show/hide eye toggle. Accepts all Input props except `type`.",
12758
12778
  "usage": [
12779
+ "Coarse pointers: affix actions use --control-affix-touch-target-size (44px by default); text space is reserved separately. Fine-pointer geometry is unchanged.",
12759
12780
  "DO use for any password / secret field instead of `<Input type=\"password\">` so users get the show/hide affordance.",
12760
12781
  "DO pass `name` + `autoComplete=\"current-password\"|\"new-password\"` for correct form/password-manager behavior.",
12761
12782
  "DON'T add your own eye button — it's built in (and excluded from the tab order)."
package/agent/index.json CHANGED
@@ -4,14 +4,14 @@
4
4
  "components": 169,
5
5
  "patterns": 28,
6
6
  "rules": 44,
7
- "tokens": 2235,
7
+ "tokens": 2238,
8
8
  "vocabulary": 14
9
9
  },
10
10
  "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
11
11
  "files": [
12
12
  {
13
13
  "file": "DESIGN.md",
14
- "note": "17 KB — the visual spec: tokens, colour scarcity, admin screen anatomy (list, detail, form, settings, dashboard), do/don't. Read before composing a screen.",
14
+ "note": "18 KB — the visual spec: tokens, colour scarcity, admin screen anatomy (list, detail, form, settings, dashboard), do/don't. Read before composing a screen.",
15
15
  "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md"
16
16
  },
17
17
  {
@@ -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/v33.9.1/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v33.11.2/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": 1906,
64
+ "component": 1909,
65
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": "33.9.1"
71
+ "version": "33.11.2"
72
72
  }
package/agent/llms.txt CHANGED
@@ -1,17 +1,17 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 169 components, 2235 design tokens,
4
- > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.9.1.
3
+ > A Japanese-enterprise React design system: 169 components, 2238 design tokens,
4
+ > 44 cardinal rules. This file is the entry point for AI agents. Catalog version 33.11.2.
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@33.9.1`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@33.11.2`). It is searchable and version-locked. These files exist for agents
8
8
  that can only fetch URLs.
9
9
 
10
10
  ## Start
11
11
 
12
12
  - [START-HERE.md](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md): the whole contract — the four rules, the token override model, a page that passes review. Read this first; it is self-contained.
13
13
  - [index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json): version, counts, and the URL of every file.
14
- - [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 (list, detail, form, settings, dashboard), do/don't. Read it before composing a screen.
14
+ - [DESIGN.md](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md): 18 KB — the visual spec: tokens, colour scarcity, admin screen anatomy (list, detail, form, settings, dashboard), do/don't. Read it before composing a screen.
15
15
 
16
16
  ## Catalog
17
17
 
@@ -19,7 +19,7 @@ that can only fetch URLs.
19
19
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 52 KB — all 169 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.3 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` — 216 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 113 `semantic` roles, 1906 `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, 1909 `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): 44 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/v33.9.1/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v33.11.2/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.