@godxjp/ui-mcp 16.7.2 → 17.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/index.js CHANGED
@@ -127,8 +127,8 @@ export default function OrdersPage() {
127
127
  {
128
128
  name: "direction",
129
129
  type: '"row" | "col"',
130
- defaultValue: '"col"',
131
- description: "Main axis direction. Use row for horizontal runs, col for vertical stacks."
130
+ defaultValue: '"row"',
131
+ description: "Main axis direction. Defaults to the CSS platform initial value, row; use col explicitly for vertical stacks."
132
132
  },
133
133
  {
134
134
  name: "gap",
@@ -338,6 +338,73 @@ export function CrmLayout({ children }: { content: React.ReactNode }) {
338
338
  storyPath: "layout/AppShell.stories.tsx",
339
339
  rules: [23]
340
340
  },
341
+ {
342
+ name: "AuthShell",
343
+ group: "layout",
344
+ tagline: "Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",
345
+ props: [
346
+ {
347
+ name: "children",
348
+ type: "ReactNode",
349
+ required: true,
350
+ description: "Centred content \u2014 typically a single auth <Card> holding the form."
351
+ },
352
+ {
353
+ name: "brand",
354
+ type: "ReactNode",
355
+ description: "Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."
356
+ },
357
+ {
358
+ name: "footer",
359
+ type: "ReactNode",
360
+ description: "Footer slot pinned to the bottom (legal links, locale switch, support)."
361
+ }
362
+ ],
363
+ usage: [
364
+ "DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",
365
+ "DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.",
366
+ "DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.",
367
+ "DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.",
368
+ "DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.",
369
+ "DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."
370
+ ],
371
+ useCases: [
372
+ "Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.",
373
+ "MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",
374
+ "Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",
375
+ 'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'
376
+ ],
377
+ related: [
378
+ "AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.",
379
+ "Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.",
380
+ "Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",
381
+ 'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).'
382
+ ],
383
+ example: `import { AuthShell } from "@godxjp/ui/layout";
384
+ import { Reveal, Logo, Button } from "@godxjp/ui/general";
385
+ import { Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
386
+ import { Field } from "@godxjp/ui/data-entry";
387
+
388
+ export function LoginPage() {
389
+ return (
390
+ <AuthShell brand={<Logo />} footer={<span>\xA9 2026 GodX</span>}>
391
+ <Reveal>
392
+ <Card>
393
+ <CardHeader>
394
+ <CardTitle>\u30ED\u30B0\u30A4\u30F3</CardTitle>
395
+ </CardHeader>
396
+ <CardContent>
397
+ {/* form fields */}
398
+ <Button fullWidth>\u7D9A\u3051\u308B</Button>
399
+ </CardContent>
400
+ </Card>
401
+ </Reveal>
402
+ </AuthShell>
403
+ );
404
+ }`,
405
+ storyPath: "layout/AuthShell.stories.tsx",
406
+ rules: [23]
407
+ },
341
408
  {
342
409
  name: "Sidebar",
343
410
  group: "layout",
@@ -867,6 +934,117 @@ import { Trash2 } from "lucide-react";
867
934
  <Heading level={2}>\u8ACB\u6C42\u66F8\u4E00\u89A7</Heading>
868
935
  <Heading level={3} tone="muted">\u88DC\u8DB3\u30BB\u30AF\u30B7\u30E7\u30F3</Heading>`
869
936
  },
937
+ {
938
+ name: "Logo",
939
+ group: "general",
940
+ tagline: "The product brand-mark box \u2014 a glyph on the primary fill. Tokenised size/radius/type; the fill reads --primary so a re-theme re-tints it. Decorative by default; pair with a wordmark.",
941
+ props: [
942
+ {
943
+ name: "glyph",
944
+ type: "React.ReactNode",
945
+ defaultValue: '"g"',
946
+ description: "The brand glyph \u2014 a short mark (letter/initials) or a custom inline <svg>."
947
+ },
948
+ {
949
+ name: "size",
950
+ type: '"xs" | "sm" | "md" | "lg"',
951
+ defaultValue: '"md"',
952
+ description: "Box size tier (tokenised)."
953
+ },
954
+ {
955
+ name: "label",
956
+ type: "string",
957
+ description: "Accessible name. Set \u2192 exposed as a named image (role img); omitted \u2192 decorative (aria-hidden), the correct default when a readable wordmark sits beside it."
958
+ }
959
+ ],
960
+ usage: [
961
+ 'DO import from `@godxjp/ui/general`: `import { Logo } from "@godxjp/ui/general";`',
962
+ 'DO use Logo INSTEAD of hand-rolling `<span aria-hidden className="grid size-7 place-items-center rounded-md bg-primary text-sm font-bold text-primary-foreground">g</span>` \u2014 that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46).',
963
+ "DO leave `label` unset when a readable wordmark sits beside the mark (shell header, topbar) \u2014 the mark stays decorative and the wordmark carries the accessible name. Set `label` only when the mark stands alone.",
964
+ "DON'T pass more than 1\u20132 glyphs \u2014 the box is square and centres its content; a long string overflows. For a full wordmark use `Text`/`Heading` beside the Logo, not inside it.",
965
+ 'DON\'T re-tint via `className="bg-*"` \u2014 the fill reads the `--primary` role token; retune it through a service theme (`--primary`, `--logo-radius`, `--logo-size-*`), not utilities.'
966
+ ],
967
+ useCases: [
968
+ 'App-shell header brand lockup \u2014 `<Logo glyph="c" /> <Text weight="medium">CoreBooks</Text>` in the sidebar/topbar, mark decorative, wordmark readable.',
969
+ 'Auth screen \u2014 a standalone labelled mark above the sign-in form: `<Logo label="CoreBooks" size="lg" />`.',
970
+ 'Tenant/workspace switcher row \u2014 a small `size="sm"` mark as the leading slot of a ListRow or menu item.',
971
+ "Custom SVG brand \u2014 pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter."
972
+ ],
973
+ related: [
974
+ "Text / Heading \u2014 use for the readable wordmark BESIDE the Logo (the full lockup); the Logo is only the square mark, not the product name.",
975
+ "Avatar \u2014 use Avatar for a PERSON/entity image or initials; use Logo for the PRODUCT brand mark. They look similar (square/rounded glyph) but carry different meaning."
976
+ ],
977
+ storyPath: "general/Logo.stories.tsx",
978
+ rules: [45, 46],
979
+ example: `import { Logo } from "@godxjp/ui/general";
980
+ import { Text } from "@godxjp/ui/general";
981
+
982
+ <span className="inline-flex items-center gap-2">
983
+ <Logo glyph="c" />
984
+ <Text weight="medium">CoreBooks</Text>
985
+ </span>
986
+
987
+ <Logo label="CoreBooks" size="lg" />`
988
+ },
989
+ {
990
+ name: "Reveal",
991
+ group: "general",
992
+ tagline: "The official entrance-motion primitive (staggered fade-up) \u2014 reads DS motion tokens and honours prefers-reduced-motion, replacing hand-rolled @keyframes + .app-reveal/.d1..d6 classes.",
993
+ props: [
994
+ {
995
+ name: "children",
996
+ type: "ReactNode",
997
+ required: true,
998
+ description: "Content to reveal on enter."
999
+ },
1000
+ {
1001
+ name: "delay",
1002
+ type: "0 | 1 | 2 | 3 | 4 | 5 | 6",
1003
+ defaultValue: "0",
1004
+ description: "Stagger ordinal \u2014 an INDEX into the motion ladder, never a raw ms. Each step adds one `--reveal-stagger-step` of delay so a column of reveals cascades. 0 = enter immediately."
1005
+ },
1006
+ {
1007
+ name: "asChild",
1008
+ type: "boolean",
1009
+ defaultValue: "false",
1010
+ description: "Merge the reveal onto the single child element (no wrapper <div>) \u2014 use when an extra box would break a grid/flex layout."
1011
+ }
1012
+ ],
1013
+ usage: [
1014
+ "DO use <Reveal> INSTEAD of hand-rolling `@keyframes auth-fade-up` + `.app-reveal` + `.d1..d6` in a consumer global.css \u2014 that repeats literal durations/delays and violates the tokens-only rule. Reveal reads `--duration-slow` / `--ease-emphasized` / `--reveal-distance` / `--reveal-stagger-step`.",
1015
+ "DO stagger a list/column by passing an increasing `delay` (1, 2, 3\u2026) to successive siblings \u2014 the ordinal maps to `--reveal-stagger-step`, so a service retunes the cascade rhythm from one token.",
1016
+ "DO pass `asChild` when wrapping an element that must keep its own box in a grid/flex row (the reveal merges onto that element instead of adding a <div>).",
1017
+ "DO rely on the built-in reduced-motion behaviour \u2014 under `prefers-reduced-motion: reduce` the animation is dropped and content renders in its final, fully-visible position with no layout shift. Never gate visibility on the animation.",
1018
+ "DO NOT set a raw ms delay or a literal translate distance \u2014 the whole point is that `delay` is a controlled ordinal and the distance/duration come from tokens."
1019
+ ],
1020
+ useCases: [
1021
+ "Auth card entrance: `<AuthShell><Reveal><Card/></Reveal></AuthShell>` \u2014 the sign-in card fades up on load, respecting reduced-motion.",
1022
+ "Staggered dashboard: map stat cards with `<Reveal delay={i + 1}>` so the row cascades in.",
1023
+ "Section reveal on a settings/detail page \u2014 wrap each Card in <Reveal> for a calm entrance without hand-written CSS.",
1024
+ "asChild on a grid item: `<Reveal asChild delay={2}><ResponsiveGrid.Item/></Reveal>` keeps the grid cell intact while animating it in."
1025
+ ],
1026
+ related: [
1027
+ "AuthShell \u2014 pairs with Reveal for the auth card entrance; AuthShell delegates all motion to Reveal.",
1028
+ "Card \u2014 the most common thing to wrap in <Reveal> (dashboard cards, auth card, settings sections).",
1029
+ "ResponsiveGrid \u2014 combine with `<Reveal delay={n}>` (or `asChild`) per grid item for a staggered grid reveal."
1030
+ ],
1031
+ example: `import { Reveal } from "@godxjp/ui/general";
1032
+ import { Card, CardContent } from "@godxjp/ui/data-display";
1033
+
1034
+ // single entrance
1035
+ <Reveal>
1036
+ <Card><CardContent>\u2026</CardContent></Card>
1037
+ </Reveal>
1038
+
1039
+ // staggered column
1040
+ {items.map((item, i) => (
1041
+ <Reveal key={item.id} delay={Math.min(i + 1, 6) as 1 | 2 | 3 | 4 | 5 | 6}>
1042
+ <Card><CardContent>{item.label}</CardContent></Card>
1043
+ </Reveal>
1044
+ ))}`,
1045
+ storyPath: "general/Reveal.stories.tsx",
1046
+ rules: []
1047
+ },
870
1048
  // ─── data-display ───────────────────────────────────────────────────────
871
1049
  {
872
1050
  name: "DataTable",
@@ -1498,15 +1676,29 @@ import { Smartphone } from "lucide-react";
1498
1676
  { name: "title", type: "string", required: true, description: "Primary empty message." },
1499
1677
  { name: "description", type: "string", description: "Secondary helper text." },
1500
1678
  { name: "icon", type: "LucideIcon", description: "Icon above the title." },
1501
- { name: "action", type: "ReactNode", description: "CTA element (e.g. a Button)." }
1679
+ { name: "action", type: "ReactNode", description: "CTA element (e.g. a Button)." },
1680
+ {
1681
+ name: "variant",
1682
+ type: '"page" | "section" | "compact"',
1683
+ defaultValue: '"page"',
1684
+ description: "Contextual visual weight. Compact omits the icon medallion."
1685
+ },
1686
+ {
1687
+ name: "tone",
1688
+ type: '"muted" | "success" | "warning" | "destructive" | "info"',
1689
+ defaultValue: '"muted"',
1690
+ description: "Medallion colour intent (a subset of the shared tone vocabulary; `destructive` is the DS name for a danger state). Tints the icon foreground + fill from the matching role token \u2014 set `success` for a confirmation zero-state (e.g. device approved) instead of hand-rolling a `.ui-success-state` class."
1691
+ }
1502
1692
  ],
1503
1693
  usage: [
1504
1694
  "DO always pass `title` \u2014 it is the only required prop and renders an `<h3>`; omitting it causes a blank silent render with no visible error.",
1695
+ 'DO use `tone="success"` (or warning/destructive/info) for a semantic confirmation/alert zero-state \u2014 it recolours the icon medallion from the role token; do NOT hand-roll a `.ui-success-state` class that scopes `--empty-state-icon-*`.',
1505
1696
  "DO use the `icon` prop (a Lucide icon component, not a JSX element) to give visual context \u2014 e.g. `icon={InboxIcon}` for empty inboxes, `icon={SearchIcon}` after a failed search. Pass the component reference, not `<InboxIcon />`.",
1506
1697
  "DO use `action` (a `ReactNode`, typically a `<Button>`) for actionable zero-states \u2014 e.g. 'Create first invoice' \u2014 so users have a clear next step instead of a dead end.",
1507
1698
  "DO NOT hand-roll a `data.length === 0 ? <EmptyState /> : <DataTable />` conditional \u2014 `DataTable` already embeds an `EmptyState` in its body when `data` is empty. Use the `empty=` prop on `DataTable` to customise it, not a wrapper conditional.",
1508
1699
  "DO NOT use EmptyState inside a `DataState` or `InfiniteQueryState` for the loading or error states \u2014 those widgets handle skeleton/error themselves; pass `EmptyState` only to their `empty=` prop for the zero-items case.",
1509
- "DO NOT add padding directly on `EmptyState` via `className` when placing it inside a `Card` \u2014 wrap it in `<CardContent>` first; EmptyState is a self-contained block with its own internal spacing via `ui-empty-state` styles."
1700
+ "DO NOT add padding directly on `EmptyState` via `className` when placing it inside a `Card` \u2014 wrap it in `<CardContent>` first; EmptyState is a self-contained block with its own internal spacing via `ui-empty-state` styles.",
1701
+ "DO omit optional secondary sections when absence has no user value. Otherwise use variant='compact' or 'section'; reserve page for the primary page job."
1510
1702
  ],
1511
1703
  useCases: [
1512
1704
  "Zero-row admin list pages (invoices, accounts, transactions) that are NOT backed by a `DataTable` \u2014 e.g. a card-grid or custom list layout where DataTable's built-in empty state doesn't apply.",
@@ -1531,20 +1723,26 @@ import { Smartphone } from "lucide-react";
1531
1723
  {
1532
1724
  name: "Progress",
1533
1725
  group: "data-display",
1534
- tagline: "Horizontal progress bar 0\u2013100 with optional label and semantic tone.",
1726
+ tagline: "Horizontal progress bar 0\u2013100 with optional label, semantic tone, and an over-capacity (striped) state for over-limit meters.",
1535
1727
  props: [
1536
1728
  {
1537
1729
  name: "value",
1538
1730
  type: "number",
1539
1731
  required: true,
1540
- description: "Progress percentage 0\u2013100 (clamped)."
1732
+ description: "Progress percentage 0\u2013100 (clamped unless `over`)."
1541
1733
  },
1542
1734
  { name: "label", type: "string", description: "Text label beside/below the bar." },
1543
1735
  {
1544
1736
  name: "tone",
1545
- type: '"success" | "warning"',
1737
+ type: '"success" | "warning" | "destructive"',
1546
1738
  defaultValue: '"success"',
1547
- description: "Bar colour tone."
1739
+ description: "Bar colour tone. Over-capacity defaults to destructive."
1740
+ },
1741
+ {
1742
+ name: "over",
1743
+ type: "boolean",
1744
+ defaultValue: "false",
1745
+ description: "Allow value > 100 to render an over-capacity fill: bar caps at 100% width but gets a diagonal hatch + destructive tone (e.g. 252%). aria-valuetext reports the real ratio. Off by default (clamps to 100)."
1548
1746
  }
1549
1747
  ],
1550
1748
  usage: [
@@ -1561,7 +1759,8 @@ import { Smartphone } from "lucide-react";
1561
1759
  "Storage or quota indicator in an admin panel \u2014 visualise disk usage, API quota, or seat licence consumption against a fixed limit.",
1562
1760
  "Sync / import job completion feedback \u2014 surface the completion percentage of a long-running background job (polling the server) without giving the user an interactive control.",
1563
1761
  "StatCard companion \u2014 pair with a `StatCard` metric to add a visual fill below the KPI number, reinforcing how close a target is to being met.",
1564
- "Multi-step onboarding or setup checklist \u2014 render one Progress per section (e.g. 3/5 steps complete = 60%) to give users a quick scan of overall progress across areas."
1762
+ "Multi-step onboarding or setup checklist \u2014 render one Progress per section (e.g. 3/5 steps complete = 60%) to give users a quick scan of overall progress across areas.",
1763
+ "Over-capacity meter \u2014 an air-cargo weight/volume load or an over-booked resource pushed past its limit (e.g. 252%): pass `over` with the real ratio to get a red diagonal-hatched bar that reads unmistakably as over-limit, not merely full."
1565
1764
  ],
1566
1765
  related: [
1567
1766
  "Slider \u2014 use Slider when the user must drag or set a bounded numeric value (volume, priority, price range); use Progress when the value is read-only and must not be interacted with.",
@@ -1572,7 +1771,8 @@ import { Smartphone } from "lucide-react";
1572
1771
  ],
1573
1772
  example: `import { Progress } from "@godxjp/ui/data-display";
1574
1773
 
1575
- <Progress value={pct} label={pct + "% \u4F7F\u7528\u4E2D"} variant={pct >= 80 ? "warning" : "success"} />`,
1774
+ <Progress value={pct} label={pct + "% \u4F7F\u7528\u4E2D"} tone={pct >= 80 ? "warning" : "success"} />
1775
+ <Progress value={252} over label="252% \u7A4D\u8F09" />`,
1576
1776
  storyPath: "data-display/Progress.stories.tsx",
1577
1777
  rules: []
1578
1778
  },
@@ -1694,6 +1894,22 @@ import { Smartphone } from "lucide-react";
1694
1894
  description: "The useQuery result."
1695
1895
  },
1696
1896
  { name: "skeleton", type: "ReactNode", required: true, description: "Shown while loading." },
1897
+ {
1898
+ name: "prerequisite",
1899
+ type: "ReactNode",
1900
+ description: "Shown when the query is disabled/unstarted (pending + fetchStatus idle)."
1901
+ },
1902
+ {
1903
+ name: "showRetry",
1904
+ type: "boolean",
1905
+ defaultValue: "false",
1906
+ description: "Force Retry even for non-transient causes. Retry is offered automatically for transient/network/5xx errors regardless of this flag."
1907
+ },
1908
+ {
1909
+ name: "onAuthError",
1910
+ type: "() => void",
1911
+ description: "Recovery for 401 / expired-token errors: renew the session or sign in again. A 401 renders this action instead of Retry."
1912
+ },
1697
1913
  {
1698
1914
  name: "children",
1699
1915
  type: "(data) => ReactNode",
@@ -1709,7 +1925,8 @@ import { Smartphone } from "lucide-react";
1709
1925
  'DO: provide `empty` + `isEmpty` together when the data can legitimately return 0 items \u2014 e.g. `isEmpty={(d) => d.items.length === 0}` paired with `empty={<EmptyState title="\u2026" />}`. Omitting `empty` means an empty array still falls through to `children`, silently rendering a blank table.',
1710
1926
  "DON'T: wrap DataState in your own conditional \u2014 e.g. `{query.isSuccess && <DataState \u2026>}`. DataState IS the conditional; the outer guard is redundant and breaks the retry/refetch skeleton.",
1711
1927
  "DON'T: use DataState for `useInfiniteQuery` results. The `query` prop type is `UseQueryResult<T>`, not `UseInfiniteQueryResult`. Use `InfiniteQueryState` (from `@godxjp/ui/query`) instead, which accepts `flatten` and renders a load-more footer.",
1712
- "DO: supply `errorRenderer` only when the default `AlertQueryError` + retry button is not enough \u2014 e.g. a full-page error boundary with navigation. Otherwise rely on `showRetry` (default `true`) and the built-in `AlertQueryError`, and override `onRetry` only if `query.refetch()` is not the right action."
1928
+ "DO: classify errors by cause. Use session renewal/sign-in for 401, access guidance for 403, contextual correction for domain errors, and opt into showRetry only for transient network/5xx errors.",
1929
+ "DO: pass prerequisite for enabled:false queries. Pending + fetchStatus idle is unstarted, not loading, and never renders a skeleton."
1713
1930
  ],
1714
1931
  useCases: [
1715
1932
  "A detail page that loads a single invoice/journal entry via `useQuery` \u2014 DataState renders the skeleton row while fetching, an error alert with retry if the API fails, and the `<InvoiceCard>` only when data is confirmed non-null.",
@@ -1730,7 +1947,12 @@ import { Smartphone } from "lucide-react";
1730
1947
  {(d) => <MemberTable items={d.items} />}
1731
1948
  </DataState>`,
1732
1949
  storyPath: "query/DataState.stories.tsx",
1733
- rules: []
1950
+ rules: [
1951
+ "Errors are classified by cause (via classifyQueryError). Retry is offered ONLY for transient/network/5xx; a 401/expired-token routes to session renewal through onAuthError; 403/404/422 present a cause-aware message with no blind retry.",
1952
+ "The user-facing detail is a localized, cause-specific message \u2014 the raw backend/token/stack text is never shown. For a domain-specific message (e.g. a 422 field error) pass a custom errorRenderer.",
1953
+ "A disabled/unstarted query (enabled:false \u2192 isPending with fetchStatus 'idle') renders the prerequisite slot, never the skeleton. Always pass prerequisite for tenant/org-gated queries.",
1954
+ "A background refetch over existing data keeps the content on screen with a polite sr-only busy status \u2014 it does not flash the skeleton. Only the initial fetch (isPending) shows the skeleton."
1955
+ ]
1734
1956
  },
1735
1957
  {
1736
1958
  name: "InfiniteQueryState",
@@ -2281,6 +2503,11 @@ import { Smartphone } from "lucide-react";
2281
2503
  type: "string",
2282
2504
  description: "Message rendered while loadOptions is resolving."
2283
2505
  },
2506
+ {
2507
+ name: "errorMessage",
2508
+ type: "string",
2509
+ description: "Message rendered when an async loadOptions REJECTS \u2014 a distinct state from empty/loading. Defaults to a localized 'Couldn\u2019t load options'. The panel shows this instead of a blank surface or a misleading 'no results'."
2510
+ },
2284
2511
  {
2285
2512
  name: "clearable",
2286
2513
  type: "boolean",
@@ -2325,6 +2552,7 @@ import { Smartphone } from "lucide-react";
2325
2552
  "DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
2326
2553
  "DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.",
2327
2554
  "DO pair id= with a <label htmlFor={id}> for a11y. The trigger renders as a button; screen readers announce the label.",
2555
+ "DO treat loading / no-options / error / disabled as DISTINCT states. A data-driven Select never opens a blank popover: a static options=[] list auto-disables the trigger (opening it would show nothing), while an async loadOptions shows a loading row, then either the options, a localized empty affordance (override with emptyMessage), or an error affordance if the fetch rejects (override with errorMessage). Disable the Select when there is nothing to pick AND no async loader; keep it enabled (it opens to load/search) whenever loadOptions is set.",
2328
2556
  "DON'T mix the two APIs: once you pass options or loadOptions, Select is data-driven \u2014 all compound sub-parts (SelectTrigger, SelectContent, SelectItem) are rendered internally. Do not wrap them manually.",
2329
2557
  "DON'T use a raw <select> element. Select is the one control for all single-select use cases. The only allowed raw <select> is a hidden aria-hidden sr-only element kept as an e2e hook paired with a visible Select.",
2330
2558
  "COMPOUND API sub-parts (when NOT using options/loadOptions): Select \u2192 SelectTrigger (contains SelectValue) \u2192 SelectContent \u2192 SelectItem. Optionally wrap items in SelectGroup + SelectLabel for headings, or add SelectSeparator between sections."
@@ -2335,7 +2563,8 @@ import { Smartphone } from "lucide-react";
2335
2563
  "Account category picker backed by an API \u2014 pass loadOptions to stream pages of accounts as the user types; use renderOption to show account code + name side by side; pass selectedLabel so the trigger shows the name on first render.",
2336
2564
  "Grouped currency picker \u2014 set option.group='Asia' / 'Europe' on each option; the plain (non-search) data-driven mode renders SelectGroup headings automatically.",
2337
2565
  "Form field in an accounting entry \u2014 use the compound API when the trigger must show a currency flag icon alongside the SelectValue; wire SelectTrigger size='sm' for dense table rows.",
2338
- "Required department select in a HR form \u2014 pass clearable=false so the user cannot clear the field once set; pair with name='department_id' for Inertia useForm submission."
2566
+ "Required department select in a HR form \u2014 pass clearable=false so the user cannot clear the field once set; pair with name='department_id' for Inertia useForm submission.",
2567
+ "Async account picker whose API can fail \u2014 pass loadOptions plus errorMessage so a rejected fetch shows a clear error affordance in the panel (not a blank surface or a false 'no results'); the loading and empty states are handled automatically."
2339
2568
  ],
2340
2569
  related: [
2341
2570
  "SearchSelect \u2014 the combobox engine Select delegates to when showSearch=true or loadOptions is set. Prefer Select with showSearch instead of reaching for SearchSelect directly (SearchSelect is now deprecated as a public API).",
@@ -7016,6 +7245,11 @@ export default function PasswordBlock() {
7016
7245
  type: "(value: string) => void",
7017
7246
  description: "Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present."
7018
7247
  },
7248
+ {
7249
+ name: "appearance",
7250
+ type: '"labeled" | "icon"',
7251
+ description: `Trigger presentation. "labeled" (default) shows the leading icon + selected value in a full-width control. "icon" is the supported icon-only topbar trigger (e.g. a globe locale switcher): it structurally drops the value text and the picker's owned width and hides the chevron, squares the box to the density-aware --control-height tap target (\u226544px on touch), and always keeps the localized aria-label so it can never ship nameless. Menu options still show localized names. Use it instead of overriding internal descendants / width classes with CSS.`
7252
+ },
7019
7253
  {
7020
7254
  name: "className",
7021
7255
  type: "string",
@@ -7033,10 +7267,12 @@ export default function PasswordBlock() {
7033
7267
  "DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.",
7034
7268
  "DO NOT: Render without AppProvider and without both controlled props \u2014 it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.",
7035
7269
  "DO: Render four instances with different kind values to build a full preferences panel; they all share the same AppProvider context and stay in sync.",
7270
+ 'DO: For an icon-only topbar utility (a globe language switcher), pass appearance="icon" \u2014 the supported compact trigger. NEVER hand-roll it by hiding the value/width with descendant-selector CSS.',
7036
7271
  "DON'T hand-roll a locale/timezone/format Select \u2014 AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."
7037
7272
  ],
7038
7273
  useCases: [
7039
7274
  'App-shell top-nav language switcher: <AppSettingPicker kind="locale" /> under AppProvider, persisting to localStorage with no extra state.',
7275
+ 'Icon-only topbar locale switcher (globe): <AppSettingPicker kind="locale" appearance="icon" /> in a Topbar `end` slot \u2014 square, value-less, keyboard + aria-label preserved.',
7040
7276
  "User settings page with all four preferences \u2014 render kind=locale, kind=timezone, kind=dateFormat, kind=timeFormat together under one AppProvider.",
7041
7277
  "Onboarding step that picks language/timezone before the rest of the app is configured \u2014 AppProvider persist={false} + controlled values to keep state local.",
7042
7278
  'Storybook/test harness without AppProvider \u2014 fully controlled: <AppSettingPicker kind="timeFormat" value="24h" onValueChange={fn} />.'
@@ -7068,6 +7304,14 @@ import { AppSettingPicker } from "@godxjp/ui/navigation";
7068
7304
  export function LocaleField() {
7069
7305
  const [locale, setLocale] = useState("en");
7070
7306
  return <AppSettingPicker kind="locale" value={locale} onValueChange={setLocale} />;
7307
+ }
7308
+
7309
+ // Icon-only topbar locale switcher (globe) \u2014 supported compact trigger, no CSS overrides
7310
+ import { Topbar } from "@godxjp/ui/layout";
7311
+ import { AppSettingPicker } from "@godxjp/ui/navigation";
7312
+
7313
+ export function TopbarLocale() {
7314
+ return <Topbar end={<AppSettingPicker kind="locale" appearance="icon" />} />;
7071
7315
  }\`}`,
7072
7316
  storyPath: "navigation/AppSettingPicker.stories.tsx",
7073
7317
  rules: [3, 5, 6, 23]
@@ -7633,6 +7877,18 @@ var TOKENS = [
7633
7877
  tier: "primitive",
7634
7878
  role: "Raw typography scale."
7635
7879
  },
7880
+ {
7881
+ name: "--font-sans-base",
7882
+ category: "semantic",
7883
+ tier: "semantic",
7884
+ role: "The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts fills this with the bundled Noto Sans JP."
7885
+ },
7886
+ {
7887
+ name: "--font-sans-{ja,ko,vi,zh-hans,zh-hant}",
7888
+ category: "semantic",
7889
+ tier: "semantic",
7890
+ role: "Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale's face by setting e.g. `--font-sans-ja: \"Noto Sans JP\", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer's font pipeline, not the library."
7891
+ },
7636
7892
  {
7637
7893
  name: "--duration-{fast,base,slow}",
7638
7894
  category: "primitive",
@@ -7651,6 +7907,12 @@ var TOKENS = [
7651
7907
  tier: "primitive",
7652
7908
  role: "Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."
7653
7909
  },
7910
+ {
7911
+ name: "--reveal-stagger-step",
7912
+ category: "primitive",
7913
+ tier: "primitive",
7914
+ role: "One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."
7915
+ },
7654
7916
  {
7655
7917
  name: "--shadow-color",
7656
7918
  category: "primitive",
@@ -8424,6 +8686,26 @@ var COMPONENT_TOKENS = [
8424
8686
  "value": "var(--space-6)",
8425
8687
  "description": "Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."
8426
8688
  },
8689
+ {
8690
+ "name": "--empty-state-section-space-y",
8691
+ "value": "var(--space-6)",
8692
+ "description": "Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."
8693
+ },
8694
+ {
8695
+ "name": "--empty-state-section-space-x",
8696
+ "value": "var(--space-4)",
8697
+ "description": "Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."
8698
+ },
8699
+ {
8700
+ "name": "--empty-state-compact-space-y",
8701
+ "value": "var(--space-3)",
8702
+ "description": "Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."
8703
+ },
8704
+ {
8705
+ "name": "--empty-state-compact-space-x",
8706
+ "value": "var(--space-2)",
8707
+ "description": "Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."
8708
+ },
8427
8709
  {
8428
8710
  "name": "--empty-state-icon-foreground",
8429
8711
  "value": "initial",
@@ -8489,6 +8771,51 @@ var COMPONENT_TOKENS = [
8489
8771
  "value": "initial",
8490
8772
  "description": "Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."
8491
8773
  },
8774
+ {
8775
+ "name": "--logo-radius",
8776
+ "value": "var(--radius)",
8777
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8778
+ },
8779
+ {
8780
+ "name": "--logo-size-xs",
8781
+ "value": "1.25rem",
8782
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8783
+ },
8784
+ {
8785
+ "name": "--logo-size-sm",
8786
+ "value": "1.5rem",
8787
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8788
+ },
8789
+ {
8790
+ "name": "--logo-size-md",
8791
+ "value": "1.75rem",
8792
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8793
+ },
8794
+ {
8795
+ "name": "--logo-size-lg",
8796
+ "value": "2.25rem",
8797
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8798
+ },
8799
+ {
8800
+ "name": "--logo-font-size-xs",
8801
+ "value": "var(--font-size-2xs)",
8802
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8803
+ },
8804
+ {
8805
+ "name": "--logo-font-size-sm",
8806
+ "value": "var(--font-size-xs)",
8807
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8808
+ },
8809
+ {
8810
+ "name": "--logo-font-size-md",
8811
+ "value": "var(--font-size-sm)",
8812
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8813
+ },
8814
+ {
8815
+ "name": "--logo-font-size-lg",
8816
+ "value": "var(--font-size-base)",
8817
+ "description": "Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."
8818
+ },
8492
8819
  {
8493
8820
  "name": "--pagination-gap",
8494
8821
  "value": "var(--space-inline-sm)",
@@ -8639,6 +8966,36 @@ var COMPONENT_TOKENS = [
8639
8966
  "value": "initial",
8640
8967
  "description": "Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."
8641
8968
  },
8969
+ {
8970
+ "name": "--auth-shell-control-height",
8971
+ "value": "var(--control-height-comfortable)",
8972
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8973
+ },
8974
+ {
8975
+ "name": "--auth-shell-heading-size",
8976
+ "value": "var(--font-size-2xl)",
8977
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8978
+ },
8979
+ {
8980
+ "name": "--auth-shell-card-max-width",
8981
+ "value": "24rem",
8982
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8983
+ },
8984
+ {
8985
+ "name": "--auth-shell-bar-padding",
8986
+ "value": "var(--space-5) var(--space-6)",
8987
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8988
+ },
8989
+ {
8990
+ "name": "--auth-shell-main-padding",
8991
+ "value": "var(--space-6)",
8992
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8993
+ },
8994
+ {
8995
+ "name": "--auth-shell-footer-padding",
8996
+ "value": "var(--space-3) var(--space-6) var(--space-4)",
8997
+ "description": "AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."
8998
+ },
8642
8999
  {
8643
9000
  "name": "--table-row-height-compact",
8644
9001
  "value": "1.75rem",
@@ -9070,48 +9427,33 @@ export function SignUpCard() {
9070
9427
  }`
9071
9428
  },
9072
9429
  {
9073
- name: "settings-tabs",
9074
- tagline: "Sectioned settings inside a Card with Tabs + FormField + Select + Switch (real @godxjp/ui API).",
9430
+ name: "settings-page-responsive",
9431
+ tagline: "Route-backed settings: persistent desktop local navigation, compact mobile navigation, and bounded form content.",
9075
9432
  tags: ["settings", "form", "tabs", "admin"],
9076
- code: `import { Card, CardContent } from "@godxjp/ui/data-display";
9077
- import { Tabs, TabsList, TabsTrigger, TabsContent } from "@godxjp/ui/navigation";
9078
- import { FormField, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Switch, Label } from "@godxjp/ui/data-entry";
9079
- import { Stack } from "@godxjp/ui/layout";
9433
+ code: `// Research basis: GitHub/Google/Microsoft/Atlassian account settings + Carbon form grid.
9434
+ // Use URLs for every destination; do not keep broad settings IA in local tab state.
9435
+ import { NavLink, Outlet } from "react-router-dom";
9436
+ import { Flex, Stack } from "@godxjp/ui/layout";
9437
+ import { FormField, Input } from "@godxjp/ui/data-entry";
9080
9438
 
9081
9439
  export function WorkspaceSettings() {
9082
9440
  return (
9083
- <Card>
9084
- <CardContent>
9085
- <Tabs defaultValue="general">
9086
- <TabsList>
9087
- <TabsTrigger value="general">\u57FA\u672C\u60C5\u5831</TabsTrigger>
9088
- <TabsTrigger value="notify">\u901A\u77E5</TabsTrigger>
9089
- </TabsList>
9090
- <TabsContent value="general">
9091
- <Stack gap="md">
9092
- <FormField id="ws-name" label="\u540D\u524D" required><Input id="ws-name" /></FormField>
9093
- <FormField id="visibility" label="\u516C\u958B\u7BC4\u56F2">
9094
- <Select defaultValue="internal">
9095
- <SelectTrigger><SelectValue /></SelectTrigger>
9096
- <SelectContent>
9097
- <SelectItem value="private">\u30D7\u30E9\u30A4\u30D9\u30FC\u30C8</SelectItem>
9098
- <SelectItem value="internal">\u793E\u5185\u516C\u958B</SelectItem>
9099
- <SelectItem value="public">\u516C\u958B</SelectItem>
9100
- </SelectContent>
9101
- </Select>
9102
- </FormField>
9103
- </Stack>
9104
- </TabsContent>
9105
- <TabsContent value="notify">
9106
- <div className="flex items-center gap-2">
9107
- <Switch id="notify-comment" defaultChecked />
9108
- <Label htmlFor="notify-comment">\u30B3\u30E1\u30F3\u30C8\u901A\u77E5\u3092\u53D7\u3051\u53D6\u308B</Label>
9109
- </div>
9110
- </TabsContent>
9111
- </Tabs>
9112
- </CardContent>
9113
- </Card>
9441
+ <Flex gap="lg" className="settings-layout">
9442
+ <nav aria-label="Settings" className="settings-local-nav">
9443
+ <NavLink to="general">\u57FA\u672C\u60C5\u5831</NavLink>
9444
+ <NavLink to="security">\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3</NavLink>
9445
+ <NavLink to="notifications">\u901A\u77E5</NavLink>
9446
+ </nav>
9447
+ <main className="settings-content"><Outlet /></main>
9448
+ </Flex>
9114
9449
  );
9450
+ }
9451
+
9452
+ // Desktop: local nav + bounded content (roughly 40rem), horizontal form rows where useful.
9453
+ // Mobile (375/390px): same route links become a compact scrollable nav; labels stack above controls.
9454
+ // Do not add a Card around the whole page. Tabs are only for 2\u20134 peer views within one task.
9455
+ export function GeneralSettingsForm() {
9456
+ return <Stack gap="md"><FormField id="ws-name" label="\u540D\u524D" layout="horizontal" controlWidth="md"><Input id="ws-name" /></FormField></Stack>;
9115
9457
  }`
9116
9458
  },
9117
9459
  {
@@ -9377,6 +9719,63 @@ const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; retur
9377
9719
  // RULE: a chip never wraps \u2014 it is pinned white-space: nowrap, so it stays one line in
9378
9720
  // narrow table cells. Centralize the domain\u2192tone map in ONE small consumer wrapper and
9379
9721
  // import that instead of the raw Badge across pages.`
9722
+ },
9723
+ {
9724
+ name: "async-data-state",
9725
+ tagline: "Mutually exclusive prerequisite, loading, data, empty, and cause-aware error states.",
9726
+ tags: ["async", "loading", "query", "error", "empty"],
9727
+ code: `import { DataState } from "@godxjp/ui/query";
9728
+ import { EmptyState, SkeletonTable } from "@godxjp/ui/data-display";
9729
+
9730
+ // enabled:false is prerequisite/idle, not loading \u2014 DataState checks fetchStatus, not just isPending.
9731
+ // Errors are classified by cause: onAuthError handles 401/expired token (session renewal, NOT retry);
9732
+ // Retry appears automatically only for transient/network/5xx; 403/404/422 show a cause-aware message.
9733
+ <DataState query={query} prerequisite={<EmptyState variant="section" title="\u7D44\u7E54\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044" />}
9734
+ skeleton={<SkeletonTable />} empty={<EmptyState variant="section" title="\u7D50\u679C\u304C\u3042\u308A\u307E\u305B\u3093" />}
9735
+ isEmpty={(data) => data.items.length === 0}
9736
+ onAuthError={() => auth.signInAgain()}>
9737
+ {(data) => <Results items={data.items} />}
9738
+ </DataState>
9739
+
9740
+ // Need a bespoke error UI? Pass errorRenderer and branch on classifyQueryError(error).category
9741
+ // ("auth" | "forbidden" | "notFound" | "validation" | "transient" | "unknown").
9742
+ // The default detail is a localized message \u2014 never raw token/endpoint/stack text.
9743
+ // Never render pagination outside the successful populated-data branch.`
9744
+ },
9745
+ {
9746
+ name: "data-table-page",
9747
+ tagline: "Filter + table + single-row footer pagination, visible only for successful multi-page data.",
9748
+ tags: ["table", "pagination", "filter", "async"],
9749
+ code: `// FilterBar is standalone. Card owns one table surface; do not nest Card/Alert surfaces.
9750
+ // Inside DataState's success branch:
9751
+ <Card><CardContent flush><DataTable data={data.items} columns={columns} />
9752
+ {data.totalPages > 1 && <Flex justify="between" align="center"><span>{range}</span><Pagination /></Flex>}
9753
+ </CardContent></Card>
9754
+ // Hide pagination for prerequisite/loading/error/empty and one-page results.
9755
+ // Preserve filters/page during a transient retry; stack only at narrow mobile widths.`
9756
+ },
9757
+ {
9758
+ name: "organization-memberships",
9759
+ tagline: "Workspace identity, current state, role and permission-aware actions; invitations are conditional.",
9760
+ tags: ["organization", "workspace", "membership", "invitation", "account"],
9761
+ code: `// Research basis: GitHub organizations and Slack workspace switching/invitations.
9762
+ // Each row: logo + recognizable name + Current badge + role + Open/Switch/Manage/Leave action.
9763
+ // Put Create organization at page level. Do not show raw membership timestamps without labels.
9764
+ // No pending invitations \u2192 omit the section entirely.
9765
+ // Few pending \u2192 compact actionable list; many/history \u2192 a focused route.
9766
+ <MembershipList memberships={memberships} currentId={currentId} />
9767
+ {pendingInvitations.length > 0 && <PendingInvitations variant="compact" items={pendingInvitations} />}`
9768
+ },
9769
+ {
9770
+ name: "account-recovery-settings",
9771
+ tagline: "Signed-in recovery method status rows, separate from password change and signed-out recovery.",
9772
+ tags: ["account", "recovery", "security", "backup-codes", "password"],
9773
+ code: `// Research basis: Google Account recovery, Microsoft Security info, GitHub recovery codes.
9774
+ // Use compact method rows: method + verified/available status + Add/Change/Regenerate action.
9775
+ // Do not stack generic Info Alerts for ordinary capability status.
9776
+ <RecoveryMethodRow method="email" value={maskedEmail} status="verified" action={<Button>\u5909\u66F4</Button>} />
9777
+ {backupCodesSupported && <RecoveryMethodRow method="backup-codes" status={codeStatus} action={<Button>\u518D\u751F\u6210</Button>} />}
9778
+ // Password change is a separate destination. Forgot-password is a signed-out journey.`
9380
9779
  }
9381
9780
  ];
9382
9781
  function findPattern(name) {
@@ -10403,6 +10802,130 @@ npm-pack TARBALL install, not a file: symlink (the symlink's nested node_modules
10403
10802
  dual-React artifact under vitest).`
10404
10803
  }
10405
10804
  ]
10805
+ },
10806
+ // ── design-complex-admin (consumer) ────────────────────────────
10807
+ {
10808
+ id: "design-complex-admin",
10809
+ audience: "consumer",
10810
+ name: "Design a complex admin console \u2014 wireframe-first, layered IA",
10811
+ whenToUse: "You are building or redesigning a COMPLEX multi-layer admin/console/back-office UI (platform admin, workspace admin, multi-tenant management, control panel) \u2014 the kind that spans several administrative tiers (Platform \u2192 Org/Tenant \u2192 Brand \u2192 Org-unit \u2192 Shop \u2192 Staff), not one flat resource list. Read this BEFORE writing JSX: it forces the right order \u2014 lock the entity model \u2192 study exemplars \u2192 design a LAYERED information architecture \u2192 wireframe (as an Artifact) \u2192 validate \u2192 only THEN code. Stops the flat-console anti-pattern (every resource in one list, create-form + list + detail crammed on one screen, no dashboards). For a single ordinary screen use compose-a-screen; for a design handoff bundle use design-to-page.",
10812
+ source: "@godxjp/ui .claude/skills/design-complex-admin/SKILL.md \u2014 the multi-tenant admin-console redesign process (entity-tree concept \u2192 layered wireframe \u2192 code)",
10813
+ sections: [
10814
+ {
10815
+ id: "entity-model-first",
10816
+ title: "Step 0 \u2014 Lock the entity model before you draw a pixel",
10817
+ tagline: "Enumerate entities, cardinality, boundaries, and what does NOT exist; get it confirmed first.",
10818
+ body: `Do NOT open an editor at a flat "here are the resources" list. First produce a
10819
+ CONCEPT artifact (a tree of entities + a glossary + the rules) and get it confirmed:
10820
+ - every entity + its cardinality (1-N / N-N) + its boundary (tenant / RLS / subscription scope);
10821
+ - the identity split matters \u2014 Principal/User (can log in, carries authz) \u2260 Employee/Staff
10822
+ (a domain record that may never log in); never fuse them in the IA;
10823
+ - explicitly list what is OUT OF SCOPE / does NOT exist, so the console doesn't invent tiers.
10824
+ Worked example (a multi-tenant admin console): Org = Tenant = \u6CD5\u4EBA \xB7 Brand \xB7 Org-unit (branch) \xB7 Shop = Branch \xB7
10825
+ Staff \xB7 Principal = User. No code until this tree is agreed \u2014 a wrong entity model is a wrong
10826
+ console, and it is far cheaper to fix here than in built screens.
10827
+ \u{1F534} Entity-category colour is NON-SEMANTIC: never use red/amber/orange to distinguish a category
10828
+ (reads as error/warning/danger). Reserve red strictly for a real \`danger\` action. Differentiate
10829
+ tiers/categories with cool hues (sky/teal/indigo/violet) + a LABEL \u2014 never colour alone
10830
+ (colour-only meaning also fails WCAG).`
10831
+ },
10832
+ {
10833
+ id: "study-exemplars",
10834
+ title: "Step 1 \u2014 Study the best-in-class consoles for this domain",
10835
+ tagline: "Borrow proven IA from Auth0/WorkOS/Stripe/Cloudflare/Vercel/Supabase/Clerk.",
10836
+ body: `Before inventing structure, learn the information architecture of the strongest
10837
+ consoles in the same shape (identity/back-office/multi-tenant): Auth0, WorkOS, Stripe Connect,
10838
+ Cloudflare, Vercel, Supabase, Clerk \u2014 pick the closest to your domain. Extract the RECURRING
10839
+ patterns they all converge on and plan to reuse them:
10840
+ - a top scope switcher (change tenant/org/project) that re-scopes the whole shell;
10841
+ - a per-scope sidebar (nav that changes with the active scope);
10842
+ - breadcrumb trails + a \u2318K command palette for deep navigation;
10843
+ - a "needs attention" surface (what's broken / pending right now) on the landing dashboard;
10844
+ - first-run / empty / onboarding states, not just the happy populated view.
10845
+ These aren't decoration \u2014 they are the skeleton the layered IA hangs on.`
10846
+ },
10847
+ {
10848
+ id: "layered-ia",
10849
+ title: "Step 2 \u2014 Layered information architecture (never flat)",
10850
+ tagline: "Map each surface to an administrative TIER; every tier gets dashboard + list + detail as SEPARATE routes.",
10851
+ body: `Reject the flat console (every resource dumped into one list). Map each admin surface
10852
+ onto an administrative TIER of the domain (Platform \u2192 Org/Tenant \u2192 Brand \u2192 Org-unit \u2192 Shop \u2192 Staff)
10853
+ so the navigation mirrors how the business is actually administered.
10854
+ Each tier gets its OWN route set, and CRUD is split across routes \u2014 never one mega-screen:
10855
+ - an overview DASHBOARD (summary of that tier: counts, health, needs-attention);
10856
+ - a LIST route (browse/search the tier's entities);
10857
+ - a DETAIL route per entity (read + its sub-resources);
10858
+ - CREATE / EDIT as their own route or a focused Dialog/Sheet \u2014 NOT inlined beside the list.
10859
+ \u{1F534} The single worst anti-pattern this skill exists to kill: create-form + list + detail crammed
10860
+ onto one screen. If you can't name the tier a screen belongs to, the IA isn't finished.`
10861
+ },
10862
+ {
10863
+ id: "scope-and-chrome",
10864
+ title: "Step 3 \u2014 Scope model + shell chrome (real @godxjp/ui primitives)",
10865
+ tagline: "AppShell + Sidebar + Topbar slots + Breadcrumb; summary before detail; encode attention visually.",
10866
+ body: `Build the shell from REAL primitives (MCP-first \u2014 get_component before you wire a
10867
+ prop), never hand-rolled nav:
10868
+ - \`AppShell\` composes the frame; \`Sidebar\` is the DATA-DRIVEN nav rail (pass its items, never
10869
+ build nav from raw buttons) and it SWaps with the active scope; \`Topbar\` is a pure slot bar \u2014
10870
+ drop the scope switcher into a slot (compose it from \`Select\`/\`DropdownMenu\`; use
10871
+ \`AppSettingPicker\` for locale/theme/density axes), the shell bakes no chrome itself.
10872
+ - \`Breadcrumb\` for the trail; a \u2318K command palette is a COMPOSITION (\`Dialog\` + \`Input\` +
10873
+ a results list / \`DropdownMenu\`) \u2014 there is no Command primitive, so build it from these or,
10874
+ if you want a first-class one, file it via draft_bug_report rather than faking it.
10875
+ - Landing = a DASHBOARD: summary BEFORE detail \u2014 a few \`StatCard\` KPIs in a \`ResponsiveGrid\`
10876
+ (StatCard is already bordered \u2014 never wrap it in Card) + ONE primary list, not an 8-card wall.
10877
+ - Encode "needs attention" VISUALLY (a \`Badge\` with \`status\`, a StatCard \`accent\` rail, a
10878
+ \`Progress\` tone) \u2014 never a bare number the eye can't triage. Status uses the FIXED semantic
10879
+ mapping; never recolour a category hue into a role.`
10880
+ },
10881
+ {
10882
+ id: "wireframe-first",
10883
+ title: "Step 4 \u2014 Wireframe FIRST (as an Artifact), then validate",
10884
+ tagline: "Low-fi but REAL content, theme-aware, token-driven; stakeholder approves before code.",
10885
+ body: `Produce a low-fidelity WIREFRAME as an Artifact and get it approved BEFORE writing
10886
+ components \u2014 fixing structure in a wireframe is far cheaper than in built screens. The wireframe must:
10887
+ - use REAL content, never lorem \u2014 real tier names, real entity fields, real empty/attention copy;
10888
+ - be theme-aware (light + dark) and token-driven (semantic tokens, not raw hex) so it maps 1:1 to
10889
+ the eventual @godxjp/ui build;
10890
+ - cover the whole IA: the nav/scope model, the tier palette, EACH tier's dashboard/list/detail,
10891
+ plus before/after and do/don't panels so reviewers see the reasoning.
10892
+ Only after sign-off do you move to code. A wireframe skipped = a redesign relitigated in code.`
10893
+ },
10894
+ {
10895
+ id: "validate-then-build",
10896
+ title: "Steps 5\u20136 \u2014 Validate against godx-ui, THEN build route-by-route",
10897
+ tagline: "Run cardinal rules / patterns / anti-AI-tells; then each page = its own route from real primitives; hand off.",
10898
+ body: `Validate the wireframe against the system before building: cross-check the cardinal
10899
+ rules + canonical patterns (get_rule / list_patterns / get_pattern), run the ui-audit / visual-audit
10900
+ lens, and scan for anti-AI tells (list_anti_ai_tells) \u2014 flat 8-stat walls, rainbow tag walls,
10901
+ category-as-error colour, placeholder-as-label.
10902
+ Then BUILD, one route at a time \u2014 and hand off the per-screen craft to the screen skills rather
10903
+ than re-deriving it here:
10904
+ - each page is its OWN route (dashboard / list / detail / create split, per Step 2);
10905
+ - every block is a real @godxjp/ui primitive \u2014 no hand-rolled UI, no raw <input>/<select>/<table>;
10906
+ - a tier list = \`PageContainer\` + \`Card\` + \`CardContent\` flush + \`DataTable\`; detail =
10907
+ \`Descriptions\` + \`StatCard\`; empty = \`EmptyState\`;
10908
+ - ui-audit clean (0/0 semantic tokens), tests \u226595%.
10909
+ HAND-OFF: use compose-a-screen (build a screen from a brief) or design-to-page (from a handoff
10910
+ bundle) for each individual screen; if a needed block has no primitive, use design-to-page's
10911
+ gaps-extend-or-ask + report-bug \u2014 never fake it.`
10912
+ },
10913
+ {
10914
+ id: "anti-patterns",
10915
+ title: "Anti-patterns this skill must block + the per-run outputs",
10916
+ tagline: "The six failures to reject on sight, and the four artifacts every run produces.",
10917
+ body: `Reject on sight (each is a FINDING, stop and fix):
10918
+ 1. Code written before an agreed entity model AND an approved wireframe exist.
10919
+ 2. A FLAT IA \u2014 every resource in one list, no administrative tiers.
10920
+ 3. create-form + list + detail crammed onto one screen (CRUD not split into routes).
10921
+ 4. A tier with no dashboard/summary \u2014 detail with nothing to orient it.
10922
+ 5. Category/tier coloured with red/amber/orange (reads as error/warning) \u2014 use cool hues + labels.
10923
+ 6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
10924
+ Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
10925
+ (2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
10926
+ Do not advance a step until the prior artifact is signed off.`
10927
+ }
10928
+ ]
10406
10929
  }
10407
10930
  ];
10408
10931
  function findSkill(id) {
@@ -10575,7 +11098,37 @@ function routeTask(task, opts) {
10575
11098
  "app-performance",
10576
11099
  "measure-first",
10577
11100
  "Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",
10578
- ["app-performance/filter-pane-memo", "app-performance/heavy-panels", "app-performance/bundle-budget"]
11101
+ [
11102
+ "app-performance/filter-pane-memo",
11103
+ "app-performance/heavy-panels",
11104
+ "app-performance/bundle-budget"
11105
+ ]
11106
+ );
11107
+ route(
11108
+ [
11109
+ "admin console",
11110
+ "admin panel",
11111
+ "control panel",
11112
+ "back-office",
11113
+ "back office",
11114
+ "management console",
11115
+ "management ui",
11116
+ "multi-tenant",
11117
+ "multi tenant",
11118
+ "platform admin",
11119
+ "workspace admin",
11120
+ "console qu\u1EA3n tr\u1ECB",
11121
+ "layered ia",
11122
+ "information architecture"
11123
+ ],
11124
+ "design-complex-admin",
11125
+ "entity-model-first",
11126
+ "A complex multi-tier admin console: lock the entity model \u2192 study exemplars \u2192 design a LAYERED IA \u2192 wireframe (Artifact) \u2192 validate \u2192 build route-by-route. Kills the flat-console anti-pattern.",
11127
+ [
11128
+ "design-complex-admin/layered-ia",
11129
+ "design-complex-admin/wireframe-first",
11130
+ "compose-a-screen/pick-primitives"
11131
+ ]
10579
11132
  );
10580
11133
  const filtered = opts?.consumerOnly ? matches.filter((m) => {
10581
11134
  const sk = findSkill(m.skill);
@@ -11465,7 +12018,7 @@ function auditRulesByCategory(category) {
11465
12018
  }
11466
12019
 
11467
12020
  // src/data/visual-rules.ts
11468
- var VISUAL_AUDIT_COMMAND = "node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (needs optional peers: playwright + @axe-core/playwright + a chromium; --strict for a CI gate, --format json, --rules to print this catalog)";
12021
+ var VISUAL_AUDIT_COMMAND = "node node_modules/@godxjp/ui/scripts/visual-audit.mjs <baseUrl> [route \u2026] (optional peers, TESTED range: playwright >=1.55 <2 [1.61.1] + @axe-core/playwright >=4.10 <5 [4.12.1] + axe-core >=4.10 <5 [4.12.1] + a chromium via `playwright install chromium`; --strict for a CI gate, --format json ALWAYS emits valid JSON with a status of ok|partial|error separating infra errors[] from product findings[], --rules to print this catalog)";
11469
12022
  var VISUAL_RULES = [
11470
12023
  {
11471
12024
  id: "axe-violations",
@@ -11507,6 +12060,65 @@ function visualRulesByCategory(category) {
11507
12060
  return category ? VISUAL_RULES.filter((r) => r.category === category) : VISUAL_RULES;
11508
12061
  }
11509
12062
 
12063
+ // package.json
12064
+ var package_default = {
12065
+ name: "@godxjp/ui-mcp",
12066
+ version: "17.0.0",
12067
+ godxUiCompatibility: "17.0.x",
12068
+ description: "Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",
12069
+ type: "module",
12070
+ main: "./dist/index.js",
12071
+ module: "./dist/index.js",
12072
+ types: "./dist/index.d.ts",
12073
+ bin: {
12074
+ "godx-ui-mcp": "./dist/index.js"
12075
+ },
12076
+ files: [
12077
+ "dist",
12078
+ "README.md"
12079
+ ],
12080
+ publishConfig: {
12081
+ registry: "https://registry.npmjs.org/",
12082
+ access: "public"
12083
+ },
12084
+ repository: {
12085
+ type: "git",
12086
+ url: "git+https://github.com/godx-jp/godxjp-ui.git",
12087
+ directory: "mcp"
12088
+ },
12089
+ homepage: "https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",
12090
+ license: "Apache-2.0",
12091
+ scripts: {
12092
+ build: "tsup",
12093
+ dev: "tsup --watch",
12094
+ start: "node dist/index.js",
12095
+ inspect: "npx @modelcontextprotocol/inspector node dist/index.js",
12096
+ "type-check": "tsc --noEmit",
12097
+ test: "vitest run",
12098
+ prepublishOnly: "npm run build"
12099
+ },
12100
+ dependencies: {
12101
+ "@modelcontextprotocol/sdk": "^1.29.0",
12102
+ zod: "^4.4.3"
12103
+ },
12104
+ devDependencies: {
12105
+ "@types/node": "^22.10.0",
12106
+ tsup: "^8.5.1",
12107
+ typescript: "^6.0.3",
12108
+ vitest: "^4.1.6"
12109
+ },
12110
+ keywords: [
12111
+ "mcp",
12112
+ "model-context-protocol",
12113
+ "godxjp",
12114
+ "ui",
12115
+ "design-system",
12116
+ "react",
12117
+ "claude",
12118
+ "cursor"
12119
+ ]
12120
+ };
12121
+
11510
12122
  // src/tools/registry.ts
11511
12123
  var TOOL_DEFINITIONS = [
11512
12124
  // ── DISCOVERY (small responses) ────────────────────────────────
@@ -11745,6 +12357,19 @@ var TOOL_DEFINITIONS = [
11745
12357
  required: ["summary"]
11746
12358
  }
11747
12359
  },
12360
+ {
12361
+ name: "check_compatibility",
12362
+ description: "Report whether the @godxjp/ui version installed in the target project matches THIS catalog. This MCP describes exactly one release train (see the `godx-ui://compatibility` resource); if the consumer runs a different @godxjp/ui minor, the props / tokens / patterns it returns may describe a build they never installed (issue #140). Pass the installed version (from the app's `node_modules/@godxjp/ui/package.json` or `npm ls @godxjp/ui`) to get an actionable match / mismatch verdict. Call it at the START of a consumer session before trusting catalog output.",
12363
+ inputSchema: {
12364
+ type: "object",
12365
+ properties: {
12366
+ version: {
12367
+ type: "string",
12368
+ description: "Installed @godxjp/ui version in the target project, e.g. '16.10.0'. Omit to just read the catalog's own version + compatible range."
12369
+ }
12370
+ }
12371
+ }
12372
+ },
11748
12373
  // ── TASK ROUTING (smallest response — pointer) ─────────────────
11749
12374
  {
11750
12375
  name: "route_task",
@@ -11827,6 +12452,8 @@ async function dispatchTool(name, args) {
11827
12452
  return routeTaskTool(String(args.task ?? ""), { consumerOnly: true });
11828
12453
  case "draft_bug_report":
11829
12454
  return draftBugReport(args);
12455
+ case "check_compatibility":
12456
+ return checkCompatibility(args.version == null ? void 0 : String(args.version));
11830
12457
  // Task routing
11831
12458
  case "route_task":
11832
12459
  return routeTaskTool(String(args.task ?? ""));
@@ -11983,6 +12610,43 @@ ${cmd}
11983
12610
  `;
11984
12611
  return out;
11985
12612
  }
12613
+ function checkCompatibility(installed) {
12614
+ const catalog = package_default.version;
12615
+ const range = package_default.godxUiCompatibility;
12616
+ const header = `# @godxjp/ui compatibility
12617
+
12618
+ - MCP / catalog version (serverInfo.version): **${catalog}**
12619
+ - Compatible @godxjp/ui range: **${range ?? "(unset)"}**
12620
+ `;
12621
+ const v = installed?.trim();
12622
+ if (!v) {
12623
+ return header + `
12624
+ Provide the installed version \u2014 \`check_compatibility version="16.10.0"\` \u2014 to get a match/mismatch verdict. Read it from the target project's \`node_modules/@godxjp/ui/package.json\` or \`npm ls @godxjp/ui\`.
12625
+ `;
12626
+ }
12627
+ const parse = (s) => {
12628
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(s);
12629
+ return m ? { major: m[1], minor: m[2] } : null;
12630
+ };
12631
+ const rangeMinor = range ? /^(\d+)\.(\d+)\.x$/.exec(range) : null;
12632
+ const iv = parse(v);
12633
+ if (!iv || range && !rangeMinor) {
12634
+ return header + `
12635
+ \u26A0\uFE0F Could not compare \u2014 installed="${v}" is not a plain x.y.z version, or the catalog range is malformed. Verify with \`npm ls @godxjp/ui\`.
12636
+ `;
12637
+ }
12638
+ const matches = rangeMinor ? iv.major === rangeMinor[1] && iv.minor === rangeMinor[2] : v === catalog;
12639
+ if (matches) {
12640
+ return header + `
12641
+ \u2705 In lockstep \u2014 installed @godxjp/ui@${v} is described by this catalog. Prop / token / pattern guidance is safe to trust.
12642
+ `;
12643
+ }
12644
+ return header + `
12645
+ \u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${v}, but this catalog describes ${range ?? catalog}. Props, defaults, tokens, and patterns it returns may not match the installed build. Align them before trusting output:
12646
+ - upgrade the app: \`npm i @godxjp/ui@${catalog}\`, or
12647
+ - point your agent at the matching MCP release: \`@godxjp/ui-mcp@${iv.major}.${iv.minor}\` (same minor as the app).
12648
+ `;
12649
+ }
11986
12650
  function listPrimitives(group) {
11987
12651
  const list = group ? componentsByGroup(group) : COMPONENTS;
11988
12652
  if (list.length === 0) return `No components${group ? ` in group "${group}"` : ""}.`;
@@ -12633,6 +13297,12 @@ Note: heuristic only \u2014 not a substitute for the full CI gate.
12633
13297
  // src/resources/registry.ts
12634
13298
  var RULE_COUNT = CARDINAL_RULES.length;
12635
13299
  var RESOURCE_DEFINITIONS = [
13300
+ {
13301
+ uri: "godx-ui://compatibility",
13302
+ name: "Package compatibility",
13303
+ description: "MCP package/server version and the compatible @godxjp/ui release range.",
13304
+ mimeType: "application/json"
13305
+ },
12636
13306
  {
12637
13307
  uri: "godx-ui://components",
12638
13308
  name: "All components",
@@ -12665,6 +13335,18 @@ var RESOURCE_DEFINITIONS = [
12665
13335
  }
12666
13336
  ];
12667
13337
  async function readResource(uri) {
13338
+ if (uri === "godx-ui://compatibility") {
13339
+ return JSON.stringify(
13340
+ {
13341
+ mcpVersion: package_default.version,
13342
+ serverVersion: package_default.version,
13343
+ compatibleUi: package_default.godxUiCompatibility,
13344
+ policy: "UI and MCP are released from the same source commit and minor release train."
13345
+ },
13346
+ null,
13347
+ 2
13348
+ );
13349
+ }
12668
13350
  if (uri === "godx-ui://components") {
12669
13351
  return JSON.stringify(COMPONENTS, null, 2);
12670
13352
  }
@@ -12758,64 +13440,6 @@ ${c.example}
12758
13440
  return out;
12759
13441
  }
12760
13442
 
12761
- // package.json
12762
- var package_default = {
12763
- name: "@godxjp/ui-mcp",
12764
- version: "16.7.2",
12765
- description: "Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",
12766
- type: "module",
12767
- main: "./dist/index.js",
12768
- module: "./dist/index.js",
12769
- types: "./dist/index.d.ts",
12770
- bin: {
12771
- "godx-ui-mcp": "./dist/index.js"
12772
- },
12773
- files: [
12774
- "dist",
12775
- "README.md"
12776
- ],
12777
- publishConfig: {
12778
- registry: "https://registry.npmjs.org/",
12779
- access: "public"
12780
- },
12781
- repository: {
12782
- type: "git",
12783
- url: "git+https://github.com/godx-jp/godxjp-ui.git",
12784
- directory: "mcp"
12785
- },
12786
- homepage: "https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",
12787
- license: "Apache-2.0",
12788
- scripts: {
12789
- build: "tsup",
12790
- dev: "tsup --watch",
12791
- start: "node dist/index.js",
12792
- inspect: "npx @modelcontextprotocol/inspector node dist/index.js",
12793
- "type-check": "tsc --noEmit",
12794
- test: "vitest run",
12795
- prepublishOnly: "npm run build"
12796
- },
12797
- dependencies: {
12798
- "@modelcontextprotocol/sdk": "^1.29.0",
12799
- zod: "^4.4.3"
12800
- },
12801
- devDependencies: {
12802
- "@types/node": "^22.10.0",
12803
- tsup: "^8.5.1",
12804
- typescript: "^6.0.3",
12805
- vitest: "^4.1.6"
12806
- },
12807
- keywords: [
12808
- "mcp",
12809
- "model-context-protocol",
12810
- "godxjp",
12811
- "ui",
12812
- "design-system",
12813
- "react",
12814
- "claude",
12815
- "cursor"
12816
- ]
12817
- };
12818
-
12819
13443
  // src/index.ts
12820
13444
  async function main() {
12821
13445
  const server = new Server(