@godxjp/ui 23.4.1 → 23.4.3

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 (40) hide show
  1. package/README.md +17 -2
  2. package/dist/components/data-display/badge.d.ts +14 -1
  3. package/dist/components/data-display/badge.js +21 -1
  4. package/dist/components/data-display/data-table.d.ts +4 -2
  5. package/dist/components/data-display/data-table.js +14 -2
  6. package/dist/components/data-entry/date-picker.js +4 -16
  7. package/dist/components/layout/draggable-panel.d.ts +3 -2
  8. package/dist/components/layout/draggable-panel.js +11 -2
  9. package/dist/components/layout/index.d.ts +1 -1
  10. package/dist/components/navigation/filter-bar.js +11 -16
  11. package/dist/components/ui/segmented.d.ts +19 -1
  12. package/dist/components/ui/segmented.js +34 -2
  13. package/dist/props/components/data-display.prop.d.ts +5 -0
  14. package/dist/props/components/index.d.ts +1 -1
  15. package/dist/props/components/layout.prop.d.ts +13 -2
  16. package/dist/props/registry.d.ts +5 -1
  17. package/dist/props/registry.js +6 -1
  18. package/dist/props/vocabulary/data.prop.d.ts +11 -0
  19. package/dist/styles/badge-layout.css +25 -0
  20. package/dist/styles/control.css +37 -0
  21. package/dist/styles/core-with-fallbacks.css +3 -0
  22. package/dist/styles/focus-ring.css +2 -0
  23. package/dist/styles/font-fallbacks.css +83 -0
  24. package/dist/styles/fonts.css +1 -82
  25. package/dist/styles/layout.css +17 -5
  26. package/dist/tokens/components/badge.css +6 -0
  27. package/dist/tokens/components/draggable-panel.css +4 -0
  28. package/dist/tokens/components/segmented.css +8 -0
  29. package/docs/CONSUMER-RULES.md +1 -1
  30. package/docs/CUSTOMER-THEMING.md +16 -2
  31. package/docs/DESIGN-AUTHORITY.md +15 -0
  32. package/docs/data-entry/segmented-in-filter-row.tsx +2 -19
  33. package/docs/data-entry/segmented.tsx +2 -19
  34. package/package.json +4 -2
  35. package/scripts/_agent-setup.mjs +90 -17
  36. package/scripts/cli.mjs +6 -2
  37. package/scripts/consumer-rule.md +76 -18
  38. package/scripts/guinea-pig-skill.md +38 -9
  39. package/scripts/postinstall.mjs +1 -1
  40. package/scripts/ui-audit.mjs +1 -1
