@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.
- package/DESIGN.md +210 -0
- package/README.md +3 -2
- package/agent/DESIGN.md +210 -0
- package/agent/START-HERE.md +25 -29
- package/agent/anti-ai-tells.json +56 -2
- package/agent/components/Anchor.json +1 -1
- package/agent/components/ChatSuggestion.json +1 -1
- package/agent/components/LegalDocumentShell.json +7 -1
- package/agent/components/PageContainer.json +1 -2
- package/agent/components/Progress.json +1 -1
- package/agent/components-index.json +1 -1
- package/agent/components.json +11 -6
- package/agent/index.json +11 -5
- package/agent/llms.txt +7 -6
- package/agent/patterns/badge-coloring.json +2 -2
- package/agent/patterns/common-fixes.json +1 -1
- package/agent/patterns/confirm-destructive.json +2 -2
- package/agent/patterns/inertia-detail-page.json +2 -2
- package/agent/patterns/inertia-list-page.json +2 -2
- package/agent/patterns/page-sections.json +5 -3
- package/agent/patterns/settings-section-rows.json +2 -2
- package/agent/patterns-index.json +9 -7
- package/agent/patterns.json +16 -14
- package/agent/tokens.json +12 -0
- package/dist/components/data-entry/select.js +5 -4
- package/dist/components/layout/page-container.js +14 -8
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/lab/legal-document-shell.js +7 -3
- package/dist/props/components/layout.prop.d.ts +13 -1
- package/dist/props/registry.d.ts +4 -0
- package/dist/props/registry.js +5 -0
- package/dist/styles/layers.json +1 -1
- package/dist/styles/layout.css +26 -0
- package/dist/styles.css +1 -1
- package/dist/tokens/components/legal-document.css +4 -0
- package/docs/CONSUMER-RULES.md +6 -5
- package/docs/README.md +1 -1
- package/docs/migrations/v32.md +10 -5
- package/package.json +3 -2
- package/scripts/_agent-setup.mjs +149 -85
- package/scripts/cli.mjs +8 -0
- package/scripts/consumer-rule.md +41 -77
- package/scripts/ui-audit.mjs +189 -29
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
--legal-document-toc-width: 15rem;
|
|
10
10
|
|
|
11
|
+
--legal-document-page-padding-inline: initial;
|
|
12
|
+
|
|
13
|
+
--legal-document-title-font-size: initial;
|
|
14
|
+
|
|
11
15
|
--legal-document-toc-inset-block-start: var(--space-6);
|
|
12
16
|
|
|
13
17
|
--legal-document-toc-max-height: calc(100dvh - var(--space-12));
|
package/docs/CONSUMER-RULES.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# @godxjp/ui — the
|
|
1
|
+
# @godxjp/ui — the consumer rules
|
|
2
2
|
|
|
3
|
-
Read this once; the audit enforces it.
|
|
3
|
+
Read this once; the audit enforces it. What the audit cannot see (how a screen is composed, what it
|
|
4
|
+
should look like) is in the package's `DESIGN.md`. Everything else in `docs/` is for contributors.
|
|
4
5
|
|
|
5
6
|
1. Load styles with `import "@godxjp/ui/styles.css"` (precompiled, no Tailwind needed) or `@import "@godxjp/ui/styles"` (Tailwind v4). Neither ships font files: fonts are opt-in since v32 (`pnpm add @fontsource/noto-sans-jp @fontsource/m-plus-2`, then `@import "@godxjp/ui/styles/fonts.css"` after the styles import; the GoDX preset does this for you). Never cherry-pick `*-layout.css`.
|
|
6
|
-
2. **Mount `<AppProvider defaultLocale="…">` once, at the root.** Without it the kit's OWN strings (dialog close/cancel/delete, counters, picker labels) fall to the resting locale
|
|
7
|
+
2. **Mount `<AppProvider defaultLocale="…">` once, at the root.** Without it the kit's OWN strings (dialog close/cancel/delete, counters, picker labels) fall to the resting locale (`<html lang>` when it names a registered locale, else `en`) and pickers to UTC (gh#1005). A development build now warns once when a kit component renders outside it; `syncI18nLocale(locale, fallback)` is the escape for an app that truly cannot mount the provider. Every page is `<PageContainer title extra footer>` (a `subtitle` only when it adds a fact the title does not); its sections are spaced by the page. Group items inside a section with `<Flex direction="col" gap>` or `<ResponsiveGrid>`.
|
|
7
8
|
3. No Tailwind layout on your own elements: no `flex`, `grid`, `gap-*`, `p-*`, `m-*`, `space-*`. Rows are `<Flex>` (default row), stacks are `<Flex direction="col">`, grids are `<ResponsiveGrid>`.
|
|
8
9
|
4. No hand-rolled surfaces: no `rounded-* border bg-*` divs. A box is `Card`, a pill is `Badge`, a person is `Avatar`, a row is `ListRow`, a label/value pair is `Descriptions`, an empty area is `EmptyState`, a read-only sample of a colour the USER chose is `Swatch`. A LIST of those rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children — `marker="none"` keeps the `<ul>`, the `<li>` semantics and the gap token while dropping the bullet and the indent. Never a raw `<ul>`/`<ol>` (it carries no gap token), never `<div role="list">` + `<div role="listitem">`, and never a wrapper around each row: the divider is `:not(:last-child)` among siblings, so a row alone in its own wrapper is always the last one and EVERY divider disappears silently. A bulleted prose list is the same `<Flex as="ul">` without `marker`.
|
|
9
|
-
5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below. **Fields live in a `<Form layout="horizontal" labelWidth controlWidth>`** — never a group of `FormField`s without one, never a hand-rolled `<Flex>` row of fields (a row that belongs together is `<
|
|
10
|
+
5. Real controls only: `Button`, `Input`, `Select`, `Textarea`, `Checkbox`… never raw `<button>`/`<input>`; a labelled control lives in `<FormField label>`. A Select outside a form takes `width="auto"`. **A disabled control's reason is visible text, never a tooltip** — see below. **Fields live in a `<Form layout="horizontal" labelWidth controlWidth>`** — never a group of `FormField`s without one, never a hand-rolled `<Flex>` row of fields (a row that belongs together is `<Flex attached>` or `<Form columns>`); size each control for its content with `controlWidth` (GOV.UK text-input width: a port ~7rem, a short enum ~10rem); three or more fields is a page, not a Dialog. The audit rules are `formfield-needs-form`, `dialog-form-too-big` and `select-width-hint`. **A case the kit cannot express is an issue, not a hand-roll:** open it on godx-jp/godxjp-ui, use the nearest valid composition, and mark it `// TODO(godxjp-ui#<n>)` (gh#998). **One action row, one Button size:** every `Button` under one parent takes the same `size` — a missing size is `default`, and `icon-sm` pairs with `sm`, `icon-xs` with `xs`, `icon` with default. Never `size="sm"` beside a default Button. The audit rules are `mixed-button-size` (static) and `mixed-button-height` (visual-audit, within 0.5px) (gh#1084).
|
|
10
11
|
6. Text is `<Text>` / `<Heading>` with `tone`, `size`, `weight`, `truncate`, `mono` — not `className="text-muted-foreground font-semibold"`. A STANDALONE glyph is `<Icon as={Lock} size="sm" tone="muted" />` from `@godxjp/ui/general`: a bare lucide icon carries its own `width="24" height="24"` and only four places re-size it for you (`Button`, a `DropdownMenuItem`, a `TopbarItem`, a `ListRow` leading slot), so everywhere else — in a `Text`, a table cell, an `<a>` — it draws at 24px beside 14px type. `size-4` and `w-[16px]` are closed to you by rules 3 and 8, and `size={16}` hard-codes a number the theme owns; `Icon` puts the glyph on the `--icon-size-*` scale (`2xs…4xl`) instead, and is `aria-hidden` unless you give it a `label` (then it is `role="img"` with that name). The audit rule is `lucide-icon-needs-size`.
|
|
11
12
|
7. Colours are semantic tokens (`tone="destructive"`, `bg-primary`), never palette names, hex, or `bg-black` / `text-white`.
|
|
12
13
|
8. Sizes come from props (`size`, `width`, `columns`), never `w-[240px]` / `max-h-[420px]`.
|
|
@@ -16,7 +17,7 @@ Read this once; the audit enforces it. Everything else in `docs/` is for contrib
|
|
|
16
17
|
the table reads as a second box inside the card, and a wide one runs past the card entirely.
|
|
17
18
|
(`flush` alone is enough: it drops the border, the radius and the inline padding. `tight` is a
|
|
18
19
|
different knob — it governs the header band, not the body's edges.)
|
|
19
|
-
11. Run `
|
|
20
|
+
11. Run `npx @godxjp/ui audit --changed` before every review (the package bin, so it works from a workspace package whose `node_modules` is hoisted to the repo root); a browser sweep with `npx @godxjp/ui visual-audit <url>` on the running app is a separate, heavier run — do it when the work is a visual review, not on every change. Zero errors is the bar.
|
|
20
21
|
|
|
21
22
|
**Opting a deliberate exception out.** Name the rule; the block form also has to say why.
|
|
22
23
|
|
package/docs/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Purpose |
|
|
4
4
|
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5
|
-
| [CONSUMER-RULES.md](./CONSUMER-RULES.md) | **Start here if you build an app with @godxjp/ui** — the
|
|
5
|
+
| [CONSUMER-RULES.md](./CONSUMER-RULES.md) | **Start here if you build an app with @godxjp/ui** — the rules the audit enforces (styles entry, layout owners, real primitives, tokens) |
|
|
6
6
|
| [DEVELOPMENT.md](./DEVELOPMENT.md) | **Contributor guideline** — role/boundary, architecture, how to add/extend, verify |
|
|
7
7
|
| [COMPONENTS.md](./COMPONENTS.md) | Component catalog by taxonomy group |
|
|
8
8
|
| [PROPS-VOCABULARY.md](./PROPS-VOCABULARY.md) | Atomic prop types (`*Prop` suffix) |
|
package/docs/migrations/v32.md
CHANGED
|
@@ -28,9 +28,11 @@ Off-cycle approved: owner, 2026-10-09 — the single v32 batch decided on godx-j
|
|
|
28
28
|
renders none.
|
|
29
29
|
- The email brand mark has no default label.
|
|
30
30
|
- The GoDX look ships as `godxPreset` (`@godxjp/ui/themes/godx`) plus `@godxjp/ui/themes/godx.css`.
|
|
31
|
-
- Since 32.0.1, `@godxjp/ui/themes/godx-tokens.css` holds the GoDX colours without the fonts
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
- Since 32.0.1, `@godxjp/ui/themes/godx-tokens.css` holds the GoDX colours without the fonts.
|
|
32
|
+
`godx.css` is `fonts.css` (which imports `@fontsource/noto-sans-jp` and `@fontsource/m-plus-2`)
|
|
33
|
+
plus `godx-tokens.css`. Import `godx-tokens.css` instead of `godx.css` when the app bundles or
|
|
34
|
+
hosts its own fonts, and in a shadow root that embeds `@godxjp/ui/styles/core` (`@font-face`
|
|
35
|
+
only works in the document). Never import both. Put it after the base stylesheet.
|
|
34
36
|
- A script that READS the GoDX colours from `themes/godx.css` (a token sync, a design-system
|
|
35
37
|
export) must read `themes/godx-tokens.css` instead: since 32.0.1, `godx.css` holds only two
|
|
36
38
|
`@import` lines.
|
|
@@ -84,7 +86,7 @@ packages. The codemod rewrites every import below; the "you finish" column is pr
|
|
|
84
86
|
| `Title`, `Paragraph`, `Typography` | `Heading`, `Text as="p"`, `Prose` | the antd compatibility shims are removed |
|
|
85
87
|
| `SkeletonAvatar`, `SkeletonButton`, `SkeletonImage`, `SkeletonNode` | — | removed: use `SkeletonArticle avatar` or a sized `Skeleton` |
|
|
86
88
|
| `FloatButton`, `DraggablePanel`, `PageCover`, `Masonry`, `LegalDocumentShell`, `Marquee`, `TextDiff`, `OrgChart`, `Carousel`, `TimelineGrid`, `RangeTimeline`, `EmojiPicker`, `SortableList`, `MegaMenu`, `Anchor` | same names from `@godxjp/ui/lab` | nothing: same API, opt-in subpath |
|
|
87
|
-
| `ChatBubble`, `ChatBubbleList`, `ChatComposer`, `ChatSuggestion`, `Conversations`, `ThoughtChain`, `Welcome`, `Attachments` | same names from `@godxjp/chat` |
|
|
89
|
+
| `ChatBubble`, `ChatBubbleList`, `ChatComposer`, `ChatSuggestion`, `Conversations`, `ThoughtChain`, `Welcome`, `Attachments` | same names from `@godxjp/chat` | add `@godxjp/chat` as a NEW dependency, same version as `@godxjp/ui` |
|
|
88
90
|
|
|
89
91
|
Unchanged: `SplitPane` stays (its merge into `MasterDetail` failed the parity test), `Actions` stays
|
|
90
92
|
in `@godxjp/ui/general`, and `FilterBar` stays an alias of `Toolbar`. The `./ui/*` subpaths are
|
|
@@ -92,7 +94,10 @@ deprecated re-exports and will be removed in a later major.
|
|
|
92
94
|
|
|
93
95
|
## How to upgrade
|
|
94
96
|
|
|
95
|
-
1. Upgrade all six packages to the latest 32.0.x together
|
|
97
|
+
1. Upgrade all six packages to the latest 32.0.x together. An app that imports `ChatSuggestion` or
|
|
98
|
+
any other chat component from `@godxjp/ui` must ADD `@godxjp/chat` to its `package.json` — it
|
|
99
|
+
is a new dependency, not a rename — at the same version as `@godxjp/ui` (the packages release
|
|
100
|
+
in lockstep). Without it the build fails on the first chat import.
|
|
96
101
|
2. Run `npx godxjp-ui codemod v32 <src> --dry-run`, review, then run it without `--dry-run`. Use
|
|
97
102
|
`--godx` for GoDX products.
|
|
98
103
|
The 32.0.0 codemod imported the fonts twice under `--godx` (`fonts.css` and `godx.css`, which
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "32.
|
|
4
|
-
"godxUiMcp": "32.
|
|
3
|
+
"version": "32.7.0",
|
|
4
|
+
"godxUiMcp": "32.7.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"description": "React UI framework built on design tokens: accessible components, a controlled prop vocabulary, and a machine-readable catalog for coding agents.",
|
|
29
29
|
"files": [
|
|
30
30
|
"LICENSE",
|
|
31
|
+
"DESIGN.md",
|
|
31
32
|
"agent",
|
|
32
33
|
"dist",
|
|
33
34
|
"scripts/ui-audit.mjs",
|
package/scripts/_agent-setup.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
chmodSync,
|
|
16
16
|
existsSync,
|
|
17
17
|
mkdirSync,
|
|
18
|
+
readdirSync,
|
|
18
19
|
readFileSync,
|
|
19
20
|
realpathSync,
|
|
20
21
|
renameSync,
|
|
@@ -78,7 +79,11 @@ function mcpConfigMismatchMessage(root, existing, expected, key = MCP_KEY) {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
/** Commands wired into the consumer's .claude/settings.json. */
|
|
81
|
-
|
|
82
|
+
// `npx --no-install` resolves @godxjp/ui up the node_modules chain, so the hook also works from a
|
|
83
|
+
// workspace package whose install is hoisted to the repo root (gh#1296); it never downloads.
|
|
84
|
+
export const AUDIT_HOOK_CMD = "npx --no-install @godxjp/ui audit-hook";
|
|
85
|
+
/** The pre-#1296 default, rewritten to AUDIT_HOOK_CMD when found verbatim (a custom command is kept). */
|
|
86
|
+
const LEGACY_AUDIT_HOOK_CMD = "node node_modules/@godxjp/ui/scripts/audit-hook.mjs";
|
|
82
87
|
export const PRIMER_CMD = "cat .claude/godxjp-ui-workflow.md";
|
|
83
88
|
|
|
84
89
|
/** What `.claude/settings.json` would get, used only for the `.godxjp-ui-suggested` sidecar. */
|
|
@@ -97,7 +102,12 @@ export const KIT_VERSION = readJson(join(SELF_ROOT, "package.json"))?.version ??
|
|
|
97
102
|
* node_modules). Returns null when absent — caller falls back to this package's release train.
|
|
98
103
|
*/
|
|
99
104
|
export function readConsumerUiMetadata(root) {
|
|
100
|
-
|
|
105
|
+
// Up the node_modules chain: a workspace package has @godxjp/ui hoisted to the repo root (gh#1296).
|
|
106
|
+
let uiPkg;
|
|
107
|
+
for (let dir = resolve(root); ; dir = dirname(dir)) {
|
|
108
|
+
uiPkg = readJson(join(dir, "node_modules/@godxjp/ui/package.json"));
|
|
109
|
+
if (uiPkg || dirname(dir) === dir) break;
|
|
110
|
+
}
|
|
101
111
|
if (!uiPkg?.version) return null;
|
|
102
112
|
return {
|
|
103
113
|
version: String(uiPkg.version),
|
|
@@ -167,87 +177,62 @@ function resyncVersionStamp(text) {
|
|
|
167
177
|
* appended repo-specific sections keeps them.
|
|
168
178
|
*/
|
|
169
179
|
export const WORKFLOW_MD = `${STAMP(KIT_VERSION)}
|
|
170
|
-
# @godxjp/ui —
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
# @godxjp/ui — workflow (read every session)
|
|
181
|
+
|
|
182
|
+
Follow this every time you create or change a page, component or form.
|
|
183
|
+
|
|
184
|
+
## Before JSX
|
|
185
|
+
1. **Design target.** Read \`node_modules/@godxjp/ui/DESIGN.md\` (MCP \`get_design\`): tokens,
|
|
186
|
+
scarcity rules, screen anatomy, do/don't.
|
|
187
|
+
2. **Route.** \`route_consumer_task "<task>"\` → \`compose-a-screen\` for app screens. The
|
|
188
|
+
marketing skills (soft, minimalist, brutalist, gpt-tasteskill, imagegen-*) are for landing
|
|
189
|
+
pages only.
|
|
190
|
+
3. **Exemplar.** \`get_exemplar\` with archetype list | detail | form | settings | dashboard |
|
|
191
|
+
approval. Match its structure; it is the visual target.
|
|
192
|
+
4. **Discover, then look up.** \`search_components "<area>"\` first, \`get_component <Name>\`
|
|
193
|
+
second — \`get_component\` cannot find a name you do not already have (a consumer hand-rolled
|
|
194
|
+
the shipped \`OrgSwitcher\` and \`TopbarItem\` this way). Code already in this repo is not the
|
|
195
|
+
authority: search even when a neighbouring file seems to answer. Never guess a prop.
|
|
196
|
+
|
|
197
|
+
## Contract (ui-audit enforces it; full text in node_modules/@godxjp/ui/docs/CONSUMER-RULES.md)
|
|
198
|
+
- Styles: \`@import "@godxjp/ui/styles"\` (or \`import "@godxjp/ui/styles.css"\`); fonts are opt-in
|
|
199
|
+
via \`@godxjp/ui/styles/fonts.css\`. Never cherry-pick layers.
|
|
200
|
+
- Layout: \`PageContainer\` spaces sections; siblings go in \`<Flex direction="col" gap>\` /
|
|
178
201
|
\`<ResponsiveGrid>\`. No \`flex\` / \`grid\` / \`gap-*\` / \`p-*\` / \`m-*\` on your own elements.
|
|
179
|
-
- Surfaces: \`Card\` / \`Badge\` / \`Avatar\` / \`ListRow\` / \`Descriptions\` / \`EmptyState
|
|
180
|
-
\`rounded border\` div. Text is \`<Text tone size weight
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
already in the code. They were only right because that code had just been fixed; a week earlier
|
|
195
|
-
the same files hand-rolled a switcher and put Buttons in topbar slots, and both agents would
|
|
196
|
-
have copied it. Search the catalog even when a local file looks like it already answers you.
|
|
197
|
-
Also \`list_audit_rules\`, \`list_visual_checks\`. Never guess a prop; never hand-roll what a
|
|
198
|
-
primitive already does.
|
|
199
|
-
2. **Real primitives only.** No raw \`<input>/<select>/<button>/<textarea>/<table>\`, no
|
|
200
|
-
styled-div fakes. A labelled control ALWAYS goes in \`<FormField label=…>\` (it owns the
|
|
201
|
-
label↔control wiring, error/aria, AND the field rhythm). Never pair a bare \`<Label>\`
|
|
202
|
-
with an \`<Input>\`.
|
|
203
|
-
3. **Tokens, not literals.** Semantic colour tokens (bg-primary, text-muted-foreground),
|
|
204
|
-
token spacing/size/radius/type — no raw palette (bg-blue-600), no hex, no arbitrary
|
|
205
|
-
\`[13px]\`. No emoji in product UI. Logical CSS (ms-/me-/ps-/pe-) for RTL.
|
|
206
|
-
|
|
207
|
-
## After writing UI
|
|
208
|
-
4. **Audit (runs automatically too).** \`node node_modules/@godxjp/ui/scripts/ui-audit.mjs\`
|
|
209
|
-
— fix every finding. A PostToolUse hook also runs it on each edit and feeds findings back.
|
|
210
|
-
5. **Before a visual review**, run the runtime audit against the running app:
|
|
211
|
-
\`node node_modules/@godxjp/ui/scripts/visual-audit.mjs <url>\` (axe + contrast + layout).
|
|
202
|
+
- Surfaces: \`Card\` / \`Badge\` / \`Avatar\` / \`ListRow\` / \`Descriptions\` / \`EmptyState\`, never a
|
|
203
|
+
\`rounded border\` div. Text is \`<Text tone size weight>\`.
|
|
204
|
+
- Real controls only; a labelled control sits in \`<FormField label>\` inside a \`<Form>\`.
|
|
205
|
+
- Semantic tokens only: no hex, palette names, arbitrary \`[13px]\`, emoji. Logical CSS (ms-/me-).
|
|
206
|
+
- Screen composition (what the audit cannot see) is in \`.ai/rules/godxjp-ui.md\`.
|
|
207
|
+
|
|
208
|
+
## After JSX
|
|
209
|
+
5. \`npx @godxjp/ui audit <files>\` — zero errors (a PostToolUse hook also runs it on each edit).
|
|
210
|
+
Run it from the package that owns the files; \`--help\` lists the flags.
|
|
211
|
+
6. Screenshot at 390 / 768 / 1280 next to the exemplar, list the top 3 differences, fix them,
|
|
212
|
+
repeat — at most 3 rounds.
|
|
213
|
+
7. Before a visual review: \`npx @godxjp/ui visual-audit <url>\` (axe + contrast + layout).
|
|
214
|
+
|
|
215
|
+
In a workspace, \`node_modules/@godxjp/ui\` may be hoisted to the repo root: read its files there,
|
|
216
|
+
or through the MCP (\`get_design\`, \`get_rule\`).
|
|
212
217
|
`;
|
|
213
218
|
|
|
214
219
|
/** Delimited block appended to the consumer's CLAUDE.md — loaded into the agent's context
|
|
215
|
-
* every turn
|
|
216
|
-
|
|
220
|
+
* every turn, so it carries only the default path and points at the workflow for the rest.
|
|
221
|
+
* Markers keep it idempotent. */
|
|
222
|
+
export const CLAUDE_MD_BLOCK = `<!-- godxjp-ui:start (managed by @godxjp/ui — rewritten by npx godxjp-ui sync-rules) -->
|
|
217
223
|
${STAMP(KIT_VERSION)}
|
|
218
|
-
## @godxjp/ui —
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
2. \`get_component <Name>\` second, once you know the name.
|
|
231
|
-
\`get_component\` answers "how do I use X" and is USELESS for finding X — you can only look up a
|
|
232
|
-
name you already have. Skipping step 1 is how a consumer hand-rolled an organization switcher
|
|
233
|
-
the package ships (\`OrgSwitcher\`) and built three topbar cells out of Buttons instead of
|
|
234
|
-
\`TopbarItem\`: both were one \`search_components\` call away, and neither was ever searched for.
|
|
235
|
-
**Existing code in this repo is NOT the authority.** Reading a neighbouring file and copying it
|
|
236
|
-
is the most common way this rule gets skipped — measured: of four fresh agents given a vague UI
|
|
237
|
-
task, all four answered correctly but two never called the MCP at all, saying the answer was
|
|
238
|
-
already in the code. They were only right because that code had just been fixed; a week earlier
|
|
239
|
-
the same files hand-rolled a switcher and put Buttons in topbar slots, and both agents would
|
|
240
|
-
have copied it. Search the catalog even when a local file looks like it already answers you.
|
|
241
|
-
Also \`list_audit_rules\`, \`list_visual_checks\`. Never guess a prop; never hand-roll what a
|
|
242
|
-
primitive already does.
|
|
243
|
-
2. **Real primitives only.** No raw \`<input>/<select>/<button>/<textarea>/<table>\`. A labelled
|
|
244
|
-
control ALWAYS goes in \`<FormField label=…>\` — never a bare \`<Label>\`+\`<Input>\`. Semantic
|
|
245
|
-
tokens, not raw palette/hex/arbitrary values. No emoji in product UI. Logical CSS for RTL.
|
|
246
|
-
3. **Scan after writing.** \`node node_modules/@godxjp/ui/scripts/ui-audit.mjs <files>\` and fix
|
|
247
|
-
every finding (a PostToolUse hook runs this automatically and feeds findings back). Before a
|
|
248
|
-
visual review: \`node node_modules/@godxjp/ui/scripts/visual-audit.mjs <url>\` (axe + layout).
|
|
249
|
-
|
|
250
|
-
Full guide: \`.claude/godxjp-ui-workflow.md\`.
|
|
224
|
+
## @godxjp/ui — UI workflow
|
|
225
|
+
|
|
226
|
+
Before any JSX for a screen:
|
|
227
|
+
1. Read \`node_modules/@godxjp/ui/DESIGN.md\` (MCP \`get_design\`).
|
|
228
|
+
2. \`route_consumer_task\` → \`compose-a-screen\`; \`search_components\` before hand-rolling anything.
|
|
229
|
+
3. \`get_exemplar\` (list | detail | form | settings | dashboard | approval) — match its structure.
|
|
230
|
+
|
|
231
|
+
After building:
|
|
232
|
+
4. \`npx @godxjp/ui audit <files>\` — zero errors.
|
|
233
|
+
5. Screenshot at 390 / 768 / 1280 beside the exemplar; fix the top 3 differences; max 3 rounds.
|
|
234
|
+
|
|
235
|
+
Rules: \`node_modules/@godxjp/ui/docs/CONSUMER-RULES.md\`. Details: \`.claude/godxjp-ui-workflow.md\`.
|
|
251
236
|
<!-- godxjp-ui:end -->
|
|
252
237
|
`;
|
|
253
238
|
|
|
@@ -550,7 +535,13 @@ export function ensureClaudeHooks(root) {
|
|
|
550
535
|
(arr ?? []).some((g) => (g.hooks ?? []).some((h) => (h.command ?? "").includes(cmd)));
|
|
551
536
|
|
|
552
537
|
json.hooks.PostToolUse = json.hooks.PostToolUse ?? [];
|
|
553
|
-
|
|
538
|
+
for (const group of json.hooks.PostToolUse)
|
|
539
|
+
for (const hook of group.hooks ?? [])
|
|
540
|
+
if (hook.command === LEGACY_AUDIT_HOOK_CMD) {
|
|
541
|
+
hook.command = AUDIT_HOOK_CMD;
|
|
542
|
+
added.push("PostToolUse:auto-audit (workspace-safe command)");
|
|
543
|
+
}
|
|
544
|
+
if (!has(json.hooks.PostToolUse, "audit-hook")) {
|
|
554
545
|
json.hooks.PostToolUse.push({
|
|
555
546
|
matcher: "Write|Edit|MultiEdit",
|
|
556
547
|
hooks: [{ type: "command", command: AUDIT_HOOK_CMD }],
|
|
@@ -719,16 +710,86 @@ export function guineaPigStamp() {
|
|
|
719
710
|
* this one. The file says so at the top, because the first draft preserved nothing and silently
|
|
720
711
|
* ate a note left inside it — measured, and the reason for that banner.
|
|
721
712
|
*/
|
|
722
|
-
|
|
723
|
-
|
|
713
|
+
const UI_FILE = /\.(tsx|jsx)$/;
|
|
714
|
+
const NOT_UI_DIR = new Set([
|
|
715
|
+
"node_modules",
|
|
716
|
+
"dist",
|
|
717
|
+
"build",
|
|
718
|
+
"out",
|
|
719
|
+
"vendor",
|
|
720
|
+
"coverage",
|
|
721
|
+
"public",
|
|
722
|
+
"storage",
|
|
723
|
+
]);
|
|
724
|
+
|
|
725
|
+
/** Does `dir` hold a .tsx/.jsx file within a few levels? Bounded, so a large tree costs little. */
|
|
726
|
+
function holdsUi(dir, depth = 5) {
|
|
727
|
+
let entries;
|
|
728
|
+
try {
|
|
729
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
if (entries.some((e) => e.isFile() && UI_FILE.test(e.name))) return true;
|
|
734
|
+
if (depth === 0) return false;
|
|
735
|
+
return entries.some(
|
|
736
|
+
(e) =>
|
|
737
|
+
e.isDirectory() &&
|
|
738
|
+
!e.name.startsWith(".") &&
|
|
739
|
+
!NOT_UI_DIR.has(e.name) &&
|
|
740
|
+
holdsUi(join(dir, e.name), depth - 1),
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Every directory that actually holds the consumer's UI, as `paths:` globs without the `/**`
|
|
746
|
+
* (gh#1296). A fixed `src` never fired in an app whose screens live in `web/` or
|
|
747
|
+
* `resources/js/`, so the rules looked installed and loaded on no edit. Conventional nested roots
|
|
748
|
+
* first (`resources/js`, `apps/*\/src`, …), then any other top-level directory holding .tsx/.jsx.
|
|
749
|
+
* A repo with no UI files yet (a fresh scaffold) falls back to the first conventional directory
|
|
750
|
+
* that exists.
|
|
751
|
+
*/
|
|
752
|
+
function detectUiDirs(root) {
|
|
753
|
+
const subdirs = (rel) => {
|
|
754
|
+
try {
|
|
755
|
+
return readdirSync(join(root, rel), { withFileTypes: true })
|
|
756
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !NOT_UI_DIR.has(e.name))
|
|
757
|
+
.map((e) => e.name)
|
|
758
|
+
.sort();
|
|
759
|
+
} catch {
|
|
760
|
+
return [];
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
const found = [];
|
|
764
|
+
const add = (glob, dirs) => {
|
|
765
|
+
if (dirs.some((d) => holdsUi(join(root, d)))) found.push(glob);
|
|
766
|
+
};
|
|
767
|
+
for (const rel of ["resources/js", "app/javascript"]) add(rel, [rel]);
|
|
768
|
+
for (const parent of ["apps", "packages"]) {
|
|
769
|
+
const dirs = subdirs(parent)
|
|
770
|
+
.map((name) => `${parent}/${name}/src`)
|
|
771
|
+
.filter((d) => existsSync(join(root, d)));
|
|
772
|
+
if (dirs.length) add(`${parent}/*/src`, dirs);
|
|
773
|
+
}
|
|
774
|
+
for (const name of subdirs("")) {
|
|
775
|
+
if (!found.some((glob) => glob.split("/")[0] === name)) add(name, [name]);
|
|
776
|
+
}
|
|
777
|
+
if (found.length) return found;
|
|
778
|
+
const scaffold = ["resources/js", "app/javascript", "src/components", "src", "app"].find((d) =>
|
|
724
779
|
existsSync(join(root, d)),
|
|
725
780
|
);
|
|
726
|
-
|
|
781
|
+
return scaffold ? [scaffold] : [];
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
export function ensureConsumerRules(root) {
|
|
785
|
+
const uiDirs = detectUiDirs(root);
|
|
786
|
+
if (uiDirs.length === 0) return false;
|
|
787
|
+
const uiDir = uiDirs.join(", ");
|
|
727
788
|
|
|
728
789
|
const dir = join(root, ".ai", "rules");
|
|
729
790
|
const target = join(dir, "godxjp-ui.md");
|
|
730
791
|
const body = readFileSync(join(SELF_ROOT, "scripts", "consumer-rule.md"), "utf8");
|
|
731
|
-
const front = `---\npaths:\n - '${
|
|
792
|
+
const front = `---\npaths:\n${uiDirs.map((d) => ` - '${d}/**'\n`).join("")}---\n\n`;
|
|
732
793
|
// The digest covers the FRONT MATTER too: the detected glob is part of what makes this file
|
|
733
794
|
// correct, and a repo that grows a `resources/js` after shipping with `src` needs the rewrite.
|
|
734
795
|
const managed = `${front}${body}`;
|
|
@@ -772,7 +833,8 @@ export function ensureConsumerRules(root) {
|
|
|
772
833
|
if (!cur.includes(".ai/rules/godxjp-ui.md")) {
|
|
773
834
|
writeFileAtomic(
|
|
774
835
|
index,
|
|
775
|
-
cur.replace(/\s*$/, "") +
|
|
836
|
+
cur.replace(/\s*$/, "") +
|
|
837
|
+
`\n| ${uiDirs.map((d) => `${d}/**`).join(", ")} | .ai/rules/godxjp-ui.md |\n`,
|
|
776
838
|
);
|
|
777
839
|
}
|
|
778
840
|
}
|
|
@@ -807,7 +869,9 @@ export function runSyncRules(root, { dryRun = false } = {}) {
|
|
|
807
869
|
if (!refused && !(r === "present" && md === "present" && !wf && !skill && !rules)) {
|
|
808
870
|
lines.push(
|
|
809
871
|
`\n @godxjp/ui → MCP in .mcp.json (${r}); workflow mandate in CLAUDE.md (${md}).\n` +
|
|
810
|
-
(rules
|
|
872
|
+
(rules
|
|
873
|
+
? ` common consumer rules in .ai/rules/godxjp-ui.md (UI dirs: ${rules}).\n`
|
|
874
|
+
: "") +
|
|
811
875
|
(skill ? " guinea-pig skill refreshed to this version (your section 8 kept).\n" : "") +
|
|
812
876
|
" Your agent now has live component + audit guidance. Restart it to pick up the MCP.\n" +
|
|
813
877
|
" For auto-audit on every edit (PostToolUse + SessionStart hooks):\n" +
|
package/scripts/cli.mjs
CHANGED
|
@@ -16,6 +16,7 @@ const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
16
16
|
const MAP = {
|
|
17
17
|
"init-agent": "init-agent-kit.mjs",
|
|
18
18
|
audit: "ui-audit.mjs",
|
|
19
|
+
"audit-hook": "audit-hook.mjs",
|
|
19
20
|
"visual-audit": "visual-audit.mjs",
|
|
20
21
|
"prune-css": "prune-css.mjs",
|
|
21
22
|
codemod: "codemod.mjs",
|
|
@@ -55,8 +56,14 @@ const HELP = {
|
|
|
55
56
|
fails rather than reporting clean when origin/main cannot be resolved
|
|
56
57
|
--format json machine-readable findings
|
|
57
58
|
--quiet print errors only (warnings hidden)
|
|
59
|
+
--base=<ref> with --changed, diff against <ref> instead of origin/main
|
|
58
60
|
--rules print the rule catalog as JSON and exit
|
|
61
|
+
--consumer inside the @godxjp/ui repo only: apply the consumer rules (always on in an app)
|
|
59
62
|
Pre-commit: npx godxjp-ui audit resources/js || exit 1`,
|
|
63
|
+
"audit-hook": `godxjp-ui audit-hook
|
|
64
|
+
|
|
65
|
+
The Claude Code PostToolUse hook init-agent wires in: reads the hook payload on stdin and audits
|
|
66
|
+
the .tsx file that was just written or edited, feeding findings back to the agent. Not for humans.`,
|
|
60
67
|
"prune-css": `godxjp-ui prune-css <src dir/glob …> [--out <file>] [--fonts]
|
|
61
68
|
|
|
62
69
|
Emit a stylesheet with only the component CSS layers your app uses (gh#971). Scans the given
|
|
@@ -95,6 +102,7 @@ commands:
|
|
|
95
102
|
init-agent install the agent forcing-kit (MCP + auto-audit hook + CLAUDE.md mandate)
|
|
96
103
|
sync-rules write/refresh package-owned agent rules (--dry-run previews)
|
|
97
104
|
audit static UI audit over source
|
|
105
|
+
audit-hook the per-edit audit hook (wired by init-agent)
|
|
98
106
|
visual-audit runtime audit (Playwright + axe-core) against a running app
|
|
99
107
|
prune-css emit a stylesheet with only the CSS layers your app uses
|
|
100
108
|
|