@godxjp/ui 23.2.1 → 23.4.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 (101) hide show
  1. package/dist/app/timezones.js +2 -1
  2. package/dist/components/charts/chart-cartesian.js +2 -4
  3. package/dist/components/charts/chart-frame.js +2 -1
  4. package/dist/components/data-display/card.d.ts +87 -1
  5. package/dist/components/data-display/card.js +88 -15
  6. package/dist/components/data-display/index.d.ts +5 -1
  7. package/dist/components/data-display/index.js +5 -0
  8. package/dist/components/data-display/thought-chain.d.ts +42 -0
  9. package/dist/components/data-display/thought-chain.js +177 -0
  10. package/dist/components/data-display/welcome.d.ts +26 -0
  11. package/dist/components/data-display/welcome.js +37 -0
  12. package/dist/components/data-entry/attachments.d.ts +19 -0
  13. package/dist/components/data-entry/attachments.js +322 -0
  14. package/dist/components/data-entry/date-picker.js +12 -14
  15. package/dist/components/data-entry/index.d.ts +2 -0
  16. package/dist/components/data-entry/index.js +2 -0
  17. package/dist/components/data-entry/number-input.js +2 -1
  18. package/dist/components/feedback/dialog.d.ts +10 -7
  19. package/dist/components/feedback/dialog.js +40 -17
  20. package/dist/components/feedback/index.d.ts +3 -3
  21. package/dist/components/feedback/index.js +2 -0
  22. package/dist/components/feedback/skeleton.d.ts +11 -2
  23. package/dist/components/feedback/skeleton.js +8 -0
  24. package/dist/components/general/actions.d.ts +59 -0
  25. package/dist/components/general/actions.js +253 -0
  26. package/dist/components/general/activity.js +1 -0
  27. package/dist/components/general/button.js +2 -1
  28. package/dist/components/general/float-button.d.ts +41 -0
  29. package/dist/components/general/float-button.js +336 -0
  30. package/dist/components/general/index.d.ts +6 -2
  31. package/dist/components/general/index.js +12 -1
  32. package/dist/components/general/typography.d.ts +79 -4
  33. package/dist/components/general/typography.js +598 -54
  34. package/dist/components/layout/draggable-panel.d.ts +18 -0
  35. package/dist/components/layout/draggable-panel.js +189 -0
  36. package/dist/components/layout/error-surface.js +3 -2
  37. package/dist/components/layout/index.d.ts +2 -0
  38. package/dist/components/layout/index.js +2 -0
  39. package/dist/components/navigation/conversations.d.ts +39 -0
  40. package/dist/components/navigation/conversations.js +371 -0
  41. package/dist/components/navigation/index.d.ts +2 -0
  42. package/dist/components/navigation/index.js +2 -0
  43. package/dist/components/ui/card.js +1 -0
  44. package/dist/components/ui/toggle.js +2 -1
  45. package/dist/i18n/messages/en.json +53 -0
  46. package/dist/i18n/messages/ja.json +53 -0
  47. package/dist/i18n/messages/vi.json +53 -0
  48. package/dist/i18n/translate.js +3 -2
  49. package/dist/lib/datetime/picker-format.js +2 -1
  50. package/dist/lib/format.js +3 -2
  51. package/dist/lib/intl-cache.d.ts +32 -0
  52. package/dist/lib/intl-cache.js +29 -0
  53. package/dist/props/components/data-display.prop.d.ts +143 -1
  54. package/dist/props/components/data-entry.prop.d.ts +87 -0
  55. package/dist/props/components/feedback.prop.d.ts +53 -1
  56. package/dist/props/components/general.prop.d.ts +440 -3
  57. package/dist/props/components/index.d.ts +2 -2
  58. package/dist/props/components/layout.prop.d.ts +63 -1
  59. package/dist/props/components/navigation.prop.d.ts +150 -0
  60. package/dist/props/registry.d.ts +431 -0
  61. package/dist/props/registry.js +551 -0
  62. package/dist/props/vocabulary/index.d.ts +1 -1
  63. package/dist/props/vocabulary/interaction.prop.d.ts +113 -0
  64. package/dist/styles/alert-layout.css +22 -0
  65. package/dist/styles/card-layout.css +34 -0
  66. package/dist/styles/core.css +1 -0
  67. package/dist/styles/data-display-layout.css +236 -0
  68. package/dist/styles/float-button-layout.css +168 -0
  69. package/dist/styles/form-layout.css +11 -1
  70. package/dist/styles/index.css +1 -0
  71. package/dist/styles/layout.css +196 -0
  72. package/dist/styles/navigation-layout.css +139 -0
  73. package/dist/styles/text-layout.css +156 -0
  74. package/dist/tokens/axes.css +5 -0
  75. package/dist/tokens/base.css +7 -0
  76. package/dist/tokens/components/actions.css +14 -0
  77. package/dist/tokens/components/attachments.css +15 -0
  78. package/dist/tokens/components/conversations.css +28 -0
  79. package/dist/tokens/components/draggable-panel.css +25 -0
  80. package/dist/tokens/components/float-button.css +39 -0
  81. package/dist/tokens/components/text.css +13 -0
  82. package/dist/tokens/components/thought-chain.css +31 -0
  83. package/dist/tokens/components/welcome.css +19 -0
  84. package/dist/tokens/foundation.css +6 -4
  85. package/docs/DESIGN-AUTHORITY.md +133 -20
  86. package/docs/FRAME-COVERAGE-REPORT.md +5 -2
  87. package/docs/data-display/card/examples/tab-list.tsx +97 -0
  88. package/docs/data-display/thought-chain.tsx +176 -0
  89. package/docs/data-display/welcome.tsx +115 -0
  90. package/docs/data-entry/attachments.tsx +160 -0
  91. package/docs/feedback/dialog.tsx +50 -0
  92. package/docs/general/actions.tsx +175 -0
  93. package/docs/general/float-button.tsx +133 -0
  94. package/docs/general/typography.tsx +63 -1
  95. package/docs/layout/draggable-panel.tsx +127 -0
  96. package/docs/navigation/conversations.tsx +176 -0
  97. package/docs/roadmap/parity-audit-layout-navigation-general.md +17 -16
  98. package/package.json +20 -4
  99. package/scripts/_agent-setup.mjs +36 -5
  100. package/scripts/guinea-pig-skill.md +14 -0
  101. package/scripts/ui-audit.mjs +69 -9
