@godxjp/ui-mcp 0.11.0 → 0.13.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/index.js CHANGED
@@ -6916,6 +6916,303 @@ export function InvoiceListHeader() {
6916
6916
  </InputOTP>`,
6917
6917
  storyPath: "data-entry/InputOTP.stories.tsx",
6918
6918
  rules: [3, 6]
6919
+ },
6920
+ {
6921
+ name: "Rating",
6922
+ group: "data-entry",
6923
+ tagline: "Star-rating input (radiogroup) \u2014 controlled via value/onValueChange, form-submittable via name, supports readOnly display.",
6924
+ props: [
6925
+ { name: "value", type: "number", description: "Controlled rating (1..max)." },
6926
+ {
6927
+ name: "defaultValue",
6928
+ type: "number",
6929
+ defaultValue: "0",
6930
+ description: "Uncontrolled initial rating."
6931
+ },
6932
+ { name: "onValueChange", type: "(value: number) => void", description: "Rating callback." },
6933
+ { name: "max", type: "number", defaultValue: "5", description: "Number of stars." },
6934
+ { name: "readOnly", type: "boolean", description: "Display-only (e.g. an average score)." },
6935
+ {
6936
+ name: "name",
6937
+ type: "string",
6938
+ description: "Hidden input name for native form submission."
6939
+ }
6940
+ ],
6941
+ usage: [
6942
+ "DO use readOnly to DISPLAY a score (e.g. product average); interactive (default) for collecting a rating.",
6943
+ "DO pass `name` to submit the value in a plain form.",
6944
+ "DON'T render raw star icons for input \u2014 this handles keyboard (radiogroup), hover preview, and a11y."
6945
+ ],
6946
+ useCases: [
6947
+ "Product / vendor review input",
6948
+ "Display an average score (readOnly)",
6949
+ "Feedback / CSAT survey",
6950
+ "Priority or quality scoring in admin"
6951
+ ],
6952
+ related: ["RadioGroup (non-star single choice)", "Slider (continuous 0-100 value)"],
6953
+ example: `import { Rating } from "@godxjp/ui/data-entry";
6954
+
6955
+ <Rating name="score" defaultValue={4} onValueChange={(v) => console.log(v)} />`,
6956
+ storyPath: "data-entry/Rating.stories.tsx",
6957
+ rules: [3, 6, 23]
6958
+ },
6959
+ {
6960
+ name: "TagInput",
6961
+ group: "data-entry",
6962
+ tagline: "Chips/tags input \u2014 type + Enter (or comma) to add a tag, Backspace to remove the last; controlled via value/onValueChange (string[]).",
6963
+ props: [
6964
+ { name: "value", type: "string[]", description: "Controlled tag list." },
6965
+ { name: "defaultValue", type: "string[]", description: "Uncontrolled initial tags." },
6966
+ {
6967
+ name: "onValueChange",
6968
+ type: "(tags: string[]) => void",
6969
+ description: "Tag-list callback."
6970
+ },
6971
+ { name: "placeholder", type: "string", description: "Shown when empty." },
6972
+ {
6973
+ name: "name",
6974
+ type: "string",
6975
+ description: "Hidden input (comma-joined) for native form submission."
6976
+ }
6977
+ ],
6978
+ usage: [
6979
+ "DO use for free-form multi-value entry (labels, emails, keywords) where options aren't a fixed list.",
6980
+ "DO note dedupe is built in; Enter/comma commits, Backspace on empty removes the last chip.",
6981
+ "DON'T use for choosing from a KNOWN set \u2014 use Select (multiple) or a multi-Combobox instead."
6982
+ ],
6983
+ useCases: [
6984
+ "Labels / tags on a record",
6985
+ "Recipient email entry",
6986
+ "Keyword / skill lists",
6987
+ "Ad-hoc filter terms"
6988
+ ],
6989
+ related: [
6990
+ "Select (multiple) \u2014 when the values come from a fixed option set",
6991
+ "Combobox (multi) \u2014 searchable known set"
6992
+ ],
6993
+ example: `import { TagInput } from "@godxjp/ui/data-entry";
6994
+
6995
+ <TagInput name="labels" placeholder="\u30E9\u30D9\u30EB\u3092\u8FFD\u52A0\u2026" onValueChange={(tags) => setTags(tags)} />`,
6996
+ storyPath: "data-entry/TagInput.stories.tsx",
6997
+ rules: [3, 6, 23]
6998
+ },
6999
+ {
7000
+ name: "ContextMenu",
7001
+ group: "navigation",
7002
+ tagline: "Context menu primitives with keyboard support and compound parts for command-style action surfaces.",
7003
+ props: [
7004
+ { name: "open", type: "boolean", description: "Controlled open state." },
7005
+ {
7006
+ name: "onOpenChange",
7007
+ type: "(open: boolean) => void",
7008
+ description: "Open-state callback."
7009
+ },
7010
+ { name: "value", type: "string", description: "Selected value (for controlled patterns)." }
7011
+ ],
7012
+ useCases: [
7013
+ "Right-click action menu",
7014
+ "Contextual menus for rows and cards",
7015
+ "Nested action rows with shortcuts"
7016
+ ],
7017
+ storyPath: "navigation/ContextMenu.stories.tsx",
7018
+ rules: [3, 6],
7019
+ example: `import {
7020
+ ContextMenu,
7021
+ ContextMenuTrigger,
7022
+ ContextMenuContent,
7023
+ ContextMenuItem,
7024
+ } from "@godxjp/ui/navigation";
7025
+
7026
+ <ContextMenu>
7027
+ <ContextMenuTrigger>open</ContextMenuTrigger>
7028
+ <ContextMenuContent>
7029
+ <ContextMenuItem>Edit</ContextMenuItem>
7030
+ <ContextMenuItem>Delete</ContextMenuItem>
7031
+ </ContextMenuContent>
7032
+ </ContextMenu>`
7033
+ },
7034
+ {
7035
+ name: "Menubar",
7036
+ group: "navigation",
7037
+ tagline: "Application menubar primitives (menus, sub-menus, and check/radio items).",
7038
+ props: [
7039
+ {
7040
+ name: "defaultValue",
7041
+ type: "string",
7042
+ description: "Uncontrolled initial selected value."
7043
+ },
7044
+ {
7045
+ name: "onValueChange",
7046
+ type: "(value: string) => void",
7047
+ description: "Selection callback."
7048
+ }
7049
+ ],
7050
+ useCases: [
7051
+ "Top-bar application command menus",
7052
+ "Workspace menus with nested items",
7053
+ "Desktop-like navigation shells"
7054
+ ],
7055
+ storyPath: "navigation/Menubar.stories.tsx",
7056
+ rules: [3, 6],
7057
+ example: `import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem } from "@godxjp/ui/navigation";
7058
+
7059
+ <Menubar>
7060
+ <MenubarMenu>
7061
+ <MenubarTrigger>\u30D5\u30A1\u30A4\u30EB</MenubarTrigger>
7062
+ <MenubarContent>
7063
+ <MenubarItem>\u65B0\u898F\u4F5C\u6210</MenubarItem>
7064
+ </MenubarContent>
7065
+ </MenubarMenu>
7066
+ </Menubar>`
7067
+ },
7068
+ {
7069
+ name: "NavigationMenu",
7070
+ group: "navigation",
7071
+ tagline: "Horizontal navigation menu with trigger/content/link primitives and viewport support.",
7072
+ props: [
7073
+ {
7074
+ name: "orientation",
7075
+ type: '"horizontal" | "vertical"',
7076
+ defaultValue: '"horizontal"',
7077
+ description: "Main-axis arrangement for the nav menu."
7078
+ },
7079
+ {
7080
+ name: "defaultValue",
7081
+ type: "string",
7082
+ description: "Uncontrolled initial selected value."
7083
+ },
7084
+ {
7085
+ name: "onValueChange",
7086
+ type: "(value: string) => void",
7087
+ description: "Selection callback."
7088
+ }
7089
+ ],
7090
+ useCases: ["Primary app navigation", "Sectioned marketing navigation", "Nested link groups"],
7091
+ storyPath: "navigation/NavigationMenu.stories.tsx",
7092
+ rules: [3, 6],
7093
+ example: `import { NavigationMenu, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger } from "@godxjp/ui/navigation";
7094
+
7095
+ <NavigationMenu>
7096
+ <NavigationMenuList>
7097
+ <NavigationMenuItem>
7098
+ <NavigationMenuTrigger>\u30DA\u30FC\u30B8</NavigationMenuTrigger>
7099
+ </NavigationMenuItem>
7100
+ </NavigationMenuList>
7101
+ </NavigationMenu>`
7102
+ },
7103
+ {
7104
+ name: "ResizablePanel",
7105
+ group: "layout",
7106
+ tagline: "Resizable panel group/child/handle primitives from react-resizable-panels.",
7107
+ props: [
7108
+ { name: "id", type: "string", description: "Panel identifier for persistence." },
7109
+ { name: "defaultSize", type: "number", description: "Initial panel size (percent/units)." },
7110
+ { name: "minSize", type: "number", description: "Minimum size constraint." },
7111
+ { name: "maxSize", type: "number", description: "Maximum size constraint." }
7112
+ ],
7113
+ useCases: ["Split-pane layouts", "Resizable sidebars", "Code editors with adjustable zones"],
7114
+ storyPath: "layout/ResizablePanel.stories.tsx",
7115
+ rules: [3, 6],
7116
+ example: `import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@godxjp/ui/layout";
7117
+
7118
+ <ResizablePanelGroup>
7119
+ <ResizablePanel>Panel A</ResizablePanel>
7120
+ <ResizableHandle />
7121
+ <ResizablePanel>Panel B</ResizablePanel>
7122
+ </ResizablePanelGroup>`
7123
+ },
7124
+ {
7125
+ name: "Carousel",
7126
+ group: "data-display",
7127
+ tagline: "Embla-backed carousel primitives including previous/next controls and context API.",
7128
+ props: [
7129
+ {
7130
+ name: "opts",
7131
+ type: "Parameters<typeof useEmblaCarousel>[0]",
7132
+ description: "Embla options."
7133
+ },
7134
+ {
7135
+ name: "plugins",
7136
+ type: "Parameters<typeof useEmblaCarousel>[1]",
7137
+ description: "Embla plugins."
7138
+ },
7139
+ {
7140
+ name: "setApi",
7141
+ type: "(api: CarouselApi) => void",
7142
+ description: "Receive carousel API for custom logic."
7143
+ }
7144
+ ],
7145
+ useCases: ["Feature cards", "Image galleries", "Horizontal stepping lists"],
7146
+ storyPath: "data-display/Carousel.stories.tsx",
7147
+ rules: [3, 6],
7148
+ example: `import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from "@godxjp/ui/data-display";
7149
+
7150
+ <Carousel>
7151
+ <CarouselContent>
7152
+ <CarouselItem>1</CarouselItem>
7153
+ <CarouselItem>2</CarouselItem>
7154
+ </CarouselContent>
7155
+ <CarouselPrevious />
7156
+ <CarouselNext />
7157
+ </Carousel>`
7158
+ },
7159
+ {
7160
+ name: "Combobox",
7161
+ group: "data-entry",
7162
+ tagline: "Single-select searchable combobox composed from Popover + Command + Button.",
7163
+ props: [
7164
+ {
7165
+ name: "options",
7166
+ type: "{ value: string; label: string }[]",
7167
+ required: true,
7168
+ description: "Available selection entries."
7169
+ },
7170
+ { name: "value", type: "string", description: "Controlled selected value." },
7171
+ { name: "defaultValue", type: "string", description: "Uncontrolled initial value." },
7172
+ {
7173
+ name: "onValueChange",
7174
+ type: "(value: string) => void",
7175
+ description: "Selection callback."
7176
+ },
7177
+ { name: "placeholder", type: "string", description: "Trigger placeholder." },
7178
+ { name: "searchPlaceholder", type: "string", description: "Input placeholder in popover." },
7179
+ { name: "emptyText", type: "string", description: "Fallback when there are no matches." }
7180
+ ],
7181
+ useCases: ["Searchable single-select", "Lookup pickers", "Static option lists"],
7182
+ storyPath: "data-entry/Combobox.stories.tsx",
7183
+ rules: [3, 6],
7184
+ example: `import { Combobox } from "@godxjp/ui/data-entry";
7185
+
7186
+ <Combobox
7187
+ options={[{ value: "a", label: "A" }, { value: "b", label: "B" }]}
7188
+ onValueChange={(value) => console.log(value)}
7189
+ />`
7190
+ },
7191
+ {
7192
+ name: "TimeInput",
7193
+ group: "data-entry",
7194
+ tagline: "Masking HH:mm input with validation and optional minute step quantization.",
7195
+ props: [
7196
+ { name: "value", type: "string", description: "Controlled HH:mm value." },
7197
+ {
7198
+ name: "defaultValue",
7199
+ type: "string",
7200
+ description: "Uncontrolled initial HH:mm value."
7201
+ },
7202
+ {
7203
+ name: "onValueChange",
7204
+ type: "(value: string) => void",
7205
+ description: "Validated value callback."
7206
+ },
7207
+ { name: "step", type: "number", defaultValue: "1", description: "Minute step." },
7208
+ { name: "name", type: "string", description: "Form field name." }
7209
+ ],
7210
+ useCases: ["Time filters", "Schedule pickers (calendar-free)", "HH:mm-only forms"],
7211
+ storyPath: "data-entry/TimeInput.stories.tsx",
7212
+ rules: [3, 6],
7213
+ example: `import { TimeInput } from "@godxjp/ui/data-entry";
7214
+
7215
+ <TimeInput value="09:00" step={15} onValueChange={(time) => console.log(time)} />`
6919
7216
  }
6920
7217
  ];
6921
7218
  function findComponent(name) {
@@ -6988,26 +7285,63 @@ var PROP_VOCABULARY = [
6988
7285
  ];
6989
7286
  function findVocab(name) {
6990
7287
  const normalized = name.trim().toLowerCase().replace(/prop(?:<.*>)?$/i, "");
6991
- return PROP_VOCABULARY.find((v) => v.name.toLowerCase().replace(/prop(?:<.*>)?$/i, "") === normalized);
7288
+ return PROP_VOCABULARY.find(
7289
+ (v) => v.name.toLowerCase().replace(/prop(?:<.*>)?$/i, "") === normalized
7290
+ );
6992
7291
  }
6993
7292
 
6994
7293
  // src/data/tokens.ts
6995
7294
  var TOKENS = [
6996
- { name: "--wa-*", category: "primitive", tier: "primitive", role: "Neutral decorative Japanese accent primitives for charts/tags/decoration only." },
6997
- { name: "--chart-1..6", category: "primitive", tier: "primitive", role: "Neutral decorative chart primitives; @theme chart colors reference these tokens." },
7295
+ {
7296
+ name: "--wa-*",
7297
+ category: "primitive",
7298
+ tier: "primitive",
7299
+ role: "Neutral decorative Japanese accent primitives for charts/tags/decoration only."
7300
+ },
7301
+ {
7302
+ name: "--chart-1..6",
7303
+ category: "primitive",
7304
+ tier: "primitive",
7305
+ role: "Neutral decorative chart primitives; @theme chart colors reference these tokens."
7306
+ },
6998
7307
  { name: "--space-0..12", category: "primitive", tier: "primitive", role: "Raw spacing scale." },
6999
- { name: "--font-size-*", category: "primitive", tier: "primitive", role: "Raw typography scale." },
7308
+ {
7309
+ name: "--font-size-*",
7310
+ category: "primitive",
7311
+ tier: "primitive",
7312
+ role: "Raw typography scale."
7313
+ },
7000
7314
  { name: "--primary", category: "semantic", tier: "semantic", role: "Brand/action color role." },
7001
7315
  { name: "--success", category: "semantic", tier: "semantic", role: "Success status role." },
7002
7316
  { name: "--warning", category: "semantic", tier: "semantic", role: "Warning status role." },
7003
- { name: "--destructive", category: "semantic", tier: "semantic", role: "Destructive/error status role." },
7317
+ {
7318
+ name: "--destructive",
7319
+ category: "semantic",
7320
+ tier: "semantic",
7321
+ role: "Destructive/error status role."
7322
+ },
7004
7323
  { name: "--info", category: "semantic", tier: "semantic", role: "Information status role." },
7005
7324
  { name: "--attention", category: "semantic", tier: "semantic", role: "Attention status role." },
7006
7325
  { name: "--badge-space-*", category: "component", tier: "component", role: "Badge spacing." },
7007
- { name: "--card-*", category: "component", tier: "component", role: "Card surface, border, spacing, and typography." },
7008
- { name: "--control-*", category: "component", tier: "component", role: "Shared form control heights, padding, icons, and focus chrome." },
7326
+ {
7327
+ name: "--card-*",
7328
+ category: "component",
7329
+ tier: "component",
7330
+ role: "Card surface, border, spacing, and typography."
7331
+ },
7332
+ {
7333
+ name: "--control-*",
7334
+ category: "component",
7335
+ tier: "component",
7336
+ role: "Shared form control heights, padding, icons, and focus chrome."
7337
+ },
7009
7338
  { name: "--table-*", category: "component", tier: "component", role: "Table row/cell sizing." },
7010
- { name: "--dialog-* / --alert-* / --skeleton-*", category: "component", tier: "component", role: "Feedback component sizing and spacing." }
7339
+ {
7340
+ name: "--dialog-* / --alert-* / --skeleton-*",
7341
+ category: "component",
7342
+ tier: "component",
7343
+ role: "Feedback component sizing and spacing."
7344
+ }
7011
7345
  ];
7012
7346
  function tokensByCategory(category) {
7013
7347
  return TOKENS.filter((t) => t.category === category);
@@ -7015,46 +7349,206 @@ function tokensByCategory(category) {
7015
7349
 
7016
7350
  // src/data/rules.ts
7017
7351
  var CARDINAL_RULES = [
7018
- { number: 1, title: "Storybook is mandatory", body: "Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark." },
7019
- { number: 2, title: "Tokens, not utilities", body: "Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)" },
7020
- { number: 3, title: "Radix for interactive primitives", body: "Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)" },
7021
- { number: 4, title: "shadcn-style ownership", body: "Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)" },
7022
- { number: 5, title: "One i18next singleton", body: "`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)" },
7023
- { number: 6, title: "WCAG 2.1 AA baseline", body: "Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces." },
7024
- { number: 7, title: "SemVer 2.0 + Keep a Changelog 1.1", body: "Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR." },
7025
- { number: 8, title: "Inclusive naming", body: "`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced." },
7026
- { number: 9, title: "No marketing speak", body: 'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.' },
7027
- { number: 10, title: "English is canonical for docs", body: "Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness." },
7028
- { number: 11, title: "Submodule discipline", body: "Two-PR workflow: (1) submodule PR \u2192 `main`, (2) umbrella PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote." },
7029
- { number: 12, title: "Branch + PR workflow", body: "`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden." },
7030
- { number: 13, title: "TypeScript strict", body: "Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link." },
7031
- { number: 14, title: "Every third-party library is shadcn / Radix-recommended", body: "Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice." },
7032
- { number: 15, title: "No `@apply` re-encoding tokens", body: "Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine." },
7033
- { number: 16, title: "CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`", body: "A primitive that needs a new color / spacing / radius adds it there FIRST, then references it." },
7034
- { number: 17, title: "`src/stories/` \u2194 `src/components/` parity", body: "Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`." },
7035
- { number: 18, title: "`docs/reference/<group>/` \u2194 `src/components/<group>/` parity", body: "Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`." },
7036
- { number: 19, title: "No service-specific anything", body: '`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.' },
7037
- { number: 20, title: 'No "platform-only" exports', body: "Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported." },
7038
- { number: 21, title: "Every component honours every theme axis", body: "`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep." },
7039
- { number: 22, title: "100% match to the design canon", body: 'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.' },
7040
- { number: 23, title: "Concept-first prop API", body: "One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one." },
7041
- { number: 24, title: "Mobile-first", body: "Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first." },
7042
- { number: 25, title: "Stories are docs; UI is the primitive", body: "When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected." },
7043
- { number: 26, title: "Library isolation", body: "`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`." },
7044
- { number: 27, title: "Per-group folder structure", body: "Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy." },
7045
- { number: 28, title: "`src/` folder taxonomy", body: "Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them." },
7046
- { number: 29, title: "Stories consume framework primitives only", body: "No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides." },
7047
- { number: 30, title: "Story `render` returns JSX directly", body: "No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`." },
7048
- { number: 31, title: "No nested wrapper / convenience primitives", body: "One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers." },
7049
- { number: 32, title: "No redundant props", body: "Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected." },
7050
- { number: 33, title: "Stories / source / docs name-synchronized", body: "No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it." },
7051
- { number: 34, title: "Storybook source panel = real, copy-paste-ready code", body: 'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.' },
7052
- { number: 35, title: "Status chips never wrap", body: "A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap." },
7053
- { number: 36, title: "Badge tone/icon are the colour escape hatch", body: "`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props." },
7054
- { number: 37, title: "DataTable is full-width \u2014 never inside a narrow grid column", body: "A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)" },
7055
- { number: 38, title: "FilterBar stays OUT of CardContent flush", body: "`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card." },
7056
- { number: 39, title: "Long text columns get an explicit width", body: "For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll." },
7057
- { number: 40, title: "Pages are mobile-first", body: "Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Stack` / `Inline` `gap` + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don't force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px." }
7352
+ {
7353
+ number: 1,
7354
+ title: "Storybook is mandatory",
7355
+ body: "Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."
7356
+ },
7357
+ {
7358
+ number: 2,
7359
+ title: "Tokens, not utilities",
7360
+ body: "Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"
7361
+ },
7362
+ {
7363
+ number: 3,
7364
+ title: "Radix for interactive primitives",
7365
+ body: "Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"
7366
+ },
7367
+ {
7368
+ number: 4,
7369
+ title: "shadcn-style ownership",
7370
+ body: "Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"
7371
+ },
7372
+ {
7373
+ number: 5,
7374
+ title: "One i18next singleton",
7375
+ body: "`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"
7376
+ },
7377
+ {
7378
+ number: 6,
7379
+ title: "WCAG 2.1 AA baseline",
7380
+ body: "Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."
7381
+ },
7382
+ {
7383
+ number: 7,
7384
+ title: "SemVer 2.0 + Keep a Changelog 1.1",
7385
+ body: "Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."
7386
+ },
7387
+ {
7388
+ number: 8,
7389
+ title: "Inclusive naming",
7390
+ body: "`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."
7391
+ },
7392
+ {
7393
+ number: 9,
7394
+ title: "No marketing speak",
7395
+ body: 'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'
7396
+ },
7397
+ {
7398
+ number: 10,
7399
+ title: "English is canonical for docs",
7400
+ body: "Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."
7401
+ },
7402
+ {
7403
+ number: 11,
7404
+ title: "Submodule discipline",
7405
+ body: "Two-PR workflow: (1) submodule PR \u2192 `main`, (2) umbrella PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."
7406
+ },
7407
+ {
7408
+ number: 12,
7409
+ title: "Branch + PR workflow",
7410
+ body: "`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."
7411
+ },
7412
+ {
7413
+ number: 13,
7414
+ title: "TypeScript strict",
7415
+ body: "Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."
7416
+ },
7417
+ {
7418
+ number: 14,
7419
+ title: "Every third-party library is shadcn / Radix-recommended",
7420
+ body: "Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."
7421
+ },
7422
+ {
7423
+ number: 15,
7424
+ title: "No `@apply` re-encoding tokens",
7425
+ body: "Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."
7426
+ },
7427
+ {
7428
+ number: 16,
7429
+ title: "CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",
7430
+ body: "A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."
7431
+ },
7432
+ {
7433
+ number: 17,
7434
+ title: "`src/stories/` \u2194 `src/components/` parity",
7435
+ body: "Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."
7436
+ },
7437
+ {
7438
+ number: 18,
7439
+ title: "`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",
7440
+ body: "Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."
7441
+ },
7442
+ {
7443
+ number: 19,
7444
+ title: "No service-specific anything",
7445
+ body: '`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'
7446
+ },
7447
+ {
7448
+ number: 20,
7449
+ title: 'No "platform-only" exports',
7450
+ body: "Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."
7451
+ },
7452
+ {
7453
+ number: 21,
7454
+ title: "Every component honours every theme axis",
7455
+ body: "`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."
7456
+ },
7457
+ {
7458
+ number: 22,
7459
+ title: "100% match to the design canon",
7460
+ body: 'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'
7461
+ },
7462
+ {
7463
+ number: 23,
7464
+ title: "Concept-first prop API",
7465
+ body: "One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."
7466
+ },
7467
+ {
7468
+ number: 24,
7469
+ title: "Mobile-first",
7470
+ body: "Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."
7471
+ },
7472
+ {
7473
+ number: 25,
7474
+ title: "Stories are docs; UI is the primitive",
7475
+ body: "When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."
7476
+ },
7477
+ {
7478
+ number: 26,
7479
+ title: "Library isolation",
7480
+ body: "`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."
7481
+ },
7482
+ {
7483
+ number: 27,
7484
+ title: "Per-group folder structure",
7485
+ body: "Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."
7486
+ },
7487
+ {
7488
+ number: 28,
7489
+ title: "`src/` folder taxonomy",
7490
+ body: "Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."
7491
+ },
7492
+ {
7493
+ number: 29,
7494
+ title: "Stories consume framework primitives only",
7495
+ body: "No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."
7496
+ },
7497
+ {
7498
+ number: 30,
7499
+ title: "Story `render` returns JSX directly",
7500
+ body: "No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."
7501
+ },
7502
+ {
7503
+ number: 31,
7504
+ title: "No nested wrapper / convenience primitives",
7505
+ body: "One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."
7506
+ },
7507
+ {
7508
+ number: 32,
7509
+ title: "No redundant props",
7510
+ body: "Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."
7511
+ },
7512
+ {
7513
+ number: 33,
7514
+ title: "Stories / source / docs name-synchronized",
7515
+ body: "No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."
7516
+ },
7517
+ {
7518
+ number: 34,
7519
+ title: "Storybook source panel = real, copy-paste-ready code",
7520
+ body: 'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'
7521
+ },
7522
+ {
7523
+ number: 35,
7524
+ title: "Status chips never wrap",
7525
+ body: "A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."
7526
+ },
7527
+ {
7528
+ number: 36,
7529
+ title: "Badge tone/icon are the colour escape hatch",
7530
+ body: "`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."
7531
+ },
7532
+ {
7533
+ number: 37,
7534
+ title: "DataTable is full-width \u2014 never inside a narrow grid column",
7535
+ body: "A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"
7536
+ },
7537
+ {
7538
+ number: 38,
7539
+ title: "FilterBar stays OUT of CardContent flush",
7540
+ body: "`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."
7541
+ },
7542
+ {
7543
+ number: 39,
7544
+ title: "Long text columns get an explicit width",
7545
+ body: "For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."
7546
+ },
7547
+ {
7548
+ number: 40,
7549
+ title: "Pages are mobile-first",
7550
+ body: "Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Stack` / `Inline` `gap` + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don't force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px."
7551
+ }
7058
7552
  ];
