@godxjp/ui 32.5.0 → 32.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/DESIGN.md +210 -0
  2. package/README.md +3 -2
  3. package/agent/DESIGN.md +210 -0
  4. package/agent/START-HERE.md +25 -29
  5. package/agent/anti-ai-tells.json +56 -2
  6. package/agent/components/Anchor.json +1 -1
  7. package/agent/components/ChatSuggestion.json +1 -1
  8. package/agent/components/LegalDocumentShell.json +7 -1
  9. package/agent/components/PageContainer.json +1 -2
  10. package/agent/components/Progress.json +1 -1
  11. package/agent/components-index.json +1 -1
  12. package/agent/components.json +11 -6
  13. package/agent/index.json +11 -5
  14. package/agent/llms.txt +7 -6
  15. package/agent/patterns/badge-coloring.json +2 -2
  16. package/agent/patterns/common-fixes.json +1 -1
  17. package/agent/patterns/confirm-destructive.json +2 -2
  18. package/agent/patterns/inertia-detail-page.json +2 -2
  19. package/agent/patterns/inertia-list-page.json +2 -2
  20. package/agent/patterns/page-sections.json +5 -3
  21. package/agent/patterns/settings-section-rows.json +2 -2
  22. package/agent/patterns-index.json +9 -7
  23. package/agent/patterns.json +16 -14
  24. package/agent/tokens.json +12 -0
  25. package/dist/components/data-entry/select.js +5 -4
  26. package/dist/components/layout/page-container.js +14 -8
  27. package/dist/contracts/measurement.json +1 -1
  28. package/dist/i18n/messages/en.json +2 -1
  29. package/dist/i18n/messages/ja.json +2 -1
  30. package/dist/i18n/messages/vi.json +2 -1
  31. package/dist/lab/legal-document-shell.js +7 -3
  32. package/dist/props/components/layout.prop.d.ts +13 -1
  33. package/dist/props/registry.d.ts +4 -0
  34. package/dist/props/registry.js +5 -0
  35. package/dist/styles/layers.json +1 -1
  36. package/dist/styles/layout.css +26 -0
  37. package/dist/styles.css +1 -1
  38. package/dist/tokens/components/legal-document.css +4 -0
  39. package/docs/CONSUMER-RULES.md +6 -5
  40. package/docs/README.md +1 -1
  41. package/docs/migrations/v32.md +10 -5
  42. package/package.json +3 -2
  43. package/scripts/_agent-setup.mjs +149 -85
  44. package/scripts/cli.mjs +8 -0
  45. package/scripts/consumer-rule.md +41 -77
  46. package/scripts/ui-audit.mjs +189 -29
@@ -71,7 +71,7 @@
71
71
  "type": "(event: React.MouseEvent<HTMLAnchorElement>, item: AnchorItemProp) => void"
72
72
  },
73
73
  {
74
- "description": "Accessible name of the <nav> landmark. antd ships an unnamed <div>; a page routinely carries a breadcrumb, a rail and this, so a localized default (\"On this page\") applies when omitted.",
74
+ "description": "SCREEN-READER-ONLY accessible name of the <nav> landmark — it is never drawn (unlike Progress `label`, which is a visible caption). Need a visible heading over the links? Render a Heading yourself and keep this as the landmark name. antd ships an unnamed <div>; a page routinely carries a breadcrumb, a rail and this, so a localized default (\"On this page\") applies when omitted.",
75
75
  "name": "label",
76
76
  "type": "string"
77
77
  },
@@ -92,7 +92,7 @@
92
92
  6
93
93
  ],
94
94
  "storyPath": "data-entry/chat-composer.tsx",