@@ -1,5 +1,14 @@
1
1
  import { useTranslation } from "@godxjp/ui/i18n";
2
- import { Button, Heading, Text, VisuallyHidden } from "@godxjp/ui/general";
2
+ import {
3
+ Button,
4
+ Heading,
5
+ Link,
6
+ Paragraph,
7
+ Text,
8
+ Title,
9
+ Typography,
10
+ VisuallyHidden,
11
+ } from "@godxjp/ui/general";
3
12
  import {
4
13
  Card,
5
14
  CardContent,
@@ -231,6 +240,59 @@ export default function Demo() {
231
240
  </Flex>
232
241
  </CardContent>
233
242
  </Card>
243
+ <Card>
244
+ <CardHeader>
245
+ <CardTitle level={2}>antd Typography · Title / Paragraph / Link</CardTitle>
246
+ <CardDescription>
247
+ antd の Typography をそのまま移植したもの。Text と Heading は変わらず、Title は 5
248
+ 段階、Paragraph は rows 付きの ellipsis、Link は target=&quot;_blank&quot; に rel
249
+ を自動で付けます。
250
+ </CardDescription>
251
+ </CardHeader>
252
+ <CardContent>
253
+ <Flex direction="col" gap="md">
254
+ <Typography>
255
+ <Title level={3}>請求サマリー</Title>
256
+ <Paragraph>今月の請求は 3 件です。明細は各行のリンクから確認できます。</Paragraph>
257
+ <Link href="#typography">変更履歴を見る</Link>
258
+ </Typography>
259
+
260
+ <Title level={5}>内訳(level 5 · Heading にはない段)</Title>
261
+
262
+ <Paragraph ellipsis={{ rows: 2, expandable: true }}>
263
+ ログイン画面の余白が狭く、パスワード再設定リンクが本文と重なって見えます。
264
+ 発生条件は 390px 幅の端末で、日本語ロケールのときのみです。
265
+ 再現手順とスクリーンショットは添付の通りです。
266
+ </Paragraph>
267
+ </Flex>
268
+ </CardContent>
269
+ </Card>
270
+ <Card>
271
+ <CardHeader>
272
+ <CardTitle level={2}>copyable · editable · 装飾</CardTitle>
273
+ <CardDescription>
274
+ antd の copyable / editable と、7 つの装飾フラグ。装飾は本物の要素を包むので、意味が
275
+ スクリーンリーダーにも届きます。
276
+ </CardDescription>
277
+ </CardHeader>
278
+ <CardContent>
279
+ <Flex direction="col" gap="sm">
280
+ <Text mono copyable>
281
+ RC-204881
282
+ </Text>
283
+ <Text editable>案件名をここで直接編集できます</Text>
284
+ <Text type="secondary">
285
+ type=&quot;secondary&quot;(tone=&quot;muted&quot; と同じ軸)
286
+ </Text>
287
+ <Text disabled>disabled · 使えないことを示します</Text>
288
+ <Text>
289
+ <Text code>npm run build</Text> と <Text keyboard>Ctrl</Text> +{" "}
290
+ <Text keyboard>S</Text>、<Text mark>強調</Text>、<Text delete>取り消し</Text>、
291
+ <Text strong>強い</Text>、<Text italic>斜体</Text>
292
+ </Text>
293
+ </Flex>
294
+ </CardContent>
295
+ </Card>
234
296
  <Card>
235
297
  <CardHeader>
236
298
  <CardTitle level={2}>{t("textExamples.preservedText")}</CardTitle>
@@ -0,0 +1,127 @@
1
+ import { useState } from "react";
2
+ import { DraggablePanel, Flex, PageContainer } from "@godxjp/ui/layout";
3
+ import type { DraggablePanelPositionProp } from "@godxjp/ui/layout";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ Badge,
11
+ Descriptions,
12
+ } from "@godxjp/ui/data-display";
13
+ import { Button, Text } from "@godxjp/ui/general";
14
+
15
+ /**
16
+ * DraggablePanel · a floating surface the person using it can MOVE.
17
+ *
18
+ * The case it exists for: an assistant docked to a corner covers the thing the
19
+ * person is asking about. Drag it by the title-bar handle, or focus the handle
20
+ * and use the arrow keys. The position is REPORTED to the page below, never
21
+ * stored by the library.
22
+ */
23
+ export default function Demo() {
24
+ const [position, setPosition] = useState<DraggablePanelPositionProp>({ x: 0, y: 0 });
25
+ const [open, setOpen] = useState(true);
26
+ const [pinned, setPinned] = useState(false);
27
+
28
+ return (
29
+ <PageContainer
30
+ title="DraggablePanel"
31
+ subtitle="floating movable surface · handle drag, arrow-key nudge, viewport-bounded"
32
+ >
33
+ <Flex direction="col" gap="lg">
34
+ <Card>
35
+ <CardHeader>
36
+ <CardTitle level={2}>Reported position</CardTitle>
37
+ <CardDescription>
38
+ Drag the panel by its handle, or focus the handle and press an arrow key. Hold Shift
39
+ for the larger step. Every frame and every nudge reports the clamped offset through
40
+ onPositionChange; the page below decides whether to remember it.
41
+ </CardDescription>
42
+ </CardHeader>
43
+ <CardContent>
44
+ <Flex direction="col" gap="md">
45
+ <Descriptions
46
+ items={[
47
+ { label: "x", value: String(Math.round(position.x)) },
48
+ { label: "y", value: String(Math.round(position.y)) },
49
+ ]}
50
+ />
51
+ <Flex gap="sm" wrap>
52
+ <Button
53
+ variant="outline"
54
+ size="sm"
55
+ onClick={() => {
56
+ setPosition({ x: 0, y: 0 });
57
+ }}
58
+ >
59
+ 角に戻す
60
+ </Button>
61
+ <Button
62
+ variant="outline"
63
+ size="sm"
64
+ onClick={() => {
65
+ setPinned((value) => !value);
66
+ }}
67
+ >
68
+ {pinned ? "移動を許可" : "その場に固定"}
69
+ </Button>
70
+ <Button
71
+ variant="outline"
72
+ size="sm"
73
+ onClick={() => {
74
+ setOpen(true);
75
+ }}
76
+ >
77
+ パネルを開く
78
+ </Button>
79
+ </Flex>
80
+ </Flex>
81
+ </CardContent>
82
+ </Card>
83
+
84
+ <Card>
85
+ <CardHeader>
86
+ <CardTitle level={2}>What it is not</CardTitle>
87
+ <CardDescription>
88
+ ResizablePanel resizes panes WITHIN a layout, Sheet is pinned to an edge, and Popover
89
+ is anchored to its trigger. None of them can be repositioned by the viewer, which is
90
+ what a floating assistant needs.
91
+ </CardDescription>
92
+ </CardHeader>
93
+ <CardContent>
94
+ <Text size="sm" tone="muted">
95
+ The panel is a labelled region, not a dialog: it interrupts nothing, traps no focus
96
+ and dims no page, so the work behind it stays usable while it is open.
97
+ </Text>
98
+ </CardContent>
99
+ </Card>
100
+ </Flex>
101
+
102
+ {open ? (
103
+ <DraggablePanel
104
+ title="アシスタント"
105
+ placement="bottom-end"
106
+ width="sm"
107
+ disabled={pinned}
108
+ position={position}
109
+ onPositionChange={setPosition}
110
+ onClose={() => {
111
+ setOpen(false);
112
+ }}
113
+ extra={<Badge tone="info">β</Badge>}
114
+ >
115
+ <Flex direction="col" gap="sm">
116
+ <Text size="sm">
117
+ この請求書の消費税区分について質問できます。テキストは選択できます。掴むのはタイトルバーのハンドルだけです。
118
+ </Text>
119
+ <Text size="xs" tone="muted">
120
+ キーボード: ハンドルにフォーカスして矢印キー · Shift で大きく移動
121
+ </Text>
122
+ </Flex>
123
+ </DraggablePanel>
124
+ ) : null}
125
+ </PageContainer>
126
+ );
127
+ }
@@ -0,0 +1,176 @@
1
+ import { useState } from "react";
2
+
3
+ import { ChatBubbleList, type ChatMessageProp } from "@godxjp/ui/data-display";
4
+ import { ChatComposer } from "@godxjp/ui/data-entry";
5
+ import { Text } from "@godxjp/ui/general";
6
+ import {
7
+ AppShell,
8
+ Flex,
9
+ MasterDetail,
10
+ PageContainer,
11
+ Sidebar,
12
+ type SidebarSectionProp,
13
+ Topbar,
14
+ } from "@godxjp/ui/layout";
15
+ import { Conversations, type ConversationsEntryProp } from "@godxjp/ui/navigation";
16
+ import { Bot, MessageSquare, Pencil, Settings, Trash2, Users } from "lucide-react";
17
+
18
+ /**
19
+ * Conversations — the session rail of a chat surface (Ant Design X `Conversations`).
20
+ *
21
+ * This page is the whole surface gh#559 is about: the rail on the left, the feed and the composer
22
+ * on the right. They were shipped one half at a time, and the missing half is why every consumer
23
+ * hand-rolled a stack of full-width Buttons — one tab stop per conversation, no per-row menu, no
24
+ * recency buckets.
25
+ *
26
+ * The three cards below are one axis each, at rest, so the API is visible without clicking:
27
+ * the live surface, the recency buckets (`groupable`), and the flat rail with a divider and a
28
+ * disabled row. Composed only from real @godxjp/ui components.
29
+ */
30
+ const sections: SidebarSectionProp[] = [
31
+ {
32
+ label: "アシスタント",
33
+ items: [
34
+ { id: "chat", label: "チャット", icon: MessageSquare },
35
+ { id: "agents", label: "エージェント", icon: Bot },
36
+ { id: "members", label: "メンバー", icon: Users },
37
+ ],
38
+ },
39
+ { label: "管理", items: [{ id: "settings", label: "設定", icon: Settings }] },
40
+ ];
41
+
42
+ /** The rail's data, bucketed by recency — the `group` field is what `groupable` reads. */
43
+ const HISTORY: ConversationsEntryProp[] = [
44
+ { key: "invoice", label: "請求書の下書きを直す", group: "今日" },
45
+ { key: "expense", label: "経費精算の規則をまとめる", group: "今日" },
46
+ { key: "travel", label: "出張手当の確認", group: "過去7日間" },
47
+ { key: "onboarding", label: "入社手続きのチェックリスト", group: "過去7日間" },
48
+ { key: "payroll", label: "給与計算の締め日について", group: "それ以前" },
49
+ ];
50
+
51
+ /** A flat rail — no buckets, one rule, one archived row. */
52
+ const FLAT: ConversationsEntryProp[] = [
53
+ { key: "invoice", label: "請求書の下書きを直す", icon: <MessageSquare /> },
54
+ { key: "expense", label: "経費精算の規則をまとめる", icon: <MessageSquare /> },
55
+ { type: "divider", key: "rule" },
56
+ { key: "archived", label: "アーカイブ済みの相談", icon: <MessageSquare />, disabled: true },
57
+ ];
58
+
59
+ const TRANSCRIPT: Record<string, ChatMessageProp[]> = {
60
+ invoice: [
61
+ { id: "m1", role: "user", content: "先月分の請求書の下書きを直したいです。" },
62
+ {
63
+ id: "m2",
64
+ role: "assistant",
65
+ content: "差分は3件でした。金額、支払期日、宛名のうちどれから直しますか。",
66
+ },
67
+ ],
68
+ expense: [
69
+ { id: "m1", role: "user", content: "経費精算の規則を3行でまとめてください。" },
70
+ {
71
+ id: "m2",
72
+ role: "assistant",
73
+ content: "領収書は原本、申請は当月末まで、3万円以上は事前承認が要ります。",
74
+ },
75
+ ],
76
+ };
77
+
78
+ export default function ConversationsDoc() {
79
+ const [active, setActive] = useState("invoice");
80
+ const [draft, setDraft] = useState("");
81
+
82
+ const menu = {
83
+ items: [
84
+ { key: "rename", label: "名前を変更", icon: <Pencil /> },
85
+ { key: "delete", label: "削除", icon: <Trash2 />, danger: true },
86
+ ],
87
+ };
88
+
89
+ return (
90
+ <AppShell
91
+ sidebar={
92
+ <Sidebar
93
+ activeId="chat"
94
+ sections={sections}
95
+ onSelect={() => {}}
96
+ product={{ name: "CoreDesk", role: "アシスタント", color: "hsl(var(--primary))" }}
97
+ />
98
+ }
99
+ topbar={<Topbar />}
100
+ >
101
+ <PageContainer
102
+ title="会話の履歴"
103
+ subtitle="過去の会話を選び、名前を変更し、不要になったものを消す · チャット面の左半分。"
104
+ >
105
+ {/* THE SURFACE. The rail is the master; the feed and the composer are the detail. */}
106
+ <MasterDetail
107
+ rail="master"
108
+ railWidth="compact"
109
+ masterViewport="compact"
110
+ masterLabel="会話の履歴"
111
+ detailLabel="選択中の会話"
112
+ detailId="assistant-transcript"
113
+ master={
114
+ <Conversations
115
+ label="会話の履歴"
116
+ items={HISTORY}
117
+ activeKey={active}
118
+ onActiveChange={setActive}
119
+ groupable={{ collapsible: true }}
120
+ creation={{ label: "新しい会話", onClick: () => setActive("invoice") }}
121
+ menu={menu}
122
+ />
123
+ }
124
+ >
125
+ <Flex direction="col" gap="md">
126
+ <ChatBubbleList
127
+ label="会話"
128
+ className="h-96"
129
+ items={TRANSCRIPT[active] ?? []}
130
+ roles={{
131
+ assistant: { placement: "start", variant: "filled" },
132
+ user: { placement: "end" },
133
+ }}
134
+ />
135
+ <ChatComposer
136
+ aria-label="メッセージ"
137
+ value={draft}
138
+ onValueChange={setDraft}
139
+ onSubmit={() => setDraft("")}
140
+ placeholder="メッセージを入力"
141
+ />
142
+ </Flex>
143
+ </MasterDetail>
144
+
145
+ {/* BUCKETS, EXPANDED. `groupable` with no options groups but never collapses. */}
146
+ <Flex direction="col" gap="sm">
147
+ <Text size="sm" weight="medium">
148
+ recency buckets · groupable
149
+ </Text>
150
+ <Text size="xs" tone="muted">
151
+ バケットの見出しは同じローヴィング順に入るので、畳む操作もレールを離れずに届く。
152
+ </Text>
153
+ <Conversations
154
+ label="会話の履歴(分類のみ)"
155
+ items={HISTORY}
156
+ defaultActiveKey="travel"
157
+ groupable
158
+ menu={menu}
159
+ />
160
+ </Flex>
161
+
162
+ {/* FLAT, WITH A RULE AND A DISABLED ROW. */}
163
+ <Flex direction="col" gap="sm">
164
+ <Text size="sm" weight="medium">
165
+ flat rail · divider and a disabled row
166
+ </Text>
167
+ <Text size="xs" tone="muted">
168
+ 無効な行は見えるが選べず、矢印キーの巡回からも外れる。無効なコントロールは
169
+ フォーカスを受け取らないため。
170
+ </Text>
171
+ <Conversations label="会話の履歴(フラット)" items={FLAT} defaultActiveKey="invoice" />
172
+ </Flex>
173
+ </PageContainer>
174
+ </AppShell>
175
+ );
176
+ }
@@ -14,8 +14,9 @@ ahead of it: `AppShell` (three navigation scopes, nav rail, mobile drawer), `Mob
14
14
  `AuthShell`, `LegalDocumentShell`, `ErrorSurface`, `AppLauncher` and `AppProvider` have no antd
15
15
  counterpart at all, and `Breadcrumb`, `Separator`, `Toolbar` and `Pagination` are already carrying
16
16
  explicit antd-parity work in their prop docs. The "missing features vs Ant Design" complaint does
17
- **not** originate here — most of what antd ships in `Space`, `Divider`, `Grid`, `Anchor`, `Affix`,
18
- `FloatButton` and `App` is genuinely a composition or an existing primitive in this system (§4.2).
17
+ **not** originate here — most of what antd ships in `Space`, `Divider`, `Grid`, `Anchor`, `Affix`
18
+ and `App` is genuinely a composition or an existing primitive in this system (§4.2). `FloatButton`
19
+ was on that list and should not have been: it shipped in gh#558. See the struck-through row in §4.2.
19
20
 
20
21
  What is real, ordered by how badly it hurts:
21
22
 
@@ -420,20 +421,20 @@ an editing lifecycle to the typographic primitive would make every `<Text>` in t
420
421
 
421
422
  ### 4.2 These antd components are **compositions** here, not gaps
422
423
 
423
- | antd | Why it is not a gap | Build it with |
424
- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
425
- | `Space` | `gap` already lives on the layout primitive, with a counted escape hatch (`data-gap-raw`). C3 fails outright. | `Flex gap` / `ResponsiveGrid gap` |
426
- | `Divider` | Exists. | `Separator` (`src/props/components/layout.prop.ts:567`) |
427
- | `Grid` / `Row` / `Col` | Exists, in the house `{base,sm,md,lg}` count model rather than a 12-column raster. | `ResponsiveGrid` + `ResponsiveGrid.Item span` (`src/components/layout/responsive-grid.tsx:115`) |
428
- | `Affix` | C1/C2/C3 all fail — it is `position: sticky` with a JS shim, and antd's own docs now recommend the CSS property. Sticky chrome is already a first-class slot here. | `PageContainer toolbar` (`layout.prop.ts:82-88`), `PageContainer stickyFooter`, `Toolbar sticky`, `DataTable sticky` |
429
- | `Anchor` | C1/C7 fail — the one real use (a long document with a contents rail) is already owned end-to-end, including scroll-spy, hash deep-linking, focus handoff and `aria-current="location"`. A second general Anchor would duplicate it. | `LegalDocumentShell` (`layout.prop.ts:1279-1331`) |
430
- | `FloatButton` / `BackTop` | C1/C2 faillayout + content + colour, positioned by the screen that wants it. `BackTop` additionally competes with the shell's own scroll ownership (`MobileShell` is deliberately the only scroll container, `layout.prop.ts:504-507`). | `Button shape="pill" size="icon-lg"` placed by the page; a token for the inset if one is missing |
431
- | `Layout.Header` / `Content` / `Footer` | Bare boxes in antd; real slots here. | `AppShell topbar` / `children` / `footer`, `Topbar`, `PageContainer` |
432
- | `App` (static-method context) | The context problem it solves does not exist here — nothing is imperative. | `AppProvider` + `Toaster` + declarative `Dialog`/`AlertDialog` |
433
- | `PageHeader` (removed from antd core) | Exists, and is wider. | `PageContainer` (`layout.prop.ts:64-139`) |
434
- | `Result` | Exists for HTTP statuses; the non-HTTP shapes are an empty state with an action. | `ErrorSurface` / `EmptyState` |
435
- | `Menu` `items` config array as the only API | Both dialects exist already: config-array for the route rails, compound parts for the overlay menus. | `Sidebar.sections` / `NavList.items` · `DropdownMenu*` |
436
- | `Dropdown.Button` | Two existing primitives plus the proposed `ButtonGroup`. | `ButtonGroup` + `Button` + `DropdownMenu` |
424
+ | antd | Why it is not a gap | Build it with |
425
+ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
426
+ | `Space` | `gap` already lives on the layout primitive, with a counted escape hatch (`data-gap-raw`). C3 fails outright. | `Flex gap` / `ResponsiveGrid gap` |
427
+ | `Divider` | Exists. | `Separator` (`src/props/components/layout.prop.ts:567`) |
428
+ | `Grid` / `Row` / `Col` | Exists, in the house `{base,sm,md,lg}` count model rather than a 12-column raster. | `ResponsiveGrid` + `ResponsiveGrid.Item span` (`src/components/layout/responsive-grid.tsx:115`) |
429
+ | `Affix` | C1/C2/C3 all fail — it is `position: sticky` with a JS shim, and antd's own docs now recommend the CSS property. Sticky chrome is already a first-class slot here. | `PageContainer toolbar` (`layout.prop.ts:82-88`), `PageContainer stickyFooter`, `Toolbar sticky`, `DataTable sticky` |
430
+ | `Anchor` | C1/C7 fail — the one real use (a long document with a contents rail) is already owned end-to-end, including scroll-spy, hash deep-linking, focus handoff and `aria-current="location"`. A second general Anchor would duplicate it. | `LegalDocumentShell` (`layout.prop.ts:1279-1331`) |
431
+ | ~~`FloatButton` / `BackTop`~~ **SHIPPED** | **This row was wrong, and it is kept struck through rather than deleted so the reversal is legible.** It read C1/C2 as failing; the repo owner's 2026-09-12 decision (docs/DESIGN-AUTHORITY.md antd IS the standard, ported 100% first) settles the question the other way, and gh#558 supplied the C1 case the row denied: pinning a `Button` to a corner requires `position: fixed` at the call site, which `ui-audit` blocks with no legal spelling. The `BackTop`-vs-shell-scroll objection was real and is answered by antd's own `target` prop, which points BackTop at a shell's scroll region instead of the document. | `FloatButton` (`src/components/general/float-button.tsx`), insets on `--float-button-offset-block-end` / `-inline-end` |
432
+ | `Layout.Header` / `Content` / `Footer` | Bare boxes in antd; real slots here. | `AppShell topbar` / `children` / `footer`, `Topbar`, `PageContainer` |
433
+ | `App` (static-method context) | The context problem it solves does not exist here — nothing is imperative. | `AppProvider` + `Toaster` + declarative `Dialog`/`AlertDialog` |
434
+ | `PageHeader` (removed from antd core) | Exists, and is wider. | `PageContainer` (`layout.prop.ts:64-139`) |
435
+ | `Result` | Exists for HTTP statuses; the non-HTTP shapes are an empty state with an action. | `ErrorSurface` / `EmptyState` |
436
+ | `Menu` `items` config array as the only API | Both dialects exist already: config-array for the route rails, compound parts for the overlay menus. | `Sidebar.sections` / `NavList.items` · `DropdownMenu*` |
437
+ | `Dropdown.Button` | Two existing primitives plus the proposed `ButtonGroup`. | `ButtonGroup` + `Button` + `DropdownMenu` |
437
438
 
438
439
  ---
439
440
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "23.2.1",
4
- "godxUiMcp": "23.2.1",
3
+ "version": "23.4.0",
4
+ "godxUiMcp": "23.4.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -397,7 +397,8 @@
397
397
  "check:number-input-step-target": "node scripts/check-number-input-step-target.mjs",
398
398
  "check:mcp-token-sync": "node scripts/gen-component-tokens.mjs --check",
399
399
  "check:email-token-sync": "node scripts/gen-email-tokens.mjs --check",
400
- "check:focus-ring-paint": "node scripts/check-focus-ring-paint.mjs"
400
+ "check:focus-ring-paint": "node scripts/check-focus-ring-paint.mjs",
401
+ "typecheck:mcp": "node node_modules/typescript-7/bin/tsc --noEmit -p mcp/tsconfig.json"
401
402
  },
402
403
  "peerDependencies": {
403
404
  "@hookform/resolvers": "^5.2.0",
@@ -412,11 +413,26 @@
412
413
  "zod": "^4.4.0"
413
414
  },
414
415
  "peerDependenciesMeta": {
415
- "recharts": {
416
+ "@hookform/resolvers": {
417
+ "optional": true
418
+ },
419
+ "@tanstack/react-query": {
416
420
  "optional": true
417
421
  },
418
422
  "playwright": {
419
423
  "optional": true
424
+ },
425
+ "react-hook-form": {
426
+ "optional": true
427
+ },
428
+ "react-router-dom": {
429
+ "optional": true
430
+ },
431
+ "recharts": {
432
+ "optional": true
433
+ },
434
+ "zod": {
435
+ "optional": true
420
436
  }
421
437
  },
422
438
  "dependencies": {
@@ -10,8 +10,17 @@
10
10
  * The guarantee is now structural: a file that exists but cannot be read, parsed, or recognised is
11
11
  * NEVER written to. We leave a `.godxjp-ui-suggested` sidecar next to it and say so.
12
12
  */
13
- import { createHash } from "node:crypto";
14
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
13
+ import { createHash, randomBytes } from "node:crypto";
14
+ import {
15
+ chmodSync,
16
+ existsSync,
17
+ mkdirSync,
18
+ readFileSync,
19
+ renameSync,
20
+ statSync,
21
+ unlinkSync,
22
+ writeFileSync,
23
+ } from "node:fs";
15
24
  import { basename, dirname, join } from "node:path";
16
25
  import { fileURLToPath } from "node:url";
17
26
 
@@ -205,9 +214,31 @@ function readJson(path) {
205
214
  * write manufactured the precondition for the overwrite.
206
215
  */
207
216
  function writeFileAtomic(path, data) {
208
- const tmp = `${path}.godxjp-ui-tmp`;
209
- writeFileSync(tmp, data);
210
- renameSync(tmp, path);
217
+ // UNIQUE temp name. A fixed `${path}.godxjp-ui-tmp` is a shared mutable file: two installs
218
+ // running at once — a workspace installing packages in parallel is enough — write over each
219
+ // other's temp and `rename` whichever finished last.
220
+ const tmp = `${path}.godxjp-ui-tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
221
+ try {
222
+ writeFileSync(tmp, data);
223
+ // `rename` does NOT carry the target's permissions: the new file is born under the process
224
+ // umask. A `.mcp.json` the consumer had chmod 600 would come back 644 after the first sync,
225
+ // silently, because the CONTENT would be right. This happens on every write, not only under
226
+ // contention, which makes it the worse half of the two.
227
+ try {
228
+ chmodSync(tmp, statSync(path).mode & 0o7777);
229
+ } catch {
230
+ // No existing file (a create), or a filesystem that will not report/set the mode. Either
231
+ // way the default is correct and this must not abort the write.
232
+ }
233
+ renameSync(tmp, path);
234
+ } catch (error) {
235
+ try {
236
+ unlinkSync(tmp);
237
+ } catch {
238
+ // Nothing to clean up.
239
+ }
240
+ throw error;
241
+ }
211
242
  }