@@ -0,0 +1,83 @@
1
+
2
+ @font-face {
3
+ font-family: "Noto Sans JP Fallback";
4
+ font-weight: 400;
5
+ src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
6
+ size-adjust: 103.02%;
7
+ ascent-override: 112.6%;
8
+ descent-override: 27.96%;
9
+ line-gap-override: 0%;
10
+ unicode-range:
11
+ U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
12
+ U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
13
+ U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
14
+ }
15
+ @font-face {
16
+ font-family: "Noto Sans JP Fallback";
17
+ font-weight: 500;
18
+ src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
19
+ size-adjust: 105.5%;
20
+ ascent-override: 109.95%;
21
+ descent-override: 27.3%;
22
+ line-gap-override: 0%;
23
+ unicode-range:
24
+ U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
25
+ U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
26
+ U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
27
+ }
28
+ @font-face {
29
+ font-family: "Noto Sans JP Fallback";
30
+ font-weight: 700;
31
+ src:
32
+ local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"),
33
+ local("LiberationSans-Bold");
34
+ size-adjust: 101.5%;
35
+ ascent-override: 114.29%;
36
+ descent-override: 28.37%;
37
+ line-gap-override: 0%;
38
+ unicode-range:
39
+ U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
40
+ U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
41
+ U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
42
+ }
43
+ @font-face {
44
+ font-family: "Noto Sans JP Fallback";
45
+ font-weight: 400;
46
+ src:
47
+ local("Hiragino Sans W3"), local("HiraginoSans-W3"), local("Hiragino Kaku Gothic ProN W3"),
48
+ local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
49
+ local("Noto Sans CJK JP"), local("NotoSansCJKjp-Regular"), local("IPAGothic");
50
+ ascent-override: 116%;
51
+ descent-override: 28.8%;
52
+ line-gap-override: 0%;
53
+ unicode-range:
54
+ U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
55
+ }
56
+ @font-face {
57
+ font-family: "Noto Sans JP Fallback";
58
+ font-weight: 500;
59
+ src:
60
+ local("Hiragino Sans W5"), local("HiraginoSans-W5"), local("Hiragino Kaku Gothic ProN W3"),
61
+ local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
62
+ local("Noto Sans CJK JP Medium"), local("NotoSansCJKjp-Medium"), local("Noto Sans CJK JP"),
63
+ local("NotoSansCJKjp-Regular"), local("IPAGothic");
64
+ ascent-override: 116%;
65
+ descent-override: 28.8%;
66
+ line-gap-override: 0%;
67
+ unicode-range:
68
+ U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
69
+ }
70
+ @font-face {
71
+ font-family: "Noto Sans JP Fallback";
72
+ font-weight: 700;
73
+ src:
74
+ local("Hiragino Sans W6"), local("HiraginoSans-W6"), local("Hiragino Kaku Gothic ProN W6"),
75
+ local("HiraKakuProN-W6"), local("Yu Gothic Bold"), local("YuGothic-Bold"), local("Meiryo Bold"),
76
+ local("Meiryo-Bold"), local("Noto Sans CJK JP Bold"), local("NotoSansCJKjp-Bold"),
77
+ local("IPAGothic");
78
+ ascent-override: 116%;
79
+ descent-override: 28.8%;
80
+ line-gap-override: 0%;
81
+ unicode-range:
82
+ U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
83
+ }
@@ -6,88 +6,7 @@
6
6
  @import "@fontsource/noto-sans-jp/500.css";
7
7
  @import "@fontsource/noto-sans-jp/700.css";
8
8
 
9
- @font-face {
10
- font-family: "Noto Sans JP Fallback";
11
- font-weight: 400;
12
- src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
13
- size-adjust: 103.02%;
14
- ascent-override: 112.6%;
15
- descent-override: 27.96%;
16
- line-gap-override: 0%;
17
- unicode-range:
18
- U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
19
- U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
20
- U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
21
- }
22
- @font-face {
23
- font-family: "Noto Sans JP Fallback";
24
- font-weight: 500;
25
- src: local("Arial"), local("ArialMT"), local("Liberation Sans"), local("LiberationSans");
26
- size-adjust: 105.5%;
27
- ascent-override: 109.95%;
28
- descent-override: 27.3%;
29
- line-gap-override: 0%;
30
- unicode-range:
31
- U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
32
- U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
33
- U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
34
- }
35
- @font-face {
36
- font-family: "Noto Sans JP Fallback";
37
- font-weight: 700;
38
- src:
39
- local("Arial Bold"), local("Arial-BoldMT"), local("Liberation Sans Bold"),
40
- local("LiberationSans-Bold");
41
- size-adjust: 101.5%;
42
- ascent-override: 114.29%;
43
- descent-override: 28.37%;
44
- line-gap-override: 0%;
45
- unicode-range:
46
- U+0000-02CC, U+02CE-02D7, U+02DA, U+02DC-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
47
- U+1D00-1DBF, U+1E00-1EFF, U+2000-206F, U+20A0-20C0, U+2113, U+2122, U+2191, U+2193, U+2212,
48
- U+2215, U+2C60-2C7F, U+A720-A7FF, U+FEFF, U+FFFD;
49
- }
50
- @font-face {
51
- font-family: "Noto Sans JP Fallback";
52
- font-weight: 400;
53
- src:
54
- local("Hiragino Sans W3"), local("HiraginoSans-W3"), local("Hiragino Kaku Gothic ProN W3"),
55
- local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
56
- local("Noto Sans CJK JP"), local("NotoSansCJKjp-Regular"), local("IPAGothic");
57
- ascent-override: 116%;
58
- descent-override: 28.8%;
59
- line-gap-override: 0%;
60
- unicode-range:
61
- U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
62
- }
63
- @font-face {
64
- font-family: "Noto Sans JP Fallback";
65
- font-weight: 500;
66
- src:
67
- local("Hiragino Sans W5"), local("HiraginoSans-W5"), local("Hiragino Kaku Gothic ProN W3"),
68
- local("HiraKakuProN-W3"), local("Yu Gothic Medium"), local("YuGothic-Medium"), local("Meiryo"),
69
- local("Noto Sans CJK JP Medium"), local("NotoSansCJKjp-Medium"), local("Noto Sans CJK JP"),
70
- local("NotoSansCJKjp-Regular"), local("IPAGothic");
71
- ascent-override: 116%;
72
- descent-override: 28.8%;
73
- line-gap-override: 0%;
74
- unicode-range:
75
- U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
76
- }
77
- @font-face {
78
- font-family: "Noto Sans JP Fallback";
79
- font-weight: 700;
80
- src:
81
- local("Hiragino Sans W6"), local("HiraginoSans-W6"), local("Hiragino Kaku Gothic ProN W6"),
82
- local("HiraKakuProN-W6"), local("Yu Gothic Bold"), local("YuGothic-Bold"), local("Meiryo Bold"),
83
- local("Meiryo-Bold"), local("Noto Sans CJK JP Bold"), local("NotoSansCJKjp-Bold"),
84
- local("IPAGothic");
85
- ascent-override: 116%;
86
- descent-override: 28.8%;
87
- line-gap-override: 0%;
88
- unicode-range:
89
- U+3000-30FF, U+31F0-31FF, U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+FF00-FFEF, U+20000-2FA1F;
90
- }
9
+ @import "./font-fallbacks.css";
91
10
 