95
- "tagline": "Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",
95
+ "tagline": "In @godxjp/chat since v32, not @godxjp/ui: add @godxjp/chat as a dependency at the same version as @godxjp/ui. Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",
96
96
  "usage": [
97
97
  "DO wire BOTH halves of the render prop: `onTrigger` from the composer's onValueChange and `onKeyDown` from its onKeyDown. With only one wired the list either never opens or cannot be driven.",
98
98
  "DO decide yourself what a pick does to the draft — onValueChange hands you the value; the typed /query is still in the box, so replace it or append to it as your screen needs.",
@@ -5,7 +5,7 @@
5
5
  "name": "LegalDocumentShell",
6
6
  "props": [
7
7
  {
8
- "description": "Document title — the <h1> that names the <article> (e.g. 'Terms of Service').",
8
+ "description": "Document title — the <h1> that names the <article> (e.g. 'Terms of Service'). Sized two type steps above the section <h2>s (--legal-document-title-font-size, default the --font-size-2xl step) so the page does not read flat. Around it, PageContainer may omit `title` so this stays the page's only <h1>.",
9
9
  "name": "title",
10
10
  "required": true,
11
11
  "type": "ReactNode"
@@ -26,6 +26,12 @@
26
26
  "name": "effectiveDate",
27
27
  "type": "string"
28
28
  },
29
+ {
30
+ "defaultValue": "\"effective\"",
31
+ "description": "What the date means, which picks its localized caption: 'effective' (施行日 / Effective, a policy in force from that date) or 'updated' (更新日 / Updated, a wiki page or help article last revised). Never prefix a label of your own to the date: word order differs per locale.",
32
+ "name": "dateKind",
33
+ "type": "\"effective\" | \"updated\""
34
+ },
29
35
  {
30
36
  "description": "Short plain-language summary rendered under the metadata, above the contents.",
31
37
  "name": "summary",
@@ -20,9 +20,8 @@
20
20
  "type": "string"
21
21
  },
22
22
  {
23
- "description": "Page heading rendered as <h1>.",
23
+ "description": "Page heading rendered as <h1>. OPTIONAL when the content draws its own heading (a document reader, LegalDocumentShell): omitted, no <h1> is rendered and a header with no other slot is dropped. The page must then still have exactly one <h1> from its content; `titleRef` has nothing to attach to.",
24
24
  "name": "title",
25
- "required": true,
26
25
  "type": "string"
27
26
  },
28
27
  {
@@ -26,7 +26,7 @@
26
26
  "type": "string"
27
27
  },
28
28
  {
29
- "description": "Text label beside/below the bar; it also becomes the accessible name. Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
29
+ "description": "VISIBLE caption beside/below the bar; it also becomes the accessible name. (Unlike Anchor `label`, which is screen-reader-only: there the name is never drawn.) Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
30
30
  "name": "label",
31
31
  "type": "string"
32
32
  },
@@ -812,7 +812,7 @@
812
812
  {
813
813
  "group": "data-entry",
814
814
  "name": "ChatSuggestion",
815
- "tagline": "Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it."
815
+ "tagline": "In @godxjp/chat since v32, not @godxjp/ui: add @godxjp/chat as a dependency at the same version as @godxjp/ui. Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it."
816
816
  },
817
817
  {
818
818
  "group": "navigation",
@@ -665,9 +665,8 @@
665
665
  "type": "string"
666
666
  },
667
667
  {
668
- "description": "Page heading rendered as <h1>.",
668
+ "description": "Page heading rendered as <h1>. OPTIONAL when the content draws its own heading (a document reader, LegalDocumentShell): omitted, no <h1> is rendered and a header with no other slot is dropped. The page must then still have exactly one <h1> from its content; `titleRef` has nothing to attach to.",
669
669
  "name": "title",
670
- "required": true,
671
670
  "type": "string"
672
671
  },
673
672
  {
@@ -2317,7 +2316,7 @@
2317
2316
  "name": "LegalDocumentShell",
2318
2317
  "props": [
2319
2318
  {
2320
- "description": "Document title — the <h1> that names the <article> (e.g. 'Terms of Service').",
2319
+ "description": "Document title — the <h1> that names the <article> (e.g. 'Terms of Service'). Sized two type steps above the section <h2>s (--legal-document-title-font-size, default the --font-size-2xl step) so the page does not read flat. Around it, PageContainer may omit `title` so this stays the page's only <h1>.",
2321
2320
  "name": "title",
2322
2321
  "required": true,
2323
2322
  "type": "ReactNode"
@@ -2338,6 +2337,12 @@
2338
2337
  "name": "effectiveDate",
2339
2338
  "type": "string"
2340
2339
  },
2340
+ {
2341
+ "defaultValue": "\"effective\"",
2342
+ "description": "What the date means, which picks its localized caption: 'effective' (施行日 / Effective, a policy in force from that date) or 'updated' (更新日 / Updated, a wiki page or help article last revised). Never prefix a label of your own to the date: word order differs per locale.",
2343
+ "name": "dateKind",
2344
+ "type": "\"effective\" | \"updated\""
2345
+ },
2341
2346
  {
2342
2347
  "description": "Short plain-language summary rendered under the metadata, above the contents.",
2343
2348
  "name": "summary",
@@ -4727,7 +4732,7 @@
4727
4732
  "type": "string"
4728
4733
  },
4729
4734
  {
4730
- "description": "Text label beside/below the bar; it also becomes the accessible name. Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
4735
+ "description": "VISIBLE caption beside/below the bar; it also becomes the accessible name. (Unlike Anchor `label`, which is screen-reader-only: there the name is never drawn.) Pass `aria-labelledby` instead when the name is ALREADY on screen (a row's company name, a card heading) — the bar then borrows it rather than repeating it.",
4731
4736
  "name": "label",
4732
4737
  "type": "string"
4733
4738
  },
@@ -14902,7 +14907,7 @@
14902
14907
  6
14903
14908
  ],
14904
14909
  "storyPath": "data-entry/chat-composer.tsx",
14905
- "tagline": "Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",
14910
+ "tagline": "In @godxjp/chat since v32, not @godxjp/ui: add @godxjp/chat as a dependency at the same version as @godxjp/ui. Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",
14906
14911
  "usage": [
14907
14912
  "DO wire BOTH halves of the render prop: `onTrigger` from the composer's onValueChange and `onKeyDown` from its onKeyDown. With only one wired the list either never opens or cannot be driven.",
14908
14913
  "DO decide yourself what a pick does to the draft — onValueChange hands you the value; the typed /query is still in the box, so replace it or append to it as your screen needs.",
@@ -15932,7 +15937,7 @@
15932
15937
  "type": "(event: React.MouseEvent<HTMLAnchorElement>, item: AnchorItemProp) => void"
15933
15938
  },
15934
15939
  {
15935
- "description": "Accessible name of the <nav> landmark. antd ships an unnamed <div>; a page routinely carries a breadcrumb, a rail and this, so a localized default (\"On this page\") applies when omitted.",
15940
+ "description": "SCREEN-READER-ONLY accessible name of the <nav> landmark — it is never drawn (unlike Progress `label`, which is a visible caption). Need a visible heading over the links? Render a Heading yourself and keep this as the landmark name. antd ships an unnamed <div>; a page routinely carries a breadcrumb, a rail and this, so a localized default (\"On this page\") applies when omitted.",
15936
15941
  "name": "label",
15937
15942
  "type": "string"
15938
15943
  },
package/agent/index.json CHANGED
@@ -1,13 +1,19 @@
1
1
  {
2
2
  "counts": {
3
- "anti-ai-tells": 26,
3
+ "anti-ai-tells": 35,
4
4
  "components": 161,
5
5
  "patterns": 24,
6
6
  "rules": 43,
7
- "tokens": 2195,
7
+ "tokens": 2197,
8
8
  "vocabulary": 14
9
9
  },
10
+ "design": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md",
10
11
  "files": [
12
+ {
13
+ "file": "DESIGN.md",
14
+ "note": "13 KB — the visual spec: tokens, colour scarcity, admin screen anatomy (list, detail, form, settings, dashboard), do/don't. Read before composing a screen.",
15
+ "url": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/DESIGN.md"
16
+ },
11
17
  {
12
18
  "file": "components-index.json",
13
19
  "note": "47 KB — name + group + tagline for all 161. FETCH THIS FIRST, then fetch only the components you chose.",
@@ -48,19 +54,19 @@
48
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.",
49
55
  "read": {
50
56
  "live": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/index.json",
51
- "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.5.0/agent/index.json"
57
+ "pinned": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/v32.7.0/agent/index.json"
52
58
  },
53
59
  "source": "mcp/src/data — the same data @godxjp/ui-mcp serves — plus the foundation and semantic token tiers, read from src/tokens/*.css",
54
60
  "start": "https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/START-HERE.md",
55
61
  "tokenTiers": {
56
62
  "component": "per-part knobs, --{component}-{part}-{property}; usually leave these alone",
57
63
  "counts": {
58
- "component": 1872,
64
+ "component": 1874,
59
65
  "foundation": 211,
60
66
  "semantic": 112
61
67
  },
62
68
  "foundation": "the seeds a consumer is invited to set — --primary, --background, --radius",
63
69
  "semantic": "named roles that follow the seeds — --ring, --text-link, --overlay-background"
64
70
  },
65
- "version": "32.5.0"
71
+ "version": "32.7.0"
66
72
  }
package/agent/llms.txt CHANGED
@@ -1,16 +1,17 @@
1
1
  # @godxjp/ui
2
2
 
3
- > A Japanese-enterprise React design system: 161 components, 2195 design tokens,
4
- > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.5.0.
3
+ > A Japanese-enterprise React design system: 161 components, 2197 design tokens,
4
+ > 43 cardinal rules. This file is the entry point for AI agents. Catalog version 32.7.0.
5
5
 
6
6
  If your client can run a process, do not read these files — run the MCP server instead
7
- (`npx @godxjp/ui-mcp@32.5.0`). It is searchable and version-locked. These files exist for agents
7
+ (`npx @godxjp/ui-mcp@32.7.0`). 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): 13 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
15
 
15
16
  ## Catalog
16
17
 
@@ -18,15 +19,15 @@ that can only fetch URLs.
18
19
  - [components-index.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components-index.json): 47 KB — all 161 components as name, group, tagline, plus `absorbed`: the names that do NOT exist and map to it (`Combobox` → `Select`).
19
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.
20
21
  - [components.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/components.json): 1.2 MB — every entry in one file. Most URL fetchers truncate a response this size without saying so; prefer the per-component files.
21
- - [tokens.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/tokens.json): every design token with its value, the reason it exists, and its `tier` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 112 `semantic` roles, 1872 `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` — 211 `foundation` seeds (`--primary`, `--background`, `--radius`: set these when you are handed a brand), 112 `semantic` roles, 1874 `component` knobs.
22
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.
23
24
  - [rules.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/rules.json): 43 cardinal rules.
24
- - [anti-ai-tells.json](https://raw.githubusercontent.com/godx-jp/godxjp-ui/main/agent/anti-ai-tells.json): 26 shapes that make generated UI look generated, each with its fix.
25
+ - [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.
25
26
 
26
27
  ## Pinning
27
28
 
28
29
  Every URL above tracks `main`. To pin to the release a project actually installed, swap `main` for
29
- the tag: `.../godx-jp/godxjp-ui/v32.5.0/agent/...`. A catalog that does not match the installed
30
+ the tag: `.../godx-jp/godxjp-ui/v32.7.0/agent/...`. A catalog that does not match the installed
30
31
  package describes props that are absent, or hides props that are present, and says nothing either way.
31
32
 
32
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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "code": "import { Badge } from \"@godxjp/ui/data-display\"\n\n// Badge auto-colours a fixed set of English LIFECYCLE keys:\n// active/completed (success ✓) · draft (neutral ○) · pending/temporary (warning ⏱)\n// scheduled/sending (info) · cancelled (neutral) · failed/deleted/bounced (destructive ✕)\n// Anything else (localized labels, tiers) falls back to neutral grey ○ unless you override.\n\n// 1) Lifecycle with localized text — map to the key, keep JP via children (icon stays):\n<Badge status=\"active\">公開中</Badge> // green ✓ 公開中\n\n// 2) Unknown label — set tone explicitly (no icon, since the key is unknown):\n<Badge status=\"公開中\" tone=\"success\" />\n\n// 3) Tier / category — coloured pill, drop the misleading glyph with icon={null}:\n<Badge status=\"プレミアム\" tone=\"success\" icon={null} />\n<Badge status=\"ゴールド\" tone=\"warning\" icon={null} />\n<Badge status=\"法人共通\" tone=\"info\" icon={null} />\n\n// tone: \"success\" | \"warning\" | \"destructive\" | \"info\" | \"neutral\" (import type ToneProp)\n// RULE: a chip never wraps — it is pinned white-space: nowrap, so it stays one line in\n// narrow table cells. Centralize the domain→tone map in ONE small consumer wrapper and\n// import that instead of the raw Badge across pages.",
2
+ "code": "import { Badge } from \"@godxjp/ui/data-display\"\n\n// Badge auto-colours a fixed set of English LIFECYCLE keys:\n// active/completed (success ✓) · draft (neutral ○) · pending/temporary (warning ⏱)\n// scheduled/sending (info) · cancelled (neutral) · failed/deleted/bounced (destructive ✕)\n// Anything else (localized labels, tiers) falls back to neutral grey ○ unless you override.\n\n// 1) Lifecycle with localized text — map to the key, keep JP via children (icon stays):\n<Badge status=\"active\">公開中</Badge> // green ✓ 公開中\n\n// 2) Unknown label — set tone explicitly (no icon, since the key is unknown):\n<Badge status=\"公開中\" tone=\"success\" />\n\n// 3) Tier / category — ONE neutral tone for all of them, no glyph. Colour means status;\n// a rainbow of tiers reads as several statuses. Prefer plain text in a table column.\n<Badge status=\"プレミアム\" tone=\"neutral\" icon={null} />\n<Badge status=\"ゴールド\" tone=\"neutral\" icon={null} />\n\n// tone: \"success\" | \"warning\" | \"destructive\" | \"info\" | \"neutral\" (import type ToneProp)\n// RULE: a chip never wraps — it is pinned white-space: nowrap, so it stays one line in\n// narrow table cells. Centralize the domain→tone map in ONE small consumer wrapper and\n// import that instead of the raw Badge across pages.",
3
3
  "name": "badge-coloring",
4
- "tagline": "Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",
4
+ "tagline": "Colour a Badge for a localized STATUS via tone + icon; tiers and categories take one neutral tone (colour means status).",
5
5
  "tags": [
6
6
  "statusbadge",
7
7
  "badge",
@@ -1,5 +1,5 @@
1
1
  {
2
- "code": "// ───────────────────────────────────────────────────────────────────────\n// 0) ★ MOST COMMON: <Card> body has NO padding (content is flush against the edges)\n// Cause: the bare <Card> has ZERO inner padding — it MUST contain <CardContent>.\n// Don't hand-roll padding with className=\"p-4\" on the Card either.\n// ❌ <Card><Flex direction=\"col\" gap=\"md\">…fields…</Flex></Card> // flush, no padding\n// ❌ <Card className=\"p-4\">…fields…</Card> // hand-rolled padding\n// ✅ <Card><CardContent><Flex direction=\"col\" gap=\"md\">…fields…</Flex></CardContent></Card>\n// Titles → <CardHeader><CardTitle>. Only go flush deliberately for a full-bleed table:\n// ✅ <Card><CardContent flush><DataTable/></CardContent></Card>\n// GENERAL RULE — compose godx-ui primitives FULLY; never hand-roll what one ships:\n// padding → CardContent (not p-4) · controls → Input/Select/Button (not raw <input>/<select>/<button>)\n// vertical spacing → Flex direction=\"col\" gap (not space-y-*) · empty rows → DataTable's built-in\n// empty / <EmptyState> (not a custom data.length===0 guard). If a primitive exists, USE it.\n\n// 1) StatCard shows a DOUBLE border (too thick)\n// Cause: StatCard IS already a bordered Card. Don't wrap it.\n// ❌ <Card><CardContent><StatCard label=\"x\" value=\"1\" /></CardContent></Card>\n// ✅ <ResponsiveGrid columns={4}><StatCard label=\"x\" value=\"1\" /></ResponsiveGrid>\n// Need a section title? Use a heading, NOT a Card:\n// ✅ <Flex direction=\"col\" gap=\"sm\"><div className=\"text-sm font-medium\">KPI</div>\n// <ResponsiveGrid columns={4}><StatCard .../></ResponsiveGrid></Flex>\n\n// 2) Badge renders grey with a ○ (no colour) for localized/tier labels\n// Cause: it auto-maps only English lifecycle keys.\n// ❌ <Badge status=\"プレミアム\" />\n// ✅ <Badge status=\"プレミアム\" tone=\"success\" icon={null} /> // tier → pill, no icon\n// ✅ <Badge status=\"active\">公開中</Badge> // lifecycle → keep icon\n\n// 3) Table text collapses to one char per line, or a chip wraps\n// ✅ give long columns a width: { key: \"name\", header: \"氏名\", width: \"w-64\" }\n// (cells + chips are pinned white-space: nowrap by the library.)\n\n// 4) Empty (icon/action) column header shows a blank grey block\n// ✅ header: \"\" renders a transparent cell — leave it empty, don't inject a space.\n\n// 5) DataTable columns are crushed / squeezed\n// Cause: the table is nested in a narrow grid column.\n// ❌ <ResponsiveGrid columns={3}><div className=\"lg:col-span-2\"><Card><DataTable/></Card></div></ResponsiveGrid>\n// ✅ Table gets its OWN full-width row: <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 6) Toolbar (filter bar) has no padding (sticks to the edge)\n// Cause: it's inside CardContent flush (flush strips padding — that's for tables).\n// ❌ <Card><CardContent flush><Toolbar/><DataTable/></CardContent></Card>\n// ✅ <Toolbar/> then <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 7) Sidebar footer looks washed-out / off-design\n// Cause: raw opacity-*/text-[11px]. Use semantic tokens.\n// ✅ <div className=\"text-muted-foreground text-xs\">\n// <div className=\"text-foreground font-medium\">{name}</div><div>{role}</div></div>\n\n// 8) Inertia: \"Objects are not valid as a React child {errors, auth, …}\"\n// Cause: persistent layout passed as a render fn. Use the ARRAY form.\n// ❌ Page.layout = (page) => <Layout>{page}</Layout>\n// ✅ Page.layout = [Layout] // Layout is ({children}) => ...\n\n// 9) Inertia v3 hydration mismatch (\"server rendered text didn't match the client\")\n// Cause: Math.random()/argless new Date() during render (SSR ≠ client).\n// ✅ seed deterministically by index, or compute in an event handler.\n\n// 10) Hide a column on mobile / sign-aware KPI delta\n// ✅ columns: [{ key: \"email\", header: \"メール\", hiddenOnMobile: true }]\n// ✅ <StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /> // + green / - red; inverse flips",
2
+ "code": "// ───────────────────────────────────────────────────────────────────────\n// 0) ★ MOST COMMON: <Card> body has NO padding (content is flush against the edges)\n// Cause: the bare <Card> has ZERO inner padding — it MUST contain <CardContent>.\n// Don't hand-roll padding with className=\"p-4\" on the Card either.\n// ❌ <Card><Flex direction=\"col\" gap=\"md\">…fields…</Flex></Card> // flush, no padding\n// ❌ <Card className=\"p-4\">…fields…</Card> // hand-rolled padding\n// ✅ <Card><CardContent><Flex direction=\"col\" gap=\"md\">…fields…</Flex></CardContent></Card>\n// Titles → <CardHeader><CardTitle>. Only go flush deliberately for a full-bleed table:\n// ✅ <Card><CardContent flush><DataTable/></CardContent></Card>\n// GENERAL RULE — compose godx-ui primitives FULLY; never hand-roll what one ships:\n// padding → CardContent (not p-4) · controls → Input/Select/Button (not raw <input>/<select>/<button>)\n// vertical spacing → Flex direction=\"col\" gap (not space-y-*) · empty rows → DataTable's built-in\n// empty / <EmptyState> (not a custom data.length===0 guard). If a primitive exists, USE it.\n\n// 1) StatCard shows a DOUBLE border (too thick)\n// Cause: StatCard IS already a bordered Card. Don't wrap it.\n// ❌ <Card><CardContent><StatCard label=\"x\" value=\"1\" /></CardContent></Card>\n// ✅ dashboard only, with comparison/trend:\n// <ResponsiveGrid columns={4}><StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /></ResponsiveGrid>\n// A list or detail page has NO StatCard row — a row count is not a KPI (Pagination showTotal).\n\n// 2) Badge renders grey with a ○ (no colour) for localized/tier labels\n// Cause: it auto-maps only English lifecycle keys.\n// ✅ <Badge status=\"active\">公開中</Badge> // lifecycle → keep icon\n// ✅ <Badge status=\"プレミアム\" tone=\"neutral\" icon={null} /> // tier → ONE neutral tone\n// Colour is for status only; a tier/category/scope is plain text or a neutral pill.\n\n// 3) Table text collapses to one char per line, or a chip wraps\n// ✅ give long columns a width: { key: \"name\", header: \"氏名\", width: \"w-64\" }\n// (cells + chips are pinned white-space: nowrap by the library.)\n\n// 4) Empty (icon/action) column header shows a blank grey block\n// ✅ header: \"\" renders a transparent cell — leave it empty, don't inject a space.\n\n// 5) DataTable columns are crushed / squeezed\n// Cause: the table is nested in a narrow grid column.\n// ❌ <ResponsiveGrid columns={3}><div className=\"lg:col-span-2\"><Card><DataTable/></Card></div></ResponsiveGrid>\n// ✅ Table gets its OWN full-width row: <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 6) Toolbar (filter bar) has no padding (sticks to the edge)\n// Cause: it's inside CardContent flush (flush strips padding — that's for tables).\n// ❌ <Card><CardContent flush><Toolbar/><DataTable/></CardContent></Card>\n// ✅ <Toolbar/> then <Card><CardContent flush><DataTable/></CardContent></Card>\n\n// 7) Sidebar footer looks washed-out / off-design\n// Cause: raw opacity-*/text-[11px]. Use semantic tokens.\n// ✅ <div className=\"text-muted-foreground text-xs\">\n// <div className=\"text-foreground font-medium\">{name}</div><div>{role}</div></div>\n\n// 8) Inertia: \"Objects are not valid as a React child {errors, auth, …}\"\n// Cause: persistent layout passed as a render fn. Use the ARRAY form.\n// ❌ Page.layout = (page) => <Layout>{page}</Layout>\n// ✅ Page.layout = [Layout] // Layout is ({children}) => ...\n\n// 9) Inertia v3 hydration mismatch (\"server rendered text didn't match the client\")\n// Cause: Math.random()/argless new Date() during render (SSR ≠ client).\n// ✅ seed deterministically by index, or compute in an event handler.\n\n// 10) Hide a column on mobile / sign-aware KPI delta\n// ✅ columns: [{ key: \"email\", header: \"メール\", hiddenOnMobile: true }]\n// ✅ <StatCard label=\"売上\" value=\"¥8.2M\" delta=\"+12%\" /> // + green / - red; inverse flips",
3
3
  "name": "common-fixes",
4
4
  "tagline": "Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before → after.",
5
5
  "tags": [
@@ -1,7 +1,7 @@
1
1
  {
2
- "code": "import { useState } from \"react\";\nimport { AlertDialogRoot, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from \"@godxjp/ui/feedback\";\nimport { Input } from \"@godxjp/ui/data-entry\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { toast } from \"sonner\";\n\nexport function DeleteProjectDialog({ open, onOpenChange, slug }: { open: boolean; onOpenChange: (v: boolean) => void; slug: string }) {\n const [confirm, setConfirm] = useState(\"\");\n return (\n <AlertDialogRoot open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>プロジェクトを削除</AlertDialogTitle>\n <AlertDialogDescription>この操作は取り消せません。確認のためプロジェクト名 \"{slug}\" と入力してください。</AlertDialogDescription>\n </AlertDialogHeader>\n <Flex direction=\"col\" gap=\"md\">\n <Input value={confirm} onChange={(e) => setConfirm(e.target.value)} placeholder={slug} />\n </Flex>\n <AlertDialogFooter>\n <AlertDialogCancel asChild><Button variant=\"outline\">キャンセル</Button></AlertDialogCancel>\n <AlertDialogAction asChild>\n <Button variant=\"destructive\" disabled={confirm !== slug} onClick={() => { toast.success(\"削除しました\"); onOpenChange(false); }}>完全に削除</Button>\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogRoot>\n );\n}",
2
+ "code": "import { AlertDialog, toast } from \"@godxjp/ui/feedback\";\n\n// The preset covers a typed challenge: confirm stays disabled until the input equals `slug`.\nexport function DeleteProjectDialog({ open, onOpenChange, slug }: { open: boolean; onOpenChange: (v: boolean) => void; slug: string }) {\n return (\n <AlertDialog\n open={open}\n onOpenChange={onOpenChange}\n title=\"プロジェクトを削除\"\n description={`この操作は取り消せません。確認のためプロジェクト名 \"${slug}\" と入力してください。`}\n challenge={slug}\n confirmLabel=\"完全に削除\"\n onConfirm={() => {\n toast.success(\"削除しました\");\n onOpenChange(false);\n }}\n />\n );\n}",
3
3
  "name": "confirm-destructive",
4
- "tagline": "Type-to-confirm destructive dialog — AlertDialogRoot + Input gate + toast (real @godxjp/ui API).",
4
+ "tagline": "Type-to-confirm destructive dialog — the AlertDialog PRESET with `challenge` (the compound AlertDialog* parts are the older way in; a body the preset does not cover is Dialog variant=\"destructive\").",
5
5
  "tags": [
6
6
  "dialog",
7
7
  "confirm",
@@ -1,7 +1,7 @@
1
1
  {
2
- "code": "import { Head, router } from \"@inertiajs/react\"\nimport { PageContainer, ResponsiveGrid, Flex } from \"@godxjp/ui/layout\"\nimport { Card, CardContent, StatCard, EmptyState, Descriptions, Badge } from \"@godxjp/ui/data-display\"\nimport { Button } from \"@godxjp/ui/general\"\nimport { formatDate } from \"@godxjp/ui/datetime\"\nimport { ArrowLeft } from \"lucide-react\"\nimport { withCrmLayout } from \"@/layouts/crm-layout\"\n\n// Detail routes pass the param as an Inertia prop:\n// Route::get('/members/{id}', fn ($id) => Inertia::render('crm/members/show', ['id' => $id]))\nfunction MemberShow({ id }: { id: string }) {\n const member = MEMBERS.find((m) => m.id === id)\n\n if (!member) {\n return (\n <>\n <Head title=\"会員詳細\" />\n <PageContainer title=\"会員詳細\" subtitle=\"会員が見つかりません\">\n <EmptyState title=\"会員が見つかりません\" description={`ID「${id}」は存在しません。`} />\n <Button variant=\"outline\" onClick={() => router.visit(\"/members\")}><ArrowLeft className=\"size-4\" />一覧へ戻る</Button>\n </PageContainer>\n </>\n )\n }\n\n return (\n <>\n <Head title={member.name} />\n <PageContainer title={member.name} subtitle={`${member.id} / ${member.rank}`}>\n <Flex direction=\"col\" gap=\"lg\">\n <ResponsiveGrid columns={4}>\n <StatCard label=\"累計購入額\" value={`¥${member.total.toLocaleString()}`} />\n <StatCard label=\"来店回数\" value={member.visits} />\n <StatCard label=\"ポイント\" value={member.points.toLocaleString()} />\n <StatCard label=\"LTV\" value={`¥${member.ltv.toLocaleString()}`} />\n </ResponsiveGrid>\n <Card>\n <CardContent>\n {/* Descriptions is COMPOUND — value goes in children, not a prop */}\n <Descriptions columns={2}>\n <Descriptions.Item label=\"氏名\">{member.name}</Descriptions.Item>\n <Descriptions.Item label=\"ランク\"><Badge status={member.rank} tone=\"info\" icon={null} /></Descriptions.Item>\n <Descriptions.Item label=\"ステータス\"><Badge status={member.status} /></Descriptions.Item>\n <Descriptions.Item label=\"登録日\">{formatDate(member.registeredAt)}</Descriptions.Item>\n </Descriptions>\n </CardContent>\n </Card>\n </Flex>\n </PageContainer>\n </>\n )\n}\n\nMemberShow.layout = withCrmLayout\nexport default MemberShow",
2
+ "code": "import { Head, router } from \"@inertiajs/react\"\nimport { PageContainer } from \"@godxjp/ui/layout\"\nimport { Card, CardContent, EmptyState, Descriptions, Badge } from \"@godxjp/ui/data-display\"\nimport { Button } from \"@godxjp/ui/general\"\nimport { formatDate } from \"@godxjp/ui/datetime\"\nimport { ArrowLeft } from \"lucide-react\"\nimport { withCrmLayout } from \"@/layouts/crm-layout\"\n\n// Detail routes pass the param as an Inertia prop:\n// Route::get('/members/{id}', fn ($id) => Inertia::render('crm/members/show', ['id' => $id]))\nfunction MemberShow({ id }: { id: string }) {\n const member = MEMBERS.find((m) => m.id === id)\n\n if (!member) {\n return (\n <>\n <Head title=\"会員詳細\" />\n <PageContainer title=\"会員詳細\">\n <EmptyState title=\"会員が見つかりません\" description={`ID「${id}」は存在しません。`} />\n <Button variant=\"outline\" onClick={() => router.visit(\"/members\")}><ArrowLeft className=\"size-4\" />一覧へ戻る</Button>\n </PageContainer>\n </>\n )\n }\n\n return (\n <>\n <Head title={member.name} />\n <PageContainer title={member.name}>\n {/* Figures about one member are facts, not KPIs: Descriptions rows, not a StatCard row. */}\n <Card>\n <CardContent>\n {/* Descriptions is COMPOUND — value goes in children, not a prop */}\n <Descriptions columns={2}>\n <Descriptions.Item label=\"会員ID\">{member.id}</Descriptions.Item>\n <Descriptions.Item label=\"ランク\">{member.rank}</Descriptions.Item>\n <Descriptions.Item label=\"ステータス\"><Badge status={member.status} /></Descriptions.Item>\n <Descriptions.Item label=\"登録日\">{formatDate(member.registeredAt)}</Descriptions.Item>\n <Descriptions.Item label=\"累計購入額\">{`¥${member.total.toLocaleString()}`}</Descriptions.Item>\n <Descriptions.Item label=\"来店回数\">{member.visits}</Descriptions.Item>\n </Descriptions>\n </CardContent>\n </Card>\n </PageContainer>\n </>\n )\n}\n\nMemberShow.layout = withCrmLayout\nexport default MemberShow",
3
3
  "name": "inertia-detail-page",
4
- "tagline": "Inertia detail page — receives {id} prop, Descriptions (compound) + StatCard + EmptyState fallback.",
4
+ "tagline": "Inertia detail page — receives {id} prop, Descriptions (compound) in one Card + EmptyState fallback. No KPI tile row.",
5
5
  "tags": [
6
6
  "inertia",
7
7
  "detail",
@@ -1,7 +1,7 @@
1
1
  {
2
- "code": "import { Head, router } from \"@inertiajs/react\"\nimport { useMemo, useState } from \"react\"\nimport { PageContainer, ResponsiveGrid, Flex } from \"@godxjp/ui/layout\"\nimport { Card, CardContent, StatCard, DataTable, EmptyState, Badge, type ColumnDef } from \"@godxjp/ui/data-display\"\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\"\nimport { Toolbar, ToolbarGroup, Pagination } from \"@godxjp/ui/navigation\"\nimport { formatDate } from \"@godxjp/ui/datetime\"\nimport { withCrmLayout } from \"@/layouts/crm-layout\" // see \"inertia-persistent-layout\"\n\ntype Coupon = { id: string; name: string; status: string; scope: string; validFrom: string; validTo: string; usage: number }\nconst PAGE_SIZE = 10\n\nfunction Coupons({ coupons }: { coupons: Coupon[] }) {\n const [q, setQ] = useState(\"\")\n const [status, setStatus] = useState(\"all\")\n const [page, setPage] = useState(1)\n\n const filtered = useMemo(() => coupons.filter((c) => {\n if (q && !c.name.toLowerCase().includes(q.toLowerCase())) return false\n if (status !== \"all\" && c.status !== status) return false\n return true\n }), [coupons, q, status])\n const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)\n\n // ColumnDef = { key, header, render?, align?: \"left\"|\"center\"|\"right\", sortable?, width? }\n const columns: ColumnDef<Coupon>[] = [\n { key: \"name\", header: \"クーポン名\", render: (c) => <span className=\"font-medium\">{c.name}</span> },\n { key: \"scope\", header: \"スコープ\", render: (c) => <Badge status={c.scope} tone=\"info\" icon={null} /> },\n { key: \"status\", header: \"ステータス\", render: (c) => <Badge status={c.status} /> },\n { key: \"valid\", header: \"有効期間\", render: (c) => `${formatDate(c.validFrom)} 〜 ${formatDate(c.validTo)}` },\n { key: \"usage\", header: \"利用数\", align: \"right\", render: (c) => c.usage.toLocaleString() },\n ]\n\n return (\n <>\n <Head title=\"クーポン管理\" />\n {/* RULE: every page wraps in PageContainer; vertical rhythm via Flex direction=\"col\"/ResponsiveGrid, never p-*/gap-* */}\n <PageContainer title=\"クーポン管理\" subtitle=\"配信中のクーポン一覧\">\n <Flex direction=\"col\" gap=\"lg\">\n <ResponsiveGrid columns={3}>\n <StatCard label=\"公開中\" value={coupons.filter((c) => c.status === \"公開中\").length} />\n <StatCard label=\"総利用数\" value={coupons.reduce((s, c) => s + c.usage, 0).toLocaleString()} />\n <StatCard label=\"件数\" value={coupons.length} />\n </ResponsiveGrid>\n\n {/* Toolbar is the filter-bar primitive (standalone, above the table surface). */}\n <Toolbar hasActiveFilters={q !== \"\" || status !== \"all\"} onClear={() => { setQ(\"\"); setStatus(\"all\"); setPage(1) }}>\n {/* SearchInput is value + onSearch(v) — NOT onChange */}\n <SearchInput placeholder=\"クーポン名で検索\" value={q} onSearch={(v) => { setQ(v); setPage(1) }} />\n <ToolbarGroup label=\"ステータス\">\n <Select value={status} onValueChange={(v) => { setStatus(v); setPage(1) }}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">全ステータス</SelectItem>\n <SelectItem value=\"公開中\">公開中</SelectItem>\n <SelectItem value=\"下書き\">下書き</SelectItem>\n </SelectContent>\n </Select>\n </ToolbarGroup>\n </Toolbar>\n\n <Card>\n <CardContent flush>\n {filtered.length === 0\n ? <EmptyState title=\"該当するクーポンがありません\" description=\"検索条件を変更してください。\" />\n : <DataTable data={paged} columns={columns} getRowId={(c) => c.id} onRowClick={(c) => router.visit(`/coupons/${c.id}`)} />}\n </CardContent>\n </Card>\n\n {/* Pagination ONLY for real multi-page data — hide it while empty or single-page. */}\n {filtered.length > PAGE_SIZE && (\n <Pagination value={page} total={filtered.length} pageSize={PAGE_SIZE} showTotal onValueChange={(p) => setPage(p)} />\n )}\n </Flex>\n </PageContainer>\n </>\n )\n}\n\nCoupons.layout = withCrmLayout\nexport default Coupons",
2
+ "code": "import { Head, router } from \"@inertiajs/react\"\nimport { useMemo, useState } from \"react\"\nimport { PageContainer, Flex } from \"@godxjp/ui/layout\"\nimport { Card, CardContent, DataTable, EmptyState, Badge, type ColumnDef } from \"@godxjp/ui/data-display\"\nimport { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@godxjp/ui/data-entry\"\nimport { Toolbar, ToolbarGroup, Pagination } from \"@godxjp/ui/navigation\"\nimport { formatDate } from \"@godxjp/ui/datetime\"\nimport { withCrmLayout } from \"@/layouts/crm-layout\" // see \"inertia-persistent-layout\"\n\ntype Coupon = { id: string; name: string; status: string; scope: string; validFrom: string; validTo: string; usage: number }\nconst PAGE_SIZE = 10\n\nfunction Coupons({ coupons }: { coupons: Coupon[] }) {\n const [q, setQ] = useState(\"\")\n const [status, setStatus] = useState(\"all\")\n const [page, setPage] = useState(1)\n\n const filtered = useMemo(() => coupons.filter((c) => {\n if (q && !c.name.toLowerCase().includes(q.toLowerCase())) return false\n if (status !== \"all\" && c.status !== status) return false\n return true\n }), [coupons, q, status])\n const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)\n\n // ColumnDef = { key, header, render?, align?: \"left\"|\"center\"|\"right\", sortable?, width? }\n const columns: ColumnDef<Coupon>[] = [\n { key: \"name\", header: \"クーポン名\", render: (c) => <span className=\"font-medium\">{c.name}</span> },\n { key: \"scope\", header: \"スコープ\" }, // a category is plain text; Badge is for the status column only\n { key: \"status\", header: \"ステータス\", render: (c) => <Badge status={c.status} /> },\n { key: \"valid\", header: \"有効期間\", render: (c) => `${formatDate(c.validFrom)} 〜 ${formatDate(c.validTo)}` },\n { key: \"usage\", header: \"利用数\", align: \"right\", render: (c) => c.usage.toLocaleString() },\n ]\n\n return (\n <>\n <Head title=\"クーポン管理\" />\n {/* RULE: every page wraps in PageContainer; vertical rhythm via Flex direction=\"col\"/ResponsiveGrid, never p-* or gap-* */}\n {/* No subtitle that restates the title, no StatCard row: the total is Pagination's showTotal. */}\n <PageContainer title=\"クーポン管理\">\n <Flex direction=\"col\" gap=\"lg\">\n {/* Toolbar is the filter-bar primitive (standalone, above the table surface). */}\n <Toolbar hasActiveFilters={q !== \"\" || status !== \"all\"} onClear={() => { setQ(\"\"); setStatus(\"all\"); setPage(1) }}>\n {/* SearchInput is value + onSearch(v) — NOT onChange */}\n <SearchInput placeholder=\"クーポン名で検索\" value={q} onSearch={(v) => { setQ(v); setPage(1) }} />\n <ToolbarGroup label=\"ステータス\">\n <Select value={status} onValueChange={(v) => { setStatus(v); setPage(1) }}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">全ステータス</SelectItem>\n <SelectItem value=\"公開中\">公開中</SelectItem>\n <SelectItem value=\"下書き\">下書き</SelectItem>\n </SelectContent>\n </Select>\n </ToolbarGroup>\n </Toolbar>\n\n <Card>\n <CardContent flush>\n {filtered.length === 0\n ? <EmptyState title=\"該当するクーポンがありません\" description=\"検索条件を変更してください。\" /> /* one sentence, no icon */\n : <DataTable data={paged} columns={columns} getRowId={(c) => c.id} onRowClick={(c) => router.visit(`/coupons/${c.id}`)} />}\n </CardContent>\n </Card>\n\n {/* Pagination ONLY for real multi-page data — hide it while empty or single-page. */}\n {filtered.length > PAGE_SIZE && (\n <Pagination value={page} total={filtered.length} pageSize={PAGE_SIZE} showTotal onValueChange={(p) => setPage(p)} />\n )}\n </Flex>\n </PageContainer>\n </>\n )\n}\n\nCoupons.layout = withCrmLayout\nexport default Coupons",
3
3
  "name": "inertia-list-page",
4
- "tagline": "Inertia + @godxjp/ui list page — PageContainer + Toolbar + DataTable + Badge + Pagination (current primitive API).",
4
+ "tagline": "Inertia + @godxjp/ui list page — PageContainer + Toolbar (above the table) + DataTable + Badge + Pagination. No KPI row, no restating subtitle.",
5
5
  "tags": [
6
6
  "inertia",
7
7
  "list",
@@ -4,15 +4,17 @@
4
4
  "page-layout",
5
5
  "stack-cards"
6
6
  ],
7
- "code": "import { PageContainer, Flex, ResponsiveGrid } from \"@godxjp/ui/layout\";\nimport { Card, CardHeader, CardTitle, CardContent } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\n\n// Every direct child of PageContainer is a page section — the page owns the gap between them\n// (--page-body-gap, the section step). Do NOT add gap-* / space-y-* / mt-* yourself.\nexport function ReportPage() {\n return (\n <PageContainer title=\"Bug report\" subtitle=\"https://app.example.com/orders\" extra={<Button>Send</Button>}>\n <Card>\n <CardHeader><CardTitle>Details</CardTitle></CardHeader>\n <CardContent>…</CardContent>\n </Card>\n <Card>\n <CardHeader><CardTitle>Screenshots</CardTitle></CardHeader>\n <CardContent>\n {/* items INSIDE a section: ResponsiveGrid for a grid, Flex for a stack/row */}\n <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }}>…</ResponsiveGrid>\n </CardContent>\n </Card>\n <Card>\n <CardContent>\n <Flex direction=\"col\" gap=\"md\">…</Flex>\n </CardContent>\n </Card>\n </PageContainer>\n );\n}\n",
7
+ "code": "import { PageContainer, Flex, ResponsiveGrid } from \"@godxjp/ui/layout\";\nimport { Card, CardContent, DataTable, Descriptions, type ColumnDef } from \"@godxjp/ui/data-display\";\nimport { Button, Heading } from \"@godxjp/ui/general\";\n\ntype Shot = { id: string; name: string };\nconst columns: ColumnDef<Shot>[] = [{ key: \"name\", header: \"File\" }];\n\n// Two steps, never one uniform gap:\n// section step — between direct children of PageContainer (--page-body-gap). Do NOT add gap-*/mt-*.\n// item step — inside a section, Flex gap=\"sm\" between its heading and its content.\n// A section is a Heading, not a Card. Card is for a real boundary: here, the table surface.\nexport function ReportPage({ shots }: { shots: Shot[] }) {\n return (\n <PageContainer title=\"Bug report\" extra={<Button>Send</Button>}>\n <Flex direction=\"col\" gap=\"sm\">\n <Heading level={2}>Details</Heading>\n <Descriptions columns={2}>\n <Descriptions.Item label=\"URL\">https://app.example.com/orders</Descriptions.Item>\n <Descriptions.Item label=\"Browser\">Safari 18</Descriptions.Item>\n </Descriptions>\n </Flex>\n <Flex direction=\"col\" gap=\"sm\">\n <Heading level={2}>Screenshots</Heading>\n <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }}>…</ResponsiveGrid>\n </Flex>\n <Flex direction=\"col\" gap=\"sm\">\n <Heading level={2}>Attachments</Heading>\n <Card>\n <CardContent flush>\n <DataTable data={shots} columns={columns} getRowId={(r) => r.id} />\n </CardContent>\n </Card>\n </Flex>\n </PageContainer>\n );\n}\n",
8
8
  "name": "page-sections",
9
- "tagline": "A page made of several Card sections: PageContainer spaces its direct children by default; group items inside a section with Flex/ResponsiveGrid, never Tailwind gap-*/p-*.",
9
+ "tagline": "A page of several sections: a Heading + section spacing marks a section, not a Card. PageContainer spaces its direct children (section step); Flex gap spaces items inside one (item step). Card only for a real boundary.",
10
10
  "tags": [
11
11
  "layout",
12
12
  "page",
13
13
  "card",
14
14
  "spacing",
15
15
  "flex",
16
- "grid"
16
+ "grid",
17
+ "section",
18
+ "heading"
17
19
  ]
18
20
  }
@@ -7,9 +7,9 @@
7
7
  "preferences-rows",
8
8
  "account-identity"
9
9
  ],
10
- "code": "// Gate 0 (docs/COMPOSITION-VS-COMPONENT.md): a \"SettingsSection\"/\"SettingsRow\"/\n// \"DangerZone\" FAILS the Framework-Component Test — it owns no behavior (C2), is fully\n// expressible from existing primitives + tokens (C3), and its API would be screen-shaped (C4).\n// So compose it. Live reference screen: the \"settings account sections\" page in the preview\n// showcase gallery (docs/showcase/, served at /showcase/<id>).\nimport { ExternalLink, ShieldAlert } from \"lucide-react\";\nimport { Button, Text } from \"@godxjp/ui/general\";\nimport {\n Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,\n Descriptions, ListRow, StatusBadge,\n} from \"@godxjp/ui/data-display\";\nimport { Switch } from \"@godxjp/ui/data-entry\";\nimport { AlertDialog } from \"@godxjp/ui/feedback\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// The sections stack in ONE bounded column. The gap between sibling Cards belongs to that stack,\n// never to the cards themselves — four bare <Card>s in a row is what the audit means by\n// sibling-cards-need-flex, and it is the same rule the DO list at the bottom of this file states.\n<Flex direction=\"col\" gap=\"lg\" className=\"max-w-3xl\">\n\n// 1) IDENTITY — metadata is a description list, so it is Descriptions (a real <dl>), never a\n// two-column flex of styled divs. One header action goes in CardAction.\n<Card>\n <CardHeader>\n <CardTitle level={2}>アカウント情報</CardTitle>\n <CardDescription>請求書・監査ログに表示される氏名とメールアドレスです。</CardDescription>\n <CardAction><Button variant=\"outline\" size=\"sm\">編集</Button></CardAction>\n </CardHeader>\n <CardContent>\n <Descriptions columns={2}>\n <Descriptions.Item label=\"表示名\">藤原 慧</Descriptions.Item>\n <Descriptions.Item label=\"メールアドレス\">satoshi@example.co.jp</Descriptions.Item>\n <Descriptions.Item label=\"従業員番号\" mono>EMP-1042-JP</Descriptions.Item>\n <Descriptions.Item label=\"利用開始日\">\n {/* ISO-8601 in the machine attribute, Intl for the human text. */}\n <time dateTime=\"2023-04-01\">\n {new Intl.DateTimeFormat(locale, { dateStyle: \"long\", timeZone: \"UTC\" }).format(since)}\n </time>\n </Descriptions.Item>\n </Descriptions>\n </CardContent>\n</Card>\n\n// 2) PREFERENCE ROWS — THE setting row pattern: CardContent flush + ListRow, whose `trailing`\n// slot IS the control. align=\"start\" + overflow=\"wrap\" keep long JA/VI titles readable and let\n// the control drop to its own line at 390px (--list-row-body-min-width).\n// ⛔ NEVER: <div className=\"flex items-center justify-between border-b py-3\">…</div>\n<Card>\n <CardHeader><CardTitle level={2}>通知の環境設定</CardTitle></CardHeader>\n <CardContent flush>\n <ListRow\n align=\"start\"\n overflow=\"wrap\"\n title={<label htmlFor=\"pref-digest\">週次サマリーをメールで受け取る</label>}\n description=\"毎週月曜日の朝に未処理の承認をまとめて送信します。\"\n trailing={<Switch id=\"pref-digest\" checked={digest} onCheckedChange={setDigest} />}\n />\n </CardContent>\n</Card>\n\n// 3) BILLING HANDOFF — the subscription state is a CANONICAL StatusBadge; the tone + icon come\n// from the library's shared domain-status map, so a page NEVER keeps its own status→colour\n// table. Money/seats/dates go through Intl (ISO 4217 minor units come from the formatter).\n// ⛔ NEVER: const STATUS_COLORS = { past_due: \"red\", trialing: \"blue\" }\n<Card>\n <CardHeader>\n <CardTitle level={2}>請求とサブスクリプション</CardTitle>\n <CardDescription>支払い方法と請求書は外部の請求ポータルで管理します。</CardDescription>\n <CardAction><StatusBadge status=\"past_due\" variant=\"outline\" /></CardAction>\n </CardHeader>\n <CardContent>\n <Descriptions columns={2}>\n <Descriptions.Item label=\"契約プラン\">ビジネス(年額)</Descriptions.Item>\n <Descriptions.Item label=\"次回請求予定額\">\n {new Intl.NumberFormat(locale, { style: \"currency\", currency: \"JPY\" }).format(1536000)}\n </Descriptions.Item>\n </Descriptions>\n </CardContent>\n <CardFooter separated>\n <Button variant=\"outline\" asChild>\n <a href={portalUrl} target=\"_blank\" rel=\"noreferrer\">\n <ExternalLink aria-hidden=\"true\" />\n 請求ポータルを開く\n {/* Never signal \"leaves the app\" with the icon alone (WCAG 1.4.1 / 2.4.4). */}\n <span className=\"sr-only\">(新しいタブで開きます)</span>\n </a>\n </Button>\n </CardFooter>\n</Card>\n\n// 4) DANGER ZONE — Card accent=\"destructive\" paints the semantic leading rail from --destructive\n// through the existing --card-accent-rail-width token. NO new component, NO new token, and no\n// baked red. Colour never carries the meaning alone: the heading says \"irreversible\", each row\n// says what is lost, and delete is gated behind type-to-confirm friction.\n<Card accent=\"destructive\">\n <CardHeader>\n <CardTitle level={2} className=\"text-destructive\">取り消せない操作</CardTitle>\n <CardDescription>以下の操作は元に戻せません。</CardDescription>\n </CardHeader>\n <CardContent flush>\n <ListRow\n align=\"start\"\n overflow=\"wrap\"\n leading={<ShieldAlert className=\"text-destructive size-4\" aria-hidden=\"true\" />}\n title=\"ワークスペースと全データを完全に削除する\"\n description=\"勤怠記録・申請履歴・監査ログがすべて削除されます。\"\n trailing={\n <Button variant=\"destructive\" size=\"sm\" onClick={() => setConfirmOpen(true)}>削除</Button>\n }\n />\n </CardContent>\n</Card>\n\n</Flex>\n\n<AlertDialog\n open={confirmOpen}\n onOpenChange={setConfirmOpen}\n title=\"ワークスペースを完全に削除しますか?\"\n description=\"この操作は取り消せません。確認のため識別子を入力してください。\"\n challenge={workspaceSlug}\n variant=\"destructive\"\n onConfirm={destroy}\n/>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Sections stack in a bounded column: <Flex direction=\"col\" gap=\"lg\" className=\"max-w-3xl\">.\n// ✅ Every setting row is a ListRow; its control lives in `trailing`, and the visible row title is\n// a <label htmlFor> pointing at that control so the switch/select is named by what you can read.\n// ⛔ Do NOT add SettingsSection / SettingsRow / DangerZone to src/components — they fail Gate 0.\n// ⛔ Do NOT wrap the WHOLE settings page in one Card (route-backed local nav owns the IA — see the\n// responsive settings page pattern); each block is its own Card.\n// ⛔ Do NOT hand-roll padding (p-4) on a Card, a status colour map, or a <dl> replacement.",
10
+ "code": "// Gate 0 (docs/COMPOSITION-VS-COMPONENT.md): a \"SettingsSection\"/\"SettingsRow\"/\"DangerZone\"\n// FAILS the Framework-Component Test, so compose it. Reference screen: get_exemplar(\"settings\")\n// (docs/showcase/settings-account-sections.tsx). Composition rules: DESIGN.md → Admin Screen Anatomy.\nimport { ExternalLink } from \"lucide-react\";\nimport { Button, Heading } from \"@godxjp/ui/general\";\nimport { Card, CardContent, Descriptions, ListRow, StatusBadge } from \"@godxjp/ui/data-display\";\nimport { Switch } from \"@godxjp/ui/data-entry\";\nimport { AlertDialog } from \"@godxjp/ui/feedback\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// Sections are a heading + spacing: the SECTION step (gap=\"xl\") is larger than the ITEM step\n// (gap=\"sm\"), which is what carries the hierarchy — not a box around every block.\n<Flex direction=\"col\" gap=\"xl\">\n\n {/* 1) IDENTITY — a section, not a Card. Metadata is Descriptions (a real <dl>). */}\n <Flex direction=\"col\" gap=\"sm\">\n <Flex justify=\"between\" align=\"center\">\n <Heading level={2}>アカウント情報</Heading>\n <Button variant=\"outline\" size=\"sm\">編集</Button>\n </Flex>\n <Descriptions columns={2}>\n <Descriptions.Item label=\"表示名\">藤原 慧</Descriptions.Item>\n <Descriptions.Item label=\"メールアドレス\">satoshi@example.co.jp</Descriptions.Item>\n <Descriptions.Item label=\"従業員番号\" mono>EMP-1042-JP</Descriptions.Item>\n <Descriptions.Item label=\"利用開始日\">\n <time dateTime=\"2023-04-01\">\n {new Intl.DateTimeFormat(locale, { dateStyle: \"long\", timeZone: \"UTC\" }).format(since)}\n </time>\n </Descriptions.Item>\n </Descriptions>\n </Flex>\n\n {/* 2) PREFERENCE ROWS — a list of setting rows IS a real boundary, so it gets one Card:\n CardContent flush + ListRow, whose trailing slot is the control. The visible title is the\n control's <label htmlFor>. NEVER a flex div with border-b rows. */}\n <Flex direction=\"col\" gap=\"sm\">\n <Heading level={2}>通知の環境設定</Heading>\n <Card>\n <CardContent flush>\n <ListRow\n align=\"start\"\n overflow=\"wrap\"\n title={<label htmlFor=\"pref-digest\">週次サマリーをメールで受け取る</label>}\n description=\"毎週月曜日の朝に未処理の承認をまとめて送信します。\"\n trailing={<Switch id=\"pref-digest\" checked={digest} onCheckedChange={setDigest} />}\n />\n </CardContent>\n </Card>\n </Flex>\n\n {/* 3) BILLING HANDOFF — a section. The state is a canonical StatusBadge (never a page-local\n status→colour map); money through Intl. */}\n <Flex direction=\"col\" gap=\"sm\">\n <Flex justify=\"between\" align=\"center\">\n <Heading level={2}>請求とサブスクリプション</Heading>\n <StatusBadge status=\"past_due\" variant=\"outline\" />\n </Flex>\n <Descriptions columns={2}>\n <Descriptions.Item label=\"契約プラン\">ビジネス(年額)</Descriptions.Item>\n <Descriptions.Item label=\"次回請求予定額\">\n {new Intl.NumberFormat(locale, { style: \"currency\", currency: \"JPY\" }).format(1536000)}\n </Descriptions.Item>\n </Descriptions>\n <div>\n <Button variant=\"outline\" asChild>\n <a href={portalUrl} target=\"_blank\" rel=\"noreferrer\">\n <ExternalLink aria-hidden=\"true\" />\n 請求ポータルを開く\n <span className=\"sr-only\">(新しいタブで開きます)</span>\n </a>\n </Button>\n </div>\n </Flex>\n\n {/* 4) DANGER ZONE — a real boundary: Card accent=\"destructive\" (the rail comes from\n --destructive). The heading and each row say what is lost; no decorative icon. */}\n <Flex direction=\"col\" gap=\"sm\">\n <Heading level={2}>取り消せない操作</Heading>\n <Card accent=\"destructive\">\n <CardContent flush>\n <ListRow\n align=\"start\"\n overflow=\"wrap\"\n title=\"ワークスペースと全データを完全に削除する\"\n description=\"勤怠記録・申請履歴・監査ログがすべて削除されます。\"\n trailing={<Button variant=\"destructive\" size=\"sm\" onClick={() => setConfirmOpen(true)}>削除</Button>}\n />\n </CardContent>\n </Card>\n </Flex>\n</Flex>\n\n<AlertDialog\n open={confirmOpen}\n onOpenChange={setConfirmOpen}\n title=\"ワークスペースを完全に削除しますか?\"\n description=\"この操作は取り消せません。確認のため識別子を入力してください。\"\n challenge={workspaceSlug}\n variant=\"destructive\"\n onConfirm={destroy}\n/>\n\n// ── DO / DON'T ────────────────────────────────────────────────────────────────────────────\n// ✅ Section step > item step (gap=\"xl\" between sections, gap=\"sm\" inside one).\n// ✅ A Card only where there is a real boundary: a list of setting rows, the danger zone.\n// ✅ Every setting row is a ListRow; its control lives in `trailing`, titled by <label htmlFor>.\n// ⛔ Do NOT add SettingsSection / SettingsRow / DangerZone to src/components — they fail Gate 0.\n// ⛔ Do NOT give every block Card + CardHeader + CardTitle + CardDescription (ui-audit card-per-section).\n// ⛔ Do NOT hand-roll padding (p-4), a status colour map, or a <dl> replacement.",
11
11
  "name": "settings-section-rows",
12
- "tagline": "The four canonical settings BLOCKS — identity · preference rows · billing handoff · danger zone — composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",
12
+ "tagline": "The four canonical settings blocks — identity · preference rows · billing handoff · danger zone. Identity and billing are SECTIONS (Heading + Descriptions, no surface); preference rows and the danger zone are Cards because they are real boundaries. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",
13
13
  "tags": [
14
14
  "settings",
15
15
  "account",
@@ -53,7 +53,7 @@
53
53
  "account-identity"
54
54
  ],
55
55
  "name": "settings-section-rows",
56
- "tagline": "The four canonical settings BLOCKS — identity · preference rows · billing handoff · danger zone — composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",
56
+ "tagline": "The four canonical settings blocks — identity · preference rows · billing handoff · danger zone. Identity and billing are SECTIONS (Heading + Descriptions, no surface); preference rows and the danger zone are Cards because they are real boundaries. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",
57
57
  "tags": [
58
58
  "settings",
59
59
  "account",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  {
85
85
  "name": "confirm-destructive",
86
- "tagline": "Type-to-confirm destructive dialog — AlertDialogRoot + Input gate + toast (real @godxjp/ui API).",
86
+ "tagline": "Type-to-confirm destructive dialog — the AlertDialog PRESET with `challenge` (the compound AlertDialog* parts are the older way in; a body the preset does not cover is Dialog variant=\"destructive\").",
87
87
  "tags": [
88
88
  "dialog",
89
89
  "confirm",
@@ -143,7 +143,7 @@
143
143
  },
144
144
  {
145
145
  "name": "inertia-list-page",
146
- "tagline": "Inertia + @godxjp/ui list page — PageContainer + Toolbar + DataTable + Badge + Pagination (current primitive API).",
146
+ "tagline": "Inertia + @godxjp/ui list page — PageContainer + Toolbar (above the table) + DataTable + Badge + Pagination. No KPI row, no restating subtitle.",
147
147
  "tags": [
148
148
  "inertia",
149
149
  "list",
@@ -157,7 +157,7 @@
157
157
  },
158
158
  {
159
159
  "name": "inertia-detail-page",
160
- "tagline": "Inertia detail page — receives {id} prop, Descriptions (compound) + StatCard + EmptyState fallback.",
160
+ "tagline": "Inertia detail page — receives {id} prop, Descriptions (compound) in one Card + EmptyState fallback. No KPI tile row.",
161
161
  "tags": [
162
162
  "inertia",
163
163
  "detail",
@@ -209,7 +209,7 @@
209
209
  },
210
210
  {
211
211
  "name": "badge-coloring",
212
- "tagline": "Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",
212
+ "tagline": "Colour a Badge for a localized STATUS via tone + icon; tiers and categories take one neutral tone (colour means status).",
213
213
  "tags": [
214
214
  "statusbadge",
215
215
  "badge",
@@ -348,14 +348,16 @@
348
348
  "stack-cards"
349
349
  ],
350
350
  "name": "page-sections",
351
- "tagline": "A page made of several Card sections: PageContainer spaces its direct children by default; group items inside a section with Flex/ResponsiveGrid, never Tailwind gap-*/p-*.",
351
+ "tagline": "A page of several sections: a Heading + section spacing marks a section, not a Card. PageContainer spaces its direct children (section step); Flex gap spaces items inside one (item step). Card only for a real boundary.",
352
352
  "tags": [
353
353
  "layout",
354
354
  "page",
355
355
  "card",
356
356
  "spacing",
357
357
  "flex",
358
- "grid"
358
+ "grid",
359
+ "section",
360
+ "heading"
359
361
  ]
360
362
  },
361
363
  {