7059
7553
  function findRule(num) {
7060
7554
  return CARDINAL_RULES.find((r) => r.number === num);
@@ -7065,7 +7559,17 @@ var PATTERNS = [
7065
7559
  {
7066
7560
  name: "common-fixes",
7067
7561
  tagline: "Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",
7068
- tags: ["fixes", "migration", "bug", "cardstat", "statusbadge", "datatable", "sidebar", "gotcha", "review"],
7562
+ tags: [
7563
+ "fixes",
7564
+ "migration",
7565
+ "bug",
7566
+ "cardstat",
7567
+ "statusbadge",
7568
+ "datatable",
7569
+ "sidebar",
7570
+ "gotcha",
7571
+ "review"
7572
+ ],
7069
7573
  code: `// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7070
7574
  // 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
7071
7575
  // Cause: the bare <Card> has ZERO inner padding \u2014 it MUST contain <CardContent>.
@@ -8137,11 +8641,13 @@ function routeTask(task) {
8137
8641
  "Generate design image first \u2192 analyze \u2192 implement."
8138
8642
  );
8139
8643
  if (matches.length === 0) {
8140
- return [{
8141
- skill: "taste",
8142
- section: "<see whenToUse>",
8143
- why: `No keyword match for "${task}". Default to the "taste" baseline \u2014 see whenToUse for sections.`
8144
- }];
8644
+ return [
8645
+ {
8646
+ skill: "taste",
8647
+ section: "<see whenToUse>",
8648
+ why: `No keyword match for "${task}". Default to the "taste" baseline \u2014 see whenToUse for sections.`
8649
+ }
8650
+ ];
8145
8651
  }
8146
8652
  return matches;
8147
8653
  }
@@ -9586,7 +10092,11 @@ ${r.body}
9586
10092
  `;
9587
10093
  }
9588
10094
  if (uri === "godx-ui://patterns") {
9589
- return JSON.stringify(PATTERNS.map(({ name, tagline, tags }) => ({ name, tagline, tags })), null, 2);
10095
+ return JSON.stringify(
10096
+ PATTERNS.map(({ name, tagline, tags }) => ({ name, tagline, tags })),
10097
+ null,
10098
+ 2
10099
+ );
9590
10100
  }
9591
10101
  if (uri.startsWith("godx-ui://patterns/")) {
9592
10102
  const name = uri.slice("godx-ui://patterns/".length);