@godxjp/ui 19.6.0 → 20.0.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/dist/components/data-display/card.d.ts +2 -2
- package/dist/components/data-display/collapsible.d.ts +23 -4
- package/dist/components/data-display/collapsible.js +119 -4
- package/dist/components/data-display/index.d.ts +3 -1
- package/dist/components/data-display/index.js +2 -0
- package/dist/components/data-display/legend.d.ts +15 -0
- package/dist/components/data-display/legend.js +11 -0
- package/dist/components/data-display/list-row.js +1 -1
- package/dist/components/data-display/popover.d.ts +58 -5
- package/dist/components/data-display/popover.js +206 -27
- package/dist/components/data-display/progress.d.ts +50 -2
- package/dist/components/data-display/progress.js +57 -9
- package/dist/components/data-entry/calendar.d.ts +1 -1
- package/dist/components/data-entry/calendar.js +67 -32
- package/dist/components/data-entry/checkbox.d.ts +19 -6
- package/dist/components/data-entry/checkbox.js +55 -16
- package/dist/components/data-entry/choice-option.d.ts +1 -1
- package/dist/components/data-entry/date-range-picker.d.ts +1 -1
- package/dist/components/data-entry/field.js +0 -1
- package/dist/components/data-entry/form.js +1 -1
- package/dist/components/data-entry/label.d.ts +3 -2
- package/dist/components/data-entry/label.js +23 -10
- package/dist/components/data-entry/month-picker.d.ts +1 -1
- package/dist/components/data-entry/month-range-picker.d.ts +1 -1
- package/dist/components/data-entry/transfer.js +16 -6
- package/dist/components/data-entry/tree-select-strategy.d.ts +1 -1
- package/dist/components/data-entry/tree-select.js +29 -12
- package/dist/components/data-entry/tree-utils.d.ts +1 -1
- package/dist/components/data-entry/upload.js +7 -2
- package/dist/components/feedback/dialog.d.ts +105 -38
- package/dist/components/feedback/dialog.js +272 -194
- package/dist/components/feedback/overlay-close-focus.d.ts +31 -0
- package/dist/components/feedback/overlay-close-focus.js +31 -0
- package/dist/components/feedback/overlay-header-tone.d.ts +1 -1
- package/dist/components/feedback/sheet.d.ts +53 -11
- package/dist/components/feedback/sheet.js +150 -81
- package/dist/components/feedback/tooltip.d.ts +51 -7
- package/dist/components/feedback/tooltip.js +107 -25
- package/dist/components/general/button.js +2 -2
- package/dist/components/general/logo.d.ts +17 -0
- package/dist/components/general/logo.js +22 -16
- package/dist/components/layout/app-shell.d.ts +1 -1
- package/dist/components/layout/app-shell.js +52 -8
- package/dist/components/layout/aspect-ratio.js +0 -1
- package/dist/components/layout/auth-divider.js +0 -1
- package/dist/components/layout/flex.d.ts +1 -1
- package/dist/components/layout/flex.js +19 -2
- package/dist/components/layout/nav-surface.d.ts +26 -0
- package/dist/components/layout/nav-surface.js +17 -0
- package/dist/components/layout/org-switcher.d.ts +5 -1
- package/dist/components/layout/org-switcher.js +5 -3
- package/dist/components/layout/separator.js +0 -1
- package/dist/components/layout/sidebar.js +4 -1
- package/dist/components/layout/split-pane.d.ts +14 -1
- package/dist/components/layout/split-pane.js +26 -13
- package/dist/components/layout/topbar-item.js +1 -2
- package/dist/components/navigation/app-setting-picker.js +21 -2
- package/dist/components/navigation/app-setting-toggle.d.ts +16 -0
- package/dist/components/navigation/app-setting-toggle.js +96 -0
- package/dist/components/navigation/dropdown-menu.d.ts +199 -18
- package/dist/components/navigation/dropdown-menu.js +344 -117
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.js +2 -0
- package/dist/components/navigation/pagination-utils.d.ts +2 -1
- package/dist/components/navigation/tabs.d.ts +47 -6
- package/dist/components/navigation/tabs.js +152 -90
- package/dist/components/ui/accordion.d.ts +50 -5
- package/dist/components/ui/accordion.js +239 -33
- package/dist/components/ui/aspect-ratio.d.ts +23 -2
- package/dist/components/ui/aspect-ratio.js +15 -13
- package/dist/components/ui/avatar.d.ts +29 -4
- package/dist/components/ui/avatar.js +111 -25
- package/dist/components/ui/hover-card.d.ts +42 -4
- package/dist/components/ui/hover-card.js +183 -27
- package/dist/components/ui/label.js +0 -1
- package/dist/components/ui/segmented.d.ts +3 -3
- package/dist/components/ui/separator.d.ts +8 -2
- package/dist/components/ui/separator.js +17 -9
- package/dist/components/ui/toggle-group.d.ts +50 -5
- package/dist/components/ui/toggle-group.js +79 -20
- package/dist/components/ui/toggle.d.ts +31 -5
- package/dist/components/ui/toggle.js +42 -3
- package/dist/i18n/messages/en.json +8 -0
- package/dist/i18n/messages/ja.json +8 -0
- package/dist/i18n/messages/vi.json +8 -0
- package/dist/lib/control-styles.d.ts +1 -1
- package/dist/lib/control-styles.js +1 -1
- package/dist/lib/slot.d.ts +32 -0
- package/dist/lib/slot.js +22 -0
- package/dist/lib/variants.d.ts +22 -3
- package/dist/lib/variants.js +56 -1
- package/dist/props/components/app.prop.d.ts +25 -1
- package/dist/props/components/data-display.prop.d.ts +17 -0
- package/dist/props/components/data-entry.prop.d.ts +32 -4
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +118 -5
- package/dist/props/components/navigation.prop.d.ts +1 -1
- package/dist/props/registry.d.ts +22 -2
- package/dist/props/registry.js +33 -2
- package/dist/props/vocabulary/content.prop.d.ts +1 -1
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/interaction.prop.d.ts +17 -1
- package/dist/props/vocabulary/layout.prop.d.ts +57 -1
- package/dist/styles/control.css +142 -1
- package/dist/styles/core.css +5 -2
- package/dist/styles/data-display-layout.css +72 -0
- package/dist/styles/focus-ring.css +4 -0
- package/dist/styles/index.css +5 -2
- package/dist/styles/layout.css +106 -0
- package/dist/styles/shell-layout.css +140 -8
- package/dist/styles/text-layout.css +5 -4
- package/dist/tokens/base.css +1 -1
- package/dist/tokens/components/data-display.css +11 -0
- package/dist/tokens/components/shell.css +3 -1
- package/dist/tokens/foundation.css +2 -0
- package/docs/COMPONENTS.md +9 -3
- package/docs/DESIGN-AUTHORITY.md +96 -71
- package/docs/FRAME-COVERAGE-REPORT.md +5 -3
- package/docs/README.md +14 -14
- package/docs/WHAT-BELONGS-HERE.md +179 -0
- package/docs/data-display/legend.tsx +145 -0
- package/docs/data-display/progress.tsx +32 -0
- package/docs/data-entry/segmented.tsx +1 -1
- package/docs/data-entry/select.tsx +2 -2
- package/docs/feedback/sheet.tsx +1 -1
- package/docs/layout/app-shell-arrangements.tsx +225 -0
- package/docs/layout/aspect-ratio.tsx +1 -1
- package/docs/navigation/app-setting-picker.tsx +26 -1
- package/docs/navigation/app-setting-toggle.tsx +111 -0
- package/package.json +11 -8
- package/scripts/_agent-setup.mjs +165 -3
- package/scripts/consumer-rule.md +98 -0
- package/scripts/guinea-pig-skill.md +322 -0
- package/scripts/init-guinea-pig.mjs +62 -0
- package/scripts/postinstall.mjs +13 -2
- package/scripts/ui-audit.mjs +115 -14
- /package/dist/tokens/{antd.generated.css → derived.css} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "
|
|
4
|
-
"godxUiMcp": "
|
|
3
|
+
"version": "20.0.0",
|
|
4
|
+
"godxUiMcp": "20.0.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"scripts/visual-audit-rules.mjs",
|
|
31
31
|
"scripts/audit-hook.mjs",
|
|
32
32
|
"scripts/init-agent-kit.mjs",
|
|
33
|
+
"scripts/init-guinea-pig.mjs",
|
|
34
|
+
"scripts/guinea-pig-skill.md",
|
|
35
|
+
"scripts/consumer-rule.md",
|
|
33
36
|
"scripts/postinstall.mjs",
|
|
34
37
|
"scripts/_agent-setup.mjs",
|
|
35
38
|
"scripts/cli.mjs",
|
|
@@ -308,6 +311,7 @@
|
|
|
308
311
|
"format": "prettier --check .",
|
|
309
312
|
"format:fix": "prettier --write .",
|
|
310
313
|
"test": "vitest run",
|
|
314
|
+
"test:manual": "vitest run --config vitest.manual.config.ts",
|
|
311
315
|
"test:visual:auth-login": "node scripts/auth-shell-login-visual.mjs",
|
|
312
316
|
"test:visual:auth-registration": "node scripts/auth-shell-registration-visual.mjs",
|
|
313
317
|
"test:visual:topbar-collision": "node scripts/topbar-collision-visual.mjs",
|
|
@@ -335,8 +339,8 @@
|
|
|
335
339
|
"check:frame-runtime": "pnpm check:data-entry-frame-runtime && pnpm check:data-entry-touch-aria && pnpm check:layout-nav-frames && pnpm check:provider-feedback-query-runtime && pnpm check:layout-nav-closure && pnpm check:final-touch-rtl && pnpm check:data-table-pagination-wrap && pnpm check:button-icon-xs",
|
|
336
340
|
"verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:frame-coverage && pnpm test",
|
|
337
341
|
"verify:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync && pnpm check:contrast && pnpm check:visual-audit && pnpm test",
|
|
338
|
-
"verify:ci": "pnpm
|
|
339
|
-
"verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:mcp-prop-sync",
|
|
342
|
+
"verify:ci": "pnpm verify:ci:static && pnpm check:frame-contracts && pnpm test",
|
|
343
|
+
"verify:ci:static": "pnpm build && pnpm check:packed-public-contract && pnpm typecheck && pnpm typecheck:docs && pnpm lint && pnpm preview:build && pnpm check:registry && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:no-consumer-coupling && pnpm check:use-client && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:no-external-assets && pnpm check:token-scale-bypass && pnpm check:no-antd-runtime && pnpm check:dist-tokens-resolve && pnpm check:no-hardcoded-geometry && pnpm check:no-hardcoded-css-values && pnpm check:no-inline-magic-numbers && pnpm check:no-tailwind-class-assertions && pnpm check:control-sizing && pnpm check:rtl && pnpm check:typography && pnpm check:mcp-lockstep && pnpm check:mcp-sync && pnpm check:doc-prop-existence && pnpm check:mcp-catalog-coverage && pnpm check:mcp-orphans && pnpm check:mcp-pattern-imports && pnpm check:audit-sync && pnpm check:gate-coverage && pnpm check:mcp-prop-sync",
|
|
340
344
|
"verify:browser": "pnpm check:contrast && pnpm check:visual-audit",
|
|
341
345
|
"verify:release": "pnpm verify:static && pnpm check:frame-contracts && pnpm check:frame-coverage && pnpm check:frame-axe",
|
|
342
346
|
"verify:publish-tree": "pnpm build && pnpm check:packed-public-contract && pnpm check:use-client && pnpm check:dist-tokens-resolve",
|
|
@@ -365,9 +369,6 @@
|
|
|
365
369
|
"check:rtl": "node scripts/check-rtl.mjs",
|
|
366
370
|
"check:typography": "node scripts/check-typography.mjs",
|
|
367
371
|
"gen:component-tokens": "node scripts/gen-component-tokens.mjs",
|
|
368
|
-
"gen:antd-tokens": "node scripts/gen-antd-tokens.mjs",
|
|
369
|
-
"check:antd-tokens": "node scripts/gen-antd-tokens.mjs --check",
|
|
370
|
-
"report:antd-tokens": "node scripts/gen-antd-tokens.mjs --report",
|
|
371
372
|
"check:no-antd-runtime": "node scripts/check-no-antd-runtime.mjs",
|
|
372
373
|
"gen:email-tokens": "node scripts/gen-email-tokens.mjs",
|
|
373
374
|
"check:core-isolation": "node scripts/check-core-isolation.mjs",
|
|
@@ -384,6 +385,7 @@
|
|
|
384
385
|
"dev": "node scripts/dev.mjs",
|
|
385
386
|
"prepublishOnly": "pnpm run build",
|
|
386
387
|
"check:audit-sync": "node scripts/check-audit-sync.mjs",
|
|
388
|
+
"check:gate-coverage": "node scripts/check-gate-coverage.mjs",
|
|
387
389
|
"check:contrast": "node scripts/check-contrast.mjs",
|
|
388
390
|
"visual-audit": "node scripts/visual-audit.mjs",
|
|
389
391
|
"check:visual-audit": "node scripts/visual-audit-smoke.mjs",
|
|
@@ -451,6 +453,7 @@
|
|
|
451
453
|
"@radix-ui/react-toggle": "^1.1.18",
|
|
452
454
|
"@radix-ui/react-toggle-group": "^1.1.19",
|
|
453
455
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
456
|
+
"@react-aria/utils": "^3.34.1",
|
|
454
457
|
"@tanstack/react-table": "^9.2.4",
|
|
455
458
|
"class-variance-authority": "^0.7.1",
|
|
456
459
|
"clsx": "^2.1.1",
|
|
@@ -460,6 +463,7 @@
|
|
|
460
463
|
"input-otp": "^1.5.0",
|
|
461
464
|
"lucide-react": "^1.37.0",
|
|
462
465
|
"qrcode.react": "4.2.0",
|
|
466
|
+
"react-aria-components": "^1.21.1",
|
|
463
467
|
"react-day-picker": "^10.0.1",
|
|
464
468
|
"react-resizable-panels": "^4.12.3",
|
|
465
469
|
"sonner": "^2.0.8",
|
|
@@ -483,7 +487,6 @@
|
|
|
483
487
|
"@types/react-dom": "^19.2.5",
|
|
484
488
|
"@vitejs/plugin-react": "^6.1.1",
|
|
485
489
|
"@vitest/coverage-v8": "^4.1.11",
|
|
486
|
-
"antd": "6.6.2",
|
|
487
490
|
"axe-core": "^4.13.0",
|
|
488
491
|
"eslint": "^10.9.1",
|
|
489
492
|
"eslint-plugin-react": "^7.37.5",
|
package/scripts/_agent-setup.mjs
CHANGED
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
* NON-DESTRUCTIVE: it only creates a missing file or ADDS a missing key, never
|
|
5
5
|
* overwrites existing config.
|
|
6
6
|
*/
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
7
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
9
|
import { dirname, join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
9
11
|
|
|
10
12
|
/** The godxjp-ui MCP server — pulled on demand via npx (no extra dependency to ship). */
|
|
13
|
+
/** This package's own root — the source of the version we stamp with. */
|
|
14
|
+
const SELF_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
|
|
11
16
|
export const MCP_SERVER = { command: "npx", args: ["@godxjp/ui-mcp"] };
|
|
12
17
|
export const MCP_KEY = "godx-ui";
|
|
13
18
|
|
|
@@ -16,7 +21,47 @@ export const AUDIT_HOOK_CMD = "node node_modules/@godxjp/ui/scripts/audit-hook.m
|
|
|
16
21
|
export const PRIMER_CMD = "cat .claude/godxjp-ui-workflow.md";
|
|
17
22
|
|
|
18
23
|
/** The per-session workflow mandate the SessionStart hook injects into the agent. */
|
|
19
|
-
export const
|
|
24
|
+
export const KIT_VERSION = readJson(join(SELF_ROOT, "package.json"))?.version ?? "0.0.0";
|
|
25
|
+
|
|
26
|
+
const STAMP = (v) => `<!-- godxjp-ui:version ${v} -->`;
|
|
27
|
+
const STAMP_RE = /<!-- godxjp-ui:version ([^\s]+) -->/;
|
|
28
|
+
|
|
29
|
+
/** The version stamped in `text`, or null when it predates stamping. */
|
|
30
|
+
export function stampedVersion(text) {
|
|
31
|
+
return text?.match(STAMP_RE)?.[1] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A stamp that tracks the CONTENT, for the files this package owns outright.
|
|
36
|
+
*
|
|
37
|
+
* A version stamp answers "which release wrote this", which is the wrong question for a file whose
|
|
38
|
+
* whole job is to carry current guidance. Measured: editing `consumer-rule.md` without bumping the
|
|
39
|
+
* package left two of three consumers holding the previous text, with a stamp that read as current
|
|
40
|
+
* and nothing anywhere reporting a difference. The rules are edited far more often than the
|
|
41
|
+
* version moves — most of all while they are being written, which is exactly when a stale copy
|
|
42
|
+
* does the most damage.
|
|
43
|
+
*
|
|
44
|
+
* So the digest goes in alongside the version: the version stays for humans reading the file, and
|
|
45
|
+
* the digest is what the refresh actually compares. Identical body → still a no-op.
|
|
46
|
+
*/
|
|
47
|
+
const DIGEST_RE = /<!-- godxjp-ui:digest ([0-9a-f]{12}) -->/;
|
|
48
|
+
const digestOf = (body) => createHash("sha256").update(body).digest("hex").slice(0, 12);
|
|
49
|
+
const OWNED_STAMP = (body) => `${STAMP(KIT_VERSION)}\n<!-- godxjp-ui:digest ${digestOf(body)} -->`;
|
|
50
|
+
|
|
51
|
+
/** The content digest stamped in `text`, or null when it predates digest stamping. */
|
|
52
|
+
export function stampedDigest(text) {
|
|
53
|
+
return text?.match(DIGEST_RE)?.[1] ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Replace the MANAGED region of a file and leave everything else alone.
|
|
58
|
+
*
|
|
59
|
+
* Refreshing is only safe if it cannot eat hand-written content, so the contract is narrow: the
|
|
60
|
+
* region is delimited, and anything outside the delimiters survives byte-for-byte. A consumer that
|
|
61
|
+
* appended repo-specific sections keeps them.
|
|
62
|
+
*/
|
|
63
|
+
export const WORKFLOW_MD = `${STAMP(KIT_VERSION)}
|
|
64
|
+
# @godxjp/ui — mandatory workflow (read every session)
|
|
20
65
|
|
|
21
66
|
You are building UI in an app that uses @godxjp/ui. Follow this EVERY time you create
|
|
22
67
|
or change a component, page, or form — no exceptions.
|
|
@@ -50,6 +95,7 @@ or change a component, page, or form — no exceptions.
|
|
|
50
95
|
/** Delimited block appended to the consumer's CLAUDE.md — loaded into the agent's context
|
|
51
96
|
* every turn (the most reliable "ensure it reads the rules"). Markers keep it idempotent. */
|
|
52
97
|
export const CLAUDE_MD_BLOCK = `<!-- godxjp-ui:start (managed by @godxjp/ui — edit .claude/godxjp-ui-workflow.md instead) -->
|
|
98
|
+
${STAMP(KIT_VERSION)}
|
|
53
99
|
## @godxjp/ui — mandatory UI workflow (do NOT skip)
|
|
54
100
|
|
|
55
101
|
This app uses @godxjp/ui. EVERY time you build or change UI:
|
|
@@ -82,6 +128,24 @@ function readJson(path) {
|
|
|
82
128
|
}
|
|
83
129
|
|
|
84
130
|
/** Ensure `.mcp.json` registers the godx-ui MCP server. Returns 'created' | 'added' | 'present'. */
|
|
131
|
+
/**
|
|
132
|
+
* The version that wrote each managed artefact, stamped so `postinstall` can tell "already there"
|
|
133
|
+
* apart from "already there and STALE".
|
|
134
|
+
*
|
|
135
|
+
* Everything below used to be install-once: `ensureMcpJson` returned early on a present key,
|
|
136
|
+
* `writeWorkflowMd` on a present file, `ensureClaudeMd` on a present marker. So `npm update
|
|
137
|
+
* @godxjp/ui` brought new components, new audit rules and new catalog entries — and left the
|
|
138
|
+
* agent reading whatever guidance shipped the day the package was FIRST installed. The library
|
|
139
|
+
* moved; the instructions for using it did not.
|
|
140
|
+
*/
|
|
141
|
+
export function refreshBlock(current, next, startMarker, endMarker) {
|
|
142
|
+
const i = current.indexOf(startMarker);
|
|
143
|
+
if (i < 0) return current.replace(/\s*$/, "") + "\n\n" + next;
|
|
144
|
+
const j = endMarker ? current.indexOf(endMarker, i) : -1;
|
|
145
|
+
const tail = j < 0 ? "" : current.slice(j + endMarker.length);
|
|
146
|
+
return current.slice(0, i) + next + tail;
|
|
147
|
+
}
|
|
148
|
+
|
|
85
149
|
export function ensureMcpJson(root) {
|
|
86
150
|
const path = join(root, ".mcp.json");
|
|
87
151
|
const json = readJson(path) ?? {};
|
|
@@ -127,7 +191,14 @@ export function ensureClaudeHooks(root) {
|
|
|
127
191
|
export function writeWorkflowMd(root) {
|
|
128
192
|
const path = join(root, ".claude", "godxjp-ui-workflow.md");
|
|
129
193
|
mkdirSync(dirname(path), { recursive: true });
|
|
130
|
-
|
|
194
|
+
// This file is owned entirely by the package — the CLAUDE.md block tells the consumer to edit
|
|
195
|
+
// it, but nothing else reads from it, so a stale copy is pure loss. Rewrite when the stamp moves.
|
|
196
|
+
if (existsSync(path)) {
|
|
197
|
+
const cur = readFileSync(path, "utf8");
|
|
198
|
+
if (stampedVersion(cur) === KIT_VERSION) return false;
|
|
199
|
+
writeFileSync(path, WORKFLOW_MD);
|
|
200
|
+
return "refreshed";
|
|
201
|
+
}
|
|
131
202
|
writeFileSync(path, WORKFLOW_MD);
|
|
132
203
|
return true;
|
|
133
204
|
}
|
|
@@ -139,7 +210,16 @@ export function writeWorkflowMd(root) {
|
|
|
139
210
|
export function ensureClaudeMd(root) {
|
|
140
211
|
const path = join(root, "CLAUDE.md");
|
|
141
212
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
142
|
-
if (existing?.includes("godxjp-ui:start"))
|
|
213
|
+
if (existing?.includes("godxjp-ui:start")) {
|
|
214
|
+
// Present — but at WHICH version? Refresh only the delimited block; anything the consumer
|
|
215
|
+
// wrote around it is untouched.
|
|
216
|
+
if (stampedVersion(existing) === KIT_VERSION) return "present";
|
|
217
|
+
writeFileSync(
|
|
218
|
+
path,
|
|
219
|
+
refreshBlock(existing, CLAUDE_MD_BLOCK, "<!-- godxjp-ui:start", "<!-- godxjp-ui:end -->"),
|
|
220
|
+
);
|
|
221
|
+
return "refreshed";
|
|
222
|
+
}
|
|
143
223
|
if (existing == null) {
|
|
144
224
|
writeFileSync(path, CLAUDE_MD_BLOCK);
|
|
145
225
|
return "created";
|
|
@@ -157,3 +237,85 @@ export function shouldSkip(root) {
|
|
|
157
237
|
if (pkg.name === "@godxjp/ui" || pkg.name === "@godxjp/ui-mcp") return "self";
|
|
158
238
|
return null;
|
|
159
239
|
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Keep an opted-in guinea-pig skill current.
|
|
243
|
+
*
|
|
244
|
+
* The file is co-authored: sections 0–7 come from this package, and section 8 onward is whatever
|
|
245
|
+
* the repo wrote about ITSELF — its package manager, its audit baseline, its deliberate
|
|
246
|
+
* exceptions. So the refresh replaces the head and keeps the tail, which is the only split that
|
|
247
|
+
* lets the guidance move without eating the consumer's own notes.
|
|
248
|
+
*
|
|
249
|
+
* Only runs where `.guinea-pig-optin` exists: the skill carries an obligation to fix things
|
|
250
|
+
* UPSTREAM, and pushing that into a repo that never asked for it would tell its agent to go edit
|
|
251
|
+
* a library it has no mandate over.
|
|
252
|
+
*/
|
|
253
|
+
export function refreshGuineaPigSkill(root) {
|
|
254
|
+
const dir = join(root, ".claude", "skills", "godx-ui-guinea-pig");
|
|
255
|
+
const target = join(dir, "SKILL.md");
|
|
256
|
+
const optin = join(dir, ".guinea-pig-optin");
|
|
257
|
+
if (!existsSync(optin) || !existsSync(target)) return false;
|
|
258
|
+
if (readFileSync(optin, "utf8").trim() === KIT_VERSION) return false;
|
|
259
|
+
|
|
260
|
+
const base = readFileSync(join(SELF_ROOT, "scripts", "guinea-pig-skill.md"), "utf8");
|
|
261
|
+
const current = readFileSync(target, "utf8");
|
|
262
|
+
const marker = "\n---\n\n# 8. ";
|
|
263
|
+
const i = current.indexOf(marker);
|
|
264
|
+
writeFileSync(target, base.replace(/\s*$/, "") + "\n" + (i < 0 ? "" : current.slice(i)));
|
|
265
|
+
writeFileSync(optin, `${KIT_VERSION}\n`);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Install the common consumer rules as a PATH-TRIGGERED file, and wire them into `.ai/rules`.
|
|
271
|
+
*
|
|
272
|
+
* The skill and this file say overlapping things on purpose, because they fire at different
|
|
273
|
+
* moments: a skill loads when the TASK matches its description — once, at the start — while an
|
|
274
|
+
* `.ai/rules` entry loads every time an agent touches a file under its glob. Measured over one
|
|
275
|
+
* session: a dashboard file was edited dozens of times and the skill was never re-read, so the
|
|
276
|
+
* laws that mattered were out of context for every edit after the first.
|
|
277
|
+
*
|
|
278
|
+
* The glob is DETECTED, not assumed. A rule wired to a directory the repo does not have is a rule
|
|
279
|
+
* that never fires, which is worse than no rule at all — it looks installed.
|
|
280
|
+
*
|
|
281
|
+
* Unlike the skill, this file is owned OUTRIGHT by the package and is rewritten whole. That is the
|
|
282
|
+
* honest shape for `.ai/rules`, where the convention is one file per concern and the index loads
|
|
283
|
+
* them all: a repo with something of its own to say writes its own rule file instead of editing
|
|
284
|
+
* this one. The file says so at the top, because the first draft preserved nothing and silently
|
|
285
|
+
* ate a note left inside it — measured, and the reason for that banner.
|
|
286
|
+
*/
|
|
287
|
+
export function ensureConsumerRules(root) {
|
|
288
|
+
const uiDir = ["resources/js", "app/javascript", "src/components", "src", "app"].find((d) =>
|
|
289
|
+
existsSync(join(root, d)),
|
|
290
|
+
);
|
|
291
|
+
if (!uiDir) return false;
|
|
292
|
+
|
|
293
|
+
const dir = join(root, ".ai", "rules");
|
|
294
|
+
const target = join(dir, "godxjp-ui.md");
|
|
295
|
+
const body = readFileSync(join(SELF_ROOT, "scripts", "consumer-rule.md"), "utf8");
|
|
296
|
+
const front = `---\npaths:\n - '${uiDir}/**'\n---\n\n`;
|
|
297
|
+
// The digest covers the FRONT MATTER too: the detected glob is part of what makes this file
|
|
298
|
+
// correct, and a repo that grows a `resources/js` after shipping with `src` needs the rewrite.
|
|
299
|
+
const managed = `${front}${body}`;
|
|
300
|
+
const next = `${OWNED_STAMP(managed)}\n${managed}`;
|
|
301
|
+
|
|
302
|
+
if (existsSync(target) && stampedDigest(readFileSync(target, "utf8")) === digestOf(managed)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
mkdirSync(dir, { recursive: true });
|
|
306
|
+
writeFileSync(target, next);
|
|
307
|
+
|
|
308
|
+
// Only touch the index when the repo keeps one; a missing index means the repo reads rule files
|
|
309
|
+
// directly, and inventing one would change how it loads everything else.
|
|
310
|
+
const index = join(dir, "index.md");
|
|
311
|
+
if (existsSync(index)) {
|
|
312
|
+
const cur = readFileSync(index, "utf8");
|
|
313
|
+
if (!cur.includes(".ai/rules/godxjp-ui.md")) {
|
|
314
|
+
writeFileSync(
|
|
315
|
+
index,
|
|
316
|
+
cur.replace(/\s*$/, "") + `\n| ${uiDir}/** | .ai/rules/godxjp-ui.md |\n`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return uiDir;
|
|
321
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# @godxjp/ui
|
|
2
|
+
|
|
3
|
+
> **Tệp này do gói `@godxjp/ui` sở hữu và bị GHI ĐÈ mỗi lần nâng cấp.**
|
|
4
|
+
> Đừng sửa ở đây — luật của riêng kho thuộc về một tệp khác trong `.ai/rules/`,
|
|
5
|
+
> và index sẽ nạp cả hai. (Khác với `.claude/skills/.../SKILL.md`, nơi mục §8
|
|
6
|
+
> trở đi là của kho và được giữ lại.)
|
|
7
|
+
|
|
8
|
+
Đây là **danh sách kiểm** bắn mỗi lần chạm một tệp UI. Lý do đầy đủ nằm ở
|
|
9
|
+
`docs/CONSUMER-RULES.md` (10 luật) và, với kho chuột bạch, ở
|
|
10
|
+
`.claude/skills/godx-ui-guinea-pig/SKILL.md`.
|
|
11
|
+
|
|
12
|
+
## Bố cục chuẩn của platform: BA CỘT, và ba cột là BA PHẠM VI
|
|
13
|
+
|
|
14
|
+
Vỏ mặc định của mọi app trên platform là ba cột, dựng bằng một `AppShell`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
navRail (3.5rem) │ sidebar (16rem) │ content
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Không tự dựng ba cột bằng cách nhét hai cột vào một khe `sidebar` rồi nới
|
|
21
|
+
`--app-shell-sidebar-width`. Hai bẫy đã đo được: `Sidebar` render
|
|
22
|
+
`.sb-root { display: contents }` nên hai `Sidebar` đặt cạnh nhau **tan vào một
|
|
23
|
+
flex row** và cùng co về 0; và nới token dùng chung khiến mép nội dung **nhảy
|
|
24
|
+
64px giữa các route**. `navRail` sở hữu track riêng nên không cần cả hai.
|
|
25
|
+
|
|
26
|
+
**Đặt một control vào cột nào là câu hỏi về PHẠM VI, không phải về chỗ trống:**
|
|
27
|
+
|
|
28
|
+
| Cột | Phạm vi | Chứa gì |
|
|
29
|
+
| --------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `navRail` | **platform** — đúng với cả tổ chức, sống sót qua việc đổi app | đổi tổ chức · đổi app · thông báo · tin nhắn · sự kiện · cài đặt tổ chức · lối tắt liên-app |
|
|
31
|
+
| `sidebar` | **app** — của riêng app đang mở | mục/kênh/route của chính app này |
|
|
32
|
+
| `topbar` | **trang** — bạn đang ở đâu, làm được gì ở đây | breadcrumb · hành động của trang · menu tài khoản |
|
|
33
|
+
|
|
34
|
+
Hai luật phủ định, và chúng làm được việc:
|
|
35
|
+
|
|
36
|
+
- Điều hướng của app **không bao giờ** vào rail. Một rail lặp lại mục của
|
|
37
|
+
sidebar là dải chrome thứ hai mang thứ hạng của dải thứ nhất, chỉ dựng đứng.
|
|
38
|
+
- Công tắc cấp platform **không bao giờ** vào sidebar — đổi app xong nó biến
|
|
39
|
+
mất, trong khi nó vẫn phải ở đó.
|
|
40
|
+
- Đích nào hợp cả hai thì thuộc **rail**: nó sống sót qua việc đổi app.
|
|
41
|
+
|
|
42
|
+
`sidebarCollapsed` chỉ gập cột `sidebar`; rail giữ nguyên bề rộng, nên đích cấp
|
|
43
|
+
platform vẫn với tới được lúc gập. Đừng dựng lại hành vi này bằng CSS của kho.
|
|
44
|
+
|
|
45
|
+
Bề rộng rail là token `--app-shell-nav-rail-width` — kho nào muốn rail rộng kiểu
|
|
46
|
+
Slack thì đặt lại **một dòng**, không fork `.app-nav-rail`.
|
|
47
|
+
|
|
48
|
+
## Trước khi viết bố cục: TRA, đừng dựng
|
|
49
|
+
|
|
50
|
+
Hỏi MCP `godxjp-ui` (`search_components`, `get_component`). Đo được trong một
|
|
51
|
+
ngày: năm thứ cần đều ĐÃ CÓ và vẫn bị dựng lại bằng thứ khác —
|
|
52
|
+
|
|
53
|
+
| Cần | Đã có |
|
|
54
|
+
| ---------------------------------------- | --------------------- |
|
|
55
|
+
| đường kẻ chạm mép Card | `<CardContent flush>` |
|
|
56
|
+
| header có kẻ khi thân là danh sách flush | `<CardHeader banded>` |
|
|
57
|
+
| một hàng LÀ liên kết (thay cho nút rời) | `<ListRow asChild>` |
|
|
58
|
+
| kẻ ô từng ngày trong lịch | `<Calendar bordered>` |
|
|
59
|
+
| dải giữa hai vùng, tự kẻ theo VỊ TRÍ | `<CardBar>` |
|
|
60
|
+
|
|
61
|
+
Lỗi không phải "đoán sai tên prop" mà là **cho rằng nó không tồn tại nên không
|
|
62
|
+
hỏi**.
|
|
63
|
+
|
|
64
|
+
## Catalog chở PROP, không chở LUẬT BỐ CỤC
|
|
65
|
+
|
|
66
|
+
`CardBar` trong manifest có đúng một prop (`extra`) — không dòng nào nói nó tự
|
|
67
|
+
kẻ theo vị trí (đầu: kẻ dưới · cuối: kẻ trên · giữa: cả hai). Luật ấy chỉ nằm
|
|
68
|
+
trong chú thích `node_modules/@godxjp/ui/src/styles/card-layout.css`.
|
|
69
|
+
|
|
70
|
+
**Làm bố cục trong một component của DS → mở tệp `*-layout.css` của nó ra đọc.**
|
|
71
|
+
|
|
72
|
+
## Card không lồng Card
|
|
73
|
+
|
|
74
|
+
Một `<Card>` trong `<Card>` cho hai mép bo cách nhau 16px và hai lớp padding
|
|
75
|
+
chồng lên. Cần viền cho thứ bên trong thì tìm trục của chính nó
|
|
76
|
+
(`Calendar bordered`), đừng bọc thêm một mặt phẳng nữa.
|
|
77
|
+
|
|
78
|
+
Cùng lý do: đừng xếp `<Alert>` thành danh sách trong Card — mỗi Alert là một mặt
|
|
79
|
+
phẳng, và `Alert` còn phát `role="alert"` nên cả danh sách sẽ tự đọc to lên khi
|
|
80
|
+
tải trang. Danh sách là `ListRow`.
|
|
81
|
+
|
|
82
|
+
## Màu chữ đọc tầng CHỮ, không đọc tầng TÔ
|
|
83
|
+
|
|
84
|
+
`--success/--warning/--info/--destructive` là màu **TÔ** (nền badge, thanh, viền
|
|
85
|
+
alert). Chữ đọc `--text-success/-warning/-info/-error`.
|
|
86
|
+
|
|
87
|
+
Đo được: `Text tone="warning"` đọc nhầm tầng cho **1,74:1**; đúng tầng cho
|
|
88
|
+
**5,90:1**. Cùng `tone` ấy trong `Badge` vẫn đạt 5,52:1 — nên cùng một prop
|
|
89
|
+
hiện đọc được ở chỗ này và không đọc được ở chỗ kia, trên cùng một màn hình.
|
|
90
|
+
|
|
91
|
+
## Audit xanh ≠ chạy đúng
|
|
92
|
+
|
|
93
|
+
`ui-audit` xanh chỉ nghĩa là **không có gì cấm** bạn. Đo được: một consumer viết
|
|
94
|
+
`modifiers` + `modifiersClassNames` cho màu cuối tuần — audit xanh, tsc xanh,
|
|
95
|
+
build xanh, và **số màu chữ trên cả lưới vẫn là 1**, vì class rơi vào `<td>` còn
|
|
96
|
+
`<button>` tự đặt màu.
|
|
97
|
+
|
|
98
|
+
Một API chết im lặng trông y hệt một API đang chạy. **Mở trang và đo** mới biết.
|