92
11
  :root {
93
12
  --font-sans-base:
@@ -1238,7 +1238,6 @@
1238
1238
  .ui-filter-bar-chip {
1239
1239
  display: inline-flex;
1240
1240
  align-items: center;
1241
- gap: var(--space-1);
1242
1241
  min-width: 0;
1243
1242
  }
1244
1243
 
@@ -1417,28 +1416,36 @@
1417
1416
 
1418
1417
  @media (width < 40rem) {
1419
1418
  .ui-flex[data-hide-below="sm"],
1420
- .ui-topbar-item[data-hide-below="sm"] {
1419
+ .ui-topbar-item[data-hide-below="sm"],
1420
+ [data-slot="table-head"][data-hide-below="sm"],
1421
+ [data-slot="table-cell"][data-hide-below="sm"] {
1421
1422
  display: none;
1422
1423
  }
1423
1424
  }
1424
1425
 
1425
1426
  @media (width < 48rem) {
1426
1427
  .ui-flex[data-hide-below="md"],
1427
- .ui-topbar-item[data-hide-below="md"] {
1428
+ .ui-topbar-item[data-hide-below="md"],
1429
+ [data-slot="table-head"][data-hide-below="md"],
1430
+ [data-slot="table-cell"][data-hide-below="md"] {
1428
1431
  display: none;
1429
1432
  }
1430
1433
  }
1431
1434
 
1432
1435
  @media (width < 64rem) {
1433
1436
  .ui-flex[data-hide-below="lg"],
1434
- .ui-topbar-item[data-hide-below="lg"] {
1437
+ .ui-topbar-item[data-hide-below="lg"],
1438
+ [data-slot="table-head"][data-hide-below="lg"],
1439
+ [data-slot="table-cell"][data-hide-below="lg"] {
1435
1440
  display: none;
1436
1441
  }
1437
1442
  }
1438
1443
 
1439
1444
  @media (width < 80rem) {
1440
1445
  .ui-flex[data-hide-below="xl"],
1441
- .ui-topbar-item[data-hide-below="xl"] {
1446
+ .ui-topbar-item[data-hide-below="xl"],
1447
+ [data-slot="table-head"][data-hide-below="xl"],
1448
+ [data-slot="table-cell"][data-hide-below="xl"] {
1442
1449
  display: none;
1443
1450
  }
1444
1451
  }
@@ -1792,6 +1799,7 @@
1792
1799
  flex-direction: column;
1793
1800
  inline-size: var(--draggable-panel-width);
1794
1801
  max-inline-size: calc(100% - var(--draggable-panel-inset) - var(--draggable-panel-inset));
1802
+ block-size: var(--draggable-panel-block-size);
1795
1803
  max-block-size: var(--draggable-panel-max-block-size);
1796
1804
  border: var(--draggable-panel-border-width) solid hsl(var(--border));
1797
1805
  border-radius: var(--draggable-panel-radius);
@@ -1811,6 +1819,10 @@
1811
1819
  inline-size: var(--draggable-panel-width-lg);
1812
1820
  }
1813
1821
 
1822
+ .ui-draggable-panel[data-width="xl"] {
1823
+ inline-size: var(--draggable-panel-width-xl);
1824
+ }
1825
+
1814
1826
  .ui-draggable-panel[data-placement^="top"] {
1815
1827
  inset-block-start: var(--draggable-panel-inset);
1816
1828
  }
@@ -12,6 +12,12 @@
12
12
 
13
13
  --badge-icon-size: var(--icon-size-xs);
14
14
 
15
+ --badge-space-x-removable: var(--space-1);
16
+ --badge-remove-offset-inline-end: calc(-1 * var(--space-1));
17
+ --badge-remove-icon-size: var(--badge-icon-size);
18
+ --badge-remove-rest-alpha: 0.7;
19
+ --badge-remove-hover-alpha: 1;
20
+
15
21
  --badge-tint-fill: 18%;
16
22
  --badge-tint-edge: 45%;
17
23
 
@@ -12,6 +12,10 @@
12
12
  --draggable-panel-width-sm: 18rem;
13
13
  --draggable-panel-width: 22rem;
14
14
  --draggable-panel-width-lg: 28rem;
15
+
16
+ --draggable-panel-width-xl: var(--centered-shell-width-md);
17
+
18
+ --draggable-panel-block-size: auto;
15
19
  --draggable-panel-max-block-size: 70vh;
16
20
 
17
21
  --draggable-panel-radius: var(--radius-lg);
@@ -22,4 +22,12 @@
22
22
 
23
23
  --segmented-item-selected-background: var(--background);
24
24
  --segmented-item-selected-shadow: var(--shadow-sm);
25
+
26
+ --segmented-count-min-width: var(--button-count-min-width);
27
+ --segmented-count-space-inline: var(--button-count-space-inline);
28
+ --segmented-count-font-size: var(--button-count-font-size);
29
+ --segmented-count-radius: var(--radius-pill);
30
+ --segmented-count-gap: var(--space-1);
31
+ --segmented-count-background: initial;
32
+ --segmented-count-color: initial;
25
33
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Read this once; the audit enforces it. Everything else in `docs/` is for contributors.
4
4
 
5
- 1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled) or `@import "@godxjp/ui/styles/core"` (no fonts). Never cherry-pick `*-layout.css`.
5
+ 1. Load styles with `@import "@godxjp/ui/styles"` (fonts bundled), `@import "@godxjp/ui/styles/core"` (no `@font-face` at all) or `@import "@godxjp/ui/styles/core-with-fallbacks"` (`core` + six `local()`-only metric-matched faces, still zero network bytes — for when you supply Noto Sans JP yourself). Never cherry-pick `*-layout.css`.
6
6
  2. Every page is `<PageContainer title subtitle extra footer>`; its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
7
7
  3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
8
8
  4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`.
@@ -16,14 +16,28 @@ That single import ships everything: colors, the bundled fonts (**Noto Sans JP**
16
16
 
17
17
  ---
18
18
 
19
- ## CSS entries — `styles` or `styles/core`, nothing smaller
19
+ ## CSS entries — `styles`, `styles/core`, `styles/core-with-fallbacks`, nothing smaller
20
20
 
21
- `@godxjp/ui/styles` bundles every component's CSS plus the fonts. When you manage fonts yourself (next/font, a system stack, a browser extension that must not ship font files), load the same layers without the faces:
21
+ `@godxjp/ui/styles` bundles every component's CSS plus the fonts: **729 woff2 subsets, ~11.7 MB on disk** at the current @fontsource versions (issue #535 measured 737 files / 13 MB in a real consumer build). When you manage fonts yourself (next/font, a system stack, a browser extension that must not ship font files), load the same layers without the faces:
22
22
 
23
23
  ```css
24
24
  @import "@godxjp/ui/styles/core"; /* every component layer, no @font-face */
25
25
  ```
26
26
 
27
+ `core` carries **zero** `@font-face` — `grep -c '@font-face' node_modules/@godxjp/ui/dist/styles/core.css` → `0` — and that number is the point of it. If you supply Noto Sans JP yourself and also want the cold-visit swap to stop reflowing the page (issue #475), take the third entry: `core` plus the six metric-matched fallback faces, every one `local()`-only, so the extra cost over `core` is **zero network bytes**.
28
+
29
+ ```css
30
+ @import "@godxjp/ui/styles/core-with-fallbacks"; /* core + 6 local()-only faces */
31
+ ```
32
+
33
+ It declares the faces and nothing else — name the family yourself, directly after your own face:
34
+
35
+ ```css
36
+ :root {
37
+ --font-sans-base: "Noto Sans JP", "Noto Sans JP Fallback", system-ui, sans-serif;
38
+ }
39
+ ```
40
+
27
41
  The per-layer files (`control`, `card-layout`, `navigation-layout`, …) are the package's internal structure, **not a public menu**. Layers share rules — a Select's rows and a menu's surface, a form's rhythm, a card's header type — so a page that loads a subset renders naked menus and unsized rows with no error. The runtime `visual-audit` reports it as `css-layers-missing`.
28
42
 
29
43
  ---
@@ -242,6 +242,21 @@ in page CSS.
242
242
  - **A capability this library already has keeps its own name.** antd's `size`
243
243
  (`small | middle | large`) IS `density` (`compact | default | comfortable`); antd's `locale` IS
244
244
  the `t()` layer. Adding the antd spelling as an alias would be duplication, not parity.
245
+ - **`SegmentedOption.count`.** antd's `SegmentedItemType` has no count field — consumers embed counts
246
+ in `label`. Nested `Badge` `secondary` is `--muted`, which matches the Segmented track
247
+ (gh#602, measured 1.00:1). This library adds `count` / `overflowCount` / `showZero` /
248
+ `countLabel` on `SegmentedOption` with a DS-owned pill (`--segmented-count-*` tokens), same
249
+ counter vocabulary as `Button` / `Toggle`.
250
+ - **`DraggablePanel.labels`.** antd's Draggable Modal demo has no i18n hook for the drag handle.
251
+ Embedded consumers cannot mount `AppProvider` without writing theme attributes on the host
252
+ `document.documentElement` (gh#606). `labels?: { close?: string; move?: string }` overrides `t()`
253
+ per control; a scoped `AppProvider` subtree remains the long-term fix.
254
+ - **`Badge.onRemove` instead of antd `Tag`'s `closable` + `onClose`.** Semantics match antd's
255
+ closable tag (a chip that draws its own × and fires one callback). The boolean is folded into
256
+ prop presence (`onRemove` omitted ⇒ no ×), and the callback is named `onRemove` rather than
257
+ `onClose`, because `onClose` already means overlay dismiss across Dialog/Drawer and would read
258
+ as closing a surface, not removing one applied filter chip. Implemented on `Badge` — the DS chip
259
+ primitive — rather than adding a separate `Tag` export beside `Badge`.
245
260
 
246
261
  **A knob that only a fork could reach is not parity either.** antd's `components`,
247
262
  `filterDropdown`, `classNames`/`styles` semantic maps and `prefixCls` all exist to let a consumer
@@ -1,11 +1,4 @@
1
- import {
2
- Badge,
3
- Card,
4
- CardContent,
5
- CardDescription,
6
- CardHeader,
7
- CardTitle,
8
- } from "@godxjp/ui/data-display";
1
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
9
2
  import { Segmented } from "@godxjp/ui/data-entry";
10
3
  import { Button, Text } from "@godxjp/ui/general";
11
4
  import { Flex, PageContainer } from "@godxjp/ui/layout";
@@ -24,17 +17,7 @@ const OPTIONS = [
24
17
  { value: "ginou", label: "技能実習", count: 82 },
25
18
  { value: "tokutei", label: "特定技能", count: 51 },
26
19
  { value: "ikusei", label: "育成就労", count: 0 },
27
- ].map(({ value, label, count }) => ({
28
- value,
29
- label: (
30
- <>
31
- {label}
32
- <Badge as="span" variant="secondary">
33
- {count}
34
- </Badge>
35
- </>
36
- ),
37
- }));
20
+ ];
38
21
 
39
22
  export default function SegmentedInFilterRow() {
40
23
  return (
@@ -1,13 +1,6 @@
1
1
  import { useState } from "react";
2
2
 
3
- import {
4
- Badge,
5
- Card,
6
- CardContent,
7
- CardDescription,
8
- CardHeader,
9
- CardTitle,
10
- } from "@godxjp/ui/data-display";
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
11
4
  import { FormField, Segmented } from "@godxjp/ui/data-entry";
12
5
  import { Text, VisuallyHidden } from "@godxjp/ui/general";
13
6
  import { Flex, PageContainer } from "@godxjp/ui/layout";
@@ -108,17 +101,7 @@ export default function Demo() {
108
101
  { value: "active", label: "実習中", count: 96 },
109
102
  { value: "pending", label: "申請中", count: 12 },
110
103
  { value: "gone", label: "失踪・帰国", count: 0 },
111
- ].map(({ value, label, count }) => ({
112
- value,
113
- label: (
114
- <>
115
- {label}
116
- <Badge as="span" variant="secondary">
117
- {count}
118
- </Badge>
119
- </>
120
- ),
121
- }))}
104
+ ]}
122
105
  />
123
106
  </CardContent>
124
107
  </Card>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "23.4.1",
4
- "godxUiMcp": "23.4.1",
3
+ "version": "23.4.3",
4
+ "godxUiMcp": "23.4.3",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -46,6 +46,8 @@
46
46
  "import": "./dist/index.js"
47
47
  },
48
48
  "./styles": "./dist/styles/index.css",
49
+ "./styles/core": "./dist/styles/core.css",
50
+ "./styles/core-with-fallbacks": "./dist/styles/core-with-fallbacks.css",
49
51
  "./styles/*": "./dist/styles/*.css",
50
52
  "./theme/*": "./dist/theme/*",
51
53
  "./tokens": "./dist/tokens/base.css",
@@ -24,13 +24,37 @@ import {
24
24
  import { basename, dirname, join } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
 
27
- /** The godxjp-ui MCP server — pulled on demand via npx (no extra dependency to ship). */
28
27
  /** This package's own root — the source of the version we stamp with. */
29
28
  const SELF_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
30
29
 
31
- export const MCP_SERVER = { command: "npx", args: ["@godxjp/ui-mcp"] };
32
30
  export const MCP_KEY = "godx-ui";
33
31
 
32
+ /** @deprecated Prefer `mcpServerFor(root)` — bare npx resolves latest on the registry (gh#543). */
33
+ export const MCP_SERVER = { command: "npx", args: ["@godxjp/ui-mcp"] };
34
+
35
+ function mcpEntryMatches(a, b) {
36
+ if (a?.command !== b?.command) return false;
37
+ const argsA = a?.args ?? [];
38
+ const argsB = b?.args ?? [];
39
+ if (argsA.length !== argsB.length || argsA.some((x, i) => x !== argsB[i])) return false;
40
+ const envA = a?.env ?? {};
41
+ const envB = b?.env ?? {};
42
+ const keysA = Object.keys(envA).sort();
43
+ const keysB = Object.keys(envB).sort();
44
+ if (keysA.length !== keysB.length || keysA.some((k, i) => k !== keysB[i])) return false;
45
+ return keysA.every((k) => envA[k] === envB[k]);
46
+ }
47
+
48
+ function mcpConfigMismatchMessage(root, existing, expected) {
49
+ const ui = readConsumerUiMetadata(root);
50
+ const pin = expected.args?.[0] ?? "@godxjp/ui-mcp";
51
+ const ver = ui?.version ?? "(unknown)";
52
+ return (
53
+ `present (custom godx-ui MCP entry — not overwritten; expected ${pin} with ` +
54
+ `env.GODX_UI_VERSION=${ver} from node_modules/@godxjp/ui)`
55
+ );
56
+ }
57
+
34
58
  /** Commands wired into the consumer's .claude/settings.json. */
35
59
  export const AUDIT_HOOK_CMD = "node node_modules/@godxjp/ui/scripts/audit-hook.mjs";
36
60
  export const PRIMER_CMD = "cat .claude/godxjp-ui-workflow.md";
@@ -46,6 +70,31 @@ const SUGGESTED_HOOKS = {
46
70
  /** The per-session workflow mandate the SessionStart hook injects into the agent. */
47
71
  export const KIT_VERSION = readJson(join(SELF_ROOT, "package.json"))?.version ?? "0.0.0";
48
72
 
73
+ /**
74
+ * Read the consumer's installed @godxjp/ui metadata (postinstall runs with UI already in
75
+ * node_modules). Returns null when absent — caller falls back to this package's release train.
76
+ */
77
+ export function readConsumerUiMetadata(root) {
78
+ const uiPkg = readJson(join(root, "node_modules/@godxjp/ui/package.json"));
79
+ if (!uiPkg?.version) return null;
80
+ return {
81
+ version: String(uiPkg.version),
82
+ godxUiMcp: uiPkg.godxUiMcp ? String(uiPkg.godxUiMcp) : String(uiPkg.version),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * MCP launch config for a consumer root: pin @godxjp/ui-mcp to godxUiMcp from the installed UI
88
+ * package and pass the real UI version via env (gh#543 — MCP must not read the repo to learn it).
89
+ */
90
+ export function mcpServerFor(root) {
91
+ const meta = readConsumerUiMetadata(root);
92
+ const mcpPin = meta?.godxUiMcp ?? KIT_VERSION;
93
+ const server = { command: "npx", args: [`@godxjp/ui-mcp@${mcpPin}`] };
94
+ if (meta?.version) server.env = { GODX_UI_VERSION: meta.version };
95
+ return server;
96
+ }
97
+
49
98
  const STAMP = (v) => `<!-- godxjp-ui:version ${v} -->`;
50
99
  const STAMP_RE = /<!-- godxjp-ui:version ([^\s]+) -->/;
51
100
 
@@ -76,6 +125,18 @@ export function stampedDigest(text) {
76
125
  return text?.match(DIGEST_RE)?.[1] ?? null;
77
126
  }
78
127
 
128
+ /**
129
+ * Refresh only the human-readable release marker when the managed body is already current.
130
+ *
131
+ * The writer keys off digest; `ui-audit` keys off version (godx-jp/id#513). When rule text is
132
+ * unchanged across releases both must still agree after sync — without rewriting the body or
133
+ * breaking idempotency when digest and version already match.
134
+ */
135
+ function resyncVersionStamp(text) {
136
+ if (stampedVersion(text) === KIT_VERSION) return null;
137
+ return text.replace(STAMP_RE, `<!-- godxjp-ui:version ${KIT_VERSION} -->`);
138
+ }
139
+
79
140
  /**
80
141
  * Replace the MANAGED region of a file and leave everything else alone.
81
142
  *
@@ -291,13 +352,11 @@ export function refreshBlock(current, next, startMarker, endMarker) {
291
352
 
292
353
  export function ensureMcpJson(root) {
293
354
  const path = join(root, ".mcp.json");
355
+ const expected = mcpServerFor(root);
356
+ const suggested = JSON.stringify({ mcpServers: { [MCP_KEY]: expected } }, null, 2) + "\n";
294
357
  const read = readJsonFile(path);
295
358
  if (read.state !== "ok" && read.state !== "missing") {
296
- return refuseAndSuggest(
297
- path,
298
- JSON.stringify({ mcpServers: { [MCP_KEY]: MCP_SERVER } }, null, 2) + "\n",
299
- READ_FAILURE[read.state],
300
- );
359
+ return refuseAndSuggest(path, suggested, READ_FAILURE[read.state]);
301
360
  }
302
361
  const json = read.state === "ok" ? read.json : {};
303
362
  if (
@@ -306,16 +365,17 @@ export function ensureMcpJson(root) {
306
365
  typeof json.mcpServers !== "object" ||
307
366
  Array.isArray(json.mcpServers))
308
367
  ) {
309
- return refuseAndSuggest(
310
- path,
311
- JSON.stringify({ mcpServers: { [MCP_KEY]: MCP_SERVER } }, null, 2) + "\n",
312
- "`mcpServers` is not an object",
313
- );
368
+ return refuseAndSuggest(path, suggested, "`mcpServers` is not an object");
314
369
  }
315
370
  json.mcpServers = json.mcpServers ?? {};
316
- if (json.mcpServers[MCP_KEY]) return "present";
371
+ if (json.mcpServers[MCP_KEY]) {
372
+ if (!mcpEntryMatches(json.mcpServers[MCP_KEY], expected)) {
373
+ return mcpConfigMismatchMessage(root, json.mcpServers[MCP_KEY], expected);
374
+ }
375
+ return "present";
376
+ }
317
377
  const created = read.state === "missing";
318
- json.mcpServers[MCP_KEY] = MCP_SERVER;
378
+ json.mcpServers[MCP_KEY] = expected;
319
379
  writeFileAtomic(path, JSON.stringify(json, null, 2) + "\n");
320
380
  return created ? "created" : "added";
321
381
  }
@@ -485,7 +545,14 @@ export function refreshGuineaPigSkill(root) {
485
545
  * to anyone reading it. Only the digest half is compared.
486
546
  */
487
547
  const stamp = `${KIT_VERSION}:${digestOf(base)}`;
488
- if (readFileSync(optin, "utf8").trim().split(":").pop() === digestOf(base)) return false;
548
+ const optinLine = readFileSync(optin, "utf8").trim();
549
+ const optinDigest = optinLine.split(":").pop();
550
+ if (optinDigest === digestOf(base)) {
551
+ if (optinLine.split(":")[0] === KIT_VERSION) return false;
552
+ // Base unchanged — only the release half of `<version>:<digest>` drifted (same split as above).
553
+ writeFileAtomic(optin, `${stamp}\n`);
554
+ return true;
555
+ }
489
556
 
490
557
  const current = readFileSync(target, "utf8");
491
558
  const marker = "\n---\n\n# 8. ";
@@ -533,8 +600,14 @@ export function ensureConsumerRules(root) {
533
600
  const managed = `${front}${body}`;
534
601
  const next = `${OWNED_STAMP(managed)}\n${managed}`;
535
602
 
536
- if (existsSync(target) && stampedDigest(readFileSync(target, "utf8")) === digestOf(managed)) {
537
- return false;
603
+ if (existsSync(target)) {
604
+ const current = readFileSync(target, "utf8");
605
+ if (stampedDigest(current) === digestOf(managed)) {
606
+ const resynced = resyncVersionStamp(current);
607
+ if (resynced == null) return false;
608
+ writeFileAtomic(target, resynced);
609
+ return uiDir;
610
+ }
538
611
  }
539
612
  mkdirSync(dir, { recursive: true });
540
613
  writeFileAtomic(target, next);
package/scripts/cli.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * @godxjp/ui CLI — `npx @godxjp/ui <command>`.
4
4
  * init-agent scaffold the agent forcing-kit (MCP + auto-audit hook + mandate)
5
+ * sync-rules refresh package-owned agent rules (same path as postinstall)
5
6
  * audit static UI audit (regex over source)
6
7
  * visual-audit runtime audit (Playwright + axe-core) against a running app
7
8
  */
@@ -12,6 +13,7 @@ import { fileURLToPath } from "node:url";
12
13
  const HERE = dirname(fileURLToPath(import.meta.url));
13
14
  const MAP = {
14
15
  "init-agent": "init-agent-kit.mjs",
16
+ "sync-rules": "postinstall.mjs",
15
17
  audit: "ui-audit.mjs",
16
18
  "visual-audit": "visual-audit.mjs",
17
19
  };
@@ -19,8 +21,10 @@ const MAP = {
19
21
  const [cmd, ...rest] = process.argv.slice(2);
20
22
  const script = MAP[cmd];
21
23
  if (!script) {
22
- console.error("usage: godxjp-ui <init-agent | audit | visual-audit> [args]");
24
+ console.error("usage: godxjp-ui <init-agent | sync-rules | audit | visual-audit> [args]");
23
25
  process.exit(1);
24
26
  }
25
- const r = spawnSync("node", [join(HERE, script), ...rest], { stdio: "inherit" });
27
+ const env =
28
+ cmd === "sync-rules" ? { ...process.env, INIT_CWD: process.env.INIT_CWD ?? process.cwd() } : process.env;
29
+ const r = spawnSync("node", [join(HERE, script), ...rest], { stdio: "inherit", env });
26
30
  process.exit(r.status ?? 0);