@godxjp/ui-mcp 19.4.0 → 19.4.2
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 +12 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as Le}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Me}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as qe,ReadResourceRequestSchema as Ue}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"status",type:"ReactNode",description:'Status/meta band beside the title (StatusBadge, environment tag, "updated \u2026" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract \u2014 never hand-lay a badge next to the <h1>.'},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"toolbar",type:"ReactNode",description:'FIXED chrome band between the page header and the body \u2014 a filter strip, a status bar, a "channel workflow" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant="flush" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted \u2014 no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.'},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"children",type:"ReactNode",description:'The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in \u2014 do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction="col" gap> or <ResponsiveGrid>.'},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"headerScale",type:'"document" | "chrome"',defaultValue:'"document"',description:"What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture \u2014 a chat channel name, a mail subject line, an IDE tab \u2014 naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way \u2014 this moves the type step only, never the element, so the screen-reader outline is untouched."},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it).'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"headerLoading",type:"boolean",defaultValue:"false",description:"Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live \u2014 they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.","CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` \u2014 there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.","DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.","DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content \u2014 a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it \u2014 the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.","DO: Know the `toolbar` band draws NO bottom rule by default \u2014 `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.","DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground \u2014 `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).","DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached \u2014 a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it \u2014 a strip padded at the call site pads only the strip, not the band.","DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame \u2014 a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.","DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>\u2026</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` \u2014 three theme declarations, zero call-site classes.","DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title \u2014 a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header \u2014 ghost drops the header's bottom pad and lets no divider inherit in \u2014 and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin \u2014 four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.","DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) \u2014 the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row \u2014 that aligns one call site's box while every other chrome page keeps the document's top-packed row.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.","DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DO: Use `subtitle` (not `description`) and `extra` (not `actions`) \u2014 those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page \u2014 the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.","A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.","A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title \u2014 `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.","A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript \u2014 `toolbar={<Toolbar>\u2026</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.","SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
2
|
+
import{Server as Le}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Me}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as qe,ReadResourceRequestSchema as Ve}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"status",type:"ReactNode",description:'Status/meta band beside the title (StatusBadge, environment tag, "updated \u2026" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract \u2014 never hand-lay a badge next to the <h1>.'},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"toolbar",type:"ReactNode",description:'FIXED chrome band between the page header and the body \u2014 a filter strip, a status bar, a "channel workflow" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant="flush" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted \u2014 no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.'},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{name:"children",type:"ReactNode",description:'The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in \u2014 do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction="col" gap> or <ResponsiveGrid>.'},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"headerScale",type:'"document" | "chrome"',defaultValue:'"document"',description:"What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture \u2014 a chat channel name, a mail subject line, an IDE tab \u2014 naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way \u2014 this moves the type step only, never the element, so the screen-reader outline is untouched."},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it).'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"headerLoading",type:"boolean",defaultValue:"false",description:"Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live \u2014 they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.","CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` \u2014 there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.","DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.","DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content \u2014 a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it \u2014 the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.","DO: Know the `toolbar` band draws NO bottom rule by default \u2014 `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.","DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground \u2014 `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).","DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached \u2014 a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it \u2014 a strip padded at the call site pads only the strip, not the band.","DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame \u2014 a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.","DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>\u2026</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` \u2014 three theme declarations, zero call-site classes.","DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title \u2014 a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header \u2014 ghost drops the header's bottom pad and lets no divider inherit in \u2014 and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin \u2014 four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.","DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) \u2014 the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row \u2014 that aligns one call site's box while every other chrome page keeps the document's top-packed row.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.","DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DO: Use `subtitle` (not `description`) and `extra` (not `actions`) \u2014 those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page \u2014 the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' className='w-full'><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.","A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.","A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title \u2014 `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.","A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript \u2014 `toolbar={<Toolbar>\u2026</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.","SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
|
|
3
3
|
import { Button } from "@godxjp/ui/general";
|
|
4
4
|
|
|
5
5
|
export default function OrdersPage() {
|
|
@@ -22,7 +22,7 @@ import { Button } from "@godxjp/ui/general";
|
|
|
22
22
|
<Button variant="outline">\u30EA\u30BB\u30C3\u30C8</Button>
|
|
23
23
|
<Button>\u9069\u7528</Button>
|
|
24
24
|
</Flex>
|
|
25
|
-
</Flex>`,storyPath:"layout/Flex.stories.tsx",rules:[2,40]},{name:"ResponsiveGrid",group:"layout",tagline:"
|
|
25
|
+
</Flex>`,storyPath:"layout/Flex.stories.tsx",rules:[2,40]},{name:"ResponsiveGrid",group:"layout",tagline:"Container-responsive card grid with configurable base, sm, md and lg columns.",props:[{name:"columns",type:"number | { base?: number; sm?: number; md?: number; lg?: number }",defaultValue:"4",description:"Numeric desktop target, or container column counts. Base defaults to 1; omitted object steps inherit upward. Use { base: 2, sm: 4 } for compact metric tiles."},{name:"gap",type:'"none" | "xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:'Token gap between cells, the same steps as Flex. "none" is a DELIBERATE zero for tiles that must read as one continuous surface (a segmented bar, a seamless tile strip) \u2014 not a way to opt out of the token scale.'},{name:"children",type:"ReactNode",required:!0,description:"Grid items \u2014 typically Card or StatCard."}],usage:["DO place StatCard tiles directly as immediate children \u2014 StatCard IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><StatCard .../><StatCard .../></ResponsiveGrid>.","DO use columns={2|3|4} to declare the target desktop column count \u2014 the grid collapses automatically to 1 column on narrow containers (mobile-first via CSS container queries), via 2-column intermediate at \u2265640px, then full target count at \u22651024px. Use columns={{ base: 2, sm: 4 }} for two mobile columns and four wider-container columns; no consumer CSS is needed.","DO NOT place a DataTable inside a ResponsiveGrid column beside a card or chart. DataTable must occupy its own full-width row in a Card with CardContent flush. Nesting a multi-column table in a grid column squeezes CJK text to one character per line (see rule 37).","DO use ResponsiveGrid for page-level spacing \u2014 it applies the correct gap token (--space-stack-md) automatically. Never add raw gap-* / p-* / space-* utilities to the page layout around tiles; compose spacing through this component instead (rule 40).","DO render SkeletonStat children in place of StatCard tiles while KPIs are loading \u2014 same columns prop, same count as the real tiles. Switch to real StatCard once data resolves.","The grid uses CSS container queries, not viewport media queries \u2014 it responds to its containing block width, not the window. Ensure the container is not artificially constrained (e.g. inside a narrow SplitPane column) or column expansion will never trigger."],useCases:["Dashboard KPI row: rendering 3\u20134 StatCard tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.","Summary header above a list page: a 2-column grid of two StatCard totals (e.g. total payable vs total paid) sitting above a Toolbar and DataTable.","Accounting period overview: 4 StatCard tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.","Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonStat /> placeholders rendered while async data is in flight, swapped for real StatCard tiles once resolved.","Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not StatCard) showing categorized read-only data groups before a detail form below.","Entity comparison panel: a columns={3} grid comparing three legal entities side-by-side with a Card+CardContent per entity, which collapses to 2-up on tablet and stacks on mobile."],related:["Flex \u2014 use Flex (direction col or row) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.","SplitPane \u2014 use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.","StatCard \u2014 the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.","SkeletonStat \u2014 the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."],example:`import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
26
26
|
import { StatCard } from "@godxjp/ui/data-display";
|
|
27
27
|
|
|
28
28
|
<ResponsiveGrid columns={4}>
|
|
@@ -277,7 +277,7 @@ import { PanelLeftClose, Search } from "lucide-react";
|
|
|
277
277
|
}
|
|
278
278
|
>
|
|
279
279
|
{children}
|
|
280
|
-
</AppShell>`,storyPath:"layout/Topbar.stories.tsx",rules:[2,3,5,6]},{name:"MasterDetail",group:"layout",tagline:"Responsive master-detail composition: a fluid list beside a token-owned 300px/320px fixed rail, with a themeable collapse threshold. `rail` picks which region is fixed \u2014 default `detail` (the canonical 1fr/320px list + detail rail); `master` for a leading navigator rail.",props:[{name:"master",type:"ReactNode",required:!0,description:"Selectable collection. Always FIRST in DOM order, so the stacked (mobile) order is list-then-detail whichever region owns the rail."},{name:"children",type:"ReactNode",required:!0,description:"Detail surface for the current master selection."},{name:"rail",type:'"master" | "detail"',defaultValue:'"detail"',description:"`master` = leading category/navigator rail beside a fluid detail surface."},{name:"railWidth",type:'"compact" | "standard"',defaultValue:'"standard"',description:"Token-owned rail preset: compact=300px (--master-detail-rail-compact), standard=320px (--master-detail-rail-standard)."},{name:"masterViewport",type:'"auto" | "compact" | "standard"',defaultValue:'"auto"',description:"Bound the master collection to a scrollable viewport. `auto` (default) never bounds it \u2014 the region grows with its content. `compact` (--master-detail-master-viewport-compact, 20rem) / `standard` (28rem) cap its block size and scroll the collection INSIDE the region, and make that region a keyboard-reachable scroll container (tabIndex 0, --master-detail-master-viewport-inset reserves focus-ring room). No raw pixel prop exists \u2014 retune the tokens in a service theme."},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl" | false',description:"Per-instance stacking threshold (sm=40rem, md=48rem, lg=64rem, xl=80rem; false never stacks). OMIT it to inherit the themeable --master-detail-collapse-below token (default 40rem) \u2014 that is the global knob a service theme retunes once."},{name:"masterLabel",type:"string",description:"Accessible name for the master region landmark."},{name:"detailLabel",type:"string",description:"Accessible name for the detail region landmark."},{name:"detailId",type:"string",description:"Id of the detail region so the selection controls inside `master` can point at it with aria-controls, and the app can move focus to it after a selection (the region carries tabIndex={-1} for exactly this)."}],usage:['DO use MasterDetail for team, member, service or settings screens where a selectable collection drives a detail surface. Default `rail="detail"` gives the canonical fluid list + 320px detail rail; pass `rail="master"` for a leading navigator rail.',"DO keep selection and keyboard semantics on the controls inside `master` (`aria-pressed`, roving focus, or listbox semantics as appropriate) \u2014 only the caller knows which APG pattern applies; MasterDetail preserves whatever you render.","DO wire the two together: give the master controls `aria-controls={detailId}`, and move focus to `document.getElementById(detailId)` when a selection replaces the detail (the region is `tabIndex={-1}` so that focus call works).","DO choose `compact` for the 300px rail and `standard` for 320px. Never reproduce these tracks with consumer CSS.","DO provide `masterLabel` and `detailLabel` when the surrounding headings do not already identify both regions.","DON'T use ResponsiveGrid for master-detail hierarchy; its equal tracks cannot represent this composition.",'DO set `masterViewport="compact"` (or `"standard"`) whenever the collection is a REAL, unbounded list. Left at `auto` a 200-row list renders ~3,700px tall, and once the layout stacks the detail lands thousands of pixels below the fold; bounded, the collection scrolls inside the rail and the detail stays near the top of the screen. Pass `masterLabel` too, so the scroll region is announced.',"DON'T reproduce that bound with a consumer `max-height`/`overflow` rule or a pixel prop \u2014 there is none by design. Retune `--master-detail-master-viewport-compact` / `-standard` in the service theme, and `--master-detail-master-viewport-inset` if the collection's focus ring needs more room.","The collapse threshold is a real token: below `--master-detail-collapse-below` (default 40rem, measured against the COMPOSITION's own inline size, never the viewport) master stacks above detail. A theme retunes it globally, `collapseBelow` overrides it per instance. It is implemented as a flex-basis threshold rather than a media query precisely because a query CONDITION cannot read a var().","Measured geometry with the defaults: 1440 \u2192 fluid master + 320px rail; 1024 \u2192 fluid master + 320px rail; 390 \u2192 stacked full-width master then detail. Same JSX at every width \u2014 no consumer-local CSS, grid tracks or per-screen spacing."],useCases:["Teams screen: the team DataTable stays fluid while the selected team's detail keeps the 320px rail.",'Organization service access: services in the compact leading rail (`rail="master"`) and selected service roles in the fluid detail surface.',"Settings navigator: categories in the leading rail and the selected configuration form in the detail surface."],related:["SplitPane \u2014 a main surface plus a COMPLEMENTARY aside (its own content), not a selection-driven detail; use MasterDetail when the trailing pane is the detail OF the selection.","ResponsiveGrid \u2014 use for equal-width independent cards, never for a master-detail relationship.","PageContainer \u2014 outer page scaffold that supplies page insets and title hierarchy around MasterDetail."],example:`import { MasterDetail } from "@godxjp/ui/layout";
|
|
280
|
+
</AppShell>`,storyPath:"layout/Topbar.stories.tsx",rules:[2,3,5,6]},{name:"MasterDetail",group:"layout",tagline:"Responsive master-detail composition: a fluid list beside a token-owned 300px/320px fixed rail, with a themeable collapse threshold. `rail` picks which region is fixed \u2014 default `detail` (the canonical 1fr/320px list + detail rail); `master` for a leading navigator rail.",props:[{name:"mobilePane",type:'"master" | "detail"',description:"One-pane navigation below the token-owned collapse threshold; omit to retain stacking. The consumer owns URL/history selection."},{name:"detailBack",type:"ReactNode",description:"Back link shown above detail in mobile navigation mode."},{name:"master",type:"ReactNode",required:!0,description:"Selectable collection. Always FIRST in DOM order, so the stacked (mobile) order is list-then-detail whichever region owns the rail."},{name:"children",type:"ReactNode",required:!0,description:"Detail surface for the current master selection."},{name:"rail",type:'"master" | "detail"',defaultValue:'"detail"',description:"`master` = leading category/navigator rail beside a fluid detail surface."},{name:"railWidth",type:'"compact" | "standard"',defaultValue:'"standard"',description:"Token-owned rail preset: compact=300px (--master-detail-rail-compact), standard=320px (--master-detail-rail-standard)."},{name:"masterViewport",type:'"auto" | "compact" | "standard"',defaultValue:'"auto"',description:"Bound the master collection to a scrollable viewport. `auto` (default) never bounds it \u2014 the region grows with its content. `compact` (--master-detail-master-viewport-compact, 20rem) / `standard` (28rem) cap its block size and scroll the collection INSIDE the region, and make that region a keyboard-reachable scroll container (tabIndex 0, --master-detail-master-viewport-inset reserves focus-ring room). No raw pixel prop exists \u2014 retune the tokens in a service theme."},{name:"collapseBelow",type:'"sm" | "md" | "lg" | "xl" | false',description:"Per-instance stacking threshold (sm=40rem, md=48rem, lg=64rem, xl=80rem; false never stacks). OMIT it to inherit the themeable --master-detail-collapse-below token (default 40rem) \u2014 that is the global knob a service theme retunes once."},{name:"masterLabel",type:"string",description:"Accessible name for the master region landmark."},{name:"detailLabel",type:"string",description:"Accessible name for the detail region landmark."},{name:"detailId",type:"string",description:"Id of the detail region so the selection controls inside `master` can point at it with aria-controls, and the app can move focus to it after a selection (the region carries tabIndex={-1} for exactly this)."}],usage:['DO use MasterDetail for team, member, service or settings screens where a selectable collection drives a detail surface. Default `rail="detail"` gives the canonical fluid list + 320px detail rail; pass `rail="master"` for a leading navigator rail.',"DO keep selection and keyboard semantics on the controls inside `master` (`aria-pressed`, roving focus, or listbox semantics as appropriate) \u2014 only the caller knows which APG pattern applies; MasterDetail preserves whatever you render.","DO wire the two together: give the master controls `aria-controls={detailId}`, and move focus to `document.getElementById(detailId)` when a selection replaces the detail (the region is `tabIndex={-1}` so that focus call works).","DO choose `compact` for the 300px rail and `standard` for 320px. Never reproduce these tracks with consumer CSS.","DO provide `masterLabel` and `detailLabel` when the surrounding headings do not already identify both regions.","DON'T use ResponsiveGrid for master-detail hierarchy; its equal tracks cannot represent this composition.",'DO set `masterViewport="compact"` (or `"standard"`) whenever the collection is a REAL, unbounded list. Left at `auto` a 200-row list renders ~3,700px tall, and once the layout stacks the detail lands thousands of pixels below the fold; bounded, the collection scrolls inside the rail and the detail stays near the top of the screen. Pass `masterLabel` too, so the scroll region is announced.',"DON'T reproduce that bound with a consumer `max-height`/`overflow` rule or a pixel prop \u2014 there is none by design. Retune `--master-detail-master-viewport-compact` / `-standard` in the service theme, and `--master-detail-master-viewport-inset` if the collection's focus ring needs more room.","The collapse threshold is a real token: below `--master-detail-collapse-below` (default 40rem, measured against the COMPOSITION's own inline size, never the viewport) master stacks above detail. A theme retunes it globally, `collapseBelow` overrides it per instance. It is implemented as a flex-basis threshold rather than a media query precisely because a query CONDITION cannot read a var().","Measured geometry with the defaults: 1440 \u2192 fluid master + 320px rail; 1024 \u2192 fluid master + 320px rail; 390 \u2192 stacked full-width master then detail. Same JSX at every width \u2014 no consumer-local CSS, grid tracks or per-screen spacing."],useCases:["Teams screen: the team DataTable stays fluid while the selected team's detail keeps the 320px rail.",'Organization service access: services in the compact leading rail (`rail="master"`) and selected service roles in the fluid detail surface.',"Settings navigator: categories in the leading rail and the selected configuration form in the detail surface."],related:["SplitPane \u2014 a main surface plus a COMPLEMENTARY aside (its own content), not a selection-driven detail; use MasterDetail when the trailing pane is the detail OF the selection.","ResponsiveGrid \u2014 use for equal-width independent cards, never for a master-detail relationship.","PageContainer \u2014 outer page scaffold that supplies page insets and title hierarchy around MasterDetail."],example:`import { MasterDetail } from "@godxjp/ui/layout";
|
|
281
281
|
|
|
282
282
|
// Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).
|
|
283
283
|
<MasterDetail
|
|
@@ -386,7 +386,7 @@ export function TermsPage() {
|
|
|
386
386
|
{ label: "\u30DB\u30FC\u30E0", to: "/" },
|
|
387
387
|
{ label: "\u4F1A\u54E1\u7BA1\u7406", to: "/members" },
|
|
388
388
|
{ label: "\u7530\u4E2D \u592A\u90CE" },
|
|
389
|
-
]} />`,storyPath:"layout/Breadcrumb.stories.tsx",rules:[]},{name:"Button",group:"general",tagline:"Core button with variant + size presets, built on cva and Radix Slot (asChild).",props:[{name:"variant",type:'"default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link"',defaultValue:'"default"',description:"Visual style. `dashed` = outline with a dashed border (Ant-style add-row / placeholder action)."},{name:"size",type:'"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg"',defaultValue:'"default"',description:"Size preset (height, padding, icon dims)."},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (control radius), `pill` (fully rounded, --radius-pill), `sharp` (square, --radius-sharp). Use the prop instead of a `rounded-*` className."},{name:"fullWidth",type:"boolean",defaultValue:"false",description:"Span the full container width (`width:100%`) instead of sizing to content."},{name:"asChild",type:"boolean",defaultValue:"false",description:"Render as Radix Slot \u2014 merge props onto the child (<a>/<Link>)."},{name:"disabled",type:"boolean",description:"Disable the button."},{name:"loading",type:"boolean",defaultValue:"false",description:'In-flight state \u2014 shows a leading `Loader2` spinner (replaces a leading icon if present), sets `aria-busy="true"`, and blocks activation (non-interactive, pointer-events disabled) while keeping the label visible so the width doesn\'t jump. Prefer this over a hand-rolled `<Loader2 className="animate-spin">` inside the button. Ignored when `asChild` (Slot requires a single child).'},{name:"loadingText",type:"string",description:"Optional label to swap in while `loading` (pass the `t()`-translated string, e.g. `loadingText={t('saving')}`). When omitted the original children stay beside the spinner."},{name:"onClick",type:"React.MouseEventHandler<HTMLButtonElement>",description:"Click handler. Does not fire while `loading` or `disabled`."},{name:"count",type:"number",description:'Trailing borderless counter pill after the label (filter tabs / segmented toggles, e.g. "Ch\u1EDD bay 18"). Localized via `Intl.NumberFormat`; styled per variant \u2014 never nest a `Badge` in a Button for this. Ignored when `asChild`.'},{name:"overflowCount",type:"number",defaultValue:"99",description:"Cap for `count` (Ant Badge parity) \u2014 when `count` exceeds it the pill renders `{overflowCount}+` (e.g. `99+`)."},{name:"showZero",type:"boolean",defaultValue:"true",description:"Whether the pill renders when `count` is 0 (Ant Badge parity). Pass `false` to hide the pill at zero."}],usage:["DO pick the right variant for intent: `default` (primary CTA, one per section), `destructive` (irreversible actions like delete/revoke), `outline` (secondary actions alongside a primary), `secondary` (less prominent actions), `ghost` (toolbar icon-only actions), `link` (inline text-style navigation without an underline by default).","DO use icon-only sizes (`icon`, `icon-xs`, `icon-sm`, `icon-lg`) exclusively for buttons that contain only an SVG \u2014 these sizes set equal width/height. For text+icon buttons use `default|sm|lg|xs` sizes; icons inside are auto-sized to 1rem via `[&_svg:not([class*='size-'])]:size-4`.","DO use `asChild` to render the button as a React Router/Inertia `<Link>` or native `<a>` while keeping all button styling and a11y: `<Button asChild variant=\"outline\"><Link href={route('invoices.show', id)}>\u8A73\u7D30</Link></Button>`. Never wrap a `<button>` around an `<a>` \u2014 that is invalid HTML.","DON'T use raw `<button>` elements anywhere in the UI \u2014 always use this `Button`. The only exception is an `aria-hidden` native control used as an e2e/a11y hook paired with a visible godx-ui control.",'DO set `type="submit"` explicitly on form submit buttons (the default HTML button type inside `<form>` is already `submit`, but being explicit prevents accidental double-submissions when a `type="button"` sibling exists). For cancel/reset actions set `type="button"` to avoid accidental form submission.',"DON'T apply raw padding, height, or `rounded-*` overrides to `Button` via `className` \u2014 the size variants encode the full box model. If a custom size is truly needed, use `buttonVariants` from `@godxjp/ui/general` to compose a new cva class rather than fighting the existing ones.","DO use the `loading` prop for async/pending actions instead of hand-rolling `<Loader2 className=\"animate-spin\">` inside the button \u2014 `loading` renders the spinner, sets `aria-busy`, and blocks activation for you; pair with `loadingText={t('saving')}` to swap the label. For a TanStack Query refetch use `ButtonRefetch` (it owns its own loading lifecycle) rather than wiring `loading` manually."],useCases:['Primary form submission in a Dialog or Sheet (e.g. `<Button type="submit" disabled={form.processing}>\u4FDD\u5B58</Button>`) \u2014 the `disabled` prop greys it out and blocks pointer events, preventing double-submit during async operations.','Destructive confirmation inside a Dialog \u2014 pair `tone="destructive"` Button as the confirm action and `variant="outline"` as Cancel; never use `variant="default"` for a delete action.','Icon-only toolbar actions in a DataTable column (edit, delete, copy) using `size="icon-sm"` + `variant="ghost"` + a Lucide icon child \u2014 gives equal-width square targets that don\'t distort the row.',"Navigation links styled as buttons (e.g. 'New Invoice', 'Back to list') using `asChild` + Inertia `<Link>` \u2014 preserves SPA navigation while using the button's visual treatment.","Async mutation trigger in an accounting workflow (e.g. 'Sync from MF', 'Export CSV') \u2014 disable on processing state; pair with `AlertMutationFeedback` for error/retry UI rather than inline `try/catch` alerts.","Refetch / retry trigger when NOT using TanStack Query \u2014 for manual cache refresh inside a TanStack Query context use `ButtonRefetch` instead, which owns its own `disabled`/`onClick` lifecycle."],related:["DropdownMenu \u2014 when a button needs to reveal a list of actions (e.g. 'Actions \u25BE' in a DataTable row), wrap the Button as a `DropdownMenuTrigger` inside a `DropdownMenu` compound; don't open a Sheet/Dialog just to show a list of options.","ButtonRefetch \u2014 a pre-wired Button variant from `@godxjp/ui/query` that binds directly to a TanStack Query result (shows spinner, auto-disables while fetching, retries on click). Use it instead of a raw Button whenever the action is a query refetch \u2014 do not pass `onClick`/`disabled` to it manually.","AlertMutationFeedback \u2014 for surfacing mutation errors and a retry action; it renders its own retry Button internally. Do not add a separate Button alongside AlertMutationFeedback for the same mutation.","PrefetchLink \u2014 use when the goal is purely navigation with hover-prefetch (Inertia v3 prefetch); it renders as an `<a>` not a button. Only reach for `Button asChild + Link` when the navigation control must look like a button (primary CTA style)."],example:`import { Button } from "@godxjp/ui/general";
|
|
389
|
+
]} />`,storyPath:"layout/Breadcrumb.stories.tsx",rules:[]},{name:"Button",group:"general",tagline:"Core button with variant + size presets, built on cva and Radix Slot (asChild).",props:[{name:"variant",type:'"default" | "destructive" | "outline" | "dashed" | "secondary" | "ghost" | "link"',defaultValue:'"default"',description:"Visual style. `dashed` = outline with a dashed border (Ant-style add-row / placeholder action)."},{name:"size",type:'"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg"',defaultValue:'"default"',description:"Size preset (height, padding, icon dims)."},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (control radius), `pill` (fully rounded, --radius-pill), `sharp` (square, --radius-sharp). Use the prop instead of a `rounded-*` className."},{name:"fullWidth",type:"boolean",defaultValue:"false",description:"Span the full container width (`width:100%`) instead of sizing to content."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allow multi-line text labels to grow the button height. Uses the active size tier as its minimum height; use with fullWidth in narrow collections."},{name:"align",type:'"start" | "center" | "end"',defaultValue:'"center"',description:"Logical content alignment. Use start for full-width collection actions; respects RTL."},{name:"asChild",type:"boolean",defaultValue:"false",description:"Render as Radix Slot \u2014 merge props onto the child (<a>/<Link>)."},{name:"disabled",type:"boolean",description:"Disable the button."},{name:"loading",type:"boolean",defaultValue:"false",description:'In-flight state \u2014 shows a leading `Loader2` spinner (replaces a leading icon if present), sets `aria-busy="true"`, and blocks activation (non-interactive, pointer-events disabled) while keeping the label visible so the width doesn\'t jump. Prefer this over a hand-rolled `<Loader2 className="animate-spin">` inside the button. Ignored when `asChild` (Slot requires a single child).'},{name:"loadingText",type:"string",description:"Optional label to swap in while `loading` (pass the `t()`-translated string, e.g. `loadingText={t('saving')}`). When omitted the original children stay beside the spinner."},{name:"onClick",type:"React.MouseEventHandler<HTMLButtonElement>",description:"Click handler. Does not fire while `loading` or `disabled`."},{name:"count",type:"number",description:'Trailing borderless counter pill after the label (filter tabs / segmented toggles, e.g. "Ch\u1EDD bay 18"). Localized via `Intl.NumberFormat`; styled per variant \u2014 never nest a `Badge` in a Button for this. Ignored when `asChild`.'},{name:"overflowCount",type:"number",defaultValue:"99",description:"Cap for `count` (Ant Badge parity) \u2014 when `count` exceeds it the pill renders `{overflowCount}+` (e.g. `99+`)."},{name:"showZero",type:"boolean",defaultValue:"true",description:"Whether the pill renders when `count` is 0 (Ant Badge parity). Pass `false` to hide the pill at zero."}],usage:["DO pick the right variant for intent: `default` (primary CTA, one per section), `destructive` (irreversible actions like delete/revoke), `outline` (secondary actions alongside a primary), `secondary` (less prominent actions), `ghost` (toolbar icon-only actions), `link` (inline text-style navigation without an underline by default).","DO use icon-only sizes (`icon`, `icon-xs`, `icon-sm`, `icon-lg`) exclusively for buttons that contain only an SVG \u2014 these sizes set equal width/height. For text+icon buttons use `default|sm|lg|xs` sizes; icons inside are auto-sized to 1rem via `[&_svg:not([class*='size-'])]:size-4`.","DO use `asChild` to render the button as a React Router/Inertia `<Link>` or native `<a>` while keeping all button styling and a11y: `<Button asChild variant=\"outline\"><Link href={route('invoices.show', id)}>\u8A73\u7D30</Link></Button>`. Never wrap a `<button>` around an `<a>` \u2014 that is invalid HTML.","DON'T use raw `<button>` elements anywhere in the UI \u2014 always use this `Button`. The only exception is an `aria-hidden` native control used as an e2e/a11y hook paired with a visible godx-ui control.",'DO set `type="submit"` explicitly on form submit buttons (the default HTML button type inside `<form>` is already `submit`, but being explicit prevents accidental double-submissions when a `type="button"` sibling exists). For cancel/reset actions set `type="button"` to avoid accidental form submission.',"DON'T apply raw padding, height, or `rounded-*` overrides to `Button` via `className` \u2014 the size variants encode the full box model. If a custom size is truly needed, use `buttonVariants` from `@godxjp/ui/general` to compose a new cva class rather than fighting the existing ones.","DO use the `loading` prop for async/pending actions instead of hand-rolling `<Loader2 className=\"animate-spin\">` inside the button \u2014 `loading` renders the spinner, sets `aria-busy`, and blocks activation for you; pair with `loadingText={t('saving')}` to swap the label. For a TanStack Query refetch use `ButtonRefetch` (it owns its own loading lifecycle) rather than wiring `loading` manually."],useCases:['Primary form submission in a Dialog or Sheet (e.g. `<Button type="submit" disabled={form.processing}>\u4FDD\u5B58</Button>`) \u2014 the `disabled` prop greys it out and blocks pointer events, preventing double-submit during async operations.','Destructive confirmation inside a Dialog \u2014 pair `tone="destructive"` Button as the confirm action and `variant="outline"` as Cancel; never use `variant="default"` for a delete action.','Icon-only toolbar actions in a DataTable column (edit, delete, copy) using `size="icon-sm"` + `variant="ghost"` + a Lucide icon child \u2014 gives equal-width square targets that don\'t distort the row.',"Navigation links styled as buttons (e.g. 'New Invoice', 'Back to list') using `asChild` + Inertia `<Link>` \u2014 preserves SPA navigation while using the button's visual treatment.","Async mutation trigger in an accounting workflow (e.g. 'Sync from MF', 'Export CSV') \u2014 disable on processing state; pair with `AlertMutationFeedback` for error/retry UI rather than inline `try/catch` alerts.","Refetch / retry trigger when NOT using TanStack Query \u2014 for manual cache refresh inside a TanStack Query context use `ButtonRefetch` instead, which owns its own `disabled`/`onClick` lifecycle."],related:["DropdownMenu \u2014 when a button needs to reveal a list of actions (e.g. 'Actions \u25BE' in a DataTable row), wrap the Button as a `DropdownMenuTrigger` inside a `DropdownMenu` compound; don't open a Sheet/Dialog just to show a list of options.","ButtonRefetch \u2014 a pre-wired Button variant from `@godxjp/ui/query` that binds directly to a TanStack Query result (shows spinner, auto-disables while fetching, retries on click). Use it instead of a raw Button whenever the action is a query refetch \u2014 do not pass `onClick`/`disabled` to it manually.","AlertMutationFeedback \u2014 for surfacing mutation errors and a retry action; it renders its own retry Button internally. Do not add a separate Button alongside AlertMutationFeedback for the same mutation.","PrefetchLink \u2014 use when the goal is purely navigation with hover-prefetch (Inertia v3 prefetch); it renders as an `<a>` not a button. Only reach for `Button asChild + Link` when the navigation control must look like a button (primary CTA style)."],example:`import { Button } from "@godxjp/ui/general";
|
|
390
390
|
import { Trash2 } from "lucide-react";
|
|
391
391
|
|
|
392
392
|
<>
|
|
@@ -560,7 +560,7 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
|
|
|
560
560
|
<Badge variant="secondary">A/B</Badge>
|
|
561
561
|
<Badge status="active">\u516C\u958B\u4E2D</Badge>
|
|
562
562
|
<Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</Badge>
|
|
563
|
-
<Badge shape="pill" color="#4488c5">\u51E6\u7406\u4E2D</Badge>`,storyPath:"data-display/Badge.stories.tsx",rules:[35]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:"Render element \u2014 `li` when the parent is a semantic `<ul>`/`<ol>`."},{name:"overflow",type:'"truncate" | "wrap"',defaultValue:'"truncate"',description:"How title/description resolve content longer than the row \u2014 `truncate` (one line + ellipsis) or `wrap` (multi-line; long unbroken tokens break via `overflow-wrap: anywhere`). Either way the content column may shrink below its intrinsic width, so the row never widens the page root."},{name:"density",type:'"default" | "compact"',defaultValue:'"default"',description:"Row geometry. It only lowers thresholds \u2014 the row and its trailing cluster still wrap, so a cluster that cannot fit drops to its own line rather than widening the page root."},{name:"unread",type:"boolean",description:"Read/unread state for notification rows \u2014 renders the indicator dot (with localized `sr-only` text, never colour alone) plus the tokenized `--list-row-unread-background`. OMIT the prop for rows with no read state; pass `false` for a read row so its title keeps the same optical axis as the unread ones."}],usage:["DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",'DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic `<ul>`.','DO use `unread` for a notification list \u2014 the dot is a SHAPE with localized `sr-only` text ("Unread"/"Read"), so it never reads as colour alone, and the row surface reads `--list-row-unread-background` (default `hsl(var(--muted))` \u2014 chosen so the xs muted description line stays WCAG AA on the emphasized surface; `--accent` would drop it to 4.23:1). DON\'T substitute a `Badge` \u2014 that renders a labelled pill, not a compact status dot.','DO pass `density="compact"` for the canonical invitation / history row \u2014 an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (\u2248326px content at 390px), or a history row whose status Badge + ISO-8601 date belongs beside the title. Measured at 390px: 62px tall vs 126px at the default density (where the actions wrapped), history row 41px vs 114px. DON\'T reach for it just to "make things tighter" on a roomy page \u2014 the default density is the entity-row measure.','DON\'T add one-off `min-width`/wrapping CSS in the consumer app for a long title + two trailing Buttons. The row already shrinks and WRAPS: the content column keeps only `min(var(--list-row-body-min-width), 100%)` and the trailing actions drop onto their own line below the threshold. Retune the threshold with `--list-row-body-min-width` (default 12rem) and the action gap with `--list-row-trailing-gap`; pass `overflow="wrap"` (usually with `align="start"`) when the title must stay fully readable at 390px instead of truncating.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",'Notifications inbox \u2014 `unread` rows carry the dot + emphasized surface, `overflow="wrap"` keeps a long JA/EN/VI title and its ISO-8601 timestamp readable, and two inline trailing Buttons (Mark as read / Open) wrap under the text at 390px.',"Pending invitations \u2014 Avatar in leading, the organization/invitation name as title, and Accept + Decline Buttons in trailing that stack at narrow widths without a horizontal page scrollbar."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
563
|
+
<Badge shape="pill" color="#4488c5">\u51E6\u7406\u4E2D</Badge>`,storyPath:"data-display/Badge.stories.tsx",rules:[35]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"asChild",type:"boolean",defaultValue:"false",description:"Supply a link child to make the entire row a native link. Use aria-current=page for the current destination; never nest interactive trailing controls."},{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:"Render element \u2014 `li` when the parent is a semantic `<ul>`/`<ol>`."},{name:"overflow",type:'"truncate" | "wrap"',defaultValue:'"truncate"',description:"How title/description resolve content longer than the row \u2014 `truncate` (one line + ellipsis) or `wrap` (multi-line; long unbroken tokens break via `overflow-wrap: anywhere`). Either way the content column may shrink below its intrinsic width, so the row never widens the page root."},{name:"density",type:'"default" | "compact"',defaultValue:'"default"',description:"Row geometry. It only lowers thresholds \u2014 the row and its trailing cluster still wrap, so a cluster that cannot fit drops to its own line rather than widening the page root."},{name:"unread",type:"boolean",description:"Read/unread state for notification rows \u2014 renders the indicator dot (with localized `sr-only` text, never colour alone) plus the tokenized `--list-row-unread-background`. OMIT the prop for rows with no read state; pass `false` for a read row so its title keeps the same optical axis as the unread ones."}],usage:["DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.",'DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic `<ul>`.','DO use `unread` for a notification list \u2014 the dot is a SHAPE with localized `sr-only` text ("Unread"/"Read"), so it never reads as colour alone, and the row surface reads `--list-row-unread-background` (default `hsl(var(--muted))` \u2014 chosen so the xs muted description line stays WCAG AA on the emphasized surface; `--accent` would drop it to 4.23:1). DON\'T substitute a `Badge` \u2014 that renders a labelled pill, not a compact status dot.','DO pass `density="compact"` for the canonical invitation / history row \u2014 an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (\u2248326px content at 390px), or a history row whose status Badge + ISO-8601 date belongs beside the title. Measured at 390px: 62px tall vs 126px at the default density (where the actions wrapped), history row 41px vs 114px. DON\'T reach for it just to "make things tighter" on a roomy page \u2014 the default density is the entity-row measure.','DON\'T add one-off `min-width`/wrapping CSS in the consumer app for a long title + two trailing Buttons. The row already shrinks and WRAPS: the content column keeps only `min(var(--list-row-body-min-width), 100%)` and the trailing actions drop onto their own line below the threshold. Retune the threshold with `--list-row-body-min-width` (default 12rem) and the action gap with `--list-row-trailing-gap`; pass `overflow="wrap"` (usually with `align="start"`) when the title must stay fully readable at 390px instead of truncating.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",'Notifications inbox \u2014 `unread` rows carry the dot + emphasized surface, `overflow="wrap"` keeps a long JA/EN/VI title and its ISO-8601 timestamp readable, and two inline trailing Buttons (Mark as read / Open) wrap under the text at 390px.',"Pending invitations \u2014 Avatar in leading, the organization/invitation name as title, and Accept + Decline Buttons in trailing that stack at narrow widths without a horizontal page scrollbar."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
564
564
|
import { Button } from "@godxjp/ui/general";
|
|
565
565
|
import { Smartphone } from "lucide-react";
|
|
566
566
|
|
|
@@ -741,7 +741,7 @@ const form = useForm({ customer_nm: "", action_mode: "regist" });
|
|
|
741
741
|
step={1}
|
|
742
742
|
prefix="\xA5"
|
|
743
743
|
aria-label="\u6570\u91CF"
|
|
744
|
-
/>`},{name:"SearchInput",group:"data-entry",tagline:"Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",props:[{name:"onSearch",type:"(q: string) => void",
|
|
744
|
+
/>`},{name:"SearchInput",group:"data-entry",tagline:"Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",props:[{name:"onSearch",type:"(q: string) => void",description:"Called after a changed query settles. Never fires for the initial value on mount or callback-only rerenders. Optional when filtering uses onValueChange."},{name:"value",type:"string",description:"Controlled value."},{name:"defaultValue",type:"string",defaultValue:'""',description:"Initial uncontrolled value."},{name:"placeholder",type:"string",description:"Input placeholder."},{name:"debounce",type:"number",defaultValue:"250",description:"Debounce delay (ms)."},{name:"id",type:"string",description:"Input id; pair with `label` or an external `<label htmlFor>`."},{name:"label",type:"React.ReactNode",description:"Optional visible label rendered above the search box (falls back to an sr-only label)."},{name:"onValueChange",type:"(value: string) => void",description:"Fires on EVERY keystroke (immediate) \u2014 required to keep a controlled `value` responsive."},{name:"ariaLabel",type:"string",description:"Accessible search name when there is no visible label."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disable search input and clearing."}],usage:["DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay \u2014 never wire your filter logic to `onChange` on SearchInput because it does not expose one.","DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` \u2014 omitting `value` puts the component in uncontrolled mode.","DO: supply an `ariaLabel` (or visible `label`) when no adjacent label exists. Without either prop, SearchInput falls back to the i18n key `common.search` rendered as a visually-hidden `<Label>` \u2014 still accessible, but providing a context-specific string (e.g. `ariaLabel='\u8ACB\u6C42\u66F8\u3092\u691C\u7D22'`) is more descriptive for screen readers.","DON'T: use SearchInput inside a `<form>` expecting native form submission. The component has no `name` prop and does not emit a form field value \u2014 it is a filter-trigger widget. For a form search field, use a plain `Input` inside `FormField`.","DON'T: hand-roll a debounced input when you need a search box. SearchInput ships the debounce, clear button (\xD7), search icon, and accessible label \u2014 recreating these with a raw `<Input>` adds code and misses the UX contract.","DON'T: place SearchInput inside a `ToolbarGroup` wrapper \u2014 `ToolbarGroup` is for Select/DatePicker controls with a label chip. SearchInput goes directly as a child of `Toolbar` (or standalone above a table), not wrapped in `ToolbarGroup`."],useCases:["List-page filter bar: placed as the first child of `Toolbar` (before any `ToolbarGroup` children) to drive text-based filtering of a `DataTable`. The `onSearch` callback updates a query param or state variable that the table's data fetch reads.","Inline client-side search over a small in-memory list (e.g. a sidebar nav list, a transfer panel, a settings category list) where results narrow immediately as the user types without a server round-trip \u2014 use uncontrolled mode (`defaultValue`) so no state is needed in the parent.","URL-synced search: controlled mode where `value` comes from `useSearchParams()` and `onSearch` pushes to the URL, enabling deep-linkable, bookmarkable filtered views on invoice/transaction/customer index pages.","Panel or dialog search: filtering a long dropdown list, a tree, or a multi-item selection panel that does not use the built-in `Command` palette \u2014 SearchInput provides the search box while the parent renders the filtered result set.","Toolbar search on a data-heavy accounting page (e.g. journal-entry search, partner lookup in a subledger view) where the 250 ms debounce prevents a flood of API calls on every keystroke without requiring the developer to implement debounce logic."],related:["Input \u2014 use `Input` (inside `FormField`) when the search field is part of a submitted form and needs a `name` attribute, or when you need full `onChange` control without any debounce or clear button. SearchInput is the right pick when the field only triggers filtering, not form submission.","Toolbar \u2014 SearchInput is almost always placed as a direct child of `Toolbar`, which provides the surrounding strip, clear-all button, and active-filter state. Do not use SearchInput as a standalone header widget when a full filter strip (with selects etc.) already exists \u2014 compose them together.","Command \u2014 use `Command` + `CommandInput` when you need a keyboard-navigable command palette or combobox list with grouped items and keyboard selection. `Command` is only meaningful when paired with `CommandList`; SearchInput is the right pick for a plain filter box with no item-selection behavior.","Select (with showSearch) \u2014 when users must pick a value from a list AND search to narrow it, use `<Select options={...} showSearch>` (which has its own built-in search input). SearchInput is for filtering an external data set, not for value selection from an option list."],example:`import { SearchInput } from "@godxjp/ui/data-entry";
|
|
745
745
|
|
|
746
746
|
<SearchInput placeholder="\u30AF\u30FC\u30DD\u30F3\u540D\u30FBID\u3067\u691C\u7D22" value={search} onSearch={setSearch} />`,storyPath:"data-entry/SearchInput.stories.tsx",rules:[23]},{name:"Select",group:"data-entry",tagline:"Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually \u2014 never use a raw <select>.",props:[{name:"options",type:"SearchSelectOptionProp[]",description:"Static option list. Passing this (or loadOptions) switches Select from the compound API to the data-driven API. Each option has { value, label, sublabel?, icon?, group?, disabled? }. `icon` (avatar / flag / lucide node) renders before the label in the rows AND on the trigger once selected. group buckets the option under an optgroup-style heading."},{name:"loadOptions",type:"(params: SearchSelectLoadParamsProp) => Promise<SearchSelectLoadResultProp>",description:"Async remote fetcher. Receives { query, page } (1-based). Must return { options, hasMore? }. Implies showSearch=true automatically."},{name:"showSearch",type:"boolean",defaultValue:"true when loadOptions is set, false otherwise",description:"Toggle the searchable combobox mode (SearchSelect engine) vs a plain Radix listbox. Set to true on a static options list to enable client-side filtering."},{name:"value",type:"string",defaultValue:'""',description:"Controlled selected value (data-driven API). Pass an empty string to represent no selection."},{name:"defaultValue",type:"string",description:"Uncontrolled initial value (data-driven API). The trigger shows the matching option's label at rest \u2014 including in searchable (showSearch) mode \u2014 so an edit form pre-filled from server data renders the label, not the placeholder. Selected option is marked by a background tint (no check icon)."},{name:"onChange",type:"(value: string, option?: SearchSelectOptionProp) => void",description:"Change handler for the data-driven API. Receives the new value string and the matching option object."},{name:"renderOption",type:"(option: SearchSelectOptionProp) => React.ReactNode",description:"Custom per-option renderer for the dropdown ROWS (Ant-Design style). Defaults to label + optional sublabel. Does not change the trigger \u2014 use `labelRender` for that."},{name:"labelRender",type:"(selected: { value: string; label: React.ReactNode; option?: SearchSelectOptionProp }) => React.ReactNode",description:"Custom renderer for the SELECTED value shown on the TRIGGER (Ant Design `labelRender`) \u2014 avatar + name + role badge, etc. `option` is undefined for an async preset whose page hasn't loaded. Only used while a value is selected; the placeholder still shows when empty."},{name:"selectedLabel",type:"string",description:"Display label for the current value when its option is not in the loaded page (async). Prevents a flash of the raw id."},{name:"selectedIcon",type:"React.ReactNode",description:"Leading icon shown on the trigger for the current value when its option isn't loaded yet (async preset) \u2014 the trigger counterpart of `selectedLabel`, so an edit form pre-filled from the server shows the avatar/flag at rest."},{name:"placeholder",type:"string",description:"Placeholder shown in the trigger when no value is selected."},{name:"searchPlaceholder",type:"string",description:"Placeholder inside the search input (combobox mode only)."},{name:"emptyMessage",type:"string",description:"Message rendered when the filtered list is empty."},{name:"loadingMessage",type:"string",description:"Message rendered while loadOptions is resolving."},{name:"errorMessage",type:"string",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'."},{name:"clearable",type:"boolean",defaultValue:"true",description:"Show a clear row when a value is selected (data-driven API). Set to false for required fields."},{name:"clearLabel",type:"string",description:"Label for the clear row (data-driven combobox mode)."},{name:"disabled",type:"boolean",description:"Disables the entire select."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Searchable mode only (showSearch/loadOptions). Value is shown (and the clear affordance hidden) but the popover cannot be opened \u2014 no new pick, no search. Mirrors the Input/NumberInput readOnly contract: stays focusable and still submits its value, unlike disabled."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',description:"Searchable mode only. Height tier forwarded to the SearchSelect trigger Button. For the compound API use SelectTrigger's own size prop instead (below)."},{name:"open",type:"boolean",description:"Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Searchable mode only. Fires on every open/close attempt \u2014 including ones ignored because open is externally pinned \u2014 so a controlled consumer stays in sync."},{name:"search",type:"string",description:"Searchable mode only. Controlled search-box query (uncontrolled by default). Pair with onSearchChange."},{name:"onSearchChange",type:"(query: string) => void",description:"Searchable mode only. Fires on every keystroke in the search box."},{name:"filterOption",type:"(option: SearchSelectOptionProp, query: string) => boolean",description:"Searchable mode only, static options (ignored with loadOptions, which owns its own server-side filtering). Overrides the default label/value substring filter. Only consulted while the query is non-empty."},{name:"renderError",type:"(params: { message: string; retry: () => void }) => React.ReactNode",description:"Searchable mode only. Custom error slot, overriding the default errorMessage row. retry() reloads from the first page."},{name:"renderLoadMore",type:"(params: { hasMore: boolean; loading: boolean; loadMore: () => void }) => React.ReactNode",description:"Searchable mode only. Custom 'load more' affordance appended below the list while another page is available \u2014 pairs with (does not replace) the built-in scroll-triggered pagination."},{name:"name",type:"string",description:"Form field name. Submits the selected value via a hidden input (data-driven API). Required for uncontrolled form submission."},{name:"id",type:"string",description:"HTML id for the trigger element. Wire to a <label htmlFor> for a11y."},{name:"className",type:"string",description:"Additional CSS classes applied to the trigger."},{name:"data-testid",type:"string",description:"Test id on the trigger. Option items get ${data-testid}-option-${value} automatically."},{name:"SelectTrigger size",type:'"sm" | "md"',defaultValue:'"md"',description:"Compound API only. Size variant on the SelectTrigger sub-component."},{name:"SelectTrigger width",type:'"full" | "auto"',defaultValue:'"full"',description:"Compound API only. `full` fills the field column (inside FormField). `auto` sizes the trigger to its label \u2014 use it in a PageContainer `extra` slot, a toolbar or a footer row, where a full-width trigger swallows the row and truncates its siblings."},{name:"SelectTrigger showIndicator",type:"boolean",defaultValue:"true",description:"Compound API only. Set false to omit the built-in chevron disclosure indicator from the DOM entirely (not a CSS hide) \u2014 for specialized triggers (icon-only, etc.) that render their own affordance, so no consumer descendant CSS is needed."}],usage:["DO use the data-driven API (options/loadOptions) for straightforward selects \u2014 it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.","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.","READING THE SELECTED CODE FROM THE DOM: the trigger publishes `data-value` = the selected VALUE, alongside the `data-field` key it inherits from FormField. Use that in e2e tests and screen automation \u2014 the trigger's visible text is the option LABEL (\u6771\u4EAC\u672C\u793E), and the only other place the code lives is Radix's aria-hidden 1x1px native <select>, which exists solely so a native submit carries the value. `data-value` is absent while nothing is selected, and it tracks uncontrolled picks too.","DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.","DO name the control with FormField (or aria-label) \u2014 NOT with a <label htmlFor>. The trigger is a button with role=combobox, and neither a wrapping <label> nor htmlFor names it: role=combobox takes no accessible name from its content either, so the visible value is the VALUE, not the name. Wrapping the Select in <FormField label=\u2026> is the supported route; for a Select with no visible label pass aria-label. This holds for BOTH APIs \u2014 the compound trigger inherits the FormField contract (label, helper, error, required) through Select, and a bare <Select aria-label=\u2026> forwards it too. Anything you set directly on SelectTrigger wins.","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.","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.","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.","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.","DO reach for open/onOpenChange (searchable mode) to drive the popover from outside \u2014 e.g. opening it programmatically after a validation error \u2014 and search/onSearchChange to seed or read the query text. Both fall back to internal state when omitted; onOpenChange/onSearchChange still fire either way so a controlled consumer stays in sync.","DO use readOnly (searchable mode) for a value that must stay visible and submittable but not editable in this view \u2014 it differs from disabled: the control stays focusable and its value still submits. clearable is ignored while readOnly.","DO use filterOption (searchable mode, static options) when the default label/value substring match isn't right \u2014 e.g. filtering by a hidden code field. It is NOT consulted when loadOptions is set (that fetcher owns its own filtering).","DO use renderError + renderLoadMore (searchable mode) to replace the default error row with a branded retry affordance, or to pair a manual 'load more' button with (not instead of) the built-in scroll-triggered pagination.","DO set SelectTrigger showIndicator={false} (compound API) on a specialized trigger \u2014 icon-only, or one with its own affordance \u2014 instead of hiding [data-slot=select-chevron] with consumer CSS."],useCases:["Status filter on an invoice list \u2014 pass options=[{value:'draft',label:'Draft'},{value:'paid',label:'Paid'}] with onChange to drive a query param; no search needed so omit showSearch.","Legal-entity switcher \u2014 static options list with showSearch=true for client-side filtering when there are many entities; use selectedLabel to show the entity name before the full list loads.","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.","Grouped currency picker \u2014 set option.group='Asia' / 'Europe' on each option; the plain (non-search) data-driven mode renders SelectGroup headings automatically.","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.","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.","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."],related:["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).","TreeSelect \u2014 use when options are hierarchical (parent/child tree). Not a drop-in for Select; has expand/collapse and a separate treeData prop.","Select with showSearch \u2014 use Select (with the `showSearch` prop) for typeahead/autocomplete lookup patterns instead of the removed Autocomplete component.","RadioGroup \u2014 use instead of Select when there are 2-4 mutually exclusive choices that must all be visible at once without opening a popover.","Combobox (if present) \u2014 compound cmdk-powered combobox for free-text + suggestion; Select is for strict value lists only."],example:`import {
|
|
747
747
|
Select,
|
|
@@ -3320,7 +3320,7 @@ export function AccountChip({ user, onSignOut }: { user: { name: string; email:
|
|
|
3320
3320
|
|
|
3321
3321
|
// In the page header, a language Select takes width="auto" so it does not swallow the row:
|
|
3322
3322
|
// <PageContainer title="\u2026" extra={<><Select \u2026><SelectTrigger width="auto">\u2026</SelectTrigger>\u2026</Select><AccountChip \u2026 /><Button>Send</Button></>}>
|
|
3323
|
-
`}];function E(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function
|
|
3323
|
+
`}];function E(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function V(t){let a=t.trim().toLowerCase();return a===""?b:b.filter(e=>e.name.includes(a)||e.aliases?.some(o=>o.includes(a))||e.tagline.toLowerCase().includes(a)||e.tags.some(o=>o.includes(a)))}function M(t){return t.audience!=="core"}var x=[{id:"taste",audience:"both",name:"Taste baseline \u2014 Senior UI/UX engineering",whenToUse:"Default for any production app screen. Metric-based rules, strict component architecture, CSS hardware acceleration, balanced design engineering.",source:"Leonxlnx/taste-skill (root) + @godxjp/ui design-thinking.ts",sections:[{id:"mobile-first",title:"Mobile-first non-negotiable",tagline:"Defaults target xs (\u22650px); enhance via sm: / md: / lg: / xl: / 2xl:",body:`Cardinal rule 24. Touch targets \u2265 44\xD744 px. NEVER read
|
|
3324
3324
|
window.innerWidth \u2014 use useBreakpoint(). Stories render at narrow
|
|
3325
3325
|
viewport first. Multi-column layouts: grid grid-cols-1 sm:grid-cols-N.
|
|
3326
3326
|
EXCEPTION: name pairs (\u59D3+\u540D) use grid-cols-2 always.`},{id:"one-intent-per-screen",title:"One intent per screen",tagline:"Pick the ONE primary question this page answers. 60-80% visual weight to it.",body:`Wall-of-cards dashboards are AI slop. Show 1-2 hero metrics
|
|
@@ -3845,7 +3845,7 @@ Only after sign-off do you move to code. A wireframe skipped = a redesign reliti
|
|
|
3845
3845
|
6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
|
|
3846
3846
|
Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
|
|
3847
3847
|
(2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
|
|
3848
|
-
Do not advance a step until the prior artifact is signed off.`}]}];function A(t){return x.find(a=>a.id===t)}function
|
|
3848
|
+
Do not advance a step until the prior artifact is signed off.`}]}];function A(t){return x.find(a=>a.id===t)}function U(t,a){return A(t)?.sections.find(e=>e.id===a)}function G(t,a){let e=t.toLowerCase(),o=[],r=(n,s,g,p,d)=>{n.some(f=>e.includes(f))&&o.push({skill:s,section:g,why:p,alsoSee:d})};r(["premium","awwwards","agency","linear","apple","high-end","luxury"],"soft","vibe-archetypes","Premium tier \u2014 pick a Vibe + Layout archetype + apply Double-Bezel.",["soft/double-bezel","soft/magnetic-hover"]),r(["landing page","marketing","hero","long scroll"],"imagegen-web","hero-composition-bias","Landing pages benefit from hero composition variety + per-section image generation.",["gpt-tasteskill/principles","soft/layout-archetypes"]),r(["mobile app","ios","android","phone screen","onboarding flow"],"imagegen-mobile","principles","Mobile app design \u2014 generate screens first, avoid phone-shaped-website.",["taste/mobile-first"]),r(["workspace","notion","document","editorial","knowledge base"],"minimalist","palette","Editorial workspace = warm monochrome + spot pastels + serif headings.",["minimalist/typography","minimalist/bento-grids"]),r(["dashboard","data heavy","tabular","ops table"],"brutalist","principles","Data-heavy dashboards work with Brutalist (rigid grids, utilitarian color).",["taste/one-intent-per-screen"]),r(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),r(["refactor","redesign","upgrade existing","audit"],"redesign","fix-priority","Existing project = run audit first, fix in priority order (font \u2192 palette \u2192 states \u2192 ...).",["redesign/audit-checklist"]),r(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),r(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),r(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Use useBreakpoint()."),r(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),r(["gsap","scrolltrigger","scroll choreography","pinning"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),r(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),r(["handoff","design bundle","claude design","prototype","build the page","implement the design","build this screen","mockup"],"design-to-page","map-to-primitives","Map every block to a real @godxjp/ui primitive (MCP-first), consume existing tokens, apply the reference-design DNA, tables central, gaps \u2192 extend-or-ask, verify.",["design-to-page/read-intent","design-to-page/dna","design-to-page/tables-central"]),r(["compose a screen","new screen","new page","create a page","create a screen","build a view","build a screen","from scratch","screen from a brief"],"compose-a-screen","pick-primitives","Build a new app screen from real @godxjp/ui primitives (MCP-first): one-intent-per-screen, real chrome, every state + a11y + i18n, verify.",["compose-a-screen/assemble-screen","compose-a-screen/state-and-a11y","taste/one-intent-per-screen"]),r(["bug in godx","godx-ui bug","report a bug","file an issue","gh issue","can't follow the rule","library is broken","primitive is broken"],"compose-a-screen","report-bug","If @godxjp/ui itself is at fault, don't fake a workaround \u2014 file a detailed gh issue (use draft_bug_report).",["design-to-page/report-bug"]),r(["slow","performance","perf","violation","long task","lag","janky","re-render","rerender","bundle size","code splitting","tree-shak","ch\u1EADm","l\u1ED7i hi\u1EC7u n\u0103ng","\u91CD\u3044"],"app-performance","measure-first","Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",["app-performance/filter-pane-memo","app-performance/heavy-panels","app-performance/bundle-budget"]),r(["admin console","admin panel","control panel","back-office","back office","management console","management ui","multi-tenant","multi tenant","platform admin","workspace admin","console qu\u1EA3n tr\u1ECB","layered ia","information architecture"],"design-complex-admin","entity-model-first","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.",["design-complex-admin/layered-ia","design-complex-admin/wireframe-first","compose-a-screen/pick-primitives"]);let i=a?.consumerOnly?o.filter(n=>{let s=A(n.skill);return!s||s.audience!=="core"}):o;return i.length===0?a?.consumerOnly?[{skill:"compose-a-screen",section:"pick-primitives",why:`No keyword match for "${t}". Default consumer path: compose the screen from real primitives via the MCP.`,alsoSee:["design-to-page/map-to-primitives","taste/one-intent-per-screen"]}]:[{skill:"taste",section:"<see whenToUse>",why:`No keyword match for "${t}". Default to the "taste" baseline \u2014 see whenToUse for sections.`}]:i}var D=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
|
|
3849
3849
|
linear gradient as hero background. Looks like every AI-generated
|
|
3850
3850
|
SaaS landing page from 2023.`,fix:`Use the framework's accent palette (\`data-accent="blue"\` /
|
|
3851
3851
|
"violet" / "cyan" / "green" / "orange" / "rose"). Solid surface
|
|
@@ -3954,7 +3954,7 @@ shell with a centered spinner. Layout shifts when content
|
|
|
3954
3954
|
arrives.`,fix:`Use Skeleton placeholders matching the eventual content shape.
|
|
3955
3955
|
Render \`<Skeleton className="h-9 w-full rounded-md" />\` in place of
|
|
3956
3956
|
each control \u2014 inside the FormField that will hold it, so the labels
|
|
3957
|
-
and grid stay put. Layout stays stable, perceived speed improves.`}];function H(t){return D.filter(a=>a.category===t)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black / dark charcoal / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add depth with an ambient gradient at 0.02-0.05 opacity, or background imagery you COMMIT to the repo. Never a hotlinked placeholder service: a third-party image that never settles blocks `networkidle`, which times out a page load outright \u2014 it cost this repo two docs pages failing every browser gate. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],j=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],W=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function _(t){return R.filter(a=>a.category===t)}var Y="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",K=[{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function $(t){return t?K.filter(a=>a.category===t):K}var X="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)",Q=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function J(t){return t?Q.filter(a=>a.category===t):Q}var m={name:"@godxjp/ui-mcp",version:"19.4.0",godxUiCompatibility:"19.4.x",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).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var ee=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",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."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function te(t,a){switch(t){case"list_skills":return ce();case"list_primitives":return ae(a.group);case"list_patterns":return ge();case"list_anti_ai_tells":return ye(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return fe(a.category);case"list_visual_checks":return be(a.category);case"get_anti_ai_tell":return ve(String(a.name??""));case"get_redesign_check":return ke(String(a.symptom??""));case"get_skill_section":return oe(String(a.skill??""),String(a.section??""));case"get_component":return Ce(String(a.name??""),a.verbose===!0);case"get_pattern":return De(String(a.name??""));case"get_rule":return Oe(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Ne(a.name==null?void 0:String(a.name));case"get_tokens":return Ie(a.category);case"list_consumer_skills":return he();case"get_consumer_skill":return pe(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return ue(a);case"check_compatibility":return me(a.version==null?void 0:String(a.version));case"route_task":return Z(String(a.task??""));case"suggest_primitive":return Ee(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return Ae(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Pe(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
|
|
3957
|
+
and grid stay put. Layout stays stable, perceived speed improves.`}];function H(t){return D.filter(a=>a.category===t)}var R=[{category:"typography",symptom:"Inter / Roboto / Open Sans everywhere \u2014 the AI default.",fix:"Pick a font with character: Geist, Outfit, Cabinet Grotesk, Satoshi for sans. For editorial / creative \u2014 pair a serif heading (Newsreader, Lyon, Playfair) with a sans body.",uiNote:"Override --font-sans + --font-serif at the consumer's root CSS. Framework reads from these tokens."},{category:"typography",symptom:"Headlines lack presence \u2014 small + thin + default tracking.",fix:"Increase display size, tighten letter-spacing (-0.02em to -0.04em), reduce line-height (1.1). Headlines should feel HEAVY and INTENTIONAL.",uiNote:"Typography.Title size={1} for hero; override fontFamily + letterSpacing inline."},{category:"typography",symptom:"Body paragraphs full-width \u2014 hard to read.",fix:"Limit paragraph max-width to ~65ch. Increase line-height to 1.6+.",uiNote:"Wrap Typography.Paragraph in `<div style={{ maxWidth: '65ch' }}>`."},{category:"typography",symptom:"Only Regular (400) + Bold (700) weights \u2014 flat hierarchy.",fix:"Introduce Medium (500) + SemiBold (600) for subtle weight contrasts."},{category:"typography",symptom:"Numbers in proportional font \u2014 columns jitter in tables.",fix:"`font-variant-numeric: tabular-nums` for data, or a monospace font like Geist Mono.",uiNote:"Table primitive already uses `tabular-nums` on `.num` cells. For ad-hoc numeric labels, add the CSS prop manually."},{category:"typography",symptom:"Orphaned words \u2014 single word on the last line of a heading.",fix:"`text-wrap: balance` (h1/h2/h3) or `text-wrap: pretty` (body)."},{category:"typography",symptom:"Title Case On Every Header.",fix:"Use sentence case instead. More modern, easier to read."},{category:"color-surface",symptom:"Pure #000000 background.",fix:"Replace with off-black / dark charcoal / tinted dark (deep navy).",uiNote:"Framework dark theme already uses tinted dark values \u2014 verify the consumer's override didn't force pure black."},{category:"color-surface",symptom:"Oversaturated accent colors.",fix:"Keep saturation below 80%. Desaturate so accents BLEND with neutrals rather than scream."},{category:"color-surface",symptom:"More than one accent color competing.",fix:"Pick ONE. Remove the rest. Consistency beats variety in palette.",uiNote:"Set ONE `data-accent` at `<html>` root. Use semantic colors (success / warning / destructive) only for genuinely semantic content."},{category:"color-surface",symptom:"Purple/blue 'AI gradient' aesthetic \u2014 most common AI fingerprint.",fix:"Replace with neutral base + ONE considered accent. Drop the gradient entirely if it has no narrative purpose."},{category:"color-surface",symptom:"Generic black `box-shadow` everywhere.",fix:"Tint shadow to match background hue (e.g. cool gray bg \u2192 cool gray shadow). Colored shadows over pure black."},{category:"color-surface",symptom:"Random dark section breaking an otherwise light page.",fix:"Either commit to full dark mode OR keep light consistently. If contrast needed, use a SLIGHTLY darker shade of the same palette \u2014 not a sudden jump to #111."},{category:"color-surface",symptom:"Empty flat sections with no visual depth.",fix:"Add depth with an ambient gradient at 0.02-0.05 opacity, or background imagery you COMMIT to the repo. Never a hotlinked placeholder service: a third-party image that never settles blocks `networkidle`, which times out a page load outright \u2014 it cost this repo two docs pages failing every browser gate. Empty flat = unfinished."},{category:"layout",symptom:"Everything centered + symmetric.",fix:"Break symmetry: offset margins, mixed aspect ratios, left-aligned header over centered body."},{category:"layout",symptom:"Three equal card columns as feature row \u2014 the most generic AI layout.",fix:"Replace with 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry. The 3-equal-cols pattern is RED FLAG #1.",uiNote:"Use Bento Grid (custom CSS grid with `gridColumn: 'span N'`) instead of `<Grid cols={3}>` for hero sections."},{category:"layout",symptom:"`height: 100vh` causing iOS Safari jump.",fix:"Use `min-height: 100dvh` (dynamic viewport) instead."},{category:"layout",symptom:"No max-width container \u2014 content stretches edge-to-edge.",fix:"Add a container constraint (1200-1440px) with `margin: auto`. Or use `max-w-4xl / max-w-5xl` for content-heavy pages.",uiNote:"Framework's PageContent constrains via `var(--container-max-width)`. Consumer may override."},{category:"layout",symptom:"Cards forced to same height by flexbox.",fix:"Allow variable heights or use masonry when content varies.",uiNote:"Use Masonry primitive \u2014 handles variable heights without flexbox stretch."},{category:"layout",symptom:"Buttons at random vertical positions in card rows.",fix:"Pin CTAs to card bottom \u2014 same Y-position across the row regardless of content above.",uiNote:"Card's `actions` footer slot bottom-aligns automatically."},{category:"layout",symptom:"Feature lists starting at different vertical positions in pricing tables.",fix:"Fixed-height title/price block + consistent spacing above the feature list. Cards align across columns."},{category:"layout",symptom:"Dashboard ALWAYS has a left sidebar.",fix:"Consider top navigation, floating command menu, or collapsible panel. Sidebar isn't the only chrome.",uiNote:"Framework supports both \u2014 AppShell with sidebar slot is optional; can use Topbar-only for some flows."},{category:"interactivity",symptom:"No hover states on buttons.",fix:"Background shift, scale, or translate on hover \u2014 150-200ms ease.",uiNote:"Framework Button has built-in hover. If overridden \u2014 restore."},{category:"interactivity",symptom:"No active/pressed feedback.",fix:"`scale(0.98)` or `translateY(1px)` on `:active`. Simulates a physical click."},{category:"interactivity",symptom:"No focus ring (`outline: none`).",fix:"Restore visible `:focus-visible` ring. Accessibility requirement, not optional."},{category:"interactivity",symptom:"Generic circular spinner for page-level loading.",fix:"Replace with Skeleton placeholders matching the eventual content shape.",uiNote:"Framework Skeleton + Form `loading={{ kind: 'skeleton' }}` handles cascading initial-fetch state."},{category:"interactivity",symptom:"No empty states \u2014 empty dashboard shows nothing.",fix:"Design a composed 'getting started' view: Empty primitive with title + description + next-action button."},{category:"interactivity",symptom:"`window.alert()` for errors.",fix:"Inline error in the relevant Field, OR toast for non-form errors, OR Dialog for blocking errors."},{category:"interactivity",symptom:"Dead links (`href='#'`).",fix:"Either link to real destinations or visually disable the button."},{category:"interactivity",symptom:"No indication of current page in navigation.",fix:"Style the active nav link distinctly.",uiNote:"Sidebar handles via `activeId` \u2014 pass it."},{category:"content",symptom:"Generic names \u2014 'John Doe', 'Jane Smith'.",fix:"Diverse, realistic names. For Japanese apps: \u7530\u4E2D \u592A\u90CE, \u4F50\u85E4 \u7F8E\u54B2, Nguy\u1EC5n Lan, Maria Cruz."},{category:"content",symptom:"Fake round numbers \u2014 '99.99%', '50%', '$100.00'.",fix:"Organic data: '47.2%', '$99.00', '+1 (312) 847-1928'."},{category:"content",symptom:"Placeholder brand names \u2014 Acme, Nexus, SmartFlow.",fix:"Invent contextual believable brands or use the consumer's real brand."},{category:"content",symptom:"AI copy clich\xE9s \u2014 'elevate', 'seamless', 'unleash', 'next-gen', 'game-changer', 'delve', 'tapestry', 'in the world of'.",fix:"Plain specific language. Numbers, nouns, verbs.",uiNote:"Framework's cardinal rule 9 bans this in framework docs; same discipline applies to consumer copy."},{category:"content",symptom:"Exclamation marks in success messages.",fix:"Remove. Be confident, not loud."},{category:"content",symptom:"'Oops!' or apologetic error messages.",fix:"Direct + specific: 'Connection failed. Please try again.' / '\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093'."},{category:"content",symptom:"Lorem Ipsum.",fix:"Real draft copy. Even rough placeholder beats Latin."},{category:"components",symptom:"Generic card look (border + shadow + white).",fix:"Remove border OR shadow OR background \u2014 keep ONE. Cards exist only when elevation communicates hierarchy."},{category:"components",symptom:"Always one filled + one ghost button.",fix:"Add text links / tertiary styles for variety.",uiNote:"Button has `variant='link'` for tertiary actions."},{category:"components",symptom:"3-card carousel testimonials with dots.",fix:"Replace with masonry wall of quotes, embedded social posts, or single rotating quote."},{category:"components",symptom:"Pricing table with 3 equal towers.",fix:"Highlight recommended tier with COLOR and emphasis, not just extra height."},{category:"components",symptom:"Modals for everything.",fix:"Use inline editing, Sheet (slide-over), or expandable Collapse for simple actions. Reserve Dialog for true blocking decisions."},{category:"components",symptom:"Footer link farm with 4 columns.",fix:"Simplify. Main nav paths + legally required links. No marketing kitchen sink."},{category:"iconography",symptom:"Lucide or Feather icons exclusively.",fix:"Use Phosphor (Bold / Fill), Heroicons, or a custom set. AI default tell.",uiNote:"Framework ships with lucide as locked dependency (rule 14). For editorial differentiation, layer Phosphor on top."},{category:"iconography",symptom:"Cliche icon metaphors \u2014 rocketship 'launch', shield 'security'.",fix:"Less obvious: bolt, fingerprint, spark, vault, gem."},{category:"iconography",symptom:"Stock 'diverse team in office' photo.",fix:"Real team photos, candid shots, or a consistent illustration style. Avatar initials fallback > generic stock person."},{category:"code-quality",symptom:"Div soup \u2014 no semantic HTML.",fix:"`<nav>`, `<main>`, `<article>`, `<aside>`, `<section>` for landmarks.",uiNote:"AppShell renders the canonical landmark structure automatically."},{category:"code-quality",symptom:"Inline styles mixed with CSS classes haphazardly.",fix:"Move styling into the project's system. Inline `style={{}}` only for layout / positioning (rule 29)."},{category:"code-quality",symptom:"Missing alt text on images.",fix:"Describe content for SR. Never leave `alt=''` or `alt='image'` on meaningful images."},{category:"code-quality",symptom:"Arbitrary z-index values like `9999`.",fix:"Establish a clean z-index scale in CSS variables."},{category:"omissions",symptom:"No legal links in footer.",fix:"Add Privacy Policy + Terms of Service."},{category:"omissions",symptom:"Dead ends in user flows \u2014 no 'back'.",fix:"Every page has a way back. Breadcrumb, back button, OR clear nav state."},{category:"omissions",symptom:"No custom 404 page.",fix:"Design a helpful branded 404 with a way home and search."},{category:"omissions",symptom:"No form validation.",fix:"Client-side validation via zod schema. Framework's Form + FormField handle field-level errors automatically."},{category:"omissions",symptom:"No 'skip to content' link.",fix:"Hidden skip-link, first focusable element. Essential for keyboard users.",uiNote:"AppShell renders one automatically."}],j=["1. Font swap \u2014 biggest instant improvement, lowest risk","2. Color palette cleanup \u2014 remove clashing / oversaturated colors","3. Hover + active states \u2014 makes the interface feel alive","4. Layout + spacing \u2014 proper grid, max-width, consistent padding","5. Replace generic components \u2014 swap cliche patterns for modern alternatives","6. Add loading, empty, error states \u2014 makes it feel finished","7. Polish typography scale + spacing \u2014 the premium final touch"],W=["Work with the existing tech stack. Do NOT migrate frameworks or styling libraries.","Do NOT break existing functionality. Test after every change.","Before importing any new library, check `package.json` first.","Keep changes reviewable + focused. Small targeted improvements over big rewrites.","Run the audit before fixing \u2014 listing issues first prevents accidental scope creep."];function _(t){return R.filter(a=>a.category===t)}var Y="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",K=[{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function $(t){return t?K.filter(a=>a.category===t):K}var X="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)",Q=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function J(t){return t?Q.filter(a=>a.category===t):Q}var m={name:"@godxjp/ui-mcp",version:"19.4.2",godxUiCompatibility:"19.4.x",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).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var ee=[{name:"list_skills",description:"List every design/taste skill bundled by this MCP (id + name + whenToUse + section ids). Use FIRST to discover skills; then `get_skill_section` to drill in.",inputSchema:{type:"object",properties:{}}},{name:"list_primitives",description:"List every @godxjp/ui primitive/composite/shell (group + tagline per entry). Optionally filter by group. Then `get_component` for one's full API.",inputSchema:{type:"object",properties:{group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_patterns",description:"List every canonical copy-paste code pattern (signup-form, settings-page, data-table-page, async-data-state, confirm-destructive, \u2026); common aliases resolve too. Use before `get_pattern`.",inputSchema:{type:"object",properties:{}}},{name:"list_anti_ai_tells",description:"List every AI-tell pattern to AVOID (optionally by category). Use to self-audit a design before shipping; then `get_anti_ai_tell` for the fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["visual","layout","copy","interaction","imagery","structure"]}}}},{name:"list_redesign_checks",description:"List the redesign audit checklist (50+ checks; optionally by category). Use when auditing an existing project; then `get_redesign_check` for a symptom's fix.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["typography","color-surface","layout","interactivity","content","components","iconography","code-quality","omissions"]}}}},{name:"list_audit_rules",description:"List the LOCAL static ui-audit rules (scripts/ui-audit.mjs) to run BEFORE any visual review \u2014 each cites the standard it enforces (WCAG/WAI-ARIA/Intl/ISO/IANA/CSS-Logical) + a fix + the run command. Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["tokens","composition","api","a11y","i18n","rtl","copy"]}}}},{name:"list_visual_checks",description:"List the RUNTIME visual-audit checks (scripts/visual-audit.mjs \u2014 Playwright + axe-core) to run against the RUNNING app: contrast/ARIA (axe), target size, rendered-accent chroma, DOM emoji, banner layout. Needs a browser (vs list_audit_rules, static). Optionally by category.",inputSchema:{type:"object",properties:{category:{type:"string",enum:["a11y","color","i18n","layout"]}}}},{name:"get_anti_ai_tell",description:"Fetch ONE anti-AI-tell \u2014 full body + concrete fix. Use after `list_anti_ai_tells`.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Exact tell name from list_anti_ai_tells."}},required:["name"]}},{name:"get_redesign_check",description:"Fetch redesign check(s) matching a symptom snippet. Returns full fix + UI note. Use after `list_redesign_checks`.",inputSchema:{type:"object",properties:{symptom:{type:"string",description:"Fragment of the symptom text (e.g. 'Inter everywhere' / '100vh')."}},required:["symptom"]}},{name:"get_skill_section",description:"Fetch ONE section of ONE skill \u2014 token-efficient. E.g. `skill='soft', section='double-bezel'`. Use after `list_skills` narrowed the relevant skill + section.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Skill id (e.g. 'soft', 'minimalist', 'taste')."},section:{type:"string",description:"Section id within that skill."}},required:["skill","section"]}},{name:"get_component",description:"Full guide for one @godxjp/ui component \u2014 import path, props/types/defaults, HOW to use it (DO/DON'T), WHEN to reach for it (use cases), related components (don't reinvent/confuse), a copy-paste example, story path, and cardinal rules. Use this before hand-rolling anything. Design-token knobs are listed compactly (name+default); pass `verbose:true` for what each token controls.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Component name (e.g. 'Button', 'DataTable')."},verbose:{type:"boolean",description:"Include the full design-token table with a 'what it controls' description per token. Default false (compact token+default only) to save context."}},required:["name"]}},{name:"get_pattern",description:"Full code snippet for one canonical pattern \u2014 copy-paste-ready.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Pattern slug (use list_patterns first)."}},required:["name"]}},{name:"get_rule",description:"Read one cardinal rule from CLAUDE.md (by number) OR all if no number.",inputSchema:{type:"object",properties:{number:{type:"number",description:"Rule number (1-N)."}}}},{name:"get_vocab",description:"Read shared prop-vocabulary type (`SizeProp`, `StatusProp`, `ColorProp`, `LoadingProp`, etc.) OR all if no name.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Vocab type name."}}}},{name:"get_tokens",description:"Read design tokens, optionally filtered by tier category (primitive / semantic / component).",inputSchema:{type:"object",properties:{category:{type:"string",enum:["primitive","semantic","component"]}}}},{name:"list_consumer_skills",description:"List the design skills relevant to an app-dev BUILDING WITH @godxjp/ui (audience consumer/both). Hides core library-maintenance skills. START HERE if you import @godxjp/ui and want guidance (design-to-page, compose-a-screen, taste, \u2026). Returns id + name + whenToUse + section ids.",inputSchema:{type:"object",properties:{}}},{name:"get_consumer_skill",description:"Fetch ONE section of ONE consumer-facing skill. Same as get_skill_section but refuses core-only skills (steers app-devs away from library-maintenance material). Use after list_consumer_skills / route_consumer_task.",inputSchema:{type:"object",properties:{skill:{type:"string",description:"Consumer skill id (e.g. 'design-to-page', 'compose-a-screen')."},section:{type:"string",description:"Section id within that skill."}},required:["skill"]}},{name:"route_consumer_task",description:"Natural-language task \u2192 consumer skill+section pointer. Like route_task but only points to consumer-facing skills (never core library-maintenance). Use FIRST when you're building an app with @godxjp/ui.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"draft_bug_report",description:"When @godxjp/ui ITSELF is at fault (missing token, a primitive lacking the controlled-vocabulary prop, a real a11y/behaviour bug, a wrong catalog example) and you cannot follow a rule \u2014 DON'T fake a workaround. This drafts a detailed GitHub issue body + a copy-paste `gh issue create` command so you can report it. Prints the command only; never runs gh.",inputSchema:{type:"object",properties:{summary:{type:"string",description:"One-line title of the bug / blocked rule."},repro:{type:"string",description:"Minimal steps or code to reproduce."},expected:{type:"string",description:"What SHOULD happen (per the rule/spec)."},actual:{type:"string",description:"What actually happens."},component:{type:"string",description:"Affected component name, if any (links to get_component)."},rule:{type:"number",description:"Cardinal rule number that can't be followed, if any."},version:{type:"string",description:"Installed @godxjp/ui version (e.g. '12.1.0')."},env:{type:"string",description:"Environment (browser/OS/framework), if relevant."}},required:["summary"]}},{name:"check_compatibility",description:"Report whether the @godxjp/ui version installed in the target project matches THIS catalog (which describes one release train). A mismatched minor means the props/tokens/patterns may describe a build they never installed (#140). Pass the installed version (`npm ls @godxjp/ui`); call it at the START of a consumer session.",inputSchema:{type:"object",properties:{version:{type:"string",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."}}}},{name:"route_task",description:"Natural-language task \u2192 skill+section pointer (e.g. 'design a premium agency hero' \u2192 soft/vibe-archetypes). Use FIRST when you don't know which skill applies.",inputSchema:{type:"object",properties:{task:{type:"string",description:"Describe what you want to build."}},required:["task"]}},{name:"suggest_primitive",description:"Use case \u2192 primitive recommendation. E.g. 'confirm a destructive delete' \u2192 DangerZone pattern + Dialog suggestion.",inputSchema:{type:"object",properties:{use_case:{type:"string"}},required:["use_case"]}},{name:"search_components",description:"Fuzzy-search primitives by name / tagline / prop. Returns ranked matches.",inputSchema:{type:"object",properties:{query:{type:"string"}},required:["query"]}},{name:"get_frame_coverage",description:"Verified preview-contract coverage for a component (issue #163). Answers 'is this state actually PROVEN?' \u2014 returns, per contract dimension (variants, tones, sizes, shapes, density, controlled/uncontrolled ownership, disabled/read-only/loading/empty/error/success, async retry/cancel/offline, responsive viewport matrix, RTL, long/localized content, keyboard/focus, accessible name/description/error, reduced motion / coarse touch), whether an EXECUTED case proves it (covered), whether nothing proves it (UNTESTED), or whether it cannot exist (not-applicable, with a reason). UNTESTED IS NOT A PASS: never infer that a component supports a state because an example renders. Call this before telling a user a component 'supports' anything. Omit `name` for the repo-wide summary and the tracked known gaps.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Public export name (e.g. 'Button', 'DataTable', 'CardFooter'). Omit for the repo-wide coverage summary."}}}},{name:"lint_jsx",description:"Heuristic check of a JSX snippet for common violations \u2014 raw `<button>` / `<input>`, `color='error'` on Tag/Badge, missing aria-label, missing source.code override on stories with cell renderers (rule 34), etc.",inputSchema:{type:"object",properties:{jsx:{type:"string"}},required:["jsx"]}}];async function te(t,a){switch(t){case"list_skills":return ce();case"list_primitives":return ae(a.group);case"list_patterns":return ge();case"list_anti_ai_tells":return ye(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return fe(a.category);case"list_visual_checks":return be(a.category);case"get_anti_ai_tell":return ve(String(a.name??""));case"get_redesign_check":return ke(String(a.symptom??""));case"get_skill_section":return oe(String(a.skill??""),String(a.section??""));case"get_component":return Ce(String(a.name??""),a.verbose===!0);case"get_pattern":return De(String(a.name??""));case"get_rule":return Oe(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Ne(a.name==null?void 0:String(a.name));case"get_tokens":return Ie(a.category);case"list_consumer_skills":return he();case"get_consumer_skill":return pe(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return ue(a);case"check_compatibility":return me(a.version==null?void 0:String(a.version));case"route_task":return Z(String(a.task??""));case"suggest_primitive":return Ee(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return Ae(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Pe(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
|
|
3958
3958
|
|
|
3959
3959
|
`;t+="Each is tagged `[audience]` \u2014 `core` = building @godxjp/ui itself, `consumer` = building an app with it, `both`. App-devs: use `list_consumer_skills` to hide core material.\n\n",t+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let a of x)t+=`## ${a.id} \u2014 ${a.name} \`[${a.audience}]\`
|
|
3960
3960
|
`,t+=`**When to use:** ${a.whenToUse}
|
|
@@ -4106,7 +4106,7 @@ ${e.whenToUse}
|
|
|
4106
4106
|
|
|
4107
4107
|
## Sections
|
|
4108
4108
|
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
4109
|
-
`;return r}let o=
|
|
4109
|
+
`;return r}let o=U(t,a);if(!o){let r=`Section "${a}" not in skill "${t}". Available:
|
|
4110
4110
|
`;for(let i of e.sections)r+=`- \`${i.id}\` \u2014 ${i.tagline}
|
|
4111
4111
|
`;return r}return`# ${e.name} \u2192 ${o.title}
|
|
4112
4112
|
|
|
@@ -4205,7 +4205,7 @@ ${e.example}
|
|
|
4205
4205
|
`:"")+"Call `get_frame_coverage` with no name for the repo-wide summary.\n"}return`# ${e.name} \u2014 contract coverage
|
|
4206
4206
|
|
|
4207
4207
|
**Group:** ${e.group}
|
|
4208
|
-
${re(e,e.name)}`}function De(t){let a=E(t);if(!a){let e=
|
|
4208
|
+
${re(e,e.name)}`}function De(t){let a=E(t);if(!a){let e=V(t);if(e.length===0)return`Pattern "${t}" not found.`;let o=`Pattern "${t}" not found. Closest:
|
|
4209
4209
|
`;for(let r of e)o+=`- ${r.name} \u2014 ${r.tagline}
|
|
4210
4210
|
`;return o}return`# Pattern: ${a.name}
|
|
4211
4211
|
|
|
@@ -4302,4 +4302,4 @@ ${t.tagline}
|
|
|
4302
4302
|
\`\`\`tsx
|
|
4303
4303
|
${t.example}
|
|
4304
4304
|
\`\`\`
|
|
4305
|
-
`,a}async function
|
|
4305
|
+
`,a}async function Ue(){let t=new Le({name:"godx-ui-mcp",version:m.version},{capabilities:{tools:{},resources:{}}});t.setRequestHandler(qe,async()=>({tools:ee})),t.setRequestHandler(Fe,async e=>{let{name:o,arguments:r}=e.params;return{content:[{type:"text",text:await te(o,r??{})}]}}),t.setRequestHandler(Be,async()=>({resources:ie})),t.setRequestHandler(Ve,async e=>{let{uri:o}=e.params,r=await se(o);return{contents:[{uri:o,mimeType:o.endsWith(".json")?"application/json":"text/markdown",text:r}]}});let a=new Me;await t.connect(a),console.error("[godx-ui-mcp] connected (stdio)")}Ue().catch(t=>{console.error("[godx-ui-mcp] fatal:",t),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui-mcp",
|
|
3
|
-
"version": "19.4.
|
|
3
|
+
"version": "19.4.2",
|
|
4
4
|
"godxUiCompatibility": "19.4.x",
|
|
5
5
|
"description": "Model Context Protocol server for @godxjp/ui — 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 — token-efficient (list → drill-down).",
|
|
6
6
|
"type": "module",
|