212
243
 
213
244
  /**
@@ -131,6 +131,20 @@ những thứ ấy trên PR. **Tới được bước này KHÔNG phải là đ
131
131
  một câu hỏi, không phải một chướng ngại. Nếu bạn tin cổng ấy sai thì nói ra và
132
132
  đưa số đo, đừng lặng lẽ sửa nó.
133
133
 
134
+ **VÀ ĐỪNG NGỒI CHỜ CI.** Đẩy nhánh, mở PR, rồi đi làm việc khác. Không có vòng lặp
135
+ `until … gh pr checks … sleep` nào cả. CI chạy là việc của CI; nếu cần theo dõi thì
136
+ mở một agent nền, đừng chặn người đang điều phối. Đo được ngày 12/09/2026: một lượt
137
+ ngồi poll bốn shard đã ăn hơn một tiếng đồng hồ của chủ dự án để nhìn một thanh tiến
138
+ trình, trong khi có việc khác đang xếp hàng.
139
+
140
+ **Và full suite thì chạy theo LỊCH, không nằm trong vòng lặp sửa code của ai.** Cùng
141
+ ngày, tôi thêm bốn shard vitest vào làn PR của kho DS và đặt chúng thành required
142
+ check, để bịt một khoảng trống có thật (hai commit vào `main` đỏ qua một làn nhanh
143
+ xanh). Khoảng trống là thật và phép đo trung thực — nhưng nó chỉ định giá **máy**.
144
+ Máy chưa bao giờ là phần đắt. Từ lúc ấy mọi PR phải chờ bốn shard mới merge được.
145
+ Đã hoàn nguyên. Một hàng rào làm người điều phối phải chờ không rẻ hơn một `main`
146
+ đỏ; nó chỉ dời chi phí sang con đường duy nhất không song song hoá được.
147
+
134
148
  ### Bước 6 — Khép vòng
135
149
 
136
150
  Phát hành → nâng gói ở consumer → **gỡ vá tạm** → **gỡ mọi chú thích "chờ