@godxjp/ui-mcp 18.4.0 → 18.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -10
  2. package/dist/index.js +827 -156
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{Server as Pe}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Oe}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Ie,ListResourcesRequestSchema as Re,ListToolsRequestSchema as Ne,ReadResourceRequestSchema as Fe}from"@modelcontextprotocol/sdk/types.js";var y=[{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:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{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:"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:"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.","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 `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.","DON'T: Confuse PageContainer's prop names with the old PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, migrate them to PageContainer.","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: 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'` stays divider-less regardless of the token."],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."],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`, `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 Me}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Le}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as Fe,ListResourcesRequestSchema as Be,ListToolsRequestSchema as Ve,ReadResourceRequestSchema as je}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:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"footer",type:"ReactNode",description:"Content area pinned below the page body."},{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:"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:"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:"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.","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 `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.","DON'T: Confuse PageContainer's prop names with the old PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, migrate them to PageContainer.","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'` stays divider-less regardless of the token."],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."],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`, `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() {
@@ -30,7 +30,7 @@ import { StatCard } from "@godxjp/ui/data-display";
30
30
  <StatCard label="\u516C\u958B\u4E2D\u30AF\u30FC\u30DD\u30F3" value="8" />
31
31
  <StatCard label="\u6708\u9593\u5229\u7528\u6570" value="3,210" />
32
32
  <StatCard label="\u5272\u5F15\u7DCF\u984D" value="\xA5480,000" />
33
- </ResponsiveGrid>`,storyPath:"layout/ResponsiveGrid.stories.tsx",rules:[24,40]},{name:"AppShell",group:"layout",tagline:"Root application shell \u2014 composes sidebar, topbar rail, main content area, and optional footer.",props:[{name:"sidebar",type:"ReactNode",required:!0,description:"Sidebar node \u2014 typically a <Sidebar>."},{name:"children",type:"ReactNode",required:!0,description:"Main page content rendered in <main>."},{name:"topbar",type:"ReactNode",description:"Full topbar override; else a rail is built from topbarLeft/topbarRight/logo."},{name:"topbarRight",type:"ReactNode",description:"Right slot of the auto-built topbar rail (user menu, switcher)."},{name:"topbarLeft",type:"ReactNode",description:"Left slot of the auto-built topbar rail."},{name:"logo",type:"ReactNode",description:"Brand mark at the far-left of the auto-built topbar rail (e.g. an Avatar)."},{name:"sidebarCollapsed",type:"boolean",defaultValue:"false",description:"Collapse the sidebar to icon-only mode."},{name:"footer",type:"ReactNode",description:"App-level footer outside the main content area."},{name:"breadcrumb",type:"BreadcrumbProp",description:"Breadcrumb trail rendered in the topbar header for back-navigation."},{name:"mobileNav",type:"ReactNode",description:"Navigation shown in the AppShell-owned mobile drawer below `lg` (where the docked sidebar is hidden). Defaults to the `sidebar` node; pass a tailored menu, or null to opt out."},{name:"mobileNavLabel",type:"string",description:"Accessible title for the mobile navigation drawer. Defaults to localized 'Menu'."},{name:"mobileNavOpen",type:"boolean",description:"Controlled open state of the mobile drawer. Omit for AppShell-owned state."},{name:"onMobileNavOpenChange",type:"(open: boolean) => void",description:"Change handler for the mobile drawer open state."}],usage:["DO pass a <Sidebar> node to `sidebar` (required) and page content to `children` (required) \u2014 these are the only two required props. Everything else is optional and omitting optional slots simply removes that zone from the rendered DOM.","DO rely on AppShell's OWNED mobile drawer below `lg`: it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring \u2014 never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).","DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar> to the `topbar` prop only when you need live handlers (entity switcher via productMenu, search, notifications, user avatar) \u2014 when `topbar` is provided, logo/topbarLeft/topbarRight are ignored entirely.","DO wire a single `sidebarCollapsed` boolean between AppShell's `sidebarCollapsed` prop and Sidebar's `collapsed` prop \u2014 AppShell sets `data-collapsed='true'` on the root div (which CSS reads for width transitions) but does NOT own the collapsed state itself; lift the state and pass it down to both.","DO place breadcrumb content in AppShell's `breadcrumb` prop (renders in the `app-breadcrumb` div inside `<main>` ABOVE children) \u2014 do NOT hand-roll a breadcrumb bar as the first child of children, and do NOT put breadcrumbs inside <Sidebar>.","DO NOT nest a second AppShell or AppShell inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.","DO NOT add padding directly to children expecting it to reach the viewport edge \u2014 AppShell's `<main>` is a scroll container; use <PageContainer> (or <PageContainer.Inset> inside a flush PageContainer) inside children to get standard page padding."],useCases:["Full admin SPA shell: AppShell wraps a <Sidebar> nav rail and a <Topbar> (with productMenu entity-switcher, onSearchOpen, onNotificationsOpen, user avatar) and every Inertia page renders as children inside a <PageContainer>.","Collapsible-sidebar layout: maintain a `collapsed` boolean in a persistent Inertia layout component, pass it to both AppShell's `sidebarCollapsed` and Sidebar's `collapsed`, wire Topbar's `onToggleCollapsed` to flip it \u2014 AppShell handles the CSS transition automatically.","Multi-tenant accounting app: pass a <Topbar productMenu={<DropdownMenuContent>\u2026</DropdownMenuContent>}> to AppShell's `topbar` slot so the legal-entity chip opens an inline switcher without a modal.","App-level footer (e.g. version/build info, compliance notice): pass a <footer> node to AppShell's `footer` prop \u2014 it renders outside `<main>` so it stays pinned below the scroll area.","Rapid prototype or internal tool where you want a branded shell with minimal topbar: skip the `topbar` prop entirely and use `logo`, `topbarLeft`, `topbarRight` to build the rail declaratively without instantiating <Topbar>.","Breadcrumb-aware shell: pass a <Breadcrumb items={\u2026}> node to AppShell's `breadcrumb` prop so the breadcrumb strip appears above all page content without each page having to render it separately."],related:["AppShell \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.","Sidebar \u2014 the canonical node to pass as AppShell's `sidebar` prop; owns activeId, collapsible submenu groups, collapsed icon-only mode, and section labels. Never hand-roll a nav list inside the sidebar slot.","Topbar \u2014 the structured topbar component to pass to AppShell's `topbar` prop when you need live product/project chip switchers, search, notifications, sidebar toggle, user avatar, or rightSlot extras. When `topbar` is provided, AppShell's logo/topbarLeft/topbarRight props are ignored.","PageContainer \u2014 the mandatory direct child inside AppShell's `children` for every page; provides title, subtitle, extra actions, breadcrumb, footer, variant (flush/narrow/ghost), and density. Never render raw content directly as AppShell's child without a PageContainer wrapper."],example:`import { AppShell, Sidebar } from "@godxjp/ui/layout";
33
+ </ResponsiveGrid>`,storyPath:"layout/ResponsiveGrid.stories.tsx",rules:[24,40]},{name:"AppShell",group:"layout",tagline:"Root application shell \u2014 composes sidebar, topbar rail, main content area, and optional footer.",props:[{name:"sidebar",type:"ReactNode",required:!0,description:"Sidebar node \u2014 typically a <Sidebar>."},{name:"children",type:"ReactNode",required:!0,description:"Main page content rendered in <main>."},{name:"topbar",type:"ReactNode",description:"Full topbar override; else a rail is built from topbarLeft/topbarRight/logo."},{name:"topbarRight",type:"ReactNode",description:"Right slot of the auto-built topbar rail (user menu, switcher)."},{name:"topbarLeft",type:"ReactNode",description:"Left slot of the auto-built topbar rail."},{name:"logo",type:"ReactNode",description:"Brand mark at the far-left of the auto-built topbar rail (e.g. an Avatar)."},{name:"sidebarCollapsed",type:"boolean",defaultValue:"false",description:"Collapse the sidebar to icon-only mode."},{name:"footer",type:"ReactNode",description:"App-level footer outside the main content area."},{name:"breadcrumb",type:"BreadcrumbProp",description:"Breadcrumb trail rendered in the topbar header for back-navigation."},{name:"mobileNav",type:"ReactNode",description:"Navigation shown in the AppShell-owned mobile drawer below `lg` (where the docked sidebar is hidden). Defaults to the `sidebar` node; pass a tailored menu, or null to opt out."},{name:"mobileNavLabel",type:"string",description:"Accessible title for the mobile navigation drawer. Defaults to localized 'Menu'."},{name:"mobileNavOpen",type:"boolean",description:"Controlled open state of the mobile drawer. Omit for AppShell-owned state."},{name:"onMobileNavOpenChange",type:"(open: boolean) => void",description:"Change handler for the mobile drawer open state."}],usage:["DO pass a <Sidebar> node to `sidebar` (required) and page content to `children` (required) \u2014 these are the only two required props. Everything else is optional and omitting optional slots simply removes that zone from the rendered DOM.","DO rely on AppShell's OWNED mobile drawer below `lg`: it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring \u2014 never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).","DO let the drawer nav own its own inset: AppShell renders `mobileNav` in a Sheet body whose inline padding is the `--app-shell-mobile-nav-inset` token (near-zero by default) instead of the generic 24px sheet chrome inset, so a <Sidebar> in the drawer is not double-padded (its own --sidebar-nav-scroll-padding already insets each row). If a custom `mobileNav` node needs the full chrome inset, set `--app-shell-mobile-nav-inset: var(--space-6)` in the service theme \u2014 never patch the drawer with a `[data-slot='sheet-body']` selector in app CSS.","DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar> to the `topbar` prop only when you need live handlers (entity switcher via productMenu, search, notifications, user avatar) \u2014 when `topbar` is provided, logo/topbarLeft/topbarRight are ignored entirely.","DO wire a single `sidebarCollapsed` boolean between AppShell's `sidebarCollapsed` prop and Sidebar's `collapsed` prop \u2014 AppShell sets `data-collapsed='true'` on the root div (which CSS reads for width transitions) but does NOT own the collapsed state itself; lift the state and pass it down to both.","DO place breadcrumb content in AppShell's `breadcrumb` prop (renders in the `app-breadcrumb` div inside `<main>` ABOVE children) \u2014 do NOT hand-roll a breadcrumb bar as the first child of children, and do NOT put breadcrumbs inside <Sidebar>.","DO NOT nest a second AppShell or AppShell inside AppShell's children \u2014 AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.","DO NOT add padding directly to children expecting it to reach the viewport edge \u2014 AppShell's `<main>` is a scroll container; use <PageContainer> (or <PageContainer.Inset> inside a flush PageContainer) inside children to get standard page padding."],useCases:["Full admin SPA shell: AppShell wraps a <Sidebar> nav rail and a <Topbar> (with productMenu entity-switcher, onSearchOpen, onNotificationsOpen, user avatar) and every Inertia page renders as children inside a <PageContainer>.","Collapsible-sidebar layout: maintain a `collapsed` boolean in a persistent Inertia layout component, pass it to both AppShell's `sidebarCollapsed` and Sidebar's `collapsed`, wire Topbar's `onToggleCollapsed` to flip it \u2014 AppShell handles the CSS transition automatically.","Multi-tenant accounting app: pass a <Topbar productMenu={<DropdownMenuContent>\u2026</DropdownMenuContent>}> to AppShell's `topbar` slot so the legal-entity chip opens an inline switcher without a modal.","App-level footer (e.g. version/build info, compliance notice): pass a <footer> node to AppShell's `footer` prop \u2014 it renders outside `<main>` so it stays pinned below the scroll area.","Rapid prototype or internal tool where you want a branded shell with minimal topbar: skip the `topbar` prop entirely and use `logo`, `topbarLeft`, `topbarRight` to build the rail declaratively without instantiating <Topbar>.","Breadcrumb-aware shell: pass a <Breadcrumb items={\u2026}> node to AppShell's `breadcrumb` prop so the breadcrumb strip appears above all page content without each page having to render it separately."],related:["AppShell \u2014 opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.","Sidebar \u2014 the canonical node to pass as AppShell's `sidebar` prop; owns activeId, collapsible submenu groups, collapsed icon-only mode, and section labels. Never hand-roll a nav list inside the sidebar slot.","Topbar \u2014 the structured topbar component to pass to AppShell's `topbar` prop when you need live product/project chip switchers, search, notifications, sidebar toggle, user avatar, or rightSlot extras. When `topbar` is provided, AppShell's logo/topbarLeft/topbarRight props are ignored.","PageContainer \u2014 the mandatory direct child inside AppShell's `children` for every page; provides title, subtitle, extra actions, breadcrumb, footer, variant (flush/narrow/ghost), and density. Never render raw content directly as AppShell's child without a PageContainer wrapper."],example:`import { AppShell, Sidebar } from "@godxjp/ui/layout";
34
34
  import { LayoutDashboard, Users } from "lucide-react";
35
35
  import { router } from "@inertiajs/react";
36
36
 
@@ -48,28 +48,42 @@ const sidebar = (
48
48
 
49
49
  export function CrmLayout({ children }: { content: React.ReactNode }) {
50
50
  return <AppShell sidebar={sidebar}>{children}</AppShell>;
51
- }`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.","DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:["Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.","Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).'],example:`import { AuthShell } from "@godxjp/ui/layout";
51
+ }`,storyPath:"layout/AppShell.stories.tsx",rules:[23]},{name:"AuthShell",group:"layout",tagline:"Centred auth/login page shell \u2014 brand bar (top) + centred card (main) + footer, over min-h-dvh, at comfortable control density.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred content \u2014 typically a single auth <Card> holding the form."},{name:"brand",type:"ReactNode",description:"Brand bar slot pinned to the top (e.g. a <Logo> / product mark)."},{name:"footer",type:"ReactNode",description:"Footer slot pinned to the bottom (legal links, locale switch, support)."},{name:"variant",type:'"default" | "canonical"',defaultValue:'"default"',description:'Visual contract for the auth surface. "canonical" applies the shared DXS compact geometry (36px controls, 22.5rem/360px card measure, responsive page insets, tighter field labels) through component tokens. "default" keeps the comfortable 44px shell with the 24rem card.'},{name:"preset",type:'"default" | "device-authorization" | "context-selection" | "account-recovery"',defaultValue:'"default"',description:'Named flow MEASURE \u2014 the page geometry contract for one canonical hosted-identity flow: the card max-width plus the desktop AND mobile page gutters, all owned by component tokens. "device-authorization" (gh#220) = a 380px card at 1440/1024 with a 5px inline page gutter at 390 (card x=5px, width=380px). "context-selection" (gh#217) = a 25rem card on desktop/tablet, edge-to-edge on mobile (0 inline gutter), plus a tokenized rhythm between the intro, the card and the trailing "remember" row. "account-recovery" (gh#233) = the 27rem/432px SCR-008 panel measure shared by the password-recovery panel (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 recovery-code \xB7 passkey-failure), with a 15px inline page gutter at 390 (panel x=15, width=360). ORTHOGONAL to `variant` (presets are applied after it), so `variant="canonical" preset="device-authorization"` keeps the canonical chrome and only re-measures. Selecting a preset REPLACES every consumer-side `--auth-shell-card-max-width` override.'},{name:"density",type:'"comfortable" | "compact"',description:'Vertical density scoped to auth-card descendants. Defaults to "compact" under variant="canonical" and "comfortable" otherwise.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the shell root."}],usage:["DO pass a single <Card> (with the form inside <CardContent>) as `children` \u2014 AuthShell centres it and constrains its width via `--auth-shell-card-max-width`; do NOT hand-roll a `.auth-shell-main` / `.ui-auth-scope` wrapper.",'DO select a `preset` instead of overriding the card width: `preset="device-authorization"` for the 380px OAuth device-grant measure, `preset="context-selection"` for the 25rem organisation/context picker that goes edge-to-edge on mobile, `preset="account-recovery"` for the 432px SCR-008 password-recovery / sign-in-MFA panel. A page-local `--auth-shell-card-max-width` override (or a forked `.auth-shell--wide` class) is the exact anti-pattern these presets replace (gh#217/gh#220/gh#233).','DO build the password-recovery and sign-in MFA CHALLENGE panels as a COMPOSITION inside `preset="account-recovery"` \u2014 there is NO PasswordRecoveryPanel and NO MfaChallengePanel component (gh#233 Gate 0): Card > CardHeader(CardTitle + CardDescription, INSIDE the bordered surface) > CardContent > AuthStack[ Alert notice \xB7 FormField fields \xB7 Button fullWidth \xB7 Flex justify="between" wrap fallback row ]. Do NOT put AuthIdentity above the panel there (it always renders the hosted mark), and NEVER reuse TwoFactorSetup \u2014 that is the ENROLLMENT dialog, not a sign-in challenge. See the `auth-recovery-panels` pattern.','DO combine `variant` and `preset` \u2014 they are orthogonal: `variant` owns control density + heading size, `preset` owns the page measure. `variant="canonical" preset="device-authorization"` is the canonical device screen.','DO let `preset="context-selection"` space the auth column: it turns the card slot into a flex column with a tokenized `--auth-shell-card-stack-gap`, so an intro (<AuthIdentity>), the choice <Card> and a trailing "remember" row pass as three siblings with NO page-local spacing.',"DO NOT add a page-local width, inset or colour to hit an artboard \u2014 if a measure is missing, it is a library gap: a new preset or token, never consumer CSS (rules #44/#45).","DO put the product/brand mark in `brand` (a <Logo> or an <Avatar>) \u2014 it renders as the top banner landmark; omit it and the banner is not rendered.","DO use `footer` for compliance/legal/support links or a locale switch \u2014 it renders as the contentinfo landmark below the card.","DO wrap the card in <Reveal> for the entrance animation (`<AuthShell><Reveal><Card/></Reveal></AuthShell>`) \u2014 Reveal honours prefers-reduced-motion; AuthShell itself stays layout-only.","DO NOT re-scope control height or heading size in the app \u2014 AuthShell already sets the comfortable control tier (44px, WCAG touch floor) and the larger auth heading via `--auth-shell-control-height` / `--auth-shell-heading-size`; a service retunes those tokens, not a bespoke class.","DO tune the COMPACT auth card through its three independent knobs, never a consumer selector on `[data-slot=\"card-content\"]` (gh#232): `--auth-shell-compact-card-inset` = the inline column, `--auth-shell-card-padding-block-compact` = the card's block (top/bottom) padding \u2014 it is wired to the card's `--card-space-shell-y` and really moves CardContent's block edges \u2014 and `--auth-shell-card-body-gap-compact` = the header\u2194body gap. Each defaults to today's canonical rhythm, so overriding one moves ONLY that axis.","DO NOT nest AuthShell inside AppShell (or vice-versa) \u2014 AuthShell is the ROOT shell for unauthenticated pages (login/mfa/passkey/device/reset); AppShell is for the authenticated app."],useCases:["Login page: <AuthShell brand={<Logo/>} footer={<AuthFooter/>}> wrapping a <Card> with the email/password form and a primary <Button fullWidth>.","MFA / passkey / device-authorisation step: same shell, a <Card> with the one-time-code <InputOTP> or a passkey prompt.",'OAuth device-grant screen (SCR-004): <AuthShell variant="canonical" preset="device-authorization"> \u2014 a 380px card at 1440/1024 and a 5px inline gutter at 390, with zero page-local CSS (gh#220).','Organisation / context selection (/select-context): <AuthShell variant="canonical" preset="context-selection" brand={<Logo mark="godx" />}> with an <AuthIdentity> intro, a <Card><CardContent flush> list of <ListRow as="li"> organisations, and a trailing "remember this choice" <Checkbox> \u2014 the preset spaces the three sections (gh#217).',"Password reset / forgot-password / accept-invite: the centred single-card flow with a brand bar and a legal footer.",'SSO landing / success confirmation: pair with an <EmptyState tone="success"> inside the card for an approved-device confirmation.'],related:["AppShell \u2014 the shell for AUTHENTICATED app pages (sidebar + topbar + main). AuthShell is its unauthenticated counterpart (brand bar + centred card + footer). Never nest the two.","Reveal \u2014 wrap the auth <Card> in <Reveal> for the entrance animation; AuthShell delegates motion (and prefers-reduced-motion handling) to it rather than baking an animation in.","Card \u2014 the canonical container for the auth form; place the form inside <CardContent>. AuthShell centres and width-constrains it.",'EmptyState \u2014 with `tone="success"` for a confirmation card inside the shell (e.g. device approved).','AuthIdentity / AuthFooter / AuthStack / AuthDivider \u2014 the auth composites that fill the shell: the GoDX identity mark + heading + requesting-client line, the mono legal footer (its `locale` slot takes an <AppSettingPicker kind="locale" appearance="labeled" compact />), the 12px section rhythm, and the labelled "or" rule.','ListRow \u2014 compose the organisation choice list for `preset="context-selection"` as <Card><CardContent flush><ul> of <ListRow as="li">: shared row dividers, no per-row card outline. There is no separate OrganizationChoiceList component \u2014 that is a composition pattern (rule #46), not a framework component.'],example:`import { AuthShell, AuthIdentity, AuthFooter } from "@godxjp/ui/layout";
52
52
  import { Reveal, Logo, Button } from "@godxjp/ui/general";
53
53
  import { Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
54
- import { Field } from "@godxjp/ui/data-entry";
54
+ import { AppSettingPicker } from "@godxjp/ui/navigation";
55
55
 
56
- export function LoginPage() {
56
+ export function DeviceAuthorizationPage() {
57
57
  return (
58
- <AuthShell brand={<Logo />} footer={<span>\xA9 2026 GodX</span>}>
58
+ // 380px card at 1440/1024 \xB7 5px inline gutter at 390 \u2014 all token-owned, no page CSS.
59
+ <AuthShell
60
+ variant="canonical"
61
+ preset="device-authorization"
62
+ brand={<Logo mark="godx" tone="success" />}
63
+ footer={
64
+ <AuthFooter
65
+ product="GoDX ID"
66
+ terms="Terms"
67
+ privacy="Privacy"
68
+ locale={<AppSettingPicker kind="locale" appearance="labeled" compact />}
69
+ />
70
+ }
71
+ >
59
72
  <Reveal>
60
73
  <Card>
61
74
  <CardHeader>
62
- <CardTitle>\u30ED\u30B0\u30A4\u30F3</CardTitle>
75
+ <AuthIdentity title="\u30C7\u30D0\u30A4\u30B9\u3092\u8A8D\u8A3C" requester="\u52E4\u6020\u7BA1\u7406\u304C\u8A8D\u8A3C\u3092\u8981\u6C42\u3057\u3066\u3044\u307E\u3059" />
76
+ <CardTitle level={2}>\u78BA\u8A8D\u30B3\u30FC\u30C9\u3092\u5165\u529B</CardTitle>
63
77
  </CardHeader>
64
78
  <CardContent>
65
- {/* form fields */}
66
- <Button fullWidth>\u7D9A\u3051\u308B</Button>
79
+ {/* InputOTP + actions */}
80
+ <Button fullWidth>\u30C7\u30D0\u30A4\u30B9\u3092\u627F\u8A8D</Button>
67
81
  </CardContent>
68
82
  </Card>
69
83
  </Reveal>
70
84
  </AuthShell>
71
85
  );
72
- }`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).","DO NOT use AuthShell for an authenticated page (it centres a narrow card VERTICALLY and has no actions slot), and DO NOT force AppShell with an empty sidebar \u2014 use CenteredShell. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell."],useCases:[`Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind="locale"/><UserMenu/></>}/>} footer={<Footer/>} width="md"> with an identity hero, an org picker, a service-launcher grid and a team list.`,"Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",'Standalone single settings page: `width="sm"` with one <Card> of <Field>s and a save action.','Service launcher / app picker after sign-in: `width="lg"` with a <ResponsiveGrid> of app cards under the brand topbar.'],related:["AppShell \u2014 the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).","AuthShell \u2014 the UNAUTHENTICATED root shell (login/mfa/reset): a narrow ~24rem card centred vertically, no actions slot. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.","Topbar \u2014 compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.","PageContainer \u2014 for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."],example:`import { CenteredShell, Topbar, Flex } from "@godxjp/ui/layout";
86
+ }`,storyPath:"layout/AuthShell.stories.tsx",rules:[23]},{name:"CenteredShell",group:"layout",tagline:"Authenticated, no-sidebar, centred-column page shell (hosted-ID My Page / account / standalone settings) \u2014 padded topbar with real actions + a width-tiered centred column, zero custom CSS.",props:[{name:"children",type:"ReactNode",required:!0,description:"Centred column content \u2014 page sections (identity hero, org picker, service grid, team list). Top-aligned and scrolls; NOT vertically centred like AuthShell's card."},{name:"topbar",type:"ReactNode",description:"Top bar slot (banner) \u2014 a <Topbar> with brand + real actions (AppSettingPicker, user menu, sign-out). Wrapped in the SAME padded chrome as AppShell's topbar (inline padding, border, backdrop) WITHOUT a sidebar; omit \u2192 no banner. Never hand-roll a bar (the bare Topbar ships no padding \u2014 the .ui-topbar zero-inset footgun)."},{name:"footer",type:"ReactNode",description:"Footer slot (contentinfo) pinned to the bottom (legal links, locale switch, support). Omit \u2192 no footer."},{name:"width",type:'"sm" | "md" | "lg"',description:"Max-width of the centred column: sm ~32rem, md (default) ~46rem, lg ~64rem \u2014 all wider than AuthShell's 24rem auth card. A service retunes each tier via --centered-shell-width-*."},{name:"align",type:'"start" | "center"',description:'Block alignment of the centred column inside the 100dvh shell. "start" (default) keeps the top-aligned flowing/scrolling page shape. "center" centres the column in the viewport \u2014 the SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice); the full-page geometry stays package-owned (--centered-shell-column-offset-block: auto) instead of a consumer writing min-h-dvh + flex centring or a className. Overflowing content still scrolls from the top, so a long localized message is never clipped.'}],usage:["DO use CenteredShell for an AUTHENTICATED page that has a topbar with actions but NO sidebar \u2014 the hosted-ID 'My Page', an account / self-service surface, a standalone settings page. It is the third shell: AppShell (needs a sidebar) \xB7 AuthShell (unauthenticated narrow card) \xB7 CenteredShell (authenticated centred column).","DO put a <Topbar start={<brand/>} end={<actions/>}/> in `topbar` \u2014 CenteredShell wraps it in the padded `.app-topbar` chrome, so you get inline padding + border + backdrop with zero custom CSS. Do NOT hand-roll a bar with raw `padding-inline` \u2014 the bare Topbar primitive ships no inset (the .ui-topbar zero-padding footgun) and content sits flush to the edge.","DO pick `width` by content: `sm` (~32rem) for a single settings form, `md` (default, ~46rem) for a My Page of stacked sections, `lg` (~64rem) for a service-launcher grid. All are wider than AuthShell's 24rem card.","DO wrap an individual section in <Reveal> for entrance motion \u2014 CenteredShell stays layout-only and delegates prefers-reduced-motion handling to Reveal (same as AuthShell).","DO NOT use AuthShell for an authenticated page (it centres a narrow card VERTICALLY and has no actions slot), and DO NOT force AppShell with an empty sidebar \u2014 use CenteredShell. Never nest it inside AppShell/AuthShell (or vice-versa); it is a ROOT shell.",'DO use `align="center"` (+ `width="sm"`) for a SYSTEM-level standalone page \u2014 a 500/503 error surface, a maintenance notice. It centres the column in the 100dvh shell at 1440/1024/390 with no consumer `min-h-dvh` / flex CSS and no className; the knob is --centered-shell-column-offset-block. There is NO ErrorSurface component: get the full 403/404/500/503 recipe from the `error-pages` pattern.'],useCases:[`Hosted GoDX ID 'My Page': <CenteredShell topbar={<Topbar start={<Brand/>} end={<><AppSettingPicker kind="locale"/><UserMenu/></>}/>} footer={<Footer/>} width="md"> with an identity hero, an org picker, a service-launcher grid and a team list.`,"Account / self-service settings surface (no admin sidebar): stacked <Card> sections (profile, security, sessions) in a centred `md` column under a topbar with a user menu.",'Standalone single settings page: `width="sm"` with one <Card> of <Field>s and a save action.','System error / maintenance page (500 \xB7 503): `<CenteredShell align="center" width="sm">` around the canonical error body (status code <Text mono tabular> + <EmptyState icon tone title description action> + optional request-ID / maintenance line). See the `error-pages` pattern.','Service launcher / app picker after sign-in: `width="lg"` with a <ResponsiveGrid> of app cards under the brand topbar.'],related:["AppShell \u2014 the shell for authenticated app pages WITH a sidebar nav rail. CenteredShell is its no-sidebar sibling (same padded topbar chrome, a centred column instead of a full-bleed main).","AuthShell \u2014 the UNAUTHENTICATED root shell (login/mfa/reset): a narrow ~24rem card centred vertically, no actions slot. CenteredShell is the AUTHENTICATED centred-page counterpart. Never nest the two.","Topbar \u2014 compose it into `topbar`; CenteredShell supplies the padded chrome the bare Topbar lacks.","PageContainer \u2014 for a titled section INSIDE the column; or compose <Card>/<ResponsiveGrid> sections directly."],example:`import { CenteredShell, Topbar, Flex } from "@godxjp/ui/layout";
73
87
  import { AppSettingPicker } from "@godxjp/ui/navigation";
74
88
  import { Avatar, AvatarFallback, Card, CardContent, CardHeader, CardTitle } from "@godxjp/ui/data-display";
75
89
  import { Button, Text } from "@godxjp/ui/general";
@@ -103,25 +117,31 @@ export function MyPage() {
103
117
  </Flex>
104
118
  </CenteredShell>
105
119
  );
106
- }`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:"Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp."},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add content: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.","DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.","DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.","DO: Render a leaf as a real link with `item.href` (a real <a>, so right-click / open-in-new-tab work) or, for a framework router <Link>, return that single element from `renderItem` \u2014 the Sidebar merges the row onto it via Slot so the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.","DO: Rely on route-synchronized group expansion \u2014 a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.","DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.","DON'T: Nest children more than one level deep \u2014 only top-level items can have children; grandchild items are not rendered."],useCases:["Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.","Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages \u2014 activeId reflects the current sub-page and the group stays open automatically.","Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.","Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.","Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections."],related:["AppShell \u2014 the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.","Topbar \u2014 the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.","PageContainer \u2014 used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."],example:`
120
+ }`,storyPath:"layout/CenteredShell.stories.tsx",rules:[23]},{name:"Sidebar",group:"layout",tagline:"Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode \u2014 never build nav manually with raw buttons.",props:[{name:"activeId",type:"string",required:!0,description:"The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches."},{name:"sections",type:"SidebarSectionProp[]",required:!0,description:"Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp."},{name:"onSelect",type:"(id: string) => void",description:"Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden."},{name:"product",type:"SidebarProductProp",description:"Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand \u2014 brand takes precedence."},{name:"onProductClick",type:"() => void",description:"Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown."},{name:"brand",type:"ReactNode",description:"Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered."},{name:"footer",type:"ReactNode",description:"Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or version info."},{name:"linkComponent",type:"SidebarLinkComponentProp",description:"THE framework-router contract (gh#213). Supply only the link ELEMENT TYPE; the library keeps composing every row \u2014 icon slot, label, badge, data-active/aria-current, the icon-only collapsed rail and its tooltip name \u2014 and passes it to the link as SidebarLinkProp children. Applied to every row that carries an href: top-level leaves, submenu children, collapsed-rail leaves and collapsed flyout entries. Build it with createSidebarLink(Link, 'to') for React Router / TanStack, createSidebarLink(Link) or inertiaSidebarLink(Link) from @godxjp/ui/inertia for Inertia / Next.js. Rows without an href keep the button + onSelect shape; a group TRIGGER stays a button because it owns aria-expanded disclosure semantics."},{name:"renderItem",type:"(item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode",description:"DEPRECATED (gh#213) \u2014 use linkComponent, or asChild on SidebarItem. Legacy escape hatch that left row CONTENT to the caller, which is how a <Link>{item.label}</Link> silently dropped every icon and badge in production. Still supported and still wins over linkComponent; rowProps now also carries the library-composed children, so spreading rowProps (or rendering rowProps.children) restores the canonical row."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Main navigation"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections \u2014 never hand-roll nav buttons alongside or instead of the Sidebar.","DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The parent item's icon is required even for groups. The group auto-opens and highlights when activeId matches any descendant.","DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop \u2014 they must stay in sync so the shell layout grid adjusts correctly.","DO: Use the footer prop for user info or status \u2014 it is pinned below the scroll area and does not scroll away.","DO: Give every navigable item an `item.href` and let the library render it. A plain `href` becomes a real <a> (context-menu open-in-new-tab, middle-click); with `linkComponent` that same href drives your framework router's <Link>. Either way the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.","DO: Wire a framework router with `linkComponent` \u2014 `createSidebarLink(Link, 'to')` (React Router / TanStack), `createSidebarLink(Link)` (Next.js), or `inertiaSidebarLink(Link)` from `@godxjp/ui/inertia`. That is the WHOLE integration: you pass the element type, the library composes the row. It threads through leaves, submenu children, the collapsed rail and the collapsed flyout. When you compose rows by hand, the same contract is `<SidebarItem item={item} asChild><Link to=\u2026 /></SidebarItem>` \u2014 write NO children; the library injects the icon, label and badge.","DON'T: Use `renderItem` in new code \u2014 it is DEPRECATED (gh#213). It hands you a className + active state and leaves the row CONTENT to you, so `renderItem={(item) => <Link href={\u2026}>{item.label}</Link>}` renders a row with NO icon and NO badge. That is the exact production regression that motivated `linkComponent`. If you must keep it, render `rowProps.children` (the library-composed row) instead of hand-writing `.sb-icon` / `.sb-label` spans, which are internal class names and not a public contract.","DO: Rely on route-synchronized group expansion \u2014 a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.","DO: Theme the nav ICON and the row/label SEPARATELY with tokens (gh#228) \u2014 the icon reads `--sidebar-nav-icon-foreground` (+ `-hover-`/`-active-`/`-disabled-` variants) and the row/label reads `--sidebar-nav-item-foreground` (+ `-hover-`/`-disabled-`). Defaults are unchanged (both = `hsl(var(--muted-foreground))`, hover/active = `hsl(var(--foreground))`), so setting `--sidebar-nav-icon-foreground: hsl(var(--foreground))` in your theme is all it takes to get canonical darker 16px icons beside muted labels. NEVER write a page-local `.sb-nav-item svg { color: \u2026 }` rule and never re-tint `--muted-foreground` globally to fix sidebar icons.","DO: Give every item an `icon` \u2014 it is required by SidebarItemProp and by the canonical rail (the collapsed mode is icon-only). An item whose data arrives without one no longer crashes the shell; it renders an EMPTY 16px icon slot so the row keeps its geometry and label column, but it reads as a hole in the rail.","DON'T: Change icon SIZE or row geometry through these colour knobs \u2014 icon size stays `--sidebar-nav-icon-size` (16px) and row geometry stays `--sidebar-nav-item-height` / `--sidebar-nav-item-gap` / `--sidebar-nav-item-padding-x`. The active row's fill/label keep `--sidebar-item-active-background` / `--sidebar-item-active-foreground`.","DON'T: Manage collapse state inside the Sidebar \u2014 it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.","DON'T: Nest children more than one level deep \u2014 only top-level items can have children; grandchild items are not rendered."],useCases:["Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.","Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages \u2014 activeId reflects the current sub-page and the group stays open automatically.","Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.","Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.","Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections."],related:["AppShell \u2014 the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.","Topbar \u2014 the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.","PageContainer \u2014 used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."],example:`
107
121
  {\`import { useState } from "react";
108
122
  import { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from "lucide-react";
109
- import { AppShell } from "@godxjp/ui/layout";
123
+ import { Link } from "react-router-dom";
124
+ import { AppShell, createSidebarLink } from "@godxjp/ui/layout";
110
125
  import { Sidebar, type SidebarSection } from "@godxjp/ui/layout";
111
126
  import { Topbar } from "@godxjp/ui/layout";
112
127
 
128
+ // The WHOLE router integration (gh#213): pass the element type, the library composes every row
129
+ // (icon \xB7 label \xB7 badge \xB7 active \xB7 collapsed rail). Inertia: inertiaSidebarLink(Link) from
130
+ // "@godxjp/ui/inertia". Next.js: createSidebarLink(Link).
131
+ const NavLink = createSidebarLink(Link, "to");
132
+
113
133
  const sections: SidebarSection[] = [
114
134
  {
115
135
  label: "Accounting",
116
136
  items: [
117
- { id: "dashboard", label: "Dashboard", icon: LayoutDashboard },
137
+ { id: "dashboard", label: "Dashboard", icon: LayoutDashboard, href: "/dashboard" },
118
138
  {
119
139
  id: "ledger",
120
140
  label: "Ledger",
121
141
  icon: BookOpen,
122
- content: [
123
- { id: "journal", label: "Journal", icon: FileText },
124
- { id: "chart-of-accounts", label: "Chart of Accounts", icon: CreditCard },
142
+ children: [
143
+ { id: "journal", label: "Journal", icon: FileText, href: "/ledger/journal" },
144
+ { id: "chart-of-accounts", label: "Chart of Accounts", icon: CreditCard, href: "/ledger/coa" },
125
145
  ],
126
146
  },
127
147
  ],
@@ -129,8 +149,8 @@ const sections: SidebarSection[] = [
129
149
  {
130
150
  label: "Administration",
131
151
  items: [
132
- { id: "users", label: "Users", icon: Users },
133
- { id: "roles", label: "Roles", icon: Shield, disabled: true },
152
+ { id: "users", label: "Users", icon: Users, href: "/users" },
153
+ { id: "roles", label: "Roles", icon: Shield, href: "/roles", disabled: true },
134
154
  ],
135
155
  },
136
156
  ];
@@ -148,6 +168,7 @@ export default function Shell() {
148
168
  collapsed={collapsed}
149
169
  onSelect={setActiveId}
150
170
  sections={sections}
171
+ linkComponent={NavLink}
151
172
  product={{ name: "CoreBooks", role: "Admin Console", color: "hsl(var(--primary))" }}
152
173
  onProductClick={() => {/* open entity switcher */}}
153
174
  footer={
@@ -171,7 +192,7 @@ export default function Shell() {
171
192
  </AppShell>
172
193
  );
173
194
  }\`}
174
- `,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`)."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself."],useCases:["Admin shell: `start` = sidebar toggle + brand mark (`Avatar`) + an entity switcher (`DropdownMenu` around a `Button`); `center` = a `Button` search trigger; `end` = `AppSettingPicker` (locale) + a notifications `Button` + a user `DropdownMenu`.","Minimal shell (no search, no notifications): pass only `start` (brand mark) and `end` (user menu). Nothing else renders \u2014 no empty chrome.","Marketing / docs header: pass `children` with a fully custom flex layout when the three-cluster model doesn't fit."],related:["AppShell \u2014 place Topbar in its `topbar` slot. AppShell also exposes its own `logo`/`topbarLeft`/`topbarRight` slots if you don't want a separate Topbar at all.",'Avatar \u2014 the brand mark for the `start` slot (use `className="rounded-md"` for a square-ish product glyph).',"AppSettingPicker \u2014 locale/theme/timezone/currency picker; the consumer drops it into `end`. Its appearance (icon-only, labelled, bordered) is configured on IT, not on Topbar.","DropdownMenu \u2014 wrap a `Button` to build an entity switcher or user menu yourself, then place it in a slot."],example:`import { Topbar, AppShell } from "@godxjp/ui/layout";
195
+ `,storyPath:"layout/Sidebar.preview.tsx",rules:[3,6,23,31]},{name:"Topbar",group:"layout",tagline:"A PURE SLOT bar for the app shell \u2014 positions three clusters (start / center / end) and owns ONLY the bar layout. It bakes NO chrome: no product switcher, no search box, no notification bell, no language picker. The CONSUMER composes those from real primitives and drops them into a slot; whether a control is icon-only / labelled / bordered is that control's own config, never the shell's.",props:[{name:"start",type:"ReactNode",defaultValue:"undefined",description:"Inline-start cluster \u2014 typically the sidebar toggle (a `Button` with a `PanelLeftClose` icon), the brand mark (an `Avatar`), and primary nav."},{name:"center",type:"ReactNode",defaultValue:"undefined",description:"Center cluster (grows + centers) \u2014 optional. e.g. a search trigger (`Button` + `Search` icon opening your command palette) or a page/entity switcher (`DropdownMenu`)."},{name:"end",type:"ReactNode",defaultValue:"undefined",description:'Inline-end cluster \u2014 settings pickers (`AppSettingPicker kind="locale"|"theme"`), a notifications `Button`, the user-menu `DropdownMenu`.'},{name:"children",type:"ReactNode",defaultValue:"undefined",description:"Escape hatch \u2014 render fully custom bar content instead of the three slots. When set, `start`/`center`/`end` are ignored."},{name:"className",type:"string",description:"Merged onto the bar element; arbitrary props (aria-*, etc.) are forwarded."}],usage:["DO compose the bar yourself: a brand mark (an `Avatar`) + sidebar toggle in `start`, a search trigger in `center`, settings pickers + notifications + user menu in `end`. The shell only positions; it never decides WHICH controls exist.",'DO build the sidebar toggle as a `Button variant="ghost" size="icon-sm"` with a `PanelLeftClose`/`PanelLeftOpen` icon and your own `t()` aria-label, wired to AppShell\'s `sidebarCollapsed`. There is no baked toggle.',"DO put a locale/theme switcher in `end` using `AppSettingPicker` (or your own control) \u2014 icon-only vs labelled, bordered vs not, is THAT component's prop, not Topbar's. Topbar does not ship or force a language picker.","DON'T look for `product`/`project`/`onSearchOpen`/`onNotificationsOpen`/`collapsed` props \u2014 they were removed. A chrome control only exists if YOU put it in a slot, so there is never a dead dropdown / empty search with nothing behind it.","DO render Topbar inside `AppShell`'s `topbar` slot (or any `<header>`). For a non-three-cluster layout, pass `children` and lay it out yourself.","DO rely on the built-in shrink contract instead of hand-tuning widths: the bar never exceeds its shell allocation, `start` shrinks first and `center` yields its whole box, each cluster CLIPS its own overflow (so a long tenant/brand string can never spill over a sibling or leak a horizontal document scroll), and `end` keeps its natural width anchored inline-end \u2014 the locale picker and user menu stay visible at 1024px with a 16rem sidebar. If a label must degrade gracefully rather than be cut, give THAT element `truncate`/`text-overflow` yourself; don't add `overflow`/`flex` overrides to the slots."],useCases:["Admin shell: `start` = sidebar toggle + brand mark (`Avatar`) + an entity switcher (`DropdownMenu` around a `Button`); `center` = a `Button` search trigger; `end` = `AppSettingPicker` (locale) + a notifications `Button` + a user `DropdownMenu`.","Minimal shell (no search, no notifications): pass only `start` (brand mark) and `end` (user menu). Nothing else renders \u2014 no empty chrome.","Marketing / docs header: pass `children` with a fully custom flex layout when the three-cluster model doesn't fit."],related:["AppShell \u2014 place Topbar in its `topbar` slot. AppShell also exposes its own `logo`/`topbarLeft`/`topbarRight` slots if you don't want a separate Topbar at all.",'Avatar \u2014 the brand mark for the `start` slot (use `className="rounded-md"` for a square-ish product glyph).',"AppSettingPicker \u2014 locale/theme/timezone/currency picker; the consumer drops it into `end`. Its appearance (icon-only, labelled, bordered) is configured on IT, not on Topbar.","DropdownMenu \u2014 wrap a `Button` to build an entity switcher or user menu yourself, then place it in a slot."],example:`import { Topbar, AppShell } from "@godxjp/ui/layout";
175
196
  import { Button } from "@godxjp/ui/general";
176
197
  import { Avatar, AvatarFallback } from "@godxjp/ui/data-display";
177
198
  import { AppSettingPicker } from "@godxjp/ui/navigation";
@@ -208,11 +229,63 @@ import { PanelLeftClose, Search } from "lucide-react";
208
229
  }
209
230
  >
210
231
  {children}
211
- </AppShell>`,storyPath:"layout/Topbar.stories.tsx",rules:[2,3,5,6]},{name:"SplitPane",group:"layout",tagline:"Two-column layout with a main content area and a fixed-width aside panel.",props:[{name:"children",type:"ReactNode",required:!0,description:"Main (left) content."},{name:"aside",type:"ReactNode",required:!0,description:"Aside (right) panel content."},{name:"asideWidth",type:'"sm" | "md"',defaultValue:'"md"',description:"Width preset for the aside column."}],usage:["DO: pass all right-panel content via the `aside` prop \u2014 it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",'DO: choose `asideWidth="sm"` for compact detail panels (filters, quick stats, key-value summaries) and the default `asideWidth="md"` for richer panels (forms, timelines, long metadata lists).',"DO: wrap SplitPane inside `PageContainer` or `PageContainer.Inset` \u2014 SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.","DON'T: expect two columns below 1080px. Below that breakpoint SplitPane stacks to a single column (main on top, aside below). Never use it for layouts that must remain side-by-side on tablet or mobile \u2014 use CSS Grid or `ResponsiveGrid` instead.","DON'T: add a CSS `overflow: hidden` or fixed height on the SplitPane wrapper; both columns carry `min-width: 0` to handle overflow correctly, and the grid uses `minmax(0, 1fr)` \u2014 adding external constraints will break the overflow contract.","DON'T: hand-roll a two-column div layout with flexbox or CSS Grid when SplitPane already ships \u2014 that duplicates the responsive breakpoint logic and the semantic `<aside>` element."],useCases:["Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (Descriptions + Timeline).",'Accounting ledger drill-down: account list on the left, chart-of-accounts metadata or running balance breakdown on the right using `asideWidth="sm"`.',"Document review workflow: PDF or rich-text viewer in `children`, approval form or annotation panel in `aside`.","Settings page with a category list or Steps navigator in `children` and a live preview or summary card in `aside`.","Kanban or task board where the main area holds the board columns and the aside shows the focused task detail without navigating away."],related:["ResponsiveGrid \u2014 use when you need more than two columns, or when both columns must have equal or percentage-based widths rather than a fixed-rem aside. SplitPane always gives main a `1fr` and aside a fixed rem width.","PageContainer \u2014 use as the outer scaffold that provides page padding and vertical rhythm; nest SplitPane inside PageContainer, not the other way around.","Sheet \u2014 use when the detail/context panel should slide in as an overlay (drawer) rather than sitting permanently beside the main content. Prefer Sheet on mobile or when the aside content is secondary and on-demand.","Flex direction='col' \u2014 use when content is purely vertical (single column, sequential sections). SplitPane is the right pick only when a persistent side panel is needed at the same hierarchy level as the main content."],example:`import { SplitPane } from "@godxjp/ui/layout";
232
+ </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:"Which region keeps the fixed track; the other one is fluid. `detail` = the canonical fluid-list + fixed detail rail. `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";
233
+
234
+ // Canonical: fluid list + fixed 320px detail rail (stacks below 40rem).
235
+ <MasterDetail
236
+ masterLabel="Teams"
237
+ detailLabel="Selected team"
238
+ detailId="team-detail"
239
+ master={<TeamTable onRowClick={select} detailId="team-detail" />}
240
+ >
241
+ <TeamDetail team={selected} />
242
+ </MasterDetail>
243
+
244
+ // Leading navigator rail instead.
245
+ <MasterDetail rail="master" railWidth="compact" masterLabel="Categories">
246
+ <SettingsForm />
247
+ </MasterDetail>
248
+
249
+ // A long real collection: bound the master so it scrolls in place and the detail
250
+ // stays near the top of a stacked mobile page.
251
+ <MasterDetail masterViewport="compact" masterLabel="Members" detailLabel="Selected member">
252
+ <MemberDetail member={selected} />
253
+ </MasterDetail>`,storyPath:"layout/MasterDetail.stories.tsx",rules:[24,40]},{name:"SplitPane",group:"layout",tagline:"Two-column layout with a main content area and a fixed-width aside panel.",props:[{name:"children",type:"ReactNode",required:!0,description:"Main (left) content."},{name:"aside",type:"ReactNode",required:!0,description:"Aside (right) panel content."},{name:"asideWidth",type:'"sm" | "md"',defaultValue:'"md"',description:"Width preset for the aside column."}],usage:["DO: pass all right-panel content via the `aside` prop \u2014 it renders inside a semantic `<aside>` element at a fixed rem width (sm=20rem, md=22rem). The `children` prop fills the main `1fr` column. Both accept any ReactNode.",'DO: choose `asideWidth="sm"` for compact detail panels (filters, quick stats, key-value summaries) and the default `asideWidth="md"` for richer panels (forms, timelines, long metadata lists).',"DO: wrap SplitPane inside `PageContainer` or `PageContainer.Inset` \u2014 SplitPane provides no page padding of its own. It is a grid primitive, not a page scaffold.","DON'T: expect two columns below 1080px. Below that breakpoint SplitPane stacks to a single column (main on top, aside below). Never use it for layouts that must remain side-by-side on tablet or mobile \u2014 use CSS Grid or `ResponsiveGrid` instead.","DON'T: add a CSS `overflow: hidden` or fixed height on the SplitPane wrapper; both columns carry `min-width: 0` to handle overflow correctly, and the grid uses `minmax(0, 1fr)` \u2014 adding external constraints will break the overflow contract.","DON'T: hand-roll a two-column div layout with flexbox or CSS Grid when SplitPane already ships \u2014 that duplicates the responsive breakpoint logic and the semantic `<aside>` element."],useCases:["Invoice / transaction detail page: list of records in `children` (DataTable), selected-record detail panel in `aside` (Descriptions + Timeline).",'Accounting ledger drill-down: account list on the left, chart-of-accounts metadata or running balance breakdown on the right using `asideWidth="sm"`.',"Document review workflow: PDF or rich-text viewer in `children`, approval form or annotation panel in `aside`.","Settings page with a category list or Steps navigator in `children` and a live preview or summary card in `aside`.","Kanban or task board where the main area holds the board columns and the aside shows the focused task detail without navigating away."],related:["ResponsiveGrid \u2014 use when you need more than two columns, or when both columns must have equal or percentage-based widths rather than a fixed-rem aside. SplitPane always gives main a `1fr` and aside a fixed rem width.","PageContainer \u2014 use as the outer scaffold that provides page padding and vertical rhythm; nest SplitPane inside PageContainer, not the other way around.","Sheet \u2014 use when the detail/context panel should slide in as an overlay (drawer) rather than sitting permanently beside the main content. Prefer Sheet on mobile or when the aside content is secondary and on-demand.","Flex direction='col' \u2014 use when content is purely vertical (single column, sequential sections). SplitPane is the right pick only when a persistent side panel is needed at the same hierarchy level as the main content."],example:`import { SplitPane } from "@godxjp/ui/layout";
212
254
 
213
255
  <SplitPane aside={<DetailPanel />} asideWidth="sm">
214
256
  <MainContent />
215
- </SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"Breadcrumb",group:"layout",tagline:"Standalone breadcrumb nav rendering an ordered trail of page segments.",props:[{name:"items",type:"BreadcrumbItemProp[]",required:!0,description:"Array of { label, to? } \u2014 omit `to` on the last (current) segment."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one Breadcrumb renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO import from `@godxjp/ui/layout` (not from a navigation or general sub-path) and pass a single `items` prop \u2014 an ordered array of `{ label, to? }` objects. No children, no sub-components, no render-prop API.",'DO omit `to` on the last (current-page) segment \u2014 the component automatically renders it as a `<span aria-current="page">` instead of a router `<Link>`. Passing `to` on the last item does NOT make it a link; drop it intentionally.',"DO pass the Breadcrumb node as a ReactNode to the `breadcrumb` prop of `AppShell` (or `AppShell`) for shell-level breadcrumbs, or to `PageContainer`'s `breadcrumb` prop (which accepts `BreadcrumbItemProp[]` directly \u2014 not a ReactNode). When passing to `PageContainer`, pass the raw array; when passing to `AppShell`, wrap it: `breadcrumb={<Breadcrumb items={\u2026} />}`.",'DON\'T hand-roll a breadcrumb strip (divs with chevrons, anchors, separators) \u2014 Breadcrumb ships the `<nav aria-label="Breadcrumb">` + `<ol>` + `aria-hidden` chevrons. Any custom trail is a violation of the no-hand-roll rule and will fail `npm run ui:audit`.',"DON'T use Breadcrumb for tab-style or step-style navigation (multi-step forms, wizard progress). Those flows belong to `Steps`. Breadcrumb is strictly a spatial location trail, not a process indicator.","The component is fully uncontrolled and stateless \u2014 it renders whatever `items` you pass. Dynamic breadcrumbs (route-derived, breadcrumb context, etc.) must be assembled in the parent and passed down as a plain array; there is no internal routing awareness."],useCases:["Per-page location trail on any admin page deeper than two levels \u2014 e.g. Home \u2192 Accounting \u2192 Invoices \u2192 Invoice #1042 \u2014 passed to `PageContainer`'s `breadcrumb` prop so it appears above the page `<h1>`.","Persistent shell-level breadcrumb in a `AppShell` or `AppShell` layout that updates as the user navigates between Inertia/React Router pages; constructed from route params and passed as a ReactNode to `AppShell`'s `breadcrumb` prop.","Master-detail drill-down in an accounting app: the detail page (journal entry, partner, bank account) shows a breadcrumb back to the list and to the domain root, giving the user a one-click escape without using the browser back button.","Embedded sub-panel breadcrumb inside a `SplitPane` or `Sheet` where a secondary content area has its own navigable hierarchy and needs a compact location indicator.","Audit log or document history page where the entity being reviewed (invoice, payment) is the current segment and the parent module (Accounting, Receivables) is a clickable ancestor.","Prefetch pairing: wrap ancestor segments' `to` values with `PrefetchLink` semantics by putting them in `items` \u2014 each non-last item with `to` is already rendered as a router `<Link>`, so hovering naturally prefetches if `PrefetchLink` is used elsewhere on the same route."],related:["PageContainer \u2014 accepts `breadcrumb` as `BreadcrumbItemProp[]` (raw array, not a ReactNode); use this when each page owns its own breadcrumb and you want it co-located with the page title, actions, and body.","AppShell \u2014 accepts `breadcrumb` as `ReactNode`; pass `<Breadcrumb items={\u2026} />` here when the breadcrumb is a persistent shell-level strip that sits above all page content rather than being owned by individual pages.","Steps \u2014 use instead of Breadcrumb when showing progress through an ordered multi-step flow (wizard, checkout, onboarding); Steps conveys sequence and completion state, not spatial location.","PrefetchLink \u2014 if ancestor breadcrumb segments should prefetch their destination query on hover/focus, consider pairing the `to` values with `PrefetchLink` in a custom breadcrumb or pre-warming the cache on mount; Breadcrumb's internal links are plain react-router-dom `<Link>` with no prefetch behaviour."],example:`import { Breadcrumb } from "@godxjp/ui/layout";
257
+ </SplitPane>`,storyPath:"layout/SplitPane.stories.tsx",rules:[24]},{name:"LegalDocumentShell",group:"layout",tagline:"Long-form legal/policy document surface (terms, privacy, DPA, cookie policy, SLA) \u2014 semantic article/nav/section landmarks, real anchors, a sticky table-of-contents rail, scroll-spy aria-current, hash deep links with a token-driven scroll offset and focus handoff. All legal text stays consumer-owned.",props:[{name:"title",type:"ReactNode",required:!0,description:"Document title \u2014 the <h1> that names the <article> (e.g. 'Terms of Service')."},{name:"sections",type:"{ id: string; title: string; content: ReactNode }[]",required:!0,description:"The document's sections in reading order. Drives BOTH the contents list and the body: `id` is the REAL anchor target (<section id> + href='#id'), `title` becomes the <h2> AND the contents label, `content` is the consumer-owned legal copy."},{name:"version",type:"string",description:"Bare version identifier (e.g. '2.4'). Rendered as a localized 'Version {version}' \u2014 never pass a pre-localized sentence."},{name:"effectiveDate",type:"string",description:"ISO 8601 calendar date (yyyy-MM-dd) or a full ISO instant. Formatted with Intl.DateTimeFormat in the active locale and emitted inside <time dateTime={effectiveDate}>, so the machine-readable value is always the ISO input. NEVER pass a pre-formatted date string."},{name:"summary",type:"ReactNode",description:"Short plain-language summary rendered under the metadata, above the contents."},{name:"contentsLabel",type:"string",description:"Accessible name + visible caption of the contents <nav>. Defaults to a localized 'Contents'. Override it when two documents share a view, so the two nav landmarks stay distinguishable (axe landmark-unique, WCAG 2.4.1)."},{name:"activeSection",type:"string",description:"Controlled active section id (the entry marked aria-current='location'). Pair with onActiveSectionChange; omit both for the uncontrolled form."},{name:"defaultActiveSection",type:"string",description:"Uncontrolled initial active section id. Defaults to the first section."},{name:"onActiveSectionChange",type:"(sectionId: string) => void",description:"Fires on contents-anchor activation, on an initial hash deep link, and continuously from the scroll spy as the reader moves through the document."},{name:"documentNavigation",type:"ReactNode",description:"Rail slot above the contents list \u2014 a switcher across the legal set (Terms \xB7 Privacy \xB7 Cookies). Rendered as a plain wrapper, so the consumer owns its semantics."},{name:"footerAction",type:"ReactNode",description:"Slot below the last section \u2014 accept / download / print / contact actions."},{name:"id",type:"string",description:"Root element id; also seeds the internal ids."},{name:"className",type:"string",description:"Root class override (rarely needed)."}],usage:["DO use LegalDocumentShell for ANY long-form legal/policy document \u2014 terms of service, privacy policy, DPA, cookie policy, SLA, EULA, security policy. It is the only primitive that owns the document behaviour: scroll-spy active section, hash deep links, scroll offset, focus handoff and reduced-motion scrolling.","DO pass `effectiveDate` as an ISO 8601 string ('2026-04-01'). The shell formats it with Intl.DateTimeFormat in the active locale and keeps the ISO value in <time dateTime>. A pre-formatted string ('April 1, 2026') is a bug \u2014 it will not localize.","DO keep ALL legal text in the consumer: the shell only receives `sections` (+ the `summary` / `documentNavigation` / `footerAction` slots). It never ships legal copy.","DO give every section a URL-safe, page-unique `id` \u2014 it is simultaneously the <section id>, the contents href, the deep-link target and the aria-current key.","DO NOT hand-roll this from CenteredShell + SplitPane + `.legal-*` CSS. The geometry is the easy half; the scroll spy, hash offset, focus handoff and aria-current wiring are what the shell exists to own, and no token can express them.","DO NOT add a consumer `className` for the measure, the rail width, the section rhythm or the dividers \u2014 every one of those is a --legal-document-* token. Dividers default to `none` (rule #44): opt in with `--legal-document-toc-border: 1px solid hsl(var(--border))`.","DO NOT expect a viewport media query: the shell owns its query container, so the one-column \u21C4 two-column split (56rem) is decided by the SHELL's own width. Below it the contents are a STATIC compact block (never pinned on a phone); above it they are a sticky rail."],useCases:['Hosted legal terms/privacy screen (SCR-005): <CenteredShell width="lg" topbar={<Topbar \u2026/>}><LegalDocumentShell title="\u5229\u7528\u898F\u7D04" version="2.4" effectiveDate="2026-04-01" contentsLabel="\u76EE\u6B21" sections={sections} activeSection={active} onActiveSectionChange={setActive} documentNavigation={<DocumentSwitcher/>} footerAction={<Button>\u540C\u610F\u3059\u308B</Button>} /></CenteredShell>',"In-app policy viewer inside a Dialog/Sheet during onboarding: the same `sections` with `footerAction` carrying the accept button; the shell stays single-column because its container is narrow.","Deep-linkable DPA / sub-processor document: link to /legal/dpa#data-retention \u2014 the shell selects, scrolls to (with the scroll offset) and focuses that section on arrival.","Multi-document legal set (Terms \xB7 Privacy \xB7 Cookies): render the switcher in `documentNavigation` so it sits above the contents in the sticky rail."],related:["CenteredShell \u2014 the page shell to put a LegalDocumentShell inside (brand bar + centred column + footer). LegalDocumentShell is the document, not the page chrome; never nest two shells of the same kind.","SplitPane \u2014 a generic main + fixed aside. It gives similar GEOMETRY but owns no scroll-spy / hash / focus behaviour, so it is the wrong pick for a document with a table of contents.","PageContainer \u2014 for a titled section inside an app page; LegalDocumentShell already renders its own document header (h1 + version + effective date + summary).","Text / Heading \u2014 compose the section `content` from these; the shell only supplies the section heading (h2) and the body wrapper."],example:`import { LegalDocumentShell, CenteredShell, Flex, Topbar } from "@godxjp/ui/layout";
258
+ import { Button, Text } from "@godxjp/ui/general";
259
+ import { useState } from "react";
260
+
261
+ export function TermsPage() {
262
+ const [activeSection, setActiveSection] = useState("acceptance");
263
+
264
+ return (
265
+ <CenteredShell width="lg" topbar={<Topbar start={<Brand />} />}>
266
+ <LegalDocumentShell
267
+ title="\u5229\u7528\u898F\u7D04"
268
+ version="2.4"
269
+ effectiveDate="2026-04-01"
270
+ summary="\u672C\u898F\u7D04\u306F\u3001\u5F53\u793E\u304C\u63D0\u4F9B\u3059\u308B\u30B5\u30FC\u30D3\u30B9\u306E\u5229\u7528\u6761\u4EF6\u3092\u5B9A\u3081\u308B\u3082\u306E\u3067\u3059\u3002"
271
+ contentsLabel="\u76EE\u6B21"
272
+ activeSection={activeSection}
273
+ onActiveSectionChange={setActiveSection}
274
+ sections={[
275
+ { id: "acceptance", title: "\u7B2C1\u6761(\u672C\u898F\u7D04\u3078\u306E\u540C\u610F)", content: <Text as="p">\u2026</Text> },
276
+ { id: "accounts", title: "\u7B2C2\u6761(\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u7BA1\u7406)", content: <Text as="p">\u2026</Text> },
277
+ ]}
278
+ documentNavigation={
279
+ <Flex direction="col" gap="xs" role="group" aria-label="\u6CD5\u7684\u6587\u66F8">
280
+ <Button variant="secondary" size="sm" fullWidth aria-current="page">\u5229\u7528\u898F\u7D04</Button>
281
+ <Button variant="ghost" size="sm" fullWidth>\u30D7\u30E9\u30A4\u30D0\u30B7\u30FC\u30DD\u30EA\u30B7\u30FC</Button>
282
+ </Flex>
283
+ }
284
+ footerAction={<Button size="sm">\u540C\u610F\u3059\u308B</Button>}
285
+ />
286
+ </CenteredShell>
287
+ );
288
+ }`,storyPath:"layout/LegalDocumentShell.stories.tsx",rules:[23,24]},{name:"Breadcrumb",group:"layout",tagline:"Standalone breadcrumb nav rendering an ordered trail of page segments.",props:[{name:"items",type:"BreadcrumbItemProp[]",required:!0,description:"Array of { label, to? } \u2014 omit `to` on the last (current) segment."},{name:"aria-label",type:"string",description:`Override the nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one Breadcrumb renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`}],usage:["DO import from `@godxjp/ui/layout` (not from a navigation or general sub-path) and pass a single `items` prop \u2014 an ordered array of `{ label, to? }` objects. No children, no sub-components, no render-prop API.",'DO omit `to` on the last (current-page) segment \u2014 the component automatically renders it as a `<span aria-current="page">` instead of a router `<Link>`. Passing `to` on the last item does NOT make it a link; drop it intentionally.',"DO pass the Breadcrumb node as a ReactNode to the `breadcrumb` prop of `AppShell` (or `AppShell`) for shell-level breadcrumbs, or to `PageContainer`'s `breadcrumb` prop (which accepts `BreadcrumbItemProp[]` directly \u2014 not a ReactNode). When passing to `PageContainer`, pass the raw array; when passing to `AppShell`, wrap it: `breadcrumb={<Breadcrumb items={\u2026} />}`.",'DON\'T hand-roll a breadcrumb strip (divs with chevrons, anchors, separators) \u2014 Breadcrumb ships the `<nav aria-label="Breadcrumb">` + `<ol>` + `aria-hidden` chevrons. Any custom trail is a violation of the no-hand-roll rule and will fail `npm run ui:audit`.',"DON'T use Breadcrumb for tab-style or step-style navigation (multi-step forms, wizard progress). Those flows belong to `Steps`. Breadcrumb is strictly a spatial location trail, not a process indicator.","The component is fully uncontrolled and stateless \u2014 it renders whatever `items` you pass. Dynamic breadcrumbs (route-derived, breadcrumb context, etc.) must be assembled in the parent and passed down as a plain array; there is no internal routing awareness."],useCases:["Per-page location trail on any admin page deeper than two levels \u2014 e.g. Home \u2192 Accounting \u2192 Invoices \u2192 Invoice #1042 \u2014 passed to `PageContainer`'s `breadcrumb` prop so it appears above the page `<h1>`.","Persistent shell-level breadcrumb in a `AppShell` or `AppShell` layout that updates as the user navigates between Inertia/React Router pages; constructed from route params and passed as a ReactNode to `AppShell`'s `breadcrumb` prop.","Master-detail drill-down in an accounting app: the detail page (journal entry, partner, bank account) shows a breadcrumb back to the list and to the domain root, giving the user a one-click escape without using the browser back button.","Embedded sub-panel breadcrumb inside a `SplitPane` or `Sheet` where a secondary content area has its own navigable hierarchy and needs a compact location indicator.","Audit log or document history page where the entity being reviewed (invoice, payment) is the current segment and the parent module (Accounting, Receivables) is a clickable ancestor.","Prefetch pairing: wrap ancestor segments' `to` values with `PrefetchLink` semantics by putting them in `items` \u2014 each non-last item with `to` is already rendered as a router `<Link>`, so hovering naturally prefetches if `PrefetchLink` is used elsewhere on the same route."],related:["PageContainer \u2014 accepts `breadcrumb` as `BreadcrumbItemProp[]` (raw array, not a ReactNode); use this when each page owns its own breadcrumb and you want it co-located with the page title, actions, and body.","AppShell \u2014 accepts `breadcrumb` as `ReactNode`; pass `<Breadcrumb items={\u2026} />` here when the breadcrumb is a persistent shell-level strip that sits above all page content rather than being owned by individual pages.","Steps \u2014 use instead of Breadcrumb when showing progress through an ordered multi-step flow (wizard, checkout, onboarding); Steps conveys sequence and completion state, not spatial location.","PrefetchLink \u2014 if ancestor breadcrumb segments should prefetch their destination query on hover/focus, consider pairing the `to` values with `PrefetchLink` in a custom breadcrumb or pre-warming the cache on mount; Breadcrumb's internal links are plain react-router-dom `<Link>` with no prefetch behaviour."],example:`import { Breadcrumb } from "@godxjp/ui/layout";
216
289
 
217
290
  <Breadcrumb items={[
218
291
  { label: "\u30DB\u30FC\u30E0", to: "/" },
@@ -232,14 +305,15 @@ import { Trash2 } from "lucide-react";
232
305
  <Text size="xs" mono tone="muted">RC-204881</Text>`},{name:"Heading",group:"general",tagline:"Section heading sized from the --heading-h* tokens. `level` sets the size AND the semantic <h1..h4>.",props:[{name:"level",type:"1 | 2 | 3 | 4",defaultValue:"2",description:"Heading level \u2014 sizes from --heading-h{1..4} and renders the matching <h*>."},{name:"as",type:'"h1" | "h2" | "h3" | "h4" | "div"',description:"Override the rendered element (e.g. a visual h2 that is a real <h1>)."},{name:"tone",type:'"default" | "muted" | "primary" | "success" | "warning" | "destructive" | "info"',defaultValue:'"default"',description:"Semantic foreground colour."},{name:"align",type:'"start" | "center" | "end"',description:"Logical text alignment."},{name:"truncate",type:"boolean",description:"Single-line ellipsis."}],usage:['DO use `<Heading level>` for section titles instead of a raw `<h2 className="text-lg font-semibold">`. The level drives both the token size and the semantic element.',"Inside a Card use `<CardTitle>`; use `<Heading>` for free-standing page/section headings not covered by a component slot."],useCases:["A section heading on a dashboard: `<Heading level={3}>\u4ECA\u6708\u306EKPI</Heading>`.",'A visually-smaller heading that must stay an <h1> for a11y: `<Heading level={1} as="h1">\u2026</Heading>`.'],storyPath:"general/typography.tsx",rules:[6,23],example:`import { Heading } from "@godxjp/ui/general";
233
306
 
234
307
  <Heading level={2}>\u8ACB\u6C42\u66F8\u4E00\u89A7</Heading>
235
- <Heading level={3} tone="muted">\u88DC\u8DB3\u30BB\u30AF\u30B7\u30E7\u30F3</Heading>`},{name:"Logo",group:"general",tagline:"The product brand-mark box \u2014 a glyph on the primary fill. Tokenised size/radius/type; the fill reads --primary so a re-theme re-tints it. Decorative by default; pair with a wordmark.",props:[{name:"glyph",type:"React.ReactNode",defaultValue:'"g"',description:"The brand glyph \u2014 a short mark (letter/initials) or a custom inline <svg>."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Box size tier (tokenised)."},{name:"label",type:"string",description:"Accessible name. Set \u2192 exposed as a named image (role img); omitted \u2192 decorative (aria-hidden), the correct default when a readable wordmark sits beside it."}],usage:['DO import from `@godxjp/ui/general`: `import { Logo } from "@godxjp/ui/general";`','DO use Logo INSTEAD of hand-rolling `<span aria-hidden className="grid size-7 place-items-center rounded-md bg-primary text-sm font-bold text-primary-foreground">g</span>` \u2014 that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46).',"DO leave `label` unset when a readable wordmark sits beside the mark (shell header, topbar) \u2014 the mark stays decorative and the wordmark carries the accessible name. Set `label` only when the mark stands alone.","DON'T pass more than 1\u20132 glyphs \u2014 the box is square and centres its content; a long string overflows. For a full wordmark use `Text`/`Heading` beside the Logo, not inside it.",'DON\'T re-tint via `className="bg-*"` \u2014 the fill reads the `--primary` role token; retune it through a service theme (`--primary`, `--logo-radius`, `--logo-size-*`), not utilities.'],useCases:['App-shell header brand lockup \u2014 `<Logo glyph="c" /> <Text weight="medium">CoreBooks</Text>` in the sidebar/topbar, mark decorative, wordmark readable.','Auth screen \u2014 a standalone labelled mark above the sign-in form: `<Logo label="CoreBooks" size="lg" />`.','Tenant/workspace switcher row \u2014 a small `size="sm"` mark as the leading slot of a ListRow or menu item.',"Custom SVG brand \u2014 pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter."],related:["Text / Heading \u2014 use for the readable wordmark BESIDE the Logo (the full lockup); the Logo is only the square mark, not the product name.","Avatar \u2014 use Avatar for a PERSON/entity image or initials; use Logo for the PRODUCT brand mark. They look similar (square/rounded glyph) but carry different meaning."],storyPath:"general/Logo.stories.tsx",rules:[45,46],example:`import { Logo } from "@godxjp/ui/general";
236
- import { Text } from "@godxjp/ui/general";
308
+ <Heading level={3} tone="muted">\u88DC\u8DB3\u30BB\u30AF\u30B7\u30E7\u30F3</Heading>`},{name:"Logo",group:"general",tagline:"The product brand mark \u2014 a glyph/identity box, or (with `wordmark`) the full mark + wordmark LOCKUP. Tokenised size/radius/type/gap; the boxed fill reads --primary, while the GoDX identity mark and wordmark read the --success identity role instead, so an action-colour re-theme never recolours the brand.",props:[{name:"glyph",type:"React.ReactNode",defaultValue:'"g"',description:'The brand glyph \u2014 a short mark (letter/initials) or a custom inline <svg>. Only read when mark="glyph".'},{name:"mark",type:'"glyph" | "godx"',defaultValue:'"glyph"',description:'Semantic mark artwork. "godx" renders THE CANONICAL GoDX IDENTITY MARK as an inline vector owned by the package \u2014 use it for hosted-identity surfaces (AuthShell brand bar, AuthIdentity, CenteredShell topbar); do NOT re-draw or import a brand SVG in the app. Its box + colour are tokenized (--logo-godx-size, --logo-godx-color, default the --success role) and it drops the boxed fill/radius. "glyph" keeps the configurable boxed-glyph treatment.'},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:'Box size tier (tokenised). Applies to the boxed glyph; mark="godx" sizes from --logo-godx-size.'},{name:"tone",type:'"primary" | "success"',defaultValue:'"primary"',description:`Semantic fill role. "success" gives the canonical green identity mark without re-tinting the application's primary action colour (it reads --logo-success-background / --logo-success-foreground, independent of --primary).`},{name:"wordmark",type:"React.ReactNode",description:'Readable product name rendered BESIDE the mark as ONE lockup \u2014 pass the localized product name (or an inline <svg> logotype when a real asset exists). Set it INSTEAD of hand-rolling `inline-flex items-center gap-2` around a Logo and a Text. The lockup root takes ref/className/\u2026props; the mark becomes decorative and the wordmark text carries the accessible name, so the pair is announced once. Colour/face/weight/tracking/size and the mark\u2194wordmark gap are tokens (--logo-wordmark-*); on mark="godx" / tone="success" the wordmark is brand-green from the identity role and NEVER reads --primary. Omit for the bare mark (unchanged behaviour).'},{name:"label",type:"string",description:"Accessible name. Set \u2192 exposed as a named image (role img); omitted \u2192 decorative (aria-hidden), the correct default when a readable wordmark sits beside it. With `wordmark` set, `label` overrides the lockup's name (the wordmark text is otherwise the name)."}],usage:['DO import from `@godxjp/ui/general`: `import { Logo } from "@godxjp/ui/general";`','DO use Logo INSTEAD of hand-rolling `<span aria-hidden className="grid size-7 place-items-center rounded-md bg-primary text-sm font-bold text-primary-foreground">g</span>` \u2014 that repeats literal size/radius and puts type utilities on a bare span (rules #45/#46).',"DO leave `label` unset when a readable wordmark sits beside the mark (shell header, topbar) \u2014 the mark stays decorative and the wordmark carries the accessible name. Set `label` only when the mark stands alone.","DON'T pass more than 1\u20132 glyphs \u2014 the box is square and centres its content; a long string overflows. The product NAME goes in `wordmark`, never in `glyph`.",'DO use `wordmark` for the full lockup: `<Logo mark="godx" tone="success" wordmark="GoDX" />`. It replaces the hand-rolled `<span className="inline-flex items-center gap-2"><Logo/><Text/></span>` \u2014 the gap, face, weight, tracking, per-tier size and the brand colour are all tokens, so a shell header / auth brand bar needs no page CSS (gh#214).',"NOTE: the package ships NO wordmark ARTWORK \u2014 `wordmark` typesets the name in the design-system display face (`--logo-wordmark-font-family`). When design supplies a real logotype, pass it as an inline `<svg>` node to `wordmark`; do not approximate letterforms in CSS.",'DON\'T re-tint via `className="bg-*"` \u2014 the fill reads the `--primary` role token; retune it through a service theme (`--primary`, `--logo-radius`, `--logo-size-*`), not utilities.',"DO use `mark=\"godx\"` for the canonical GoDX identity mark on hosted-identity screens \u2014 it is ALREADY in the package as real inline vector artwork. DON'T pass a hand-drawn brand SVG as `glyph`, and don't ship a brand asset in the app, to reproduce it."],useCases:['App-shell header brand lockup \u2014 `<Logo glyph="c" wordmark="CoreBooks" />` in the sidebar/topbar: mark decorative, wordmark readable, spacing tokenized.','Auth screen \u2014 a standalone labelled mark above the sign-in form: `<Logo label="CoreBooks" size="lg" />`.','Tenant/workspace switcher row \u2014 a small `size="sm"` mark as the leading slot of a ListRow or menu item.',"Custom SVG brand \u2014 pass an inline `<svg>` as `glyph` to render a real logomark on the primary fill instead of a letter.",'Hosted GoDX identity surface \u2014 `<Logo mark="godx" tone="success" />` in an AuthShell `brand` bar or inside <AuthIdentity>: the canonical GoDX mark the package already owns. There is no separate identity-mark component and no asset to import.','Brand lockup in a shell header / auth brand bar \u2014 `<Logo mark="godx" tone="success" wordmark="GoDX" />`: one element, brand-green, no wrapper div and no page CSS.'],related:["AuthIdentity (@godxjp/ui/layout) \u2014 the canonical auth heading block; it already renders the GoDX mark, so don't add a second Logo above it.","Text / Heading \u2014 only for a bespoke lockup the `wordmark` prop cannot express; for the ordinary mark + product name, use `wordmark` (it owns the gap, face and brand colour as tokens).","Avatar \u2014 use Avatar for a PERSON/entity image or initials; use Logo for the PRODUCT brand mark. They look similar (square/rounded glyph) but carry different meaning."],storyPath:"general/Logo.stories.tsx",rules:[45,46],example:`import { Logo } from "@godxjp/ui/general";
237
309
 
238
- <span className="inline-flex items-center gap-2">
239
- <Logo glyph="c" />
240
- <Text weight="medium">CoreBooks</Text>
241
- </span>
310
+ // Full lockup \u2014 mark + wordmark as ONE element (no wrapper, no page CSS).
311
+ <Logo glyph="c" wordmark="CoreBooks" />
242
312
 
313
+ // Canonical brand-green GoDX lockup \u2014 identity role, independent of --primary.
314
+ <Logo mark="godx" tone="success" wordmark="GoDX" />
315
+
316
+ // Bare mark standing alone \u2192 give it an accessible name.
243
317
  <Logo label="CoreBooks" size="lg" />`},{name:"Reveal",group:"general",tagline:"The official entrance-motion primitive (staggered fade-up) \u2014 reads DS motion tokens and honours prefers-reduced-motion, replacing hand-rolled @keyframes + .app-reveal/.d1..d6 classes.",props:[{name:"children",type:"ReactNode",required:!0,description:"Content to reveal on enter."},{name:"delay",type:"0 | 1 | 2 | 3 | 4 | 5 | 6",defaultValue:"0",description:"Stagger ordinal \u2014 an INDEX into the motion ladder, never a raw ms. Each step adds one `--reveal-stagger-step` of delay so a column of reveals cascades. 0 = enter immediately."},{name:"asChild",type:"boolean",defaultValue:"false",description:"Merge the reveal onto the single child element (no wrapper <div>) \u2014 use when an extra box would break a grid/flex layout."}],usage:["DO use <Reveal> INSTEAD of hand-rolling `@keyframes auth-fade-up` + `.app-reveal` + `.d1..d6` in a consumer global.css \u2014 that repeats literal durations/delays and violates the tokens-only rule. Reveal reads `--duration-slow` / `--ease-emphasized` / `--reveal-distance` / `--reveal-stagger-step`.","DO stagger a list/column by passing an increasing `delay` (1, 2, 3\u2026) to successive siblings \u2014 the ordinal maps to `--reveal-stagger-step`, so a service retunes the cascade rhythm from one token.","DO pass `asChild` when wrapping an element that must keep its own box in a grid/flex row (the reveal merges onto that element instead of adding a <div>).","DO rely on the built-in reduced-motion behaviour \u2014 under `prefers-reduced-motion: reduce` the animation is dropped and content renders in its final, fully-visible position with no layout shift. Never gate visibility on the animation.","DO NOT set a raw ms delay or a literal translate distance \u2014 the whole point is that `delay` is a controlled ordinal and the distance/duration come from tokens."],useCases:["Auth card entrance: `<AuthShell><Reveal><Card/></Reveal></AuthShell>` \u2014 the sign-in card fades up on load, respecting reduced-motion.","Staggered dashboard: map stat cards with `<Reveal delay={i + 1}>` so the row cascades in.","Section reveal on a settings/detail page \u2014 wrap each Card in <Reveal> for a calm entrance without hand-written CSS.","asChild on a grid item: `<Reveal asChild delay={2}><ResponsiveGrid.Item/></Reveal>` keeps the grid cell intact while animating it in."],related:["AuthShell \u2014 pairs with Reveal for the auth card entrance; AuthShell delegates all motion to Reveal.","Card \u2014 the most common thing to wrap in <Reveal> (dashboard cards, auth card, settings sections).","ResponsiveGrid \u2014 combine with `<Reveal delay={n}>` (or `asChild`) per grid item for a staggered grid reveal."],example:`import { Reveal } from "@godxjp/ui/general";
244
318
  import { Card, CardContent } from "@godxjp/ui/data-display";
245
319
 
@@ -253,7 +327,7 @@ import { Card, CardContent } from "@godxjp/ui/data-display";
253
327
  <Reveal key={item.id} delay={Math.min(i + 1, 6) as 1 | 2 | 3 | 4 | 5 | 6}>
254
328
  <Card><CardContent>{item.label}</CardContent></Card>
255
329
  </Reveal>
256
- ))}`,storyPath:"general/Reveal.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). This is the single table component \u2014 the former DataGrid has been merged in and removed. Never hand-roll a data.length===0 guard around it.",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end' }. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). DataTable dev-warns when a column has an empty header and no ariaLabel."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.","Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries.","Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
330
+ ))}`,storyPath:"general/Reveal.stories.tsx",rules:[]},{name:"DataTable",group:"data-display",tagline:"The one TanStack-powered compound admin list \u2014 sticky header, sorting, global search, column visibility ('set view'), bulk selection, BOTH cursor and numbered pagination, density, and built-in empty/loading states. Keep the SIMPLE `data` + lean `columns` (ColumnDef) API for the common case; opt into the full grid chrome via the compound parts. Internally driven by @tanstack/react-table (a real dependency). Lives on @godxjp/ui/data-display only (it is NOT on the runtime-neutral root/admin barrel because it pulls TanStack). This is the single table component \u2014 the former DataGrid has been merged in and removed. Never hand-roll a data.length===0 guard around it.",props:[{name:"data",type:"T[]",required:!0,description:"Array of row data. When empty and loading is false, a built-in EmptyState renders automatically inside the table body \u2014 no external guard needed."},{name:"columns",type:"ColumnDef<T>[]",required:!0,description:"Lean column definitions (adapted to TanStack internally). Each column: { key: string; header: ReactNode; ariaLabel?: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string; align?: 'left'|'center'|'right'; hiddenOnMobile?: boolean; enableHiding?: boolean; pin?: 'end' }. If render is omitted, the raw value at row[key] is rendered as a string. sortable opts the column into the sort cycle (client-side by default, or server-side via sort+onSortChange). enableHiding (default true) lists the column in DataTable.ViewOptions; set false to keep a key/actions column always visible. pin:'end' sticks the column (typically row actions) to the inline-end edge on horizontal scroll with a separating shadow \u2014 pin at most one column. ariaLabel gives a VISUALLY-EMPTY header (header='' \u2014 an action or selection column) a screen-reader name (e.g. 'Actions'/'Select'): it renders as an sr-only label inside the <th> so the column is never nameless (axe: empty-table-header). DataTable dev-warns when a column has an empty header and no ariaLabel."},{name:"getRowId",type:"(row: T) => string",defaultValue:"(row) => String(row.id)",description:"Extracts a stable unique string key per row. Required when selectable is true or rows lack an 'id' field. Falls back to row.id cast to string."},{name:"selectable",type:"boolean",defaultValue:"false",description:"Adds a checkbox column and a SelectAll header checkbox. Use with selected + onSelectChange for controlled selection, or omit both for uncontrolled."},{name:"selected",type:"Set<string>",description:"Controlled set of selected row IDs. Pair with onSelectChange. Omit for uncontrolled."},{name:"onSelectChange",type:"(next: Set<string>) => void",description:"Called with the full new selection set after any checkbox interaction."},{name:"onRowClick",type:"(row: T) => void",description:"Makes rows clickable for navigation. Row click is suppressed when the user clicks an interactive descendant (button, a, input, select, textarea, [role=menuitem])."},{name:"density",type:"'compact' | 'default' | 'comfortable'",defaultValue:"'compact'",description:"Controlled row density across all three tiers (compact 28 / default 36 / comfortable 48) \u2014 drive it from a \u8868\u793A\u5BC6\u5EA6 radio. Omit to let DataTable manage it internally (DataTable.DensityToggle flips compact\u2194comfortable)."},{name:"onDensityChange",type:"(density: 'compact' | 'default' | 'comfortable') => void",description:"Called when the user toggles density. Only needed when density is controlled."},{name:"striped",type:"boolean",defaultValue:"false",description:"Zebra-stripe the body rows (even rows get a subtle muted fill)."},{name:"hoverable",type:"boolean",defaultValue:"false",description:"Highlight a row on hover even when it is not clickable. onRowClick already implies hover; use this for read-only tables that still want the hover affordance."},{name:"stickyHeader",type:"boolean",defaultValue:"true",description:"Pin the header to the top while the body scrolls (\u30D8\u30C3\u30C0\u8FFD\u5F93). Set false to let it scroll away with the rows."},{name:"sort",type:"{ key: string; direction: 'asc' | 'desc' }",description:"Active sort state (controlled/server surface). When provided alongside onSortChange, sortable columns show directional arrow icons and clicking the active column twice clears sort (calls onSortChange(undefined)). Omit both sort and onSortChange to sort client-side via TanStack."},{name:"onSortChange",type:"(sort: { key: string; direction: 'asc' | 'desc' } | undefined) => void",description:"Called when a sortable column header is clicked. Receives undefined when sort is cleared (third click on same column). Providing sort or onSortChange opts into the controlled (server) sort surface; omit both and the table sorts client-side via TanStack."},{name:"globalFilter / onGlobalFilterChange",type:"string / (next: string) => void",description:"Global search term surfaced by DataTable.Search. Omit both for client-side filtering; pass them to drive a server query (with manualFiltering)."},{name:"pagination / onPaginationChange / rowCount",type:"{ pageIndex: number; pageSize: number } / OnChangeFn / number",description:"Numbered-pagination state surfaced by DataTable.Pagination (page-size form). For server pagination pass all three (rowCount = total) with manualPagination; omit for client pagination."},{name:"columnVisibility / onColumnVisibilityChange",type:"VisibilityState / OnChangeFn<VisibilityState>",description:"Column show/hide state surfaced by DataTable.ViewOptions ('set view'). Internal if omitted."},{name:"manualSorting / manualFiltering / manualPagination",type:"boolean",defaultValue:"false",description:"Default false so the simple data+columns case sorts/filters/paginates in-browser. Set the relevant flag true and drive the matching state from your query for server-side behaviour."},{name:"loading",type:"boolean",defaultValue:"false",description:"When true, swaps the body for SHAPED skeleton rows rendered inside the table's own grid (one border, aligned columns) \u2014 never a separate <SkeletonTable> in a Card (that double-borders). With React Query keepPreviousData, drive this off isPlaceholderData (pagination/search) || isLoading (first load), NOT isLoading alone. Suppresses the empty state while true."},{name:"empty",type:"ReactNode",description:"Custom content rendered inside the table body when data is empty and loading is false. Defaults to a built-in EmptyState with a localised 'No data' message. Pass a custom <EmptyState title='...' description='...' action={...}/> to tailor the message."},{name:"error",type:"ReactNode",description:"FAILURE state (gh#216). Pass error={isError} \u2014 `true` renders the built-in localized destructive EmptyState ('Couldn't load this list') announced with role='alert'; any other node REPLACES that copy (e.g. an <Alert> carrying an error code + request id). `false`/`undefined` means the read succeeded. NEVER pass a raw Error object (it is not renderable). Suppresses the empty state."},{name:"denied",type:"ReactNode",description:"PERMISSION-DENIED state (gh#216) \u2014 the read was REFUSED (403), not failed. `true` renders the built-in localized warning EmptyState ('You don't have access to this list') with NO retry, announced politely (aria-live) because a permission boundary is expected information, not a fault. Takes precedence over `error`. Any other node replaces the copy."},{name:"onRetry",type:"() => void",description:"Retry handler surfaced as a Retry button inside the BUILT-IN error state only. Omit it to render the error without a retry affordance; it is intentionally never offered for `denied` (repeating a 403 cannot succeed)."},{name:"className",type:"string",description:"Extra classes applied to the root wrapper div (ui-data-table-root)."},{name:"children",type:"ReactNode",description:"Compound sub-parts: DataTable.Toolbar, DataTable.Search (global filter), DataTable.ViewOptions (column show/hide), DataTable.SelectAll, DataTable.BulkActions (ReactNode children OR a (count)=>node render-prop), DataTable.DensityToggle, DataTable.Pagination (cursor first/next when given cursor+hasMore+onChange, else numbered page-size form), DataTable.RowActions (kebab trigger), DataTable.Content. If no DataTable.Content is present in children, one is auto-rendered."}],usage:["DO pass loading={isFetching} during data fetches \u2014 it renders a loading row in the table body and suppresses the empty state. Never show a spinner outside DataTable while the table is visible.","DO NOT add a data.length===0 conditional around DataTable. When data is empty and loading is false, the built-in EmptyState renders automatically. Pass empty={<EmptyState title='...'/>} only when you need a custom message.","SIX STATES, ZERO HAND-ROLLING (gh#216): loading (`loading`), empty (automatic / `empty`), error (`error` + optional `onRetry`), denied (`denied`), pagination (`DataTable.Pagination`), row actions (`DataTable.RowActions`). Wire them straight off the query \u2014 `<DataTable loading={isPending} error={isError} denied={status === 403} onRetry={refetch} \u2026/>` \u2014 and never branch the page around the table to render your own alert/empty/forbidden block. Precedence is loading > denied > error > empty > rows, so exactly one state ever shows.","DO provide getRowId when selectable is true or when rows do not have a string/number 'id' field \u2014 the default falls back to row.id and silently returns '' for missing IDs, which breaks selection.","DO use DataTable.Toolbar as the immediate child that wraps search/filter controls on the left and DataTable.DensityToggle/action buttons on the right. DataTable.BulkActions inside the toolbar auto-hides when selection count is 0; it accepts either plain ReactNode children (built-in 'N selected' status bar) or a (count)=>node render-prop (you own the whole bar).","DO reach for the grid chrome (DataTable.Search, DataTable.ViewOptions, DataTable.Pagination pageSizeOptions) when you need global search, a column 'set view' picker, or numbered pagination \u2014 these are the merged former-DataGrid features, now on the one DataTable. Drive them client-side by default; pass the matching state + manual* flag for a server query.","DO use ColumnDef.render for custom cell content (Badge, Link, RowActions). For plain string/number fields render can be omitted \u2014 DataTable falls back to String(row[key]).","DO give every visually-empty column an accessible header via `ariaLabel` \u2014 a row-actions column (`header: ''`, `pin: 'end'`) sets `ariaLabel: t('actions')`, so screen readers announce the column and axe reports no `empty-table-header`. DataTable dev-warns any column that renders a `<th>` with neither visible text nor an `ariaLabel`. The selection column added by `selectable` is already named by its SelectAll checkbox \u2014 no `ariaLabel` needed there.","COLUMN SEMANTICS + KEYBOARD: a `sortable` header renders as a real <button> inside the <th> with `aria-sort` (ascending/descending/none) on the <th>; it is Tab-reachable and toggles asc \u2192 desc \u2192 cleared on Enter/Space/click. A selection column exposes a header 'select all' Checkbox (indeterminate when a subset is selected) and a per-row Checkbox, each keyboard-operable with Space. An action column is visually empty but carries an `ariaLabel`; its per-row controls (kebab menu / buttons) own their own accessible names and keyboard behavior. Row click (`onRowClick`) is suppressed when the user activates an interactive descendant.","DO NOT nest DataTable.Content in a conditional \u2014 it is already guarded internally. If you need to override the table body slot, drop exactly one <DataTable.Content /> in children; DataTable auto-detects it by displayName and skips the default."],useCases:["Admin list pages (invoices, customers, orders, accounts) where rows are clickable for detail navigation via onRowClick.","Bulk-action workflows (e.g. mark invoices paid, export selected rows) \u2014 use selectable + DataTable.BulkActions to show contextual action buttons only when something is selected.","Server-side sorted tables: pass sort + onSortChange and update the data prop after the API call; DataTable renders asc/desc/neutral icons on the header automatically.","Cursor-paginated lists: add DataTable.Pagination with cursor + hasMore + onChange inside children to get First/Next navigation without offset arithmetic. For page-size + numbered prev/next instead, use DataTable.Pagination with pageSizeOptions (no cursor/onChange) driven by the internal TanStack pagination.","Full grid screens (global search + column 'set view' + numbered pagination): compose DataTable.Search, DataTable.ViewOptions, DataTable.DensityToggle in the toolbar and DataTable.Pagination pageSizeOptions={[\u2026]} \u2014 client-side by default, or server-side by passing globalFilter/pagination/sort state with the matching manual* flag.","Responsive admin tables where lower-priority columns (e.g. internal IDs, dates) should collapse below mobile breakpoints \u2014 set hiddenOnMobile: true on those ColumnDef entries.","Loading skeletons during initial page load or filter change: set loading={true} alongside an empty data={[]} to show the loading row without flashing an empty state."],related:["Table \u2014 raw primitive (TableHeader/TableBody/TableRow/TableCell). Use DataTable instead; only reach for Table directly when you need a non-standard layout that DataTable cannot express.","SkeletonTable \u2014 standalone skeleton placeholder rendered before any DataTable mounts (e.g. in a Suspense fallback or deferred-prop skeleton slot). DataTable.loading covers in-table loading; SkeletonTable covers pre-mount skeletons.","EmptyState \u2014 standalone empty state for non-table lists. DataTable already embeds EmptyState in its body; only use bare EmptyState for card content, non-tabular lists, or zero-state pages outside a DataTable.","LineChart / BarChart / AreaChart / PieChart (@godxjp/ui/charts) \u2014 when the SHAPE or trend of aggregated data matters more than exact per-row figures, visualize it with a chart instead of (or alongside) the table; keep DataTable when users need to read, sort, or act on individual rows.","DataState / InfiniteQueryState \u2014 TanStack Query lifecycle widgets from @godxjp/ui/query. Prefer these over DataTable when your list is driven by useQuery/useInfiniteQuery and you want automatic skeleton/empty/error handling at the query level rather than at the table level."],example:`import { useState } from "react";
257
331
  import { Badge, DataTable, type ColumnDef } from "@godxjp/ui/data-display";
258
332
  import { EmptyState } from "@godxjp/ui/data-display";
259
333
 
@@ -321,7 +395,7 @@ export default function InvoiceList({
321
395
  </DataTable.Toolbar>
322
396
  </DataTable>
323
397
  );
324
- }`,storyPath:"data-display/DataTable.stories.tsx",rules:[24,31,35,37]},{name:"Card",group:"data-display",tagline:'Surface container with optional accent stripe, variant fill, size, and density. \u26A0\uFE0F The bare <Card> has NO inner padding \u2014 body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className="p-4"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For a tab/toolbar/filter strip (view tabs, list controls) use <CardBar extra={\u2026}> \u2014 a positionable bar that auto-draws its separator from its position (top\u2192bottom border, bottom\u2192top border, middle\u2192both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. Never hand-roll a bordered div for this.',props:[{name:"accent",type:'"primary" | "success" | "warning" | "info" | "attention" | "destructive"',description:"3px left-edge semantic accent stripe."},{name:"variant",type:'"default" | "muted" | "outline" | "featured"',defaultValue:'"default"',description:"Surface fill style."},{name:"size",type:'"md" | "compact"',defaultValue:'"md"',description:"Card size preset."},{name:"density",type:'"tight" | "cozy"',description:"Internal padding density (base 16 / tight 12 / cozy 20)."}],usage:['DO always wrap body content in <CardContent> \u2014 the bare <Card> div has zero inner padding; content renders flush against card edges without it. Never add className="p-4" directly on <Card> as a substitute.',"DO put titles/descriptions in <CardHeader>/<CardTitle>/<CardDescription>. Use <CardHeader banded> for a visually separated muted-background header band (mirrors <CardFooter separated>). Pair with <CardAction> inside a flex-row CardHeader for header-level action buttons.",'DO set <CardTitle level={n}> to keep a valid document outline (h1 \u2192 h2 \u2192 h3, no skipped levels): CardTitle renders <h3> by default, so a section card directly under a page <h1> needs level={2}. Pick the level by OUTLINE position, NEVER for visual size \u2014 the title size is fixed by tokens and does not change with level. When the card title is a styled label rather than a section heading, use <CardTitle as="p"> so it is not announced as a heading.',"DO use <CardContent flush> for edge-to-edge children such as DataTable, Table, or a Tabs list \u2014 this removes horizontal padding. Combine with <CardContent tight> when there is no visual gap needed after the header, and <CardContent solo> when there is no CardHeader above (top padding matches the card shell).","DO use <CardFooter separated> to render a top-bordered action band (Save/Cancel buttons, table summary row). Use <CardFooter flush> for a full-bleed footer bar.","DO use <CardCover> as the first child for full-bleed cover media \u2014 the header below it uses card-section top spacing, not the card shell.","DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout.","SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section \u2014 a `banded` header or a `separated` footer, i.e. one carrying a divider border \u2014 pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-inset`, no bottom, and the body supplies the gap via `--card-space-body-y`. Special case: a header above `<CardContent flush>` with a <Table> gets its own `--card-space-body-y` bottom gap (the flush table zeroes its top), so the title never butts the table. `--card-space-gap` is the in-slot stack gap (title\u2195description). Tune the band rhythm once at `--card-space-divided-y`; tune the accent stripe width at `--card-accent-rail-width` (default 6px)."],useCases:['Dashboard KPI summary row: wrap each metric in <StatCard> (or a plain <Card size="compact"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.','Invoice or order detail panel: <Card accent="primary"> with <CardHeader banded><CardTitle>, <CardContent> body rows (use <Descriptions> inside), and <CardFooter separated> holding approve/reject buttons.',"Section container on a settings or form page: a single <Card> wrapping a <CardHeader><CardTitle> plus <CardContent> containing <FormField> groups, with <CardFooter separated> for Save/Cancel.","Data table with toolbar: <Card> + <CardHeader> (title + filter controls in <CardAction>) + <CardContent flush> containing <DataTable> \u2014 <CardContent flush> removes horizontal padding so the table header spans full width.",'Featured announcement or alert card: <Card variant="featured"> with an accent stripe (<accent="warning">) to visually elevate a card above sibling cards on the page.',"Media/cover card (e.g. entity profile): <CardCover> first (full-bleed image), then <CardHeader> + <CardContent> below it for structured metadata."],related:["StatCard \u2014 use instead of a plain Card when rendering a KPI/metric tile (label + value + optional delta/hint). StatCard is a Card internally; do not re-wrap it in another Card.","CardContent \u2014 mandatory inner wrapper for all body content inside Card. Provides the correct padding and supports flush/tight/solo variants. The only correct way to put padded content inside Card.","Descriptions \u2014 use inside <CardContent> when body content is a label-value metadata list (e.g. entity details, invoice fields); do not hand-roll a dl/dt/dd grid.","DataState / InfiniteQueryState \u2014 use instead of Card when the content is a TanStack Query-driven list that needs automatic skeleton, empty, and error states; Card does not manage loading lifecycle."],example:`import { Card, CardHeader, CardTitle, CardContent } from "@godxjp/ui/data-display";
398
+ }`,storyPath:"data-display/DataTable.stories.tsx",rules:[24,31,35,37]},{name:"Card",group:"data-display",tagline:'Surface container with optional accent stripe, variant fill, size, and density. \u26A0\uFE0F The bare <Card> has NO inner padding \u2014 body content MUST be wrapped in <CardContent> (titles in <CardHeader>), or it sits FLUSH against the card edges. Never hand-roll padding with className="p-4"; use <CardContent>. Compose with CardHeader/CardTitle/CardContent/CardFooter. For a tab/toolbar/filter strip (view tabs, list controls) use <CardBar extra={\u2026}> \u2014 a positionable bar that auto-draws its separator from its position (top\u2192bottom border, bottom\u2192top border, middle\u2192both) and pins `extra` content to the inline-end edge; place it as first/last child of the Card. Never hand-roll a bordered div for this.',props:[{name:"accent",type:'"primary" | "success" | "warning" | "info" | "attention" | "destructive"',description:"3px left-edge semantic accent stripe."},{name:"variant",type:'"default" | "muted" | "outline" | "featured"',defaultValue:'"default"',description:"Surface fill style."},{name:"size",type:'"md" | "compact"',defaultValue:'"md"',description:"Card size preset."},{name:"density",type:'"tight" | "cozy"',description:"Internal padding density (base 16 / tight 12 / cozy 20)."}],usage:['DO always wrap body content in <CardContent> \u2014 the bare <Card> div has zero inner padding; content renders flush against card edges without it. Never add className="p-4" directly on <Card> as a substitute.',"DO put titles/descriptions in <CardHeader>/<CardTitle>/<CardDescription>. Use <CardHeader banded> for a visually separated muted-background header band (mirrors <CardFooter separated>). Pair with <CardAction> inside a flex-row CardHeader for header-level action buttons.",'DO set <CardTitle level={n}> to keep a valid document outline (h1 \u2192 h2 \u2192 h3, no skipped levels): CardTitle renders <h3> by default, so a section card directly under a page <h1> needs level={2}. Pick the level by OUTLINE position, NEVER for visual size \u2014 the title size is fixed by tokens and does not change with level. When the card title is a styled label rather than a section heading, use <CardTitle as="p"> so it is not announced as a heading.',"DO use <CardContent flush> for edge-to-edge children such as DataTable, Table, or a Tabs list \u2014 this removes horizontal padding. Combine with <CardContent tight> when there is no visual gap needed after the header, and <CardContent solo> when there is no CardHeader above (top padding matches the card shell).","DO use <CardFooter separated> to render a top-bordered action band (Save/Cancel buttons, table summary row). Use <CardFooter flush> for a full-bleed footer bar.","DO use <CardCover> as the first child for full-bleed cover media \u2014 the header below it uses card-section top spacing, not the card shell.","DON'T hand-roll a stat/KPI tile with <Card> + raw divs \u2014 use <StatCard> (label, value, hint, delta, layout, inverse props) which is already a Card internally with correct token-driven layout.","SPACING IS BORDER-AWARE & token-driven (theme via src/tokens/components/card.css, never hard-code padding on slots): `--card-space-inset` is the shared horizontal column every slot (header/content/footer) aligns to. A DIVIDED section \u2014 a `banded` header or a `separated` footer, i.e. one carrying a divider border \u2014 pads SYMMETRICALLY top+bottom from `--card-space-divided-y` (a band reads as its own region). A PLAIN header flows into the body instead: top `--card-space-shell-y`, no bottom, and the body supplies the gap via `--card-space-body-y`. THE TWO AXES ARE INDEPENDENT (gh#232): `--card-space-inset` is inline-only, while `--card-space-shell-y` owns the BLOCK shell edges (plain-header top, `solo` body top, terminal slot bottom) and defaults to the inset \u2014 so a shell/theme can make a card SHORTER without narrowing its column by overriding `--card-space-shell-y` alone (this is how AuthShell's `--auth-shell-card-padding-block-compact` reaches CardContent). Never bridge it with a consumer selector on the card-content slot. Special case: a header above `<CardContent flush>` with a <Table> gets its own `--card-space-body-y` bottom gap (the flush table zeroes its top), so the title never butts the table. `--card-space-gap` is the in-slot stack gap (title\u2195description). Tune the band rhythm once at `--card-space-divided-y`; tune the accent stripe width at `--card-accent-rail-width` (default 6px)."],useCases:['Dashboard KPI summary row: wrap each metric in <StatCard> (or a plain <Card size="compact"> with <CardContent>) to render a uniform grid of labeled value tiles with optional trend deltas.','Invoice or order detail panel: <Card accent="primary"> with <CardHeader banded><CardTitle>, <CardContent> body rows (use <Descriptions> inside), and <CardFooter separated> holding approve/reject buttons.',"Section container on a settings or form page: a single <Card> wrapping a <CardHeader><CardTitle> plus <CardContent> containing <FormField> groups, with <CardFooter separated> for Save/Cancel.","Data table with toolbar: <Card> + <CardHeader> (title + filter controls in <CardAction>) + <CardContent flush> containing <DataTable> \u2014 <CardContent flush> removes horizontal padding so the table header spans full width.",'Featured announcement or alert card: <Card variant="featured"> with an accent stripe (<accent="warning">) to visually elevate a card above sibling cards on the page.',"Media/cover card (e.g. entity profile): <CardCover> first (full-bleed image), then <CardHeader> + <CardContent> below it for structured metadata."],related:["StatCard \u2014 use instead of a plain Card when rendering a KPI/metric tile (label + value + optional delta/hint). StatCard is a Card internally; do not re-wrap it in another Card.","CardContent \u2014 mandatory inner wrapper for all body content inside Card. Provides the correct padding and supports flush/tight/solo variants. The only correct way to put padded content inside Card.","Descriptions \u2014 use inside <CardContent> when body content is a label-value metadata list (e.g. entity details, invoice fields); do not hand-roll a dl/dt/dd grid.","DataState / InfiniteQueryState \u2014 use instead of Card when the content is a TanStack Query-driven list that needs automatic skeleton, empty, and error states; Card does not manage loading lifecycle."],example:`import { Card, CardHeader, CardTitle, CardContent } from "@godxjp/ui/data-display";
325
399
 
326
400
  <Card accent="success">
327
401
  <CardHeader><CardTitle>\u6CE8\u6587\u30B5\u30DE\u30EA\u30FC</CardTitle></CardHeader>
@@ -343,11 +417,45 @@ import { ResponsiveGrid } from "@godxjp/ui/layout";
343
417
  </ResponsiveGrid>
344
418
 
345
419
  // \u274C Double border \u2014 do NOT wrap StatCard in a Card:
346
- // <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>`,storyPath:"data-display/StatCard.stories.tsx",rules:[]},{name:"Badge",group:"data-display",tagline:"Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",props:[{name:"variant",type:'"default" | "secondary" | "outline" | "dashed"',defaultValue:'"default"',description:"STRUCTURAL emphasis only (fill/border style) \u2014 NOT colour. Use `tone` for semantic colour. `dashed` = dashed border."},{name:"tone",type:'"default" | "primary" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral"',description:'SEMANTIC colour intent (BadgeTone = ToneProp + `primary`). This is the colour knob \u2014 success/warning/destructive/info/etc. `primary` is a SOFT brand pill (tinted brand fill + brand text) for the dashboard role-pill pattern; for a SOLID brand fill use `variant="default"`. Keep variant for structure, tone for meaning.'},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (badge radius), `pill` (fully rounded), `sharp` (square). Use the prop instead of a `rounded-*` className."},{name:"status",type:"string",description:"Lifecycle key. Known keys auto-map to tone + icon + i18n label; unknown keys fall back to neutral."},{name:"icon",type:"React.ComponentType<{ className?: string }> | null",description:"Leading icon override. Pass null to suppress the auto status icon."},{name:"children",type:"ReactNode",description:"Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."}],usage:["DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.","DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.","DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.","Badge renders as a `<div>` (HTMLAttributes<HTMLDivElement>). It carries no interactive semantics. If you need a clickable chip, wrap it in a `<button>` or use a Button with a matching variant \u2014 never add an `onClick` directly to Badge without an accessible role.","Badge is a leaf \u2014 pass plain text or a short ReactNode as children. Do NOT nest another Badge, a Button, or interactive controls inside it; that breaks focus order and creates invalid HTML (div-in-inline-context).","Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`."],useCases:['Category or tier labels on table rows \u2014 e.g. plan tier (`<Badge variant="secondary">Pro</Badge>`), document type (`<Badge variant="outline">Invoice</Badge>`), or locale tag (`<Badge variant="secondary">EN</Badge>`).','Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge tone="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge tone="warning">\u8981\u78BA\u8A8D</Badge>`.',"Inline count or highlight next to a heading or nav item \u2014 e.g. `<Badge variant=\"destructive\">3</Badge>` beside 'Overdue invoices' to draw attention to a non-zero count.",'Feature flags or experiment variant labels on admin records \u2014 e.g. `<Badge variant="outline">A/B</Badge>` alongside a campaign row to indicate it is in a split test.',"Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."],related:["Button \u2014 use instead of Badge when the chip must be interactive (clickable, toggleable). Badge carries no button role or keyboard handler; a naked `onClick` on Badge is inaccessible."],example:`import { Badge } from "@godxjp/ui/data-display";
420
+ // <Card><CardContent><StatCard label="x" value="1" /></CardContent></Card>`,storyPath:"data-display/StatCard.stories.tsx",rules:[]},{name:"ServiceLauncherCard",group:"data-display",tagline:"Token-owned downstream-service launcher tile with semantic icon, status, metadata, action, disabled reason, matching skeleton, and companion catalog CTA.",props:[{name:"icon",type:"LucideIcon",required:!0,description:"Decorative service glyph rendered in the canonical semantic icon surface."},{name:"title",type:"ReactNode",required:!0,description:"Real downstream service display name."},{name:"titleLevel",type:"1 | 2 | 3 | 4",defaultValue:"2",description:"Semantic heading level; visual styling remains token-owned."},{name:"statusLabel",type:"ReactNode",description:"Consumer-provided access/readiness label. The component never infers status."},{name:"statusTone",type:'"success" | "warning" | "destructive" | "info" | "neutral" | "muted"',defaultValue:'"neutral"',description:"Semantic tone corresponding to the real statusLabel."},{name:"description",type:"ReactNode",description:"Localized service summary."},{name:"metadata",type:"ReactNode",description:"Compact mono metadata such as real hostname and subscribed plan."},{name:"action",type:"ReactNode",required:!0,description:"Real launch/detail action, normally a Button or Button asChild link."},{name:"disabledReason",type:"ReactNode",description:"Localized prose reason accompanying a disabled/unavailable action. Rendered ABOVE the action (so assistive tech meets the explanation before the disabled control) and marks the tile data-unavailable, which mutes the medallion via --card-service-launcher-unavailable-icon-*. It never disables the action itself \u2014 that stays the consumer's Button prop."}],usage:["DO provide status, hostname, plan, access state and action from the product's real API contract. ServiceLauncherCard deliberately performs no entitlement or URL inference \u2014 it has no href/entitlement/available prop at all.","DO own the layout with ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }} \u2014 the canonical 3\u21922\u21921 launcher grid. ResponsiveGrid queries its OWN container (40/48/64rem), so never hand-write grid-template-columns or a media query in the page. The shorthand columns={3} also works but widens to 2 columns earlier (40rem).","DO render ServiceLauncherCard directly as a grid child; it already owns its Card shell, its 36px medallion (--control-height-lg tier) and the canonical internal rhythm.","DO replace it with ServiceLauncherCardSkeleton while loading (it carries a required `label` and aria-busy, and deliberately opens no live region). Use ServiceCatalogCta as the peer tile only when a real catalog/add route exists.","DO keep `metadata` to machine identifiers (hostname \xB7 plan) \u2014 it is the only mono line. Sentences belong in `description` / `disabledReason`.","DON'T recreate launcher geometry with page-local CSS, utility padding, grid tracks, or a hand-built Card hierarchy. Retune it with the --card-service-launcher-* tokens instead.","DON'T show LIVE, a hostname, subscribed plan, or launch action merely because a service is active in the global catalog."],useCases:["Organization console launcher showing subscribed downstream applications with a real SSO launch action.","Service picker where unavailable apps remain visible with a disabled action and permission/subscription reason.","Responsive 3\u21922\u21921 launcher grid with a final ServiceCatalogCta tile linked to an existing catalog route."],related:["ServiceLauncherCardSkeleton \u2014 shape-matched initial loading placeholder (required `label`, aria-busy, no live region).","ServiceCatalogCta \u2014 dashed companion tile for an existing catalog/add route.","ResponsiveGrid \u2014 OWNS the 3\u21922\u21921 layout around launcher tiles; the launcher never ships grid tracks of its own.","Card \u2014 general-purpose surface; use ServiceLauncherCard instead for this established composite."],example:`import { Clock } from "lucide-react";
421
+ import {
422
+ ServiceCatalogCta,
423
+ ServiceLauncherCard,
424
+ ServiceLauncherCardSkeleton,
425
+ } from "@godxjp/ui/data-display";
426
+ import { Button } from "@godxjp/ui/general";
427
+ import { ResponsiveGrid } from "@godxjp/ui/layout";
428
+
429
+ // ResponsiveGrid owns the canonical 3 \u2192 2 \u2192 1 ladder; the page writes no tracks.
430
+ <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }}>
431
+ {loading
432
+ ? services.map((s) => <ServiceLauncherCardSkeleton key={s.id} label={t("loadingService")} />)
433
+ : services.map((s) => (
434
+ <ServiceLauncherCard
435
+ key={s.id}
436
+ icon={Clock}
437
+ title={s.name}
438
+ statusLabel={s.accessLabel}
439
+ statusTone={s.accessTone}
440
+ description={s.description}
441
+ metadata={s.hostnameAndPlan}
442
+ disabledReason={s.blockedReason}
443
+ action={
444
+ <Button asChild={s.canLaunch} disabled={!s.canLaunch}>
445
+ {s.canLaunch ? <a href={s.launchUrl}>{t("launch")}</a> : t("launch")}
446
+ </Button>
447
+ }
448
+ />
449
+ ))}
450
+ <ServiceCatalogCta
451
+ title={t("addFromCatalog")}
452
+ action={<Button variant="outline">{t("viewCatalog")}</Button>}
453
+ />
454
+ </ResponsiveGrid>`,storyPath:"data-display/ServiceLauncherCard.stories.tsx",rules:[40]},{name:"Badge",group:"data-display",tagline:"Plain or lifecycle badge. Use `variant` for static chips, or `status` to auto-map lifecycle keys to semantic tone + icon. Labels never wrap.",props:[{name:"variant",type:'"default" | "secondary" | "outline" | "dashed"',defaultValue:'"default"',description:"STRUCTURAL emphasis only (fill/border style) \u2014 NOT colour. Use `tone` for semantic colour. `dashed` = dashed border."},{name:"tone",type:'"default" | "primary" | "success" | "warning" | "destructive" | "info" | "muted" | "neutral"',description:'SEMANTIC colour intent (BadgeTone = ToneProp + `primary`). This is the colour knob \u2014 success/warning/destructive/info/etc. `primary` is a SOFT brand pill (tinted brand fill + brand text) for the dashboard role-pill pattern; for a SOLID brand fill use `variant="default"`. Keep variant for structure, tone for meaning.'},{name:"shape",type:'"default" | "pill" | "sharp"',defaultValue:'"default"',description:"Corner radius from the tokens \u2014 `default` (badge radius), `pill` (fully rounded), `sharp` (square). Use the prop instead of a `rounded-*` className."},{name:"status",type:"string",description:"Lifecycle key. Known keys auto-map to tone + icon + i18n label; unknown keys fall back to neutral."},{name:"icon",type:"React.ComponentType<{ className?: string }> | null",description:"Leading icon override. Pass null to suppress the auto status icon."},{name:"children",type:"ReactNode",description:"Badge label. When omitted with status, Badge renders the translated lifecycle label or raw status."}],usage:["DO pick the correct variant semantically: `success` (approved/paid), `warning` (pending/overdue), `destructive` (rejected/error), `secondary` (neutral category), `outline` (subtle label), `default` (primary accent). Never force a colour just for aesthetics \u2014 agents and screen readers read the variant as intent.","DO use `status` for entity lifecycle statuses (active, draft, pending, cancelled, failed, scheduled, etc.) so the component resolves the correct tone, icon, and i18n label.","DO pass `variant` explicitly for localized labels or categorical tiers, and pass `icon={null}` when a lifecycle glyph would be misleading.","Badge renders as a `<div>` (HTMLAttributes<HTMLDivElement>). It carries no interactive semantics. If you need a clickable chip, wrap it in a `<button>` or use a Button with a matching variant \u2014 never add an `onClick` directly to Badge without an accessible role.","Badge is a leaf \u2014 pass plain text or a short ReactNode as children. Do NOT nest another Badge, a Button, or interactive controls inside it; that breaks focus order and creates invalid HTML (div-in-inline-context).","Use semantic tokens for any className overrides (`text-muted-foreground`, `bg-destructive`) \u2014 never raw Tailwind palette classes like `bg-green-500`."],useCases:['Category or tier labels on table rows \u2014 e.g. plan tier (`<Badge variant="secondary">Pro</Badge>`), document type (`<Badge variant="outline">Invoice</Badge>`), or locale tag (`<Badge variant="secondary">EN</Badge>`).','Approval or review state in an accounting list where the value is not a lifecycle key in Badge\'s STATUS_MAP \u2014 e.g. a custom approval tier like `<Badge tone="success">\u627F\u8A8D\u6E08</Badge>` or `<Badge tone="warning">\u8981\u78BA\u8A8D</Badge>`.',"Inline count or highlight next to a heading or nav item \u2014 e.g. `<Badge variant=\"destructive\">3</Badge>` beside 'Overdue invoices' to draw attention to a non-zero count.",'Feature flags or experiment variant labels on admin records \u2014 e.g. `<Badge variant="outline">A/B</Badge>` alongside a campaign row to indicate it is in a split test.',"Read-only metadata chips inside a Descriptions.Item or Card header where a lifecycle icon would be visually heavy \u2014 e.g. currency code, payment method, or region tag."],related:["Button \u2014 use instead of Badge when the chip must be interactive (clickable, toggleable). Badge carries no button role or keyboard handler; a naked `onClick` on Badge is inaccessible."],example:`import { Badge } from "@godxjp/ui/data-display";
347
455
 
348
456
  <Badge variant="secondary">A/B</Badge>
349
457
  <Badge status="active">\u516C\u958B\u4E2D</Badge>
350
- <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</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>`."}],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>`.'],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."],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";
458
+ <Badge status="\u30D7\u30EC\u30DF\u30A2\u30E0" tone="success" icon={null}>\u30D7\u30EC\u30DF\u30A2\u30E0</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:"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.','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";
351
459
  import { Button } from "@godxjp/ui/general";
352
460
  import { Smartphone } from "lucide-react";
353
461
 
@@ -369,6 +477,26 @@ import { Smartphone } from "lucide-react";
369
477
  trailing={<Button size="xs" variant="outline">\u30ED\u30B0\u30A2\u30A6\u30C8</Button>}
370
478
  />
371
479
  </CardContent>
480
+ </Card>
481
+
482
+ // Notifications \u2014 unread dot + emphasized surface, wrapping title, two inline actions
483
+ <Card>
484
+ <CardContent flush>
485
+ <ListRow
486
+ unread
487
+ align="start"
488
+ overflow="wrap"
489
+ title="\u7D44\u7E54\u300C\u30B0\u30ED\u30FC\u30D0\u30EB\u30FB\u30C8\u30E9\u30F3\u30B9\u30D5\u30A9\u30FC\u30E1\u30FC\u30B7\u30E7\u30F3\u63A8\u9032\u672C\u90E8\u300D\u3078\u306E\u62DB\u5F85\u304C\u5C4A\u3044\u3066\u3044\u307E\u3059"
490
+ description="2026-07-30 09:12 JST"
491
+ trailing={
492
+ <>
493
+ <Button size="xs" variant="ghost">\u65E2\u8AAD\u306B\u3059\u308B</Button>
494
+ <Button size="xs" variant="outline">\u958B\u304F</Button>
495
+ </>
496
+ }
497
+ />
498
+ <ListRow unread={false} align="start" overflow="wrap" title="\u8ACB\u6C42\u66F8\u304C\u767A\u884C\u3055\u308C\u307E\u3057\u305F" description="2026-07-28 18:40 JST" />
499
+ </CardContent>
372
500
  </Card>`,storyPath:"data-display/ListRow.stories.tsx",rules:[42,44]},{name:"CredentialReveal",group:"data-display",tagline:"One-time secret surface \u2014 masked-by-default value with a show/hide toggle, a copy button that confirms the copy, optional download, and an optional acknowledge action to pair with Dialog. The GitHub/Stripe token-reveal pattern as a real primitive so consumers stop hand-rolling it.",props:[{name:"secret",type:"string",required:!0,description:"The one-time secret value."},{name:"label",type:"string",description:"Accessible name / caption for the secret (e.g. 'API key')."},{name:"warning",type:"React.ReactNode | null",description:"Caution banner copy; defaults to a localized 'shown only once' warning. Pass null to suppress the banner."},{name:"revealed",type:"boolean",description:"Controlled reveal state (with defaultRevealed / onRevealedChange)."},{name:"defaultRevealed",type:"boolean",defaultValue:"false",description:"Uncontrolled initial reveal state."},{name:"onRevealedChange",type:"(revealed: boolean) => void",description:"Reveal toggle handler."},{name:"onCopy",type:"(secret: string) => void",description:"Called after the secret is written to the clipboard."},{name:"onAcknowledge",type:"() => void",description:"Renders a confirm button; wire it to the Dialog's onOpenChange(false)."},{name:"downloadable",type:"boolean",defaultValue:"false",description:"Offer a download-as-file button."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Action button size tier."},{name:"tone",type:'"warning" | "destructive" | "info"',defaultValue:'"warning"',description:"Caution banner severity."}],usage:["DO use for a secret shown exactly once after creation (device credential, API key, service-account secret) \u2014 it masks by default and confirms the copy.","DO pair it inside a Dialog and reset via controlled `revealed`/`onRevealedChange` (or let it re-blur automatically when the `secret` prop changes) so a reopened dialog starts masked.","DO pass `onAcknowledge` to gate the dialog close behind an explicit 'I've saved it' confirmation.","DON'T use it for an editable password field \u2014 that's PasswordInput. CredentialReveal is read-only display of an issued secret.","DON'T hand-roll the copy button + copied-state + aria-live announcement; it's built in."],useCases:["Device credential issued after enrollment","API key / personal access token shown once on creation","Service-account secret / client secret reveal","Recovery code or one-time bootstrap password"],related:["PasswordInput \u2014 editable password/secret ENTRY with a show/hide toggle (data-entry); CredentialReveal is read-only DISPLAY of an issued secret.","Dialog \u2014 the modal CredentialReveal is designed to live inside.","Alert \u2014 the caution banner CredentialReveal composes internally."],example:`import { CredentialReveal } from "@godxjp/ui/data-display";
373
501
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@godxjp/ui/feedback";
374
502
 
@@ -661,7 +789,7 @@ function CreateDialog() {
661
789
  setOpen(false);
662
790
  }}
663
791
  variant="destructive"
664
- />`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"Sheet",group:"feedback",tagline:"Side-panel drawer (Radix Dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"width",type:"number | string",description:"On SheetContent (side left/right): desired panel width (number\u2192px). Caps at the viewport \u2014 full-width on a small screen (min(width,100%)), NOT a hard fixed width. Default w-3/4 sm:max-w-md."},{name:"title / subtitle / extra / tone",type:"ReactNode / ReactNode / ReactNode / ToneProp",description:"On SheetHeader (Ant-style): title (\u2192 SheetTitle, accessible name), subtitle (\u2192 SheetDescription), right-aligned extra actions, and a soft semantic `tone` background band. Children still supported."}],usage:["DO build the panel with SheetHeader (pass `title`/`subtitle`/`extra`/`tone` OR children) > SheetBody (scrollable, ring-safe) > SheetFooter (pinned). SheetTitle is required for a11y \u2014 the `title` prop renders it for you. Never skip the title.","DO set `width` on SheetContent for a wider/narrower panel (e.g. width={480}); it caps at the viewport so small screens still get a full-width panel.","DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y \u2014 maps to Radix DialogPrimitive.Title, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.","DO control state explicitly with open + onOpenChange on Sheet root when you need to close programmatically (e.g. after form submit). Uncontrolled (no props) works for simple trigger-only cases but gives you no hook to reset form state on close.","DO use SheetTrigger asChild to wrap a Button or other interactive element \u2014 this avoids a nested <button> in the DOM. Never render a raw <button> as a direct child of SheetTrigger.","DO wrap a long/scrolling body in SheetBody (between SheetHeader and a pinned SheetFooter). It is the ring-safe scroll slot: a hand-rolled <div className='overflow-y-auto'> clips the 3px focus ring of a full-width Input/Select at the scroll edges \u2014 SheetBody insets the content so the ring never clips.","DO use SheetFooter (renders at the bottom via mt-auto, symmetric 16/24 padding, full-bleed top border) for primary/cancel action Buttons. Never float action Buttons inside the body \u2014 they will not stick to the panel bottom.","DON'T set showCloseButton={false} on SheetContent unless you provide your own SheetClose element; omitting both leaves users with no keyboard-accessible close path and breaks a11y.","DON'T put a Sheet inside a Dialog (nested Radix portals conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."],useCases:["Filter/search panel: slide in from the right with filter FormFields (Select, DateRangePicker, CheckboxGroup) that affect a DataTable \u2014 preferred over a Dialog because filters do not require confirmation and benefit from seeing the table behind the overlay.","Quick-edit drawer: open an entity's editable fields (e.g. invoice line items, account settings) without navigating away, with Save/Cancel in SheetFooter \u2014 use side='right' and keep the main page visible as context.","Detail peek panel: show read-only Descriptions / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}.","Mobile-first navigation drawer: side='left' sheet acting as a slide-in nav menu on small viewports when the AppShell Sidebar is hidden \u2014 triggered by a hamburger Button.","Step-by-step wizard side panel: multi-step form (Steps component inside SheetContent) for onboarding or import flows where full-page navigation would lose list context."],related:["Dialog \u2014 use Dialog (centered modal) when the action is destructive, requires full user focus, or needs a confirm/alertdialog (mode='confirm'). Use Sheet when the user benefits from seeing the page content behind the slide-over (filters, detail peek, quick-edit).","Toolbar/ToolbarGroup \u2014 use Toolbar for inline persistent filter controls above a DataTable (no overlay). Use Sheet when the filter set is large (>4 controls) or on mobile where inline controls collapse poorly.","Popover \u2014 use Popover for lightweight, anchor-positioned context menus or single-control overlays (date picker, color picker). Use Sheet when the panel has a header, multiple fields, or footer actions that need a dedicated panel.","SplitPane \u2014 use SplitPane for a persistent side-by-side layout where both panes are always visible. Use Sheet when the secondary panel is transient and should overlay the primary content."],example:`import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from "@godxjp/ui/feedback";
792
+ />`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"Sheet",group:"feedback",tagline:"Side-panel drawer / responsive detail panel (Radix Dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom, responsive=auto|side|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"width",type:"number | string",description:"On SheetContent (side left/right): desired panel width (number\u2192px). Caps at the viewport \u2014 full-width on a small screen (min(width,100%)), NOT a hard fixed width. Default w-3/4 sm:max-w-md."},{name:"responsive",type:'"auto" | "side" | "bottom"',defaultValue:'"side"',description:'On SheetContent: the responsive drawer / detail-panel contract. "side" (default) always renders the physical `side` you named. "auto" renders the desktop side panel above --sheet-responsive-breakpoint-width (48rem/768px) and the mobile BOTTOM sheet at/below it, capped by --sheet-bottom-max-height (85dvh). "bottom" pins the bottom-sheet presentation. The resolved presentation is exposed as data-side on the panel. Never hand-roll useMediaQuery in app code \u2014 import useSheetResponsiveMode() if a composite must make the same decision.'},{name:"title / subtitle / extra / tone",type:"ReactNode / ReactNode / ReactNode / ToneProp",description:"On SheetHeader (Ant-style): title (\u2192 SheetTitle, accessible name), subtitle (\u2192 SheetDescription), right-aligned extra actions, and a soft semantic `tone` background band. Children still supported."}],usage:["DO build the panel with SheetHeader (pass `title`/`subtitle`/`extra`/`tone` OR children) > SheetBody (scrollable, ring-safe) > SheetFooter (pinned). SheetTitle is required for a11y \u2014 the `title` prop renders it for you. Never skip the title.","DO set `width` on SheetContent for a wider/narrower panel (e.g. width={480}); it caps at the viewport so small screens still get a full-width panel.",'DO use responsive="auto" for a record detail panel / drawer that must be a desktop side panel and a mobile bottom sheet \u2014 ONE <Sheet>, no page-local media query. The breakpoint is the --sheet-responsive-breakpoint-width token, so a service moves the line once for every overlay. `width` is ignored while the bottom presentation is active (a bottom sheet is full-bleed).',`DON'T hardcode an overlay breakpoint in app code (useMediaQuery("(max-width: 390px)")). If a composite must swap a desktop surface for a mobile sheet (a Popover\u2192Sheet switcher, for example), call the exported useSheetResponsiveMode("auto") hook so it reads the same themeable token.`,"DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y \u2014 maps to Radix DialogPrimitive.Title, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.","DO control state explicitly with open + onOpenChange on Sheet root when you need to close programmatically (e.g. after form submit). Uncontrolled (no props) works for simple trigger-only cases but gives you no hook to reset form state on close.","DO use SheetTrigger asChild to wrap a Button or other interactive element \u2014 this avoids a nested <button> in the DOM. Never render a raw <button> as a direct child of SheetTrigger.","DO wrap a long/scrolling body in SheetBody (between SheetHeader and a pinned SheetFooter). It is the ring-safe scroll slot: a hand-rolled <div className='overflow-y-auto'> clips the 3px focus ring of a full-width Input/Select at the scroll edges \u2014 SheetBody insets the content so the ring never clips.","DO use SheetFooter (renders at the bottom via mt-auto, symmetric 16/24 padding, full-bleed top border) for primary/cancel action Buttons. Never float action Buttons inside the body \u2014 they will not stick to the panel bottom.","DON'T set showCloseButton={false} on SheetContent unless you provide your own SheetClose element; omitting both leaves users with no keyboard-accessible close path and breaks a11y.","DON'T put a Sheet inside a Dialog (nested Radix portals conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."],useCases:["Filter/search panel: slide in from the right with filter FormFields (Select, DateRangePicker, CheckboxGroup) that affect a DataTable \u2014 preferred over a Dialog because filters do not require confirmation and benefit from seeing the table behind the overlay.","Quick-edit drawer: open an entity's editable fields (e.g. invoice line items, account settings) without navigating away, with Save/Cancel in SheetFooter \u2014 use side='right' and keep the main page visible as context.","Detail peek panel: show read-only Descriptions / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}. Add responsive='auto' so the same panel becomes a bottom sheet on a phone instead of a 100%-wide slab.","Mobile-first navigation drawer: side='left' sheet acting as a slide-in nav menu on small viewports when the AppShell Sidebar is hidden \u2014 triggered by a hamburger Button.","Step-by-step wizard side panel: multi-step form (Steps component inside SheetContent) for onboarding or import flows where full-page navigation would lose list context."],related:["Dialog \u2014 use Dialog (centered modal) when the action is destructive, requires full user focus, or needs a confirm/alertdialog (mode='confirm'). Use Sheet when the user benefits from seeing the page content behind the slide-over (filters, detail peek, quick-edit).","Toolbar/ToolbarGroup \u2014 use Toolbar for inline persistent filter controls above a DataTable (no overlay). Use Sheet when the filter set is large (>4 controls) or on mobile where inline controls collapse poorly.","Popover \u2014 use Popover for lightweight, anchor-positioned context menus or single-control overlays (date picker, color picker). Use Sheet when the panel has a header, multiple fields, or footer actions that need a dedicated panel.","SplitPane \u2014 use SplitPane for a persistent side-by-side layout where both panes are always visible. Use Sheet when the secondary panel is transient and should overlay the primary content."],example:`import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from "@godxjp/ui/feedback";
665
793
  import { Button } from "@godxjp/ui/general";
666
794
 
667
795
  <Sheet open={open} onOpenChange={setOpen}>
@@ -684,7 +812,7 @@ import { Toaster } from "@godxjp/ui/feedback";
684
812
  // anywhere \u2014 import toast from "sonner"
685
813
  import { toast } from "sonner";
686
814
  toast.success("\u30AF\u30FC\u30DD\u30F3\u3092\u516C\u958B\u3057\u307E\u3057\u305F");
687
- toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled (gh#175)."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"DO: use `defaultValue` (uncontrolled) for simple local state; use `value` + `onValueChange` together (controlled) when the active tab is driven by URL query params, router state, or parent state. NEVER set both simultaneously.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'DO: pass `orientation="vertical"` to `<Tabs>` (not to `TabsList`) for a side-rail layout \u2014 the CSS group classes on root and triggers respond automatically, so no extra className gymnastics are needed.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles including the `after:` line element for the `line` variant. Adding your own underline breaks the design.","DO trust the horizontal `TabsList` to scroll its own overflow (hidden scrollbar, swipeable) instead of clipping when tab labels \u2014 especially long localized ones (Japanese, German) \u2014 don't fit a narrow container. Don't wrap it in your own `overflow-x-auto` div or truncate labels to work around clipping; that was gh#175 and is now the framework's job (#175).","DON'T assume the first item is ever auto-selected when it is `disabled` \u2014 Tabs always resolves the fallback to the first ENABLED item (or none, if all are disabled). A `disabled: true` first item is safe to author without also setting `defaultValue`."],useCases:["Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `Tabs` cannot pass.","Controlled tabs driven by URL search params (e.g. `?tab=history`) where the parent reads/writes the active key and passes it to `value` / `onValueChange`.",'Vertical side-rail navigation inside a `SplitPane` or settings layout where `orientation="vertical"` on the root and `variant="line"` on `TabsList` combine to produce a sidebar-style tab strip.',"Lightweight widget tabs on a dashboard card \u2014 e.g. switching a `DataTable` between 'Pending' and 'Paid' invoice views \u2014 where an uncontrolled `defaultValue` is sufficient and no URL state is needed.","Admin entity profile pages (company, partner, employee) where each `TabsContent` wraps an Inertia deferred prop panel, lazy-loading expensive data only when the tab is first activated."],related:["Steps (@godxjp/ui/navigation) \u2014 sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.","Toolbar / ToolbarGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.","DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."],example:`import { Tabs } from "@godxjp/ui/navigation";
815
+ toast.error("\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F");`,storyPath:"feedback/Toaster.stories.tsx",rules:[]},{name:"Tabs",group:"navigation",tagline:"Radix tab container with optional Ant-style `items` API. Pass items for the common full TabsList/TabsContent set, or compose TabsList/TabsTrigger/TabsContent manually when you need per-panel control.",props:[{name:"items",type:"{ value: string; label: React.ReactNode; content: React.ReactNode; disabled?: boolean }[]",description:"Optional data-driven tab list. When provided, Tabs renders all triggers and content panels. When Tabs owns the initial selection (no `value`, and no `defaultValue` naming an existing ENABLED item), it falls back to the first item that is NOT `disabled` \u2014 never a disabled one \u2014 and selects nothing if every item is disabled (gh#175)."},{name:"value",type:"string",description:"Controlled active tab key."},{name:"defaultValue",type:"string",description:"Uncontrolled initial tab key. Ignored (falls back to the first enabled item) when it names a disabled item or an unknown key."},{name:"onValueChange",type:"(value: string) => void",description:"Active-tab change handler."}],usage:["DO pass `items` when all tab content is known up front \u2014 each item needs a unique `value`, trigger `label`, and panel `content`.",'When not using `items`, compose the full four-part tree \u2014 `<Tabs>` root, `<TabsList>` trigger bar, one `<TabsTrigger value="\u2026">` per tab, one `<TabsContent value="\u2026">` per matching trigger.',"DO: use `defaultValue` (uncontrolled) for simple local state; use `value` + `onValueChange` together (controlled) when the active tab is driven by URL query params, router state, or parent state. NEVER set both simultaneously.","DO use `variant` on Tabs when using `items`; when composing manually, set `variant` on `TabsList`.",'DO: pass `orientation="vertical"` to `<Tabs>` (not to `TabsList`) for a side-rail layout \u2014 the CSS group classes on root and triggers respond automatically, so no extra className gymnastics are needed.',"DON'T: hand-roll the active-indicator underline or selected-state ring \u2014 `TabsTrigger` already applies `data-[state=active]` styles including the `after:` line element for the `line` variant. Adding your own underline breaks the design.","DO trust the horizontal `TabsList` to scroll its own overflow (hidden scrollbar, swipeable) instead of clipping when tab labels \u2014 especially long localized ones (Japanese, German) \u2014 don't fit a narrow container. Don't wrap it in your own `overflow-x-auto` div or truncate labels to work around clipping; that was gh#175 and is now the framework's job (#175).","DON'T assume the first item is ever auto-selected when it is `disabled` \u2014 Tabs always resolves the fallback to the first ENABLED item (or none, if all are disabled). A `disabled: true` first item is safe to author without also setting `defaultValue`.",'DON\'T write your own resize/scroll-into-view effect to keep the selected tab on screen \u2014 `TabsList` observes its own size and its triggers\' `data-state` and re-pins the active (or focused, under `activationMode="manual"`) trigger with `scrollIntoView({ block: "nearest", inline: "nearest" })`, honoring `prefers-reduced-motion` and leaving a deliberate manual scroll alone. Before gh#204 a 1440 \u2192 1024 \u2192 390 resize could strand the ACTIVE FIRST tab entirely outside the strip while it still reported `aria-selected="true"`.'],useCases:["Detail drawers or pages that need full per-panel control \u2014 e.g. an accounting journal-entry sheet where one panel has `forceMount` to keep a live chart mounted, requiring custom `TabsContent` props that `Tabs` cannot pass.","Controlled tabs driven by URL search params (e.g. `?tab=history`) where the parent reads/writes the active key and passes it to `value` / `onValueChange`.",'Vertical side-rail navigation inside a `SplitPane` or settings layout where `orientation="vertical"` on the root and `variant="line"` on `TabsList` combine to produce a sidebar-style tab strip.',"Lightweight widget tabs on a dashboard card \u2014 e.g. switching a `DataTable` between 'Pending' and 'Paid' invoice views \u2014 where an uncontrolled `defaultValue` is sufficient and no URL state is needed.","Admin entity profile pages (company, partner, employee) where each `TabsContent` wraps an Inertia deferred prop panel, lazy-loading expensive data only when the tab is first activated."],related:["Steps (@godxjp/ui/navigation) \u2014 sequential wizard/progress indicator. Use Steps when order and completion state matter (multi-step forms, onboarding flows); use Tabs when panels are non-sequential and any tab can be visited freely.","Toolbar / ToolbarGroup (@godxjp/ui/navigation) \u2014 horizontal filter chip row. Visually resembles `line`-variant tabs but is semantically different: Toolbar filters a dataset, it does not switch content panels. Never use Tabs as a filter control.","DropdownMenu (@godxjp/ui/navigation) \u2014 use for space-constrained contexts where showing all tab triggers at once is impractical (e.g. mobile overflow menu). If only 2-3 options exist and screen space is tight, a DropdownSidebar is a lighter alternative to a full tab strip."],example:`import { Tabs } from "@godxjp/ui/navigation";
688
816
 
689
817
  <Tabs
690
818
  defaultValue="overview"
@@ -706,7 +834,7 @@ import { Button } from "@godxjp/ui/general";
706
834
  </DropdownMenuContent>
707
835
  </DropdownMenu>`,storyPath:"navigation/DropdownMenu.stories.tsx",rules:[]},{name:"Steps",group:"navigation",tagline:"Multi-step progress indicator \u2014 horizontal or vertical, default or dot style.",props:[{name:"items",type:"StepItemProp[]",description:"Array of { title, subtitle?, description?, icon?, status? }."},{name:"value",type:"number",defaultValue:"0",description:"Active step index (0-based)."},{name:"defaultValue",type:"number",defaultValue:"0",description:"Base offset for the first rendered step index."},{name:"orientation",type:'"horizontal" | "vertical"',defaultValue:'"horizontal"',description:"Layout direction."},{name:"status",type:'"wait" | "process" | "finish" | "error"',description:"Status of the CURRENT step (drives the active step colour)."},{name:"type",type:'"default" | "dot"',description:"Render full steps or compact dots."},{name:"size",type:'"md" | "sm"',description:"Step size."},{name:"titlePlacement",type:'"horizontal" | "vertical"',description:"Lay step titles beside or below the step icons."},{name:"onValueChange",type:"(value: number) => void",description:"Fires with the clicked step index (0-based)."}],usage:["DO: Pass all steps via the `items` array (each `{ title, subtitle?, description?, icon?, status?, disabled? }`) \u2014 Steps is a single-component API with no child sub-components to compose manually.","DO: Control the active step with `value` (0-based index). For async operations, set the top-level `status` prop (`'process'|'error'|'finish'`) to override the current step's icon \u2014 e.g. `status='error'` turns the active step red without touching `items`.","DO: Use per-item `status` to pin individual steps independently of `value` (e.g. a skipped or already-errored step). Per-item `status` takes precedence over the derived status from `value`.","DON'T: Use Steps for navigation that needs URL routing or tab-switching \u2014 it has no built-in panel rendering. Pair it with your own conditional panel or a `Tabs`/`Tabs` body; Steps only renders the indicator bar.","DON'T: Wire `onValueChange` unless you actually support non-linear navigation. `onValueChange` makes every non-disabled step clickable (rendered as `<button>`); omitting it makes all steps non-interactive (`cursor-default`). Never set `disabled` on an item without also providing `onValueChange`, or the prop is meaningless.","A11y: The `<ol>` is given `aria-label='Progress'` automatically. Individual steps render as `<button type='button'>` when `onValueChange` is present \u2014 ensure each `item.title` is descriptive enough to serve as the button label; avoid icon-only steps without a visible title."],useCases:["Multi-step form wizard (entity onboarding, invoice creation): render Steps above a form, drive `current` from local state, advance on validated submit \u2014 use `status='error'` on the current step when server validation fails.","Async background job tracker: display steps for a long-running import/export pipeline; poll job status and map job phases to `StepStatusProp` values (`'process'` with spinner for in-flight, `'finish'` for done, `'error'` for failed).","Document approval workflow (accounting, contracts): map approval stages (Draft \u2192 Review \u2192 Approved \u2192 Archived) to `items` with per-item `status` reflecting the real state from the server \u2014 use `orientation='vertical'` for a sidebar timeline feel.","Onboarding checklist sidebar: `orientation='vertical'` + `type='dot'` + `size='sm'` for a compact sidebar progress guide alongside a multi-section settings page.","Non-linear step navigation (e.g. revisit a previous step to correct data): provide `onValueChange` and leave only future steps `disabled`; past and current steps become clickable buttons."],related:["Timeline \u2014 use Timeline (from @godxjp/ui) when you need a chronological event log with timestamps and variable content per entry; use Steps when the number of stages is fixed and forward-progress is the semantic.","Tabs / Tabs \u2014 use Tabs when each section has its own rendered panel and users switch freely between them; use Steps when stages are ordered and the indicator communicates completion state rather than just selection.","Progress \u2014 use Progress for a single continuous percentage (file upload, quota fill); use Steps for discrete named stages with individual pass/fail status.","Breadcrumb \u2014 use Breadcrumb for hierarchical location within a page tree; use Steps for sequential workflow progress where order and completion matter."],example:`import { Steps } from "@godxjp/ui/navigation";
708
836
 
709
- <Steps value={1} items={[{ title: "\u7533\u8ACB" }, { title: "\u5BE9\u67FB\u4E2D" }, { title: "\u5B8C\u4E86" }]} />`,storyPath:"navigation/Steps.stories.tsx",rules:[]},{name:"Toolbar",group:"navigation",tagline:"List-page filter strip (the framework FilterBar) \u2014 SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",props:[{name:"children",type:"ReactNode",required:!0,description:"Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup."},{name:"onClear",type:"() => void",description:"Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Whether any filter is applied \u2014 gates the clear-all button visibility."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Pin the strip to the top of its scroll container while the list scrolls beneath it (#197). Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs."},{name:"className",type:"string",description:"Extra classes on the role='toolbar' element."}],usage:["DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.","DO drive the clear-all button with `hasActiveFilters` + `onClear` \u2014 it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.","DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.","DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.","DON'T hand-roll a debounced search box or a raw `<select>` \u2014 compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."],useCases:["Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.","Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.","Filtered views that surface applied conditions as removable chips (clear-one via each chip's \xD7 Button, clear-all via `onClear`)."],related:["ToolbarGroup \u2014 the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).","DataTable.Toolbar \u2014 the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.","SearchInput \u2014 the debounced free-text control placed as the first child of Toolbar.","Badge \u2014 compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."],example:`import { Toolbar, ToolbarGroup } from "@godxjp/ui/navigation";
837
+ <Steps value={1} items={[{ title: "\u7533\u8ACB" }, { title: "\u5BE9\u67FB\u4E2D" }, { title: "\u5B8C\u4E86" }]} />`,storyPath:"navigation/Steps.stories.tsx",rules:[]},{name:"Toolbar",group:"navigation",tagline:"List-page filter strip (the framework FilterBar) \u2014 SearchInput + labelled ToolbarGroup filter slots + a clear-all affordance, optionally sticky.",props:[{name:"children",type:"ReactNode",required:!0,description:"Filter controls. Place SearchInput directly; wrap each labelled Select/DatePicker in a ToolbarGroup."},{name:"onClear",type:"() => void",description:"Clear-all handler. When provided AND hasActiveFilters is true, Toolbar renders the trailing 'Clear filters' button."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Whether any filter is applied \u2014 gates the clear-all button visibility."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Pin the strip to the top of its scroll container while the list scrolls beneath it (#197). Opt-in; tune offset/fill via the --filter-bar-sticky-offset / --filter-bar-sticky-background theme knobs."},{name:"overflow",type:"'wrap' | 'scroll'",defaultValue:"'wrap'",description:"Responsive overflow strategy (gh#216). 'wrap' stacks the groups into one column below 640px, then wraps onto extra rows. 'scroll' keeps ONE bounded row at >=640px that scrolls inline (groups never shrink; the clear-all button stays sticky at the inline end) \u2014 use it when many filters with long JA/EN/VI labels would otherwise push the table below the fold. Below 640px 'scroll' still stacks, so a 390px viewport never hides a filter behind an invisible horizontal scroll. Gutter knob: --filter-bar-scroll-padding-y."},{name:"className",type:"string",description:"Extra classes on the role='toolbar' element."}],usage:["DO place Toolbar ABOVE the table Card, never inside a `CardContent flush`. Put SearchInput as a direct child (it self-labels) and wrap every other control in a ToolbarGroup with a `label`.","DO drive the clear-all button with `hasActiveFilters` + `onClear` \u2014 it only renders when both are truthy. The strip collapses to a single stacked column below 640px automatically.","DO set `sticky` for long list pages so the filters stay reachable while scrolling; if a topbar sits above the list, raise `--filter-bar-sticky-offset` so the strip parks below it.","DO pass `controlId` on a ToolbarGroup that wraps ONE control (gh#216) and give that control the same `id` \u2014 the visible caption then becomes the control's real `<label htmlFor>` (WCAG 2.5.3 label-in-name). Without it the caption only names the group wrapper and the control itself is NAMELESS (axe: select-name / button-name), so you must fall back to an `aria-label` that repeats the caption.","DO switch to `overflow='scroll'` when a list page carries more filters than fit one row (gh#216) \u2014 one bounded row that scrolls inline keeps the table above the fold, where the default `wrap` would grow a 3-row strip with long JA/EN/VI labels. Never re-implement the geometry in the page: no page-local flex/grid/width rules on a filter strip.","DON'T build active-filter chips by nesting a Button inside a Badge (invalid markup + broken focus). Render each chip as a Badge label with a SIBLING icon Button (`aria-label` = 'clear <filter>'); a ghost `size='sm'` Button clears all.","DON'T hand-roll a debounced search box or a raw `<select>` \u2014 compose SearchInput and Select. Toolbar is layout + clear-all only; the controls own their own state and a11y."],useCases:["Master list screens (members, organizations, subscriptions, invoices) that need free-text search plus a few dropdown filters above a DataTable.","Sticky filter strip over a tall list where the filters must remain visible as the user scrolls the results.","Filtered views that surface applied conditions as removable chips (clear-one via each chip's \xD7 Button, clear-all via `onClear`)."],related:["ToolbarGroup \u2014 the labelled wrapper for each individual filter control inside a Toolbar (SearchInput is placed directly, without a group).","DataTable.Toolbar \u2014 the in-table strip for column/density/bulk-action controls; Toolbar (this) is the page-level filter strip that sits ABOVE the table.","SearchInput \u2014 the debounced free-text control placed as the first child of Toolbar.","Badge \u2014 compose Badge + a sibling icon Button to render each active-filter chip; Badge itself is a non-interactive leaf."],example:`import { Toolbar, ToolbarGroup } from "@godxjp/ui/navigation";
710
838
  import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@godxjp/ui/data-entry";
711
839
 
712
840
  <Toolbar sticky hasActiveFilters={hasFilters} onClear={clearAll}>
@@ -720,7 +848,7 @@ import { SearchInput, Select, SelectContent, SelectItem, SelectTrigger, SelectVa
720
848
  </SelectContent>
721
849
  </Select>
722
850
  </ToolbarGroup>
723
- </Toolbar>`,docPath:"navigation/toolbar",storyPath:"navigation/toolbar.tsx",rules:[23,44,45,46]},{name:"AppProvider",group:"providers",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:'"ja" | "en" | "vi"',defaultValue:'"vi"',description:"Initial locale."},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. Persisted; change via setTheme / <AppSettingPicker kind="theme">.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | null',defaultValue:"null",description:"Brand-palette axis \u2192 <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Density axis \u2192 <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally."},{name:"scaling",type:"number | null",defaultValue:"null",description:"Continuous global size multiplier \u2192 inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis)."},{name:"fontSize",type:'"sm" | "default" | "lg"',defaultValue:'"default"',description:"Base type-size axis \u2192 <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling."}],usage:['DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY \u2014 they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind="density" | "fontSize" | "theme" | "brand" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().',"DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children \u2014 every godx-ui picker (LocalePicker, TimezonePicker, DateFormatPicker, TimeFormatPicker), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.","DO NOT omit AppProvider and then try to use LocalePicker, TimezonePicker, or formatDate standalone \u2014 useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.","DO use the `persist={false}` prop on AppProvider when writing isolated tests or standalone settings forms where localStorage should not be read or written. With the default `persist={true}` the provider reads from localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.","DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone \u2014 use the named aliases.","DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.","DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider \u2014 all TimezonePicker instances that omit their own `options` prop will inherit this restricted list automatically from context. Without it, TimezonePicker renders the full IANA list (~600 entries)."],useCases:["App bootstrap in a multi-locale SaaS admin (ja/en/vi) \u2014 mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.","User settings page \u2014 render LocalePicker, TimezonePicker, DateFormatPicker, and TimeFormatPicker as zero-prop children inside the existing AppProvider; each picker reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.","Server-rendered Inertia app with SSR hydration \u2014 pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.","Multi-entity accounting dashboard \u2014 use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.","Isolated preview / Storybook story \u2014 wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.","Test harness \u2014 wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."],related:["LocalePicker \u2014 the language-selector control that reads/writes AppProvider locale context automatically when used as a zero-prop child. Prefer LocalePicker over calling setLocale from useAppContext() directly in UI.","TimezonePicker \u2014 the timezone-selector control; inherits `timezoneOptions` from AppProvider context when its own `options` prop is omitted. Both pickers require AppProvider to be in the tree unless controlled props are passed.","formatDate \u2014 the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.","AppShell \u2014 the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it \u2014 do not add a second one."],example:`import { AppProvider } from "@godxjp/ui/app";
851
+ </Toolbar>`,docPath:"navigation/toolbar",storyPath:"navigation/toolbar.tsx",rules:[23,44,45,46]},{name:"AppProvider",group:"providers",tagline:"Root locale/timezone/date-time context \u2014 wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",props:[{name:"defaultLocale",type:'"ja" | "en" | "vi"',defaultValue:'"vi"',description:"Initial locale."},{name:"defaultTimezone",type:'string | "browser" | "system"',defaultValue:'"browser"',description:"Initial IANA timezone."},{name:"defaultDateFormat",type:'"iso" | "dmy" | "mdy" | "locale"',defaultValue:'"locale"',description:"Initial date display format."},{name:"defaultTimeFormat",type:'"24h" | "12h" | "locale"',defaultValue:'"locale"',description:"Initial clock format."},{name:"theme",type:'"light" | "dark"',defaultValue:'"light"',description:'Theme axis \u2192 <html data-theme>. Equal alias of the legacy .dark class. Persisted; change via setTheme / <AppSettingPicker kind="theme">.'},{name:"brand",type:'"brand" | "crm" | "logistics" | "partner" | "slate" | "dxs" | null',defaultValue:"null",description:'Brand-palette axis \u2192 <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines. "dxs" is THE CANONICAL DXS PRESET and is more than a tint: it also binds the canonical hosted-identity surface contract (36px auth controls, 22.5rem auth card measure, 16px page inset / 15px below 30rem), so a DXS surface needs ZERO page CSS for auth geometry, density, insets, logo colour, divider or footer. Stylesheet-only apps (no provider) import "@godxjp/ui/theme/dxs.canonical.css" instead \u2014 same contract, guarded against drift by a test.'},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Density axis \u2192 <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally."},{name:"scaling",type:"number | null",defaultValue:"null",description:"Continuous global size multiplier \u2192 inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis)."},{name:"fontSize",type:'"sm" | "default" | "lg"',defaultValue:'"default"',description:"Base type-size axis \u2192 <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling."}],usage:['DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY \u2014 they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind="density" | "fontSize" | "theme" | "brand" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().',"DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children \u2014 every godx-ui picker (LocalePicker, TimezonePicker, DateFormatPicker, TimeFormatPicker), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.","DO NOT omit AppProvider and then try to use LocalePicker, TimezonePicker, or formatDate standalone \u2014 useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.","DO use the `persist={false}` prop on AppProvider when writing isolated tests or standalone settings forms where localStorage should not be read or written. With the default `persist={true}` the provider reads from localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.","DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone \u2014 use the named aliases.","DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.","DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider \u2014 all TimezonePicker instances that omit their own `options` prop will inherit this restricted list automatically from context. Without it, TimezonePicker renders the full IANA list (~600 entries)."],useCases:["App bootstrap in a multi-locale SaaS admin (ja/en/vi) \u2014 mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.","User settings page \u2014 render LocalePicker, TimezonePicker, DateFormatPicker, and TimeFormatPicker as zero-prop children inside the existing AppProvider; each picker reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.","Server-rendered Inertia app with SSR hydration \u2014 pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.","Multi-entity accounting dashboard \u2014 use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.","Isolated preview / Storybook story \u2014 wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.","Test harness \u2014 wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."],related:["LocalePicker \u2014 the language-selector control that reads/writes AppProvider locale context automatically when used as a zero-prop child. Prefer LocalePicker over calling setLocale from useAppContext() directly in UI.","TimezonePicker \u2014 the timezone-selector control; inherits `timezoneOptions` from AppProvider context when its own `options` prop is omitted. Both pickers require AppProvider to be in the tree unless controlled props are passed.","formatDate \u2014 the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.","AppShell \u2014 the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it \u2014 do not add a second one."],example:`import { AppProvider } from "@godxjp/ui/app";
724
852
 
725
853
  <AppProvider defaultLocale="ja" defaultTimezone="Asia/Tokyo" defaultDateFormat="iso" defaultTimeFormat="24h">
726
854
  {children}
@@ -1495,7 +1623,7 @@ export default function PasswordBlock() {
1495
1623
  <PasswordStrength value={value} rules={rules} />
1496
1624
  </div>
1497
1625
  );
1498
- }`,storyPath:"data-entry/PasswordStrength.stories.tsx",rules:[3,6]},{name:"InputOTP",group:"data-entry",tagline:"One-time-code / 2FA input (input-otp) \u2014 N single-character slots that behave as one field. Compose InputOTP > InputOTPGroup > InputOTPSlot.",props:[{name:"maxLength",type:"number",required:!0,description:"Number of slots (e.g. 6)."},{name:"value",type:"string",description:"Controlled value."},{name:"onChange",type:"(value: string) => void",description:"Value callback (this is a true text input \u2014 onChange is the DOM-style value handler here)."},{name:"pattern",type:"string",description:"Allowed-char regex (e.g. digits only)."}],usage:["DO set `maxLength` to the code length and render that many InputOTPSlot with sequential `index`.","DO wrap slots in InputOTPGroup; use InputOTPSeparator between groups (e.g. 3 + 3).","DON'T build N separate Inputs \u2014 this is ONE field with paste, arrow-key, and caret handling built in."],useCases:["2FA / OTP verification code","Email / SMS confirmation code","PIN entry","Invite / redemption code"],related:["Input (a normal single text field)","PasswordInput (masked secret field)"],example:`import { InputOTP, InputOTPGroup, InputOTPSlot } from "@godxjp/ui/data-entry";
1626
+ }`,storyPath:"data-entry/PasswordStrength.stories.tsx",rules:[3,6]},{name:"InputOTP",group:"data-entry",tagline:"One-time-code / 2FA input (input-otp) \u2014 N single-character slots that behave as one field. Compose InputOTP > InputOTPGroup > InputOTPSlot.",props:[{name:"maxLength",type:"number",required:!0,description:"Number of slots (e.g. 6)."},{name:"value",type:"string",description:"Controlled value."},{name:"onChange",type:"(value: string) => void",description:"Value callback (this is a true text input \u2014 onChange is the DOM-style value handler here)."},{name:"pattern",type:"string",description:"Allowed-char regex (e.g. digits only)."}],usage:["DO set `maxLength` to the code length and render that many InputOTPSlot with sequential `index`.","DO wrap slots in InputOTPGroup; use InputOTPSeparator between groups (e.g. 3 + 3).","DON'T build N separate Inputs \u2014 this is ONE field with paste, arrow-key, and caret handling built in.","DO widen the slots with `--otp-slot-size` (gh#233) when a challenge row must fill a wide auth panel \u2014 it defaults to the live `--control-height` tier, so re-scoping `--control-height` on the card instead would also resize the submit button and every other input in it. Set a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset.","DO drive the sign-in MFA challenge from FormField: `error` wires aria-invalid + aria-errormessage + a role=alert message onto the single field, and the slot borders turn destructive. State is never colour-only."],useCases:["2FA / OTP verification code","Email / SMS confirmation code","PIN entry","Invite / redemption code"],related:["Input (a normal single text field)","PasswordInput (masked secret field)"],example:`import { InputOTP, InputOTPGroup, InputOTPSlot } from "@godxjp/ui/data-entry";
1499
1627
 
1500
1628
  <InputOTP maxLength={6}>
1501
1629
  <InputOTPGroup>
@@ -1553,7 +1681,7 @@ export default function PasswordBlock() {
1553
1681
  <CarouselPrevious />
1554
1682
  <CarouselNext />
1555
1683
  <CarouselDots />
1556
- </Carousel>`},{name:"AppSettingPicker",group:"navigation",tagline:"One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) \u2014 covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",props:[{name:"kind",type:'"locale" | "timezone" | "dateFormat" | "timeFormat" | "theme" | "brand" | "density" | "fontSize"',description:"Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null \u2192 app token)."},{name:"value",type:"string",description:"Controlled value for the chosen kind. When omitted, reads the current value from AppProvider context for that kind."},{name:"onValueChange",type:"(value: string) => void",description:"Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present."},{name:"appearance",type:'"labeled" | "icon"',description:'Trigger presentation. "labeled" (default) shows the leading icon + selected value: it hugs its content below `sm` (`w-auto max-w-full`) and takes a per-kind fixed width from `sm` up \u2014 it no longer stretches to `w-full` on narrow screens, so it fits a topbar (pass className="w-full" for a full-width form field). "icon" is the supported icon-only topbar trigger (e.g. a globe locale switcher): it structurally drops the value text and the picker\'s owned width and hides the chevron, squares the box to the density-aware --control-height tap target (\u226544px on touch), and always keeps the localized aria-label so it can never ship nameless. Menu options still show localized names (the selected value is checked in the popup). Use these instead of overriding internal descendants / width classes with CSS.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the SelectTrigger."},{name:"disabled",type:"boolean",description:"Disables the Select control."},{name:"id",type:"string",description:"HTML id forwarded to the SelectTrigger for label association."}],usage:["DO: Mount inside <AppProvider> for zero-config use \u2014 the picker reads and writes the context value named by kind, no value/onValueChange needed.","DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.","DO NOT: Render without AppProvider and without both controlled props \u2014 it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.","DO: Render four instances with different kind values to build a full preferences panel; they all share the same AppProvider context and stay in sync.",'DO: For an icon-only topbar utility (a globe language switcher), pass appearance="icon" \u2014 the supported compact trigger. NEVER hand-roll it by hiding the value/width with descendant-selector CSS.',"DON'T hand-roll a locale/timezone/format Select \u2014 AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."],useCases:['App-shell top-nav language switcher: <AppSettingPicker kind="locale" /> under AppProvider, persisting to localStorage with no extra state.','Icon-only topbar locale switcher (globe): <AppSettingPicker kind="locale" appearance="icon" /> in a Topbar `end` slot \u2014 square, value-less, keyboard + aria-label preserved.',"User settings page with all four preferences \u2014 render kind=locale, kind=timezone, kind=dateFormat, kind=timeFormat together under one AppProvider.","Onboarding step that picks language/timezone before the rest of the app is configured \u2014 AppProvider persist={false} + controlled values to keep state local.",'Storybook/test harness without AppProvider \u2014 fully controlled: <AppSettingPicker kind="timeFormat" value="24h" onValueChange={fn} />.'],related:["AppProvider \u2014 required peer unless fully controlled. Supplies locale/timezone/dateFormat/timeFormat plus their setters and the i18n context.","Select \u2014 the data-entry primitive AppSettingPicker is built on; reach for Select directly for any non-AppProvider dropdown.","formatDate \u2014 reads the same AppProvider date/time context that kind='dateFormat'/'timeFormat' write to."],example:`{\`// Uncontrolled \u2014 AppProvider manages and persists every setting
1684
+ </Carousel>`},{name:"AppSettingPicker",group:"navigation",tagline:"One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) \u2014 covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",props:[{name:"kind",type:'"locale" | "timezone" | "dateFormat" | "timeFormat" | "theme" | "brand" | "density" | "fontSize"',description:"Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null \u2192 app token)."},{name:"value",type:"string",description:"Controlled value for the chosen kind. When omitted, reads the current value from AppProvider context for that kind."},{name:"onValueChange",type:"(value: string) => void",description:"Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present."},{name:"appearance",type:'"labeled" | "icon" | "inline"',description:'Trigger presentation. "labeled" shows the leading icon + selected value: it hugs its content below `sm` (`w-auto max-w-full`) and takes a per-kind fixed width from `sm` up \u2014 it no longer stretches to `w-full` on narrow screens, so it fits a topbar (pass className="w-full" for a full-width form field). "icon" is the supported icon-only topbar trigger (e.g. a globe locale switcher): it structurally drops the value text and the picker\'s owned width and hides the chevron, squares the box to the density-aware --control-height tap target (\u226544px on touch), and always keeps the localized aria-label so it can never ship nameless. "inline" renders the selected value as a chrome-less text trigger for a legal/auth footer (no border, no box). DEFAULT IS KIND-DEPENDENT: kind="locale" defaults to "icon" (its product contract is the compact language switcher); every other kind defaults to "labeled". Use these instead of overriding internal descendants / width classes with CSS.'},{name:"compact",type:"boolean",defaultValue:"false",description:'Compact trigger density (gh#217): re-tiers the box to the official --control-height-sm tier and DROPS the picker\'s owned per-kind width, so a labelled trigger hugs its value. This is the supported auth/legal-footer locale switch \u2014 `<AppSettingPicker kind="locale" appearance="labeled" compact />` \u2014 when the square icon-only default reads as a stray button but the full labelled trigger is too tall. All geometry is tokenized (--app-setting-picker-compact-{control-height,padding-x,gap,font-size}); the accessible name and the visible value are both preserved. No effect on appearance="inline", which is already chrome-less.'},{name:"className",type:"string",description:"Extra CSS classes merged onto the SelectTrigger."},{name:"disabled",type:"boolean",description:"Disables the Select control."},{name:"id",type:"string",description:"HTML id forwarded to the SelectTrigger for label association."}],usage:["DO: Mount inside <AppProvider> for zero-config use \u2014 the picker reads and writes the context value named by kind, no value/onValueChange needed.","DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.","DO NOT: Render without AppProvider and without both controlled props \u2014 it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.","DO: Render four instances with different kind values to build a full preferences panel; they all share the same AppProvider context and stay in sync.",'DO: For an icon-only topbar utility (a globe language switcher), pass appearance="icon" \u2014 the supported compact trigger. NEVER hand-roll it by hiding the value/width with descendant-selector CSS.','DO: For an AUTH/LEGAL FOOTER locale switch, pass appearance="labeled" compact \u2014 a small, content-hugging labelled trigger (readable language name, --control-height-sm box). Use appearance="inline" instead when the footer wants no control chrome at all. NEVER re-size the trigger with a page-local height/width class.',"DON'T hand-roll a locale/timezone/format Select \u2014 AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."],useCases:['App-shell top-nav language switcher: <AppSettingPicker kind="locale" /> under AppProvider, persisting to localStorage with no extra state.','Icon-only topbar locale switcher (globe): <AppSettingPicker kind="locale" appearance="icon" /> in a Topbar `end` slot \u2014 square, value-less, keyboard + aria-label preserved.','Auth-footer locale switch: <AppSettingPicker kind="locale" appearance="labeled" compact /> inside an <AuthFooter locale={\u2026}> slot \u2014 the readable language name at the small control tier, hugging its value.',"User settings page with all four preferences \u2014 render kind=locale, kind=timezone, kind=dateFormat, kind=timeFormat together under one AppProvider.","Onboarding step that picks language/timezone before the rest of the app is configured \u2014 AppProvider persist={false} + controlled values to keep state local.",'Storybook/test harness without AppProvider \u2014 fully controlled: <AppSettingPicker kind="timeFormat" value="24h" onValueChange={fn} />.'],related:["AppProvider \u2014 required peer unless fully controlled. Supplies locale/timezone/dateFormat/timeFormat plus their setters and the i18n context.","Select \u2014 the data-entry primitive AppSettingPicker is built on; reach for Select directly for any non-AppProvider dropdown.","formatDate \u2014 reads the same AppProvider date/time context that kind='dateFormat'/'timeFormat' write to."],example:`{\`// Uncontrolled \u2014 AppProvider manages and persists every setting
1557
1685
  import { AppProvider } from "@godxjp/ui/app";
1558
1686
  import { AppSettingPicker } from "@godxjp/ui/navigation";
1559
1687
 
@@ -1610,7 +1738,18 @@ export function NotifyRow() {
1610
1738
  categoryKey="region"
1611
1739
  series={[{ dataKey: "sales", label: t("metric.sales") }]}
1612
1740
  numberFormat={{ notation: "compact" }}
1613
- />`,storyPath:"charts/BarChart.stories.tsx",rules:[]},{name:"AreaChart",group:"data-display",importPath:"@godxjp/ui/charts",tagline:"Magnitude over an ordered category axis \u2014 overlay or `stacked` areas, optional `curved` smoothing, localized formatting + text alternative. Data-visualization graph / plot.",props:[{name:"data",type:"ChartDatum[]",required:!0,description:"Row data: one category per row with a numeric value per series."},{name:"series",type:"ChartSeriesProp[]",required:!0,description:"Plotted series: { dataKey, label?, color? }."},{name:"categoryKey",type:"string",required:!0,description:"Key into each datum holding the x-axis category label."},{name:"label",type:"string",required:!0,description:"Accessible name + visible caption."},{name:"description",type:"string",description:"Extra context appended to the screen-reader description."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Canvas height preset. Ignored when `height` is set."},{name:"height",type:"number",description:"Explicit canvas height in px (overrides `size`)."},{name:"showLegend",type:"boolean",defaultValue:"true",description:"Show the series legend."},{name:"showGrid",type:"boolean",defaultValue:"true",description:"Show the cartesian background grid."},{name:"numberFormat",type:"Intl.NumberFormatOptions",description:"Locale-aware formatting for ticks + tooltip values."},{name:"stacked",type:"boolean",defaultValue:"false",description:"Stack series areas instead of overlaying them."},{name:"curved",type:"boolean",defaultValue:"false",description:"Render smooth (monotone) areas instead of straight segments."},{name:"emptyMessage",type:"string",description:"Message shown when `data` is empty."}],usage:['DO import from the charts entry: `import { AreaChart } from "@godxjp/ui/charts";` (recharts optional peer required).',"DO use `stacked` to show how parts accumulate into a total over time.","DON'T overlay more than 2-3 unstacked areas \u2014 fill opacity makes dense overlays unreadable; switch to LineChart."],useCases:["Cumulative volume over time (e.g. total transactions per day).","Stacked composition trend (traffic sources, revenue streams)."],related:["LineChart \u2014 when only the trend line matters, not the filled magnitude.","BarChart \u2014 discrete category comparison."],example:`import { AreaChart } from "@godxjp/ui/charts";
1741
+ />`,storyPath:"charts/BarChart.stories.tsx",rules:[]},{name:"CompactBarTrend",group:"data-display",importPath:"@godxjp/ui/charts",tagline:"DEPENDENCY-FREE compact vertical bar trend for dashboard summary cards \u2014 N category/value pairs, muted marks plus ONE emphasized 'current' bar, all geometry from --chart-trend-* tokens. Needs NO recharts. Sparkline / micro-chart / activity pulse / KPI trend strip.",props:[{name:"data",type:"ChartDatum[]",required:!0,description:"Row data \u2014 one bar per row. Any point count (7 is not hard-coded)."},{name:"categoryKey",type:"string",required:!0,description:"Key into each datum holding the category (tick) label."},{name:"valueKey",type:"string",required:!0,description:"Key into each datum holding the plotted numeric value."},{name:"label",type:"string",required:!0,description:"Accessible name + visible caption."},{name:"description",type:"string",description:"Extra context appended to the screen-reader description."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"xs"',description:"Plot-height tier (--chart-trend-plot-height-*). xs is the dashboard summary-card density."},{name:"emphasizedIndex",type:"number",description:"Index of the emphasized 'current' bar. Negative counts from the end (-1 = latest); out of range = no emphasis. Also annotated in the text alternative, so it is never colour-only."},{name:"showCategoryLabels",type:"boolean",defaultValue:"true",description:"Render the category tick labels under the plot."},{name:"numberFormat",type:"Intl.NumberFormatOptions",description:"Locale-aware formatting for the values in the text alternative."},{name:"footer",type:"ReactNode",description:"Activity footer slot below the plot, rendered OUTSIDE the role=img graphic so links/buttons in it stay reachable."},{name:"emptyMessage",type:"string",description:"Message shown when `data` is empty."},{name:"ref",type:"React.Ref<HTMLElement>",description:"Forwarded to the <figure> element."}],usage:['DO import from the charts entry: `import { CompactBarTrend } from "@godxjp/ui/charts";` \u2014 unlike BarChart it needs NO `recharts` peer, so an app that may not add dependencies can still use a real framework chart.','DO reach for it INSIDE a dashboard summary card (a 7-day signup/organization/activity strip under a StatCard headline) \u2014 that is the density `size="xs"` is tuned for.',"DO mark the current period with `emphasizedIndex={-1}` (or an explicit index); the highlight is duplicated in the screen-reader text alternative, so it never depends on colour alone.","DO retheme through the `--chart-trend-*` tokens (bar gap/radius/width, plot heights, muted + emphasis fills, opt-in baseline). NEVER add page-local CSS, an inline height calculation, or a hardcoded colour.","DON'T use it when you need axes, a grid, tooltips, multiple series, or a continuous trend \u2014 that is BarChart / LineChart / AreaChart (recharts peer). DON'T fake bars with styled divs in the app.",'DON\'T put interactive content in the plot: anything clickable goes in `footer`, which renders outside the `role="img"` graphic.'],useCases:["Seven-day new-organizations / new-users trend inside an admin dashboard summary card (SCR-201).","Weekly activity pulse beside a KPI headline, where the SHAPE matters more than exact figures.","A dependency-constrained app (no recharts) that still needs a token-driven chart."],related:["BarChart \u2014 full cartesian bar chart with axes/grid/tooltip/legend (requires the recharts optional peer).","StatCard \u2014 the KPI headline this trend usually sits under.","Progress \u2014 one ratio against a target, not a series over time."],example:`import { CompactBarTrend } from "@godxjp/ui/charts";
1742
+
1743
+ <CompactBarTrend
1744
+ label={t("dashboard.newOrganizations7d")}
1745
+ description={t("dashboard.newOrganizationsHint")}
1746
+ data={trend}
1747
+ categoryKey="date"
1748
+ valueKey="count"
1749
+ emphasizedIndex={-1}
1750
+ size="xs"
1751
+ footer={<Text size="xs" tone="muted">{t("dashboard.lastUpdated", { at })}</Text>}
1752
+ />`,storyPath:"charts/CompactBarTrend.stories.tsx",rules:[]},{name:"AreaChart",group:"data-display",importPath:"@godxjp/ui/charts",tagline:"Magnitude over an ordered category axis \u2014 overlay or `stacked` areas, optional `curved` smoothing, localized formatting + text alternative. Data-visualization graph / plot.",props:[{name:"data",type:"ChartDatum[]",required:!0,description:"Row data: one category per row with a numeric value per series."},{name:"series",type:"ChartSeriesProp[]",required:!0,description:"Plotted series: { dataKey, label?, color? }."},{name:"categoryKey",type:"string",required:!0,description:"Key into each datum holding the x-axis category label."},{name:"label",type:"string",required:!0,description:"Accessible name + visible caption."},{name:"description",type:"string",description:"Extra context appended to the screen-reader description."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Canvas height preset. Ignored when `height` is set."},{name:"height",type:"number",description:"Explicit canvas height in px (overrides `size`)."},{name:"showLegend",type:"boolean",defaultValue:"true",description:"Show the series legend."},{name:"showGrid",type:"boolean",defaultValue:"true",description:"Show the cartesian background grid."},{name:"numberFormat",type:"Intl.NumberFormatOptions",description:"Locale-aware formatting for ticks + tooltip values."},{name:"stacked",type:"boolean",defaultValue:"false",description:"Stack series areas instead of overlaying them."},{name:"curved",type:"boolean",defaultValue:"false",description:"Render smooth (monotone) areas instead of straight segments."},{name:"emptyMessage",type:"string",description:"Message shown when `data` is empty."}],usage:['DO import from the charts entry: `import { AreaChart } from "@godxjp/ui/charts";` (recharts optional peer required).',"DO use `stacked` to show how parts accumulate into a total over time.","DON'T overlay more than 2-3 unstacked areas \u2014 fill opacity makes dense overlays unreadable; switch to LineChart."],useCases:["Cumulative volume over time (e.g. total transactions per day).","Stacked composition trend (traffic sources, revenue streams)."],related:["LineChart \u2014 when only the trend line matters, not the filled magnitude.","BarChart \u2014 discrete category comparison."],example:`import { AreaChart } from "@godxjp/ui/charts";
1614
1753
 
1615
1754
  <AreaChart
1616
1755
  label={t("dashboard.trafficByChannel")}
@@ -1630,7 +1769,56 @@ export function NotifyRow() {
1630
1769
  nameKey="category"
1631
1770
  numberFormat={{ style: "currency", currency: "JPY" }}
1632
1771
  donut
1633
- />`,storyPath:"charts/PieChart.stories.tsx",rules:[]}];function P(a){let t=a.trim().toLowerCase();return y.find(e=>e.name.toLowerCase()===t)}function E(a){return y.filter(t=>t.group===a)}var w=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]}];function z(a){let t=a.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return w.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===t)}var x=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:"The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts fills this with the bundled Noto Sans JP."},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:"Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale's face by setting e.g. `--font-sans-ja: \"Noto Sans JP\", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer's font pipeline, not the library."},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Brand/action color role."},{name:"--success",category:"semantic",tier:"semantic",role:"Success status role."},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."}];function O(a){return x.filter(t=>t.category===a)}var L=[{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"6px",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-line-height",value:"1.1",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-size",value:"2.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-glyph-size",value:"1.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--control-height-compact",value:"1.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"2rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"2.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"1px",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(0.875rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-focus-ring-width",value:"var(--focus-ring-width)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-description-gap",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-control-offset",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-compact",value:"2rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-compact",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-track-height",value:"0.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-thumb-size",value:"1rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-input-width",value:"6.5rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-input-padding-x",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-padding",value:"var(--space-1)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-x",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-edge-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--control-height-compact",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--control-height-default",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-radius",value:"var(--radius)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--form-label-width",value:"max-content",description:"Width of the label column in horizontal/inline layout. A service theme sets * this once (e.g. 110px) to align every form to its design grid; the Form/ * FormField `labelWidth` prop overrides per form/field."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys \u2026). Sits in a flush CardContent; * rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-gap",value:"var(--space-3)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-sm",value:"11rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-md",value:"14rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--tabs-list-max-inline-size",value:"100%",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--tabs-list-overflow",value:"auto",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"3rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-page-max-width",value:"80rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-nav-item-height",value:"2rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-nav-item-font-size",value:"0.8125rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--topbar-search-max-width",value:"26.25rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-bar-padding-x",value:"var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-sm",value:"32rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-md",value:"46rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-width-lg",value:"64rem",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--table-row-height-compact",value:"1.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"2rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"2.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:"Table component tokens: row height, cell padding."},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."}];var u=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Two-PR workflow: (1) submodule PR \u2192 `main`, (2) downstream PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don\'t force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists. If a real need has NO prop/token/primitive, that is a library GAP \u2014 file it (draft_bug_report), don't paper over it with ad-hoc Tailwind."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`. Born from real consumption: PageContainer's header divider was undisableable until tokenised."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app. Born from real consumption: `--form-label-width` / `--form-label-gap` (design spec said 110px/8px; the values were prop-only and hard-coded `--space-4`).'},{number:46,title:"Typography is tokens, default is base",body:"A UI framework gives consumers knobs: every font-size in `src/styles/*.css` MUST reference a token \u2014 the global modular scale `var(--font-size-{2xs|xs|sm|base|lg|xl|2xl})` or a per-component `var(--{component}-\u2026-font-size)` knob (rule #45) \u2014 never a hard-coded literal (`font-size: 12px` can't be re-themed). The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync). Enforced by `check:typography`."}];function S(a){return u.find(t=>t.number===a)}var g=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1772
+ />`,storyPath:"charts/PieChart.stories.tsx",rules:[]},{name:"CommandPalette",group:"data-entry",tagline:"Searchable command dialog with controlled or uncontrolled open state and consumer-owned selection.",props:[{name:"groups",type:"CommandPaletteGroup[]",required:!0,description:"Grouped command items."},{name:"labels",type:"CommandPaletteLabels",required:!0,description:"Localized dialog and search copy."},{name:"onSelect",type:"(item: CommandPaletteItem) => void",required:!0,description:"Consumer-owned selection handler."},{name:"open",type:"boolean",description:"Controlled open state."},{name:"defaultOpen",type:"boolean",defaultValue:"false",description:"Initial uncontrolled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state callback."},{name:"loading",type:"boolean",defaultValue:"false",description:"Shows the supplied loading content."},{name:"error",type:"ReactNode",description:"Consumer-supplied error content."}],usage:["Provide localized labels and real command groups; the component does not fetch commands.","Use either `open` plus `onOpenChange` or `defaultOpen`; do not mirror both state models."],example:`import { CommandPalette } from "@godxjp/ui/data-entry";
1773
+
1774
+ <CommandPalette
1775
+ groups={[{ id: "pages", label: "Pages", items: [{ id: "home", label: "Home" }] }]}
1776
+ labels={{ open: "Open commands", title: "Commands", description: "Choose a command", placeholder: "Search", empty: "No results" }}
1777
+ onSelect={(item) => navigate(item.id)}
1778
+ />`,storyPath:"data-entry/CommandPalette.stories.tsx",rules:[]},{name:"TwoFactorSetup",group:"feedback",tagline:"Canonical two-factor enrollment dialog for QR/manual-key verification and recovery-code acknowledgement.",props:[{name:"open",type:"boolean",required:!0,description:"Controlled dialog state."},{name:"onOpenChange",type:"(open: boolean) => void",required:!0,description:"Dialog state callback."},{name:"manualKey",type:"string",required:!0,description:"Consumer-provided enrollment secret."},{name:"qrValue",type:"string",description:"Optional QR payload for the same enrollment secret."},{name:"code",type:"string",required:!0,description:"Current verification code."},{name:"onCodeChange",type:"(code: string) => void",required:!0,description:"Verification-code callback."},{name:"onConfirm",type:"() => void",required:!0,description:"Consumer-owned verification action."},{name:"recoveryCodes",type:"string[]",description:"Codes shown only after successful enrollment."},{name:"onAcknowledge",type:"() => void",required:!0,description:"Recovery-code acknowledgement action."},{name:"labels",type:"TwoFactorSetupLabels",required:!0,description:"All localized copy."},{name:"pending",type:"boolean",defaultValue:"false",description:"Disables actions while the consumer request runs."}],usage:["Keep enrollment, verification, persistence, and secret lifecycle in the consumer; this component is presentational.","Only pass recovery codes after verification succeeds, and clear them when the dialog lifecycle ends."],example:`import { TwoFactorSetup } from "@godxjp/ui/feedback";
1779
+
1780
+ <TwoFactorSetup
1781
+ open={open}
1782
+ onOpenChange={setOpen}
1783
+ qrValue={enrollment.qr}
1784
+ manualKey={enrollment.secret}
1785
+ code={code}
1786
+ onCodeChange={setCode}
1787
+ onConfirm={verify}
1788
+ recoveryCodes={recoveryCodes}
1789
+ onAcknowledge={finish}
1790
+ labels={labels}
1791
+ />`,storyPath:"feedback/TwoFactorSetup.stories.tsx",rules:[]},{name:"AuthDivider",group:"layout",tagline:"Localized auth-form divider with equal rules and an accessible separator label.",props:[{name:"label",type:"string",required:!0,description:"Localized conjunction such as \u201Cor\u201D."},{name:"className",type:"string",description:"Optional structural class override."}],example:`import { AuthDivider } from "@godxjp/ui/layout";
1792
+
1793
+ <AuthDivider label="or" />`,storyPath:"layout/AuthDivider.stories.tsx",rules:[]},{name:"AuthFooter",group:"layout",tagline:"Compact hosted-auth footer for consumer-owned product, legal, privacy, and locale content.",props:[{name:"product",type:"ReactNode",required:!0,description:"Product identity content."},{name:"terms",type:"ReactNode",required:!0,description:"Terms link or localized text."},{name:"privacy",type:"ReactNode",required:!0,description:"Privacy link or localized text."},{name:"locale",type:"ReactNode",description:"Optional consumer-owned locale control."},{name:"className",type:"string",description:"Optional structural class override merged onto the footer root."}],usage:["Pass real links and locale controls from the consumer; AuthFooter never invents navigation.","DO drop it into AuthShell's `footer` slot \u2014 that slot supplies the contentinfo landmark, so AuthFooter itself renders a plain div and can also sit inside an existing footer without nesting landmarks.","AuthFooter owns ONLY the geometry: mono ramp, wrap, and a `\xB7` separator between the slots that are actually PRESENT (omit `locale` and its separator disappears). Don't hand-write separators into the slot content.","Public type: `AuthFooterProp` (alias `AuthFooterProps`) from `@godxjp/ui/layout` \u2014 registered in the prop registry, not a local interface.","Retune the line through `--auth-footer-content-gap` / `--auth-footer-text-font-size`, never page CSS (rule #45)."],example:`import { AuthFooter } from "@godxjp/ui/layout";
1794
+
1795
+ <AuthFooter product="Acme ID" terms={<a href="/terms">Terms</a>} privacy={<a href="/privacy">Privacy</a>} locale="English" />`,storyPath:"layout/AuthFooter.stories.tsx",rules:[45]},{name:"AuthIdentity",group:"layout",tagline:"Hosted-auth identity heading with the shared mark and optional requesting-client context.",props:[{name:"title",type:"ReactNode",required:!0,description:"Primary auth heading."},{name:"requester",type:"ReactNode",description:"Optional real requesting-client context."},{name:"className",type:"string",description:"Optional structural class override merged onto the identity root."}],usage:["Only show `requester` when the consumer has authoritative client context.",'It ALREADY renders the canonical brand-green GoDX mark (`Logo mark="godx" tone="success"`, independent of --primary) plus the page h1 \u2014 don\'t add a second Logo or heading above it.',"Centring and rhythm are token-owned (`--auth-identity-gap` / `--auth-requester-*`); no page CSS (rule #45).","Public type: `AuthIdentityProp` (alias `AuthIdentityProps`) from `@godxjp/ui/layout` \u2014 registered in the prop registry, not a local interface."],example:`import { AuthIdentity } from "@godxjp/ui/layout";
1796
+
1797
+ <AuthIdentity title="Sign in" requester="Acme Portal is requesting access" />`,storyPath:"layout/AuthIdentity.stories.tsx",rules:[45]},{name:"AuthStack",group:"layout",tagline:"Token-spaced vertical stack for direct sections inside a canonical auth card.",props:[{name:"children",type:"ReactNode",description:"Auth sections in visual order."},{name:"className",type:"string",description:"Optional structural class override."}],example:`import { AuthStack } from "@godxjp/ui/layout";
1798
+
1799
+ <AuthStack><PasskeyAction /><CredentialsForm /></AuthStack>`,storyPath:"layout/AuthStack.stories.tsx",rules:[]},{name:"OrgSwitcher",group:"layout",tagline:"Searchable organization switcher with canonical trigger geometry, desktop popover, and mobile sheet.",props:[{name:"organizations",type:"readonly OrgSwitcherOrganization[]",required:!0,description:"Consumer-provided organization choices."},{name:"value",type:"string",description:"Selected organization id."},{name:"onValueChange",type:"(value: string) => void",description:"Selection callback; persistence remains consumer-owned."},{name:"labels",type:"OrgSwitcherLabels",required:!0,description:"Localized trigger, search, state, and retry copy."},{name:"collapsed",type:"boolean",defaultValue:"false",description:"Compact trigger presentation."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disables the trigger."},{name:"loading",type:"boolean",defaultValue:"false",description:"Shows the loading state."},{name:"error",type:"ReactNode",description:"Consumer-supplied error state."},{name:"onRetry",type:"() => void",description:"Consumer-owned retry callback."},{name:"responsive",type:'"auto" | "popover" | "sheet"',defaultValue:'"auto"',description:'Responsive presentation contract. "auto" resolves through the SHARED Sheet hook useSheetResponsiveMode(): desktop popover above --sheet-responsive-breakpoint-width (48rem/768px), focus-trapped bottom Sheet at/below it. Move that one token to move the line for every overlay.'},{name:"open",type:"boolean",description:"Controlled open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state callback."}],usage:["Provide only organizations the current user may select; the component does not authorize or fetch tenants.","Keep persistence and navigation in `onValueChange`; use loading/error props for the real query state.","DO put a plan/status affordance in `organization.badge` (a <Badge>) instead of stuffing it into `meta` \u2014 the badge is end-aligned in the expanded trigger and in the menu row, and hidden in the collapsed rail. ALWAYS pair it with a localized `organization.badgeLabel`: the trigger's accessible name comes from `labels.trigger`, so the badge is announced as an aria-describedby DESCRIPTION and the raw node is marked presentational (WCAG 1.1.1). `badgeLabel` also becomes a search keyword.",'DON\'T wrap OrgSwitcher in your own media query to pick popover vs sheet \u2014 `responsive="auto"` already reads the shared --sheet-responsive-breakpoint-width token.'],example:`import { OrgSwitcher } from "@godxjp/ui/layout";
1800
+ import { Badge } from "@godxjp/ui/data-display";
1801
+
1802
+ <OrgSwitcher
1803
+ organizations={[
1804
+ {
1805
+ id: "dxs",
1806
+ name: "DXS Holdings",
1807
+ meta: t("org.role.owner"),
1808
+ badge: <Badge variant="secondary">{t("org.plan.trial")}</Badge>,
1809
+ badgeLabel: t("org.plan.trial.sr"),
1810
+ },
1811
+ ]}
1812
+ value={organizationId}
1813
+ onValueChange={setOrganizationId}
1814
+ labels={labels}
1815
+ />`,storyPath:"layout/OrgSwitcher.stories.tsx",rules:[]},{name:"FilterBar",group:"navigation",tagline:"Domain-neutral list-page filter toolbar with optional clear action and labelled groups.",props:[{name:"children",type:"ReactNode",description:"Filter controls and groups."},{name:"onClear",type:"() => void",description:"Consumer-owned clear action."},{name:"hasActiveFilters",type:"boolean",defaultValue:"true",description:"Shows clear only when filters are active."},{name:"sticky",type:"boolean",defaultValue:"false",description:"Uses the token-owned sticky presentation."},{name:"overflow",type:"'wrap' | 'scroll'",defaultValue:"'wrap'",description:"Responsive overflow strategy (gh#216). 'wrap': stacked column below 640px, wrapping rows above. 'scroll': one bounded inline-scrolling row above 640px (still stacked below) with the clear-all action pinned at the inline end. The geometry is entirely token/CSS owned \u2014 never re-implement it in the page."},{name:"className",type:"string",description:"Optional structural class override."}],usage:["Compose real controls as children; filter state and URL synchronization remain consumer-owned.","Give each FilterBarGroup a `controlId` matching its single control's `id` (gh#216) so the visible caption is that control's real <label>; otherwise the control is nameless to a screen reader.","Reach for `overflow='scroll'` on filter-heavy list pages so a long JA/EN/VI label set never grows the strip into multiple rows and pushes the table below the fold."],example:`import { FilterBar, FilterBarGroup } from "@godxjp/ui/navigation";
1816
+
1817
+ <FilterBar overflow="scroll" onClear={clearFilters} hasActiveFilters={hasFilters}>
1818
+ <FilterBarGroup label="Status" controlId="f-status">
1819
+ <StatusSelect id="f-status" />
1820
+ </FilterBarGroup>
1821
+ </FilterBar>`,storyPath:"navigation/FilterBar.stories.tsx",rules:[]}];function O(t){let a=t.trim().toLowerCase();return v.find(e=>e.name.toLowerCase()===a)}function F(t){return v.filter(a=>a.group===t)}var k=[{name:"ValueProp<T = string>",concept:"Abstract controlled value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs","SearchSelect"]},{name:"DefaultValueProp<T = string>",concept:"Abstract uncontrolled initial value.",values:["generic"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Tabs"]},{name:"OnValueChangeProp<T = string>",concept:"Callback for abstract value changes. DOM events continue to use onChange.",values:["(value: T) => void"],usedBy:["CheckboxGroup","Upload","Cascader","TreeSelect","Transfer","settings pickers"]},{name:"OpenProp / DefaultOpenProp / OnOpenChangeProp",concept:"Disclosure state.",values:["boolean","(open: boolean) => void"],usedBy:["Dialog","Sheet","Popover"]},{name:"SizeProp",concept:"Shared public size names.",values:["xs","sm","md","lg"],usedBy:["Button","Steps","Switch"],notes:"Component-specific subsets must be documented. Old alias small is sm."},{name:"ToneProp",concept:"Semantic status/color intent.",values:["default","success","warning","destructive","info","muted","neutral"],usedBy:["Badge","Alert"],notes:"Status values belong in tone, not variant."},{name:"GapProp",concept:"Shared layout gap scale.",values:["xs","sm","md","lg","xl"],usedBy:["Flex"],notes:'The single shared gap scale on Flex \u2014 the one layout primitive (default direction="row"; use direction="col" for vertical rhythm; these replaced the removed Stack/Inline).'},{name:"TitleProp",concept:"Primary heading text.",values:["React.ReactNode"],usedBy:["PageContainer","PageHeader","EmptyState","Dialog"]},{name:"DensityProp",concept:"Page/subtree density.",values:["compact","default","comfortable"],usedBy:["PageContainer"]},{name:"AuthShellPresetProp",concept:"Named hosted-identity FLOW MEASURE for AuthShell \u2014 the card max-width plus the desktop and mobile page gutters, all owned by component tokens.",values:["default","device-authorization","context-selection"],usedBy:["AuthShell"],notes:'Orthogonal to AuthShell\'s `variant` (which owns control density + heading size) and applied after it, so `variant="canonical" preset="device-authorization"` composes. device-authorization = 380px card / 5px inline gutter at 390 (gh#220); context-selection = 25rem card, edge-to-edge on mobile (gh#217). Selecting a preset REPLACES any consumer-side --auth-shell-card-max-width override \u2014 a page-local width is the anti-pattern these presets exist to remove.'}];function B(t){let a=t.trim().toLowerCase().replace(/prop(?:<.*>)?$/i,"");return k.find(e=>e.name.toLowerCase().replace(/prop(?:<.*>)?$/i,"")===a)}var C=[{name:"--wa-*",category:"primitive",tier:"primitive",role:"Neutral decorative Japanese accent primitives for charts/tags/decoration only."},{name:"--chart-1..6",category:"primitive",tier:"primitive",role:"Neutral decorative chart series palette. The @godxjp/ui/charts components (LineChart/BarChart/AreaChart/PieChart) read these by series index automatically \u2014 a service rethemes every chart at once by overriding --chart-1..6; per-series/per-slice overrides go through the component's series.color / colors props."},{name:"--space-0..12",category:"primitive",tier:"primitive",role:"Raw spacing scale."},{name:"--font-size-*",category:"primitive",tier:"primitive",role:"Raw typography scale."},{name:"--font-sans-base",category:"semantic",tier:"semantic",role:'The default sans face. FONT-AGNOSTIC by default (pure system stack) \u2014 the library ships no hardcoded brand font. Override this for one face everywhere. --font-family-sans defaults to it; --font-family-display / --font-family-body default to --font-family-sans (override those for a dual display+body brand). Consumers supply the actual @font-face (next/font, @fontsource, self-host); the opt-in @godxjp/ui/styles/fonts OVERWRITES this (and --font-sans-vi) with the bundled DXS stack \u2014 "M PLUS 2" primary (incl. the Vietnamese coverage) with "Noto Sans JP" as the CJK fallback. BUNDLE CHANGED IN v18: v16 bundled Noto Sans JP + Montserrat, v18 bundles M PLUS 2 + Noto Sans JP \u2014 if a design spec names the v16 faces, set the tokens explicitly instead of relying on the bundle. IMPORT ORDER IS LOAD-BEARING: styles/fonts must be imported AFTER styles/base, because foundation.css declares this system-stack default on :root at the same specificity (0,1,0) and the later declaration wins (the all-in-one @godxjp/ui/styles entry already orders it correctly \u2014 issue #210).'},{name:"--font-sans-{ja,ko,vi,zh-hans,zh-hant}",category:"semantic",tier:"semantic",role:'Per-language font SLOT tokens. styles/base.css wires each [lang] to read its slot with --font-sans-base as fallback, so a consumer switches a locale\'s face by setting e.g. `--font-sans-ja: "Noto Sans JP", var(--font-sans-base)` \u2014 NO [lang] selectors to write. Empty by default. zh-hans matches lang zh|zh-Hans|zh-CN; zh-hant matches zh-Hant|zh-TW. Deciding which @font-face ships on which locale route is the consumer\'s font pipeline, not the library. The opt-in @godxjp/ui/styles/fonts only fills --font-sans-vi (with the bundled "M PLUS 2", "Noto Sans JP" stack); the other slots stay empty and fall back to --font-sans-base.'},{name:"--duration-{fast,base,slow}",category:"primitive",tier:"primitive",role:"Motion durations (150 / 250 / 500ms). Read these instead of a literal `0.5s` for enter/transition timing (rule #2). the reference design keeps motion short; honour `prefers-reduced-motion` at the call site."},{name:"--ease-{standard,emphasized,decelerate,accelerate}",category:"primitive",tier:"primitive",role:"Motion easing curves. `standard` for most transitions, `emphasized` for entrances/overlays (the vaul drawer curve), `decelerate` for settling in, `accelerate` for exits. Read instead of a literal `cubic-bezier(\u2026)`."},{name:"--reveal-distance",category:"primitive",tier:"primitive",role:"Distance (10px) a revealed element travels on enter (translateY/-X). Read instead of a literal `translateY(10px)` for staggered reveals."},{name:"--reveal-stagger-step",category:"primitive",tier:"primitive",role:"One step (60ms) of the Reveal stagger ladder \u2014 `<Reveal delay={n}>` waits n \xD7 this before entering, so a column of reveals cascades. Read instead of a literal `.04s`/`.09s` per-item delay."},{name:"--shadow-color",category:"primitive",tier:"primitive",role:"RGB channels (space-separated, e.g. `12 26 49`) that tint the WHOLE elevation ramp (--shadow-xs..2xl) at once. Default `0 0 0`. Single-brand :root only \u2014 a scoped override does not re-resolve the ramp (the steps compute at :root); for a scoped/multi-tenant card lift set --card-shadow to a literal value."},{name:"--shadow-glow",category:"primitive",tier:"primitive",role:"Opt-in brand GLOW halo layered on the primary CTA's resting shadow. Default invisible (`0 0 0 0 transparent`, valid inside a comma shadow list). A service sets the whole value, e.g. `--shadow-glow: 0 8px 20px hsl(var(--primary) / .32)` \u2014 works scoped under [data-tenant] because the service declares it inside the scope."},{name:"--focus-ring-color",category:"primitive",tier:"primitive",role:"Themeable hue of EVERY keyboard-focus ring (HSL components, default var(--ring)). A leaf token, so it re-resolves at the element that paints the ring \u2014 override it once, even scoped under [data-tenant], to retint all focus rings. Pair with --focus-ring-width."},{name:"--focus-ring-width",category:"primitive",tier:"primitive",role:"Thickness (2px) of the solid keyboard-focus ring. Leaf token \u2014 :focus-visible rules read it directly as `0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color))`, never via an intermediate composite (which would freeze at :root). Propagates scoped."},{name:"--gradient-{brand,hero,glow}",category:"primitive",tier:"primitive",role:"Opt-in decorative gradient fills, default `none`. --gradient-hero paints the PageContainer header (hero banner); --gradient-glow paints the AppShell .app-main (ambient brand wash); --gradient-brand is a spare. A service sets the full gradient, e.g. `--gradient-glow: radial-gradient(60% 80% at 50% 0%, hsl(var(--primary) / .25), transparent)`."},{name:"--primary",category:"semantic",tier:"semantic",role:"Brand/action color role."},{name:"--success",category:"semantic",tier:"semantic",role:"Success status role."},{name:"--warning",category:"semantic",tier:"semantic",role:"Warning status role."},{name:"--destructive",category:"semantic",tier:"semantic",role:"Destructive/error status role."},{name:"--info",category:"semantic",tier:"semantic",role:"Information status role."},{name:"--attention",category:"semantic",tier:"semantic",role:"Attention status role."},{name:"--page-header-divider",category:"semantic",tier:"semantic",role:"PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."},{name:"--overlay-background",category:"semantic",tier:"semantic",role:"Modal scrim \u2014 the single backdrop colour shared by every overlay (Dialog, AlertDialog, Sheet, Drawer, AppShell mobile nav). Default `rgb(0 0 0 / 0.5)`. A service tints it once, e.g. a navy `rgb(12 26 49 / .55)`. HOW IT REACHES AN OVERLAY (gh#215 \u2014 until v18 it reached NOTHING; every overlay carried a private literal and this token was dead): each surface declares its own `--*-overlay-background` knob `initial` and resolves a SHARE of this colour at the call site, `color-mix(in srgb, var(--overlay-background) var(--*-overlay-alpha), transparent)`, so the calibrated per-surface depth survives (Sheet/drawer 40% = 0.2, Dialog 60% = 0.3) while ONE override here retints them all. NOTE: portaled overlays render outside a [data-tenant] subtree, so for multi-tenant scoping put the tenant attribute on the portal container too."},{name:"--page-header-pad-bottom",category:"semantic",tier:"semantic",role:"PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."},{name:"--master-detail-rail-{compact,standard}",category:"semantic",tier:"semantic",role:"MasterDetail fixed rail track \u2014 18.75rem (300px) / 20rem (320px). The `railWidth` prop picks the preset; a service theme retunes the presets themselves. Consumers never author a grid track."},{name:"--master-detail-gap",category:"semantic",tier:"semantic",role:"Gap between the MasterDetail master and detail regions (default --space-stack-md), side by side and stacked."},{name:"--master-detail-collapse-below",category:"semantic",tier:"semantic",role:"MasterDetail stacking threshold (default 40rem / the `sm` step), measured against the composition's OWN inline size, not the viewport: wider keeps the split, narrower stacks master above detail. A real knob \u2014 the layout resolves it inside a flex-basis calc(), because a media/container-query CONDITION cannot read a var(). The `collapseBelow` prop overrides it per instance (rule #45)."},{name:"--master-detail-master-viewport-{compact,standard}",category:"semantic",tier:"semantic",role:'Bounded MasterDetail master viewport \u2014 20rem (320px) / 28rem (448px), selected by the semantic `masterViewport` prop. `masterViewport="auto"` (the default) applies NO bound, so existing compositions are unchanged; the presets cap the collection\'s block size and scroll it INSIDE the region, which is what stops a long real collection from pushing the detail below the fold once the layout stacks. Retune the presets in a service theme exactly like --master-detail-rail-*; never author a pixel max-height in consumer CSS.'},{name:"--master-detail-master-viewport-inset",category:"semantic",tier:"semantic",role:"Focus-ring room inside a bounded MasterDetail master region (default --space-1 / 4px), and its scroll-padding. An overflow container clips BOTH axes, so without this inset the focus ring of a row at the region's edge would be cut off; it also keeps a row scrolled into view off the clip edge. Only applies when `masterViewport` is compact/standard."},{name:"--page-header-extra-measure",category:"semantic",tier:"semantic",role:'Inline measure of the PageContainer header `extra` region while `headerLayout="responsive-inline"` and the page is below the 640px step (default 11rem / 176px). The default `headerLayout="stack"` never reads it \u2014 `extra` keeps wrapping onto its own full-width line under the subtitle. At >=640px both arrangements are identical, so this token only governs the compact range.'},{name:"--badge-space-*",category:"component",tier:"component",role:"Badge spacing."},{name:"--logo-godx-{size,color} / --logo-success-{background,foreground}",category:"component",tier:"component",role:'The GoDX IDENTITY mark, deliberately independent of --primary so a re-themed action colour never recolours the brand. Role-mirror knobs: declared `initial` at :root with the role default at the CALL SITE (`hsl(var(--logo-godx-color, var(--success)))`), so a scoped `.dark`/`[data-tenant]` override of --success actually reaches the mark. Applies to `<Logo mark="godx">` / `tone="success"`.'},{name:"--logo-wordmark-{gap,font-size-xs|sm|md|lg,font-weight,letter-spacing,font-family,color}",category:"component",tier:"component",role:'Wordmark/lockup knobs for `<Logo wordmark="\u2026" />` \u2014 the mark\u2194wordmark gap, the per-size-tier wordmark type ramp, and its face/weight/tracking/colour. The package ships NO wordmark artwork: `--logo-wordmark-font-family` defaults (at the call site) to --font-family-display, so the wordmark is typeset in the design-system face; pass an inline <svg> as `wordmark` when a real logotype exists. `--logo-wordmark-color` is a role-mirror knob (`initial`) whose call-site default is hsl(var(--foreground)), or the --success identity role on the godx / tone="success" lockup \u2014 never --primary. Setting it once in a service theme re-colours every lockup with no page CSS (gh#214).'},{name:"--card-*",category:"component",tier:"component",role:"Card surface, border, spacing, and typography."},{name:"--control-*",category:"component",tier:"component",role:"Shared form control heights, padding, icons, and focus chrome."},{name:"--table-*",category:"component",tier:"component",role:"Table row/cell sizing."},{name:"--form-label-width",category:"component",tier:"component",role:"Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."},{name:"--form-label-gap",category:"component",tier:"component",role:"Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."},{name:"--dialog-* / --alert-* / --skeleton-*",category:"component",tier:"component",role:"Feedback component sizing and spacing."},{name:"--sheet-responsive-breakpoint-width / --sheet-bottom-max-height",category:"component",tier:"component",role:`The responsive drawer / detail-panel contract (SheetContent responsive="auto"). --sheet-responsive-breakpoint-width (default 48rem = 768px, the library's canonical mobile line) is the viewport width at and below which the desktop side panel becomes a mobile bottom sheet; --sheet-bottom-max-height (default 85dvh) caps THAT bottom presentation only (a plain side="bottom" sheet stays content-sized). Because a CSS @media cannot resolve a custom property, the breakpoint is read off :root at runtime by the exported useSheetResponsiveMode() hook \u2014 which is also what OrgSwitcher responsive="auto" uses, so ONE knob moves the popover\u2192sheet and side\u2192bottom line for every overlay at once. Accepts px/rem/em.`},{name:"--qr-code-*",category:"component",tier:"component",role:"QrCode scanner-safe foreground/background and xs/sm/md/lg natural-size tiers. Keep foreground/background at strong contrast; the defaults remain dark-on-light in dark application themes."},{name:"--chart-trend-plot-height{,-xs,-sm,-md,-lg}",category:"component",tier:"component",role:"CompactBarTrend plot-height tiers (xs 3.5rem = dashboard summary-card density, sm 5rem, md 7.5rem, lg 10rem). The `size` prop picks a step via data-size; a service retunes the whole scale here. This is why a consumer NEVER writes an inline height for a compact trend."},{name:"--chart-trend-bar-{gap,radius,max-width,min-height}",category:"component",tier:"component",role:"CompactBarTrend mark geometry: gap between bars (default --space-2), rounded data-end radius (--radius-sm), the per-mark width ceiling (1.5rem, so a 3-point trend does not render slabs) and the min height floor (2px, so a zero value still reads as a plotted category). All service-tunable (rule #45)."},{name:"--chart-trend-bar-background{,-alpha} / --chart-trend-bar-emphasis-background",category:"component",tier:"component",role:"CompactBarTrend fills \u2014 role-mirror knobs declared `initial` with the role default at the call site. Muted marks default to hsl(var(--muted-foreground) / --chart-trend-bar-background-alpha) (0.75, which clears the 3:1 non-text contrast floor); the emphasized 'current' mark defaults to hsl(var(--primary)). Point them at --chart-N to tint a trend to a series colour."},{name:"--chart-trend-baseline-border",category:"component",tier:"component",role:"CompactBarTrend baseline rule. QUIET by default (rule #44 \u2014 resolves to `none`); a service opts in with `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-{tick-gap,tick-font-size,footer-gap}",category:"component",tier:"component",role:"CompactBarTrend category-tick and activity-footer rhythm \u2014 the plot\u2194tick gap, tick type size (--font-size-xs) and the plot\u2194footer gap."},{name:"--email-shell-{width,padding,page-padding,border-width} / --email-card-{radius,reference-height} / --email-stack-gap{,-sm}",category:"component",tier:"component",role:"Transactional-email shell geometry: the 480px card width, its 32px inset, the 24px viewport gutter, the 1px edge, the 10px radius (mirrors --card-radius) and the block rhythm. The canonical invitation reference measures 480\xD7407 (--email-card-reference-height is a visual-regression target, NEVER a fixed height on the card). Values are LITERAL px \u2014 email clients resolve neither var() nor rem \u2014 and are consumed through the `@godxjp/ui/email` export (EMAIL_SHELL), never by a stylesheet."},{name:"--email-{body,heading}-{font-size,line-height,font-weight} / --email-font-family-sans / --email-mark-{width,height}",category:"component",tier:"component",role:"Email type ramp + brand-mark box. Body 14px/1.7 (mirrors --font-size-base / --line-height-body), heading 20px/1.25 (--heading-h1 / --line-height-tight), mark 32\xD732 (--logo-godx-size). The font stack is email-safe system faces only \u2014 no @font-face survives an email client. Exposed as EMAIL_TYPOGRAPHY / EMAIL_BRAND_MARK."},{name:"--email-cta-{height,line-height,padding-x,radius,font-size,font-weight} / --email-focus-border-width",category:"component",tier:"component",role:"The single primary email CTA: 44px tall (mirrors --control-height-comfortable, the coarse-pointer target) with line-height EQUAL to the height so the label centres without flexbox in Outlook, 24px inline padding, the 6px --radius base, 14px/700 label. --email-focus-border-width mirrors --focus-ring-width for the webmail panes that honour :focus-visible. Exposed as EMAIL_CTA / EMAIL_FOCUS."},{name:"--email-footer-{font-size,line-height,link-gap,padding-top,border-width} / --email-mobile-*",category:"component",tier:"component",role:"Legal-footer typography (12px/1.5 quiet type, 12px between adjacent links, 20px above the hairline) and the narrow-viewport reflow (520px breakpoint, fluid card, 20px inset, 12px gutter, 18px heading, full-bleed CTA). Exposed as EMAIL_FOOTER / EMAIL_MOBILE; every reflow rule needs an inline fallback because most clients strip <style>."},{name:"--auth-shell-device-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="device-authorization"` measure (gh#220) \u2014 the canonical OAuth device-grant artboard: a 23.75rem/380px card at 1440/1024 and a 15px-block \xB7 5px-inline page gutter at 390 (so the card renders x=5px, width=380px). LITERAL lengths, not --space-*, because the artboard specifies exact device pixels that must not drift with --scaling. Selecting the preset replaces a consumer-side --auth-shell-card-max-width override or a forked `.auth-shell--wide` class; the canonical 360px/15px default is untouched.'},{name:"--auth-shell-context-{card-max-width,main-padding,main-padding-mobile,card-stack-gap}",category:"component",tier:"component",role:'AuthShell `preset="context-selection"` measure (gh#217) \u2014 the organisation/context picker: a 25rem card on desktop/tablet, edge-to-edge on mobile (0 inline gutter), and a 1rem rhythm between the three direct sections of the auth column (intro \xB7 choice card \xB7 "remember" row).'},{name:"--auth-shell-recovery-{card-max-width,main-padding,main-padding-mobile}",category:"component",tier:"component",role:'AuthShell `preset="account-recovery"` measure (gh#233) \u2014 the SCR-008 27rem/432px panel, shared by the password-recovery panel (request \xB7 sent \xB7 new-password \xB7 expired) AND the sign-in MFA challenge panel (OTP \xB7 recovery-code \xB7 passkey-failure), because both canonical desktop panels measure w=432 at 1440. Mobile gutter = 15px inline at <=30rem, so the panel is x=15, width=360 \u2014 the SAME rhythm as the canonical Login flow, chosen deliberately: the supplied 390 reference is a desktop 2x2 composite that crops horizontally and is NOT a valid source. The canonical 360px Login measure and the 24rem un-preset shell are untouched.'},{name:"--otp-slot-size",category:"component",tier:"component",role:"InputOTP slot box (gh#233). Default `var(--control-height)` \u2014 byte-identical to before and still density-aware \u2014 but its OWN knob, so an auth panel can widen the 6-slot challenge row to fill a wide surface WITHOUT re-scoping --control-height (which would resize the primary button and every other input in the same card). Set it to a NAMED tier (`var(--control-height-lg)`), never an ad-hoc calc offset."},{name:"--auth-shell-{main-align,card-stack-gap}",category:"component",tier:"component",role:"AuthShell column knobs shared by every preset. --auth-shell-main-align is the block alignment of the auth column (default `center`, the vertically-centred card; set `start` for a tall intro+card+footnote stack). --auth-shell-card-stack-gap is the gap between the card slot's direct sections \u2014 default `0px` (quiet, rule #44) so an existing single-card consumer is byte-for-byte unchanged; a preset opts in."},{name:"--card-space-shell-y",category:"component",tier:"component",role:"Card BLOCK-axis shell padding (gh#232) \u2014 a plain header's top, a `solo` body's top and the terminal slot's bottom. Split off --card-space-inset, which is now inline-only, so a theme can make a card SHORTER without narrowing its column. Declared `initial`: the default resolves at the CALL SITE to --card-space-inset, so a card that overrides neither (and `density=\"tight|cozy\"`, which re-declares it `initial` to re-arm the per-instance inset) renders exactly as before."},{name:"--auth-shell-{compact-card-inset,card-padding-block-compact,card-body-gap-compact,card-gap-compact}",category:"component",tier:"component",role:"The compact AuthShell card's four independent knobs (gh#232) \u2014 one per axis, so the canonical Login card is tunable WITHOUT a consumer selector on the card-content slot: `compact-card-inset` = the inline column (\u2192 --card-space-inset); `card-padding-block-compact` = the card's block/top-bottom padding (\u2192 --card-space-shell-y, `initial` so its default mirrors the live inline inset and canonical output is unchanged); `card-body-gap-compact` = the header\u2194body gap (\u2192 --card-space-body-y, 12px, the role the block knob used to be mis-wired to); `card-gap-compact` = the in-slot title\u2195description stack gap (\u2192 --card-space-gap)."},{name:"--app-setting-picker-compact-{control-height,padding-x,gap,font-size}",category:"component",tier:"component",role:'AppSettingPicker `compact` trigger (gh#217) \u2014 the small, content-hugging LABELLED switcher for an auth/legal footer (`kind="locale" appearance="labeled" compact`). The box height reads the official --control-height-sm tier (never a literal or an ad-hoc calc), and padding/gap/type are separate knobs so a service theme retunes the footer switcher without forking CSS.'},{name:"--centered-shell-column-offset-block",category:"component",tier:"component",role:'CenteredShell column block offset (gh#221) \u2014 default `0` (the quiet state, rule #44: the top-aligned flowing/scrolling page). `<CenteredShell align="center">` flips it to `auto`, which centres the column in the 100dvh shell: the package-owned geometry for a SYSTEM-level standalone surface (a 500/503 error page, a maintenance notice) at 1440/1024/390, so a consumer never writes `min-h-dvh` + flex-centring CSS or a className. Auto block offsets collapse to 0 when the content is taller than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--legal-document-*",category:"component",tier:"component",role:"LegalDocumentShell knobs (gh#222) \u2014 the whole legal/policy document surface is themeable without a single consumer `.legal-*` class. Geometry: `--legal-document-measure-max-width` (46rem readable measure, caps header/body/footer), `--legal-document-toc-width` \xB7 `--legal-document-column-gap` \xB7 `--legal-document-toc-inset-block-start` (sticky offset) \xB7 `--legal-document-toc-max-height`, `--legal-document-section-gap` \xB7 `--legal-document-section-title-gap` \xB7 `--legal-document-header-gap` \xB7 `--legal-document-meta-gap` \xB7 `--legal-document-nav-gap` \xB7 `--legal-document-footer-gap`, `--legal-document-meta-font-size`, `--legal-document-body-line-height` (1.8 long-form leading) and `--legal-document-scroll-offset` (the `scroll-margin-block-start` a hash jump leaves above a heading \u2014 this is why no app needs JS offset arithmetic). Contents entries: `--legal-document-toc-font-size` \xB7 `--legal-document-toc-title-font-size` \xB7 `--legal-document-toc-item-padding` \xB7 `--legal-document-toc-item-radius` \xB7 `--legal-document-toc-marker-width`. Chrome is QUIET by default (rule #44): `--legal-document-toc-border` / `--legal-document-header-border` / `--legal-document-footer-border` are all `none`; a service opts in with `1px solid hsl(var(--border))`. The six colour knobs are ROLE-MIRRORS declared `initial` with the role default at the call site \u2014 `--legal-document-toc-foreground` (\u2192 --muted-foreground, also paints the contents caption), `--legal-document-toc-active-foreground` (\u2192 --foreground), `--legal-document-toc-active-background` (\u2192 --accent), `--legal-document-toc-marker-color` (\u2192 --primary), `--legal-document-meta-foreground` (\u2192 --muted-foreground) and `--legal-document-summary-foreground` (\u2192 --muted-foreground) \u2014 so a scoped [data-tenant]/.dark override of the role still reaches them. The one-column \u21C4 two-column split is a CONTAINER query at 56rem on the shell's own width, not a viewport media query."},{name:"--empty-state-description-max-width",category:"component",tier:"component",role:"EmptyState description MEASURE (gh#221) \u2014 default 28rem (~65 Latin characters per line). A service or locale that needs a shorter/longer measure for its own copy (JA/VI error-page descriptions, a narrow system surface) retunes this one knob instead of forking `.ui-empty-state-description` (rule #45)."},{name:"--sidebar-nav-item-{foreground,hover-foreground,disabled-foreground} / --sidebar-nav-icon-{foreground,hover-foreground,active-foreground,disabled-foreground}",category:"component",tier:"component",role:"Sidebar navigation row + icon COLOUR, split so they can differ (gh#228). Previously `.sb-nav-item` set one `color` for the whole row, so the Lucide SVG inherited `--muted-foreground` along with the label and the icons read as 'missing' against a canonical shell that wants darker 16px icons. `--sidebar-nav-item-*` drives the row/label (incl. sub rows), `--sidebar-nav-icon-*` drives `.sb-icon`. All are role-mirror knobs declared `initial` at `:root` with the role default at the call site, so a scoped `[data-tenant]`/`.dark` override reaches them; icon defaults resolve to `currentColor`, i.e. rendering is unchanged until a service opts in (`--sidebar-nav-icon-foreground: hsl(var(--foreground))` is the canonical darker-icon setting). Colour ONLY \u2014 the 16px icon, 32px row height and 10px gap are untouched. The active row keeps `--sidebar-item-active-{background,foreground}`."},{name:"--list-row-{body-min-width,trailing-gap,indicator-size,read-background,unread-background,indicator-color}",category:"component",tier:"component",role:"ListRow shrink + unread state (gh#224, gh#225). `--list-row-body-min-width` (12rem) is consumed as `min(<token>, 100%)` \u2014 the `min()` is load-bearing, a bare length re-inflates the row's intrinsic width and reintroduces page-root overflow. `--list-row-trailing-gap` spaces wrapped trailing actions. The unread trio are role-mirror knobs declared `initial` with the role at the call site: `--list-row-unread-background` defaults to `hsl(var(--muted))` NOT `--accent` \u2014 accent measured 4.23:1 against the xs muted-foreground description line, below WCAG AA; muted is 4.63:1 light / 5.47:1 dark. `--list-row-indicator-color` (\u2192 `--primary`) and `--list-row-indicator-size` drive the unread dot, whose meaning is carried by localized sr-only text, never by colour alone."},{name:"--filter-bar-scroll-padding-y",category:"component",tier:"component",role:'Scrollbar gutter for `<FilterBar overflow="scroll">` (gh#216), default `var(--space-1)`. Only applies at \u2265640px, where a wide filter set becomes a single nowrap strip with a sticky inline-end clear-all; below that the bar still stacks, so the gutter would be dead space. Pair with the existing `--filter-bar-sticky-background` (already an `initial` role-mirror knob) when theming the strip.'},{name:"--app-shell-{sidebar-width,rail-width}",category:"component",tier:"component",role:"AppShell docked navigation rail widths (gh#213) \u2014 `--app-shell-sidebar-width` (default 16rem) is the expanded rail, `--app-shell-rail-width` (default 4rem) the icon-only rail used at `<AppShell sidebarCollapsed>`. These were hard-coded `grid-template-columns` literals; a service on a different design grid (e.g. a 255px rail) now sets ONE token in its theme instead of forking `.app-root`. Below the 900px shell breakpoint both collapse to a single full-width column and the sidebar moves into AppShell's drawer."},{name:"--app-shell-bar-{inset,inset-compact,gap}",category:"component",tier:"component",role:"AppShell top-bar chrome (gh#213) \u2014 `--app-shell-bar-inset` is the bar's inline padding (default --space-4), `--app-shell-bar-inset-compact` the tighter value applied below the 900px breakpoint where the docked rail is gone (default --space-3), `--app-shell-bar-gap` the gap between the bar's direct children (default --space-3). Height stays `--app-shell-bar-height` (3rem), which now holds at EVERY width \u2014 a duplicate 768px rule used to override it with a literal."},{name:"--topbar-{height,inset,gap}",category:"component",tier:"component",role:"Standalone `<Topbar>` box knobs (gh#213). Topbar is a pure slot bar, so the defaults are the QUIET ones and rendering is unchanged from before they existed: `--topbar-height: auto` and `--topbar-inset: 0px` (inside AppShell the `.app-topbar` grid row owns height and inset), `--topbar-gap: var(--space-2)`. A service that mounts Topbar DIRECTLY on a page sizes it from the theme (`--topbar-height: 3.5rem; --topbar-inset: var(--space-4)`) instead of an app-local class. `--topbar-gap` is both the gap BETWEEN the start/center/end clusters and the gap INSIDE each, so one knob re-rhythms the whole bar. None of them touch the gh#226 shrink contract (start clips, center yields first, end stays anchored inline-end)."},{name:"--{dialog,sheet}-overlay-background / --app-shell-mobile-nav-background (+ -alpha)",category:"component",tier:"component",role:"Overlay scrims (gh#215). All three are ROLE-MIRROR knobs declared `initial` at `:root`, with the default resolved at the CALL SITE as a SHARE of the one semantic `--overlay-background` \u2014 `color-mix(in srgb, var(--overlay-background) var(--<x>-overlay-alpha), transparent)`. Previously each carried a private literal, so setting `--overlay-background` had NO effect anywhere. The share knobs (`--dialog-overlay-alpha` 60%, `--sheet-overlay-alpha` 40%, `--app-shell-mobile-nav-alpha` 40%) keep every default byte-identical (0.3 / 0.2 / 0.2 black) while letting one `--overlay-background` override retint the whole system, including scoped `[data-tenant]` / `.dark` themes. Setting a `*-overlay-background` knob directly still wins outright. NOTE: overlays render in a portal, so a multi-tenant scope must also sit on the portal container."}];function I(t){return C.filter(a=>a.category===t)}var V=[{name:"--badge-space-gap",value:"var(--space-inline-xs)",description:"Badge component tokens."},{name:"--badge-space-x",value:"var(--space-2)",description:"Badge component tokens."},{name:"--badge-space-y",value:"var(--space-1)",description:"Badge component tokens."},{name:"--badge-font-size",value:"var(--font-size-xs)",description:"Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."},{name:"--card-space-inset",value:"var(--space-section-active)",description:"Horizontal inset of every slot (header / content / footer) + the resting top/bottom * shell padding. This is the column the title, body and footer all align to."},{name:"--card-space-shell-y",value:"initial",description:'BLOCK-axis (top/bottom) shell padding of the slotted card rhythm \u2014 a plain header\'s top, a * `solo` body\'s top, and the terminal slot\'s bottom (gh#232). Split off --card-space-inset so a * shell (AuthShell compact) can retune the card\'s height WITHOUT moving the inline column. * Declared `initial` \u2014 NOT `var(--card-space-inset)` \u2014 so the default resolves at the CALL SITE: * a :root binding would freeze at the :root inset and `[data-density="tight"|"cozy"]` (which * override --card-space-inset on the card element) would stop reaching it. See docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`" \u2014 the same call-site rule applies to derived knobs.'},{name:"--card-space-header-y",value:"var(--space-stack-sm)",description:"Vertical padding of a BANDED header band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-body-y",value:"var(--space-section-active)",description:"Gap between the header and the body, and the body's own top padding \u2014 the breathing * room under a title before content begins."},{name:"--card-space-solo-y",value:"initial",description:'Symmetric block padding for a SOLO content slot, so a composed shell can tune block and * inline spacing independently without targeting CardContent. * Declared `initial` for the SAME reason as --card-space-shell-y above: written as * `var(--card-space-inset)` here it would freeze at the :root inset, and a card carrying * [data-density="tight"|"cozy"] (which override --card-space-inset ON THE CARD) would keep the * :root value. The default now resolves at the call site through the chain * solo-y \u2192 shell-y \u2192 inset, so a solo body follows the general block knob when only that is * set, and an explicit solo-y still wins.'},{name:"--card-space-footer-y",value:"var(--space-stack-sm)",description:"Vertical padding of a SEPARATED footer band (top = bottom). Drives --card-space-divided-y."},{name:"--card-space-divided-y",value:"var(--card-space-header-y)",description:"DIVIDED-section vertical padding (rule #44/#45). A header/footer that carries a divider * border (banded header, separated footer) reads as its own band, so it pads SYMMETRICALLY * top+bottom \u2014 distinct from a plain header that flows into the body (top inset, no bottom). * One themeable knob keeps the header- and footer-band rhythm in sync; a service theme tunes * the band density here instead of forking per-slot CSS."},{name:"--card-space-gap",value:"var(--space-stack-xs)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-size",value:"var(--font-size-base)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-line-height",value:"var(--line-height-tight)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-title-font-weight",value:"var(--font-weight-semibold)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-font-size",value:"var(--font-size-sm)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-description-line-height",value:"var(--line-height-normal)",description:"Vertical gap between stacked items WITHIN a slot (e.g. title \u2195 description in the header)."},{name:"--card-background",value:"initial",description:'Card fill + edge \u2014 opt-in knobs that DEFAULT to the live --card / --border roles. Declared * `initial` (not `var(--card)`) so the default re-resolves at the call site under a scoped theme: * a :root binding to a role var freezes at the :root value and a scoped `[data-tenant]` override of * the role never reaches it (see docs/STANDARDS-vocabulary-tokens.md \xB7 "role-mirror knobs"). A * service still overrides the knob directly (--card-background: \u2026) to win over the role default.'},{name:"--card-border",value:"initial",description:"default = hsl(var(--card))"},{name:"--card-header-background",value:"initial",description:"Banded-header fill \u2014 role-tintable (rule #45): a service points this at any role, * e.g. --card-header-background: var(--primary), and tunes --card-header-background-alpha for * the wash strength. Default = the live --muted role (resolved at the call site)."},{name:"--card-header-background-alpha",value:"0.55",description:"default = hsl(var(--muted))"},{name:"--card-header-border-bottom",value:"initial",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-radius",value:"var(--radius-xl)",description:"Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header. * Default = 1px solid hsl(var(--card-border)) (resolved at the call site)."},{name:"--card-shadow",value:"var(--shadow-sm)",description:"The DXS hi-fi baseline uses a quiet 10px data surface with one shadow-sm * elevation layer. Consumers can still flatten or lift cards through this knob."},{name:"--card-glow",value:"0 0 0 0 transparent",description:"Brand glow layer \u2014 invisible no-op at rest (rule #44). Paired AFTER --card-shadow in the * surface box-shadow so a service can wash every card with the global glow, e.g. * --card-glow: var(--shadow-glow), with no markup change."},{name:"--card-tint",value:"transparent",description:"Fill tint \u2014 subtle role wash over the card background (default transparent = invisible). * Painted as an overlay so a service sets --card-tint: hsl(var(--primary) / 0.04) once."},{name:"--card-accent-rail-width",value:"6px",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-font-weight",value:"var(--font-weight-medium)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-label-letter-spacing",value:"0.04em",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-size",value:"var(--font-size-2xl)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-line-height",value:"1.1",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-value-font-weight",value:"var(--font-weight-semibold)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-hint-font-size",value:"var(--font-size-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-gap",value:"var(--space-stack-xs)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-size",value:"2.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-glyph-size",value:"1.25rem",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-radius",value:"var(--radius-md)",description:"Accent edge \u2014 width of the semantic leading-edge stripe (data-accent). * Tokenised (rule #44) so a service theme can re-tune it without forking CSS. * The slot padding compensation in card-layout.css subtracts the same token, * so content stays aligned on the shell whatever the rail width."},{name:"--stat-card-icon-background",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-icon-foreground",value:"initial",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--stat-card-delta-font-size",value:"var(--font-size-xs)",description:"Medallion tint \u2014 soft brand wash + brand glyph by default; a service retints by overriding * --primary or these tokens directly (rule #44/#45). `initial` so the --primary default * re-resolves at the call site under a scoped theme (no :root freeze). * Defaults = hsl(var(--primary) / 0.1) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-space-gap",value:"var(--space-stack-sm)",description:"\u2500\u2500 ServiceLauncherCard (gh#219) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Internal rhythm of a launcher tile: one gap knob drives the vertical stack AND the * icon\u2194title\u2194status row, so a service theme retunes the whole tile density once."},{name:"--card-service-launcher-icon-size",value:"var(--control-height-lg)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-glyph-size",value:"var(--space-5)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-radius",value:"var(--radius-md)",description:"Semantic icon surface. `--control-height-lg` is the 36px control tier the hi-fi calls for \u2014 * a tier token, never a literal, so the medallion tracks --scaling with its sibling controls."},{name:"--card-service-launcher-icon-background",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-icon-foreground",value:"initial",description:"Available-service medallion tint. `initial` (role-mirror rule, docs/TOKENS.md) so the role * defaults resolve at the CALL SITE and a scoped [data-tenant]/.dark override reaches them. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--primary)) glyph."},{name:"--card-service-launcher-unavailable-icon-background",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-unavailable-icon-foreground",value:"initial",description:"Unavailable medallion tint \u2014 applied when the consumer supplies `disabledReason`, so an * un-launchable service never reads as brand-live. `initial` for the same role-mirror reason. * Defaults = hsl(var(--muted)) fill \xB7 hsl(var(--muted-foreground)) glyph."},{name:"--card-service-launcher-description-font-size",value:"var(--font-size-xs)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-description-line-height",value:"var(--line-height-body)",description:"Description prose \u2014 also drives the disabled-reason line and the catalog-CTA caption."},{name:"--card-service-launcher-metadata-font-size",value:"var(--font-size-xs)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-metadata-line-height",value:"var(--line-height-normal)",description:"Mono metadata line (hostname \xB7 plan) \u2014 tighter leading than prose by design."},{name:"--card-service-launcher-cta-min-height",value:"calc(var(--control-height-lg) * 4)",description:"Dashed catalog/add companion tile: kept tall enough to sit level with a populated launcher * tile in the same grid row. Four control tiers \u2248 medallion + title + description + action."},{name:"--card-service-launcher-skeleton-title-width",value:"var(--space-12)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--card-service-launcher-skeleton-status-width",value:"var(--space-10)",description:"Skeleton bar widths \u2014 shape-matched placeholders for the title and status badge."},{name:"--chart-trend-plot-height-xs",value:"3.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-sm",value:"5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-md",value:"7.5rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height-lg",value:"10rem",description:"\u2500\u2500 Plot height tiers (size = xs|sm|md|lg) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * `xs` is the canonical dashboard-summary-card density: a seven-day trend that sits under * a KPI headline without stealing its vertical rhythm. A service retunes the whole scale * here; a screen picks a step with the `size` prop."},{name:"--chart-trend-plot-height",value:"var(--chart-trend-plot-height-xs)",description:"Active tier \u2014 chart-layout.css re-points this per `data-size`; overriding it directly * pins one height for every trend regardless of `size`."},{name:"--chart-trend-bar-gap",value:"var(--space-2)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-radius",value:"var(--radius-sm)",description:"\u2500\u2500 Bar marks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Thin marks, a surface gap between neighbours and a rounded data-end anchored to the * baseline (the data-visualization mark spec)."},{name:"--chart-trend-bar-max-width",value:"1.5rem",description:"Ceiling on a single mark so a 3-point trend does not render slabs."},{name:"--chart-trend-bar-min-height",value:"2px",description:"Floor so a zero/absent value still reads as a plotted category rather than a hole."},{name:"--chart-trend-bar-background",value:"initial",description:'Muted mark fill \u2014 role-mirror knob, declared `initial` so the role default re-resolves * at the CALL SITE under `.dark` / `[data-tenant]` (docs/TOKENS.md \xB7 "Role-mirror knobs * MUST be `initial`"). Default = hsl(var(--muted-foreground) / 0.75), which clears the * 3:1 non-text contrast floor (WCAG 1.4.11) against the card surface.'},{name:"--chart-trend-bar-background-alpha",value:"0.75",description:"Opacity applied to the muted-fill role default."},{name:"--chart-trend-bar-emphasis-background",value:"initial",description:'Emphasized ("current") mark fill \u2014 role-mirror knob. Default = hsl(var(--primary)).'},{name:"--chart-trend-baseline-border",value:"initial",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-gap",value:"var(--space-1)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-tick-font-size",value:"var(--font-size-xs)",description:"\u2500\u2500 Baseline + category ticks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * Chrome defaults QUIET (rule #44): no baseline rule at rest. A service opts in with * `--chart-trend-baseline-border: 1px solid hsl(var(--border));`."},{name:"--chart-trend-footer-gap",value:"var(--space-2)",description:"Gap between the plot block and the optional activity footer slot."},{name:"--control-height-compact",value:"1.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-default",value:"2rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-comfortable",value:"2.75rem",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-compact",value:"var(--space-2)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-default",value:"var(--space-3)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-padding-x-comfortable",value:"var(--space-4)",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height",value:"calc(var(--control-height-default) * var(--scaling))",description:"Control primitive tokens: heights, horizontal padding, adjacent control sizes."},{name:"--control-height-sm",value:"calc(var(--control-height) - calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-lg",value:"calc(var(--control-height) + calc(0.25rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-height-xs",value:"calc(var(--control-height) - calc(0.5rem * var(--scaling)))",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-padding-x",value:"var(--control-padding-x-default)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap",value:"var(--space-inline-sm)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-gap-sm",value:"var(--space-inline-xs)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--control-radius",value:"var(--radius)",description:"Adjacent control sizes, derived from the active --control-height. The \xB1step * is scaled too so the whole control ladder stays proportional under --scaling."},{name:"--button-radius",value:"var(--radius-md)",description:"Button corner radius \u2014 defaults to the button's historical `rounded-md` so nothing * changes by default, but is its OWN knob so a service theme can retune the button * radius INDEPENDENTLY of input/control radius (issue #124)."},{name:"--control-font-size",value:"var(--font-size-base)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-border-width",value:"1px",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-shadow",value:"var(--shadow-xs)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-icon-size-sm",value:"calc(0.875rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--control-focus-ring-width",value:"var(--focus-ring-width)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--checkbox-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-gap",value:"var(--space-inline-sm)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-x",value:"var(--space-6)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-group-gap-y",value:"var(--space-3)",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-description-gap",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--choice-control-offset",value:"0.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width",value:"calc(2.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-compact",value:"2rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-width-comfortable",value:"2.5rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height",value:"calc(1.25rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-compact",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-height-comfortable",value:"1.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-size-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate",value:"calc(1rem * var(--scaling))",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-compact",value:"0.875rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--switch-thumb-translate-comfortable",value:"1.125rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-track-height",value:"0.375rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--slider-thumb-size",value:"1rem",description:"Control surface knobs \u2014 font-size, border width and resting shadow of every * `.ui-control` (input / picker trigger). Tokenised so a service theme tunes them * once instead of each component hard-coding Tailwind utilities. Defaults preserve * the historical look (font-size-base, 1px border, shadow-xs)."},{name:"--otp-slot-size",value:"initial",description:'InputOTP slot box (gh#233). Its own knob so an auth surface can widen the 6-slot challenge row * to fill a wide panel WITHOUT re-scoping --control-height (which would resize every other * control in the same card). Declared `initial` \u2014 the tier-mirror form of the role-mirror rule in * docs/TOKENS.md: the default must resolve at the CALL SITE (`var(--otp-slot-size, * var(--control-height))`), because `--otp-slot-size: var(--control-height)` here would FREEZE at * the :root tier (32px) and an OTP row inside `.ui-auth-shell[data-variant="canonical"]`, which * re-scopes --control-height to 36px, would silently shrink. Verified in Chromium: 36px before * and after. A service opts in with a NAMED tier (`var(--control-height-lg)`), never a calc.'},{name:"--checkbox-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--switch-checked-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-on-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-track-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--slider-range-background",value:"initial",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-input-width",value:"6.5rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-list-max-height",value:"min(300px, 50vh)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-input-padding-x",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-padding",value:"var(--space-1)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-item-padding-x",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-width",value:"35rem",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-inset-top",value:"calc(var(--space-6) * 4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-viewport-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-padding-y",value:"var(--space-2)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-padding-x",value:"var(--space-4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-palette-hint-gap",value:"var(--space-4)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-edge-inset",value:"var(--space-3)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-start-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-end-padding",value:"calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--choice-description-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--color-picker-hex-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--command-group-heading-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--search-input-label-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--tag-input-chip-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--toggle-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--button-sm-font-size",value:"var(--font-size-xs)",description:'Checked/on/active fills \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme (a :root binding to var(--primary) freezes at the :root value and a scoped * [data-tenant] override of --primary never reaches it). A service retints the "selected" state * by overriding these directly. Defaults = hsl(var(--primary)) \xB7 slider track 0.2\u03B1.'},{name:"--control-height-compact",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--control-height-default",value:"2.75rem",description:"Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."},{name:"--progress-label-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-title-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--tree-item-description-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--timeline-note-font-size",value:"var(--font-size-xs)",description:"Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--avatar-background",value:"initial",description:"Avatar surface \u2014 `initial` so the --muted default re-resolves at the call site under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override never reaches it). A service re-tints the placeholder fill once (e.g. --avatar-background: hsl(var(--accent))). Default = hsl(var(--muted))."},{name:"--avatar-tint",value:"transparent",description:"Optional role wash over the avatar (default transparent = invisible, rule #44). Painted as an overlay so a service sets --avatar-tint: hsl(var(--primary) / 0.08)."},{name:"--progress-track-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--progress-fill-background",value:"initial",description:"Progress track + fill \u2014 `initial` so the role defaults re-resolve under a scoped theme. Track reads --secondary, fill reads --success; a service re-tones once. Defaults = hsl(var(--secondary)) track \xB7 hsl(var(--success)) fill."},{name:"--timeline-dot-done-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-dot-current-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--timeline-line-completed-background",value:"initial",description:"Timeline accents \u2014 `initial` so the dot/line role defaults re-resolve under a scoped theme. Defaults = hsl(var(--success)) done \xB7 hsl(var(--primary)) current/line."},{name:"--tree-item-active-border",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--tree-item-active-background",value:"initial",description:"Tree active item \u2014 border + soft bg tint over the --primary role. `initial` so the default re-resolves under a scoped theme. Defaults = hsl(var(--primary) / 0.3) border \xB7 0.05 fill."},{name:"--qr-code-foreground",value:"0 0% 0%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-background",value:"0 0% 100%",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-xs",value:"6rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-sm",value:"8rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-md",value:"10rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--qr-code-size-lg",value:"12.5rem",description:"QR codes stay scanner-safe in light and dark application themes. Consumers may override these component tokens only when the resulting pair retains strong contrast."},{name:"--password-strength-score-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--password-strength-checklist-font-size",value:"var(--font-size-xs)",description:"Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."},{name:"--descriptions-label-width",value:"8rem",description:'Width of the label column when <Descriptions layout="horizontal">. Labels align to this * shared column so the values line up (the horizontal-detail look, mirroring <Form layout>). * A rem value gives a fixed aligned column; set `max-content` to size each label to its text. * (rule #44/#45 \u2014 a service theme tunes it here instead of forking CSS.)'},{name:"--email-shell-width",value:"480px",description:"\u2500\u2500 Shell \u2014 the 480px transactional-email geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-shell-padding",value:"32px",description:"canonical transactional card width"},{name:"--email-shell-page-padding",value:"24px",description:"card inset \u2014 mirrors --space-8 (2rem)"},{name:"--email-shell-border-width",value:"1px",description:"gutter between the client viewport and the card (--space-6)"},{name:"--email-card-radius",value:"10px",description:"card edge \u2014 mirrors the 1px --border hairline"},{name:"--email-card-reference-height",value:"407px",description:"mirrors --card-radius (--radius-xl = 6px \xD7 \u03C6 \u2248 10px)"},{name:"--email-stack-gap",value:"24px",description:"canonical invitation reference measures 480\xD7407"},{name:"--email-stack-gap-sm",value:"12px",description:"block rhythm \u2014 mirrors --space-stack-lg (1.5rem)"},{name:"--email-mark-width",value:"32px",description:"\u2500\u2500 Brand mark \u2014 the canonical GoDX capsule (see EMAIL_BRAND_MARK) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mark-height",value:"32px",description:"mirrors --logo-godx-size (2rem)"},{name:"--email-font-family-sans",value:"-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif",description:"\u2500\u2500 Typography \u2014 literal px so no client has to resolve the rem scale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-body-font-size",value:"14px",description:"\u2500\u2500 Typography \u2014 literal px so no client has to resolve the rem scale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-body-line-height",value:"1.7",description:"mirrors --font-size-base (0.875rem)"},{name:"--email-body-font-weight",value:"400",description:"mirrors --line-height-body"},{name:"--email-heading-font-size",value:"20px",description:"mirrors --font-weight-normal"},{name:"--email-heading-line-height",value:"1.25",description:"mirrors --heading-h1 (base \xD7 \u03C6^\xBC\xB3 \u2248 19.8px)"},{name:"--email-heading-font-weight",value:"700",description:"mirrors --line-height-tight"},{name:"--email-cta-height",value:"44px",description:"\u2500\u2500 Primary CTA \u2014 the one action button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-cta-line-height",value:"44px",description:"mirrors --control-height-comfortable (2.75rem coarse-pointer target)"},{name:"--email-cta-padding-x",value:"24px",description:"equal to the height: bulletproof vertical centring in Outlook"},{name:"--email-cta-radius",value:"6px",description:"mirrors --space-6"},{name:"--email-cta-font-size",value:"14px",description:"mirrors the --radius base (0.375rem)"},{name:"--email-cta-font-weight",value:"700",description:"mirrors --font-size-base"},{name:"--email-footer-font-size",value:"12px",description:"\u2500\u2500 Legal footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-footer-line-height",value:"1.5",description:"mirrors --font-size-xs (base \xF7 \u03C6^\xBC \u2248 12.5px)"},{name:"--email-footer-link-gap",value:"12px",description:"mirrors --line-height-normal"},{name:"--email-footer-padding-top",value:"20px",description:"horizontal separation between footer links (--space-3)"},{name:"--email-footer-border-width",value:"1px",description:"space above the legal band"},{name:"--email-focus-border-width",value:"2px",description:"\u2500\u2500 Focus affordance (webmail preview panes that DO honour :focus) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-max-width",value:"520px",description:"\u2500\u2500 Mobile reflow \u2014 applied under the narrow media query and by inline fallbacks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--email-mobile-width",value:"100%",description:"the reflow breakpoint (card width + both gutters)"},{name:"--email-mobile-padding",value:"20px",description:"the card goes fluid below the breakpoint"},{name:"--email-mobile-page-padding",value:"12px",description:"reduced card inset \u2014 mirrors --space-5"},{name:"--email-mobile-heading-font-size",value:"18px",description:"reduced viewport gutter \u2014 mirrors --space-3"},{name:"--email-mobile-cta-width",value:"100%",description:"mirrors --heading-h2 (base \xD7 \u03C6^\xBC\xB2 \u2248 17.6px)"},{name:"--sheet-width-default",value:"22.5rem",description:"Feedback primitive tokens: dialog, alert, empty state."},{name:"--sheet-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--sheet-overlay-alpha",value:"40%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-width-default",value:"32rem",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-viewport-inset",value:"1.5rem",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-overlay-background",value:"initial",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-overlay-alpha",value:"60%",description:"Overlay scrims \u2014 `initial` so the shared semantic --overlay-background re-resolves at the CALL * SITE under a scoped [data-tenant]/.dark theme (a :root binding to it freezes at :root and a * scoped override never reaches the overlay). Each surface takes a SHARE of that one scrim rather * than a private literal, so a service retints every backdrop with one --overlay-background while * the calibrated per-surface depth survives: a slide-in Sheet washes the page more lightly than a * modal Dialog. Defaults resolve to rgb(0 0 0 / 0.2) (sheet) and rgb(0 0 0 / 0.3) (dialog)."},{name:"--dialog-space-x",value:"var(--space-chrome-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-y",value:"var(--space-chrome-y)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-inset",value:"var(--dialog-space-y) var(--dialog-space-x)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-space-gap",value:"var(--space-stack-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--dialog-close-space-offset",value:"var(--space-4)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-inset",value:"var(--space-section-active)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-space-gap",value:"var(--space-inline-md)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-inner-space-gap",value:"var(--space-stack-sm)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-dismiss-space-offset",value:"var(--space-3)",description:"Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."},{name:"--alert-bg-alpha",value:"0.05",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--alert-border-alpha",value:"0.3",description:"Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."},{name:"--dialog-content-glow",value:"0 0 0 0 transparent",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-y",value:"var(--space-10)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-space-x",value:"var(--space-6)",description:"Brand glow layer for the raised dialog/sheet panel \u2014 invisible no-op at rest (rule #44). * Paired AFTER --shadow-lg in the surface box-shadow so a service can wash the overlay with the * global glow, e.g. --dialog-content-glow: var(--shadow-glow), with no markup change."},{name:"--empty-state-description-max-width",value:"28rem",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-y",value:"var(--space-6)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-section-space-x",value:"var(--space-4)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-y",value:"var(--space-3)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-compact-space-x",value:"var(--space-2)",description:"Measure of the description paragraph (rule #45 \u2014 a service-tunable constant gets a knob). * 28rem reads ~65 Latin characters per line; a JA/VI service that needs a shorter or longer * measure for its own copy retunes this once instead of forking `.ui-empty-state-description`."},{name:"--empty-state-icon-foreground",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--empty-state-icon-tint",value:"initial",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-row-gap",value:"var(--space-stack-sm)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-cell-gap",value:"var(--space-inline-lg)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-card-inset",value:"var(--space-section-active)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-radius",value:"var(--radius)",description:"EmptyState icon medallion colour \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (rule #44). A service recolours the glyph (--empty-state-icon-foreground) * or washes the medallion fill (--empty-state-icon-tint) without forking. * Defaults = hsl(var(--muted-foreground)) glyph \xB7 hsl(var(--muted)) fill."},{name:"--skeleton-background",value:"initial",description:"Skeleton placeholder fill \u2014 `initial` so the --muted default re-resolves at the call site under * a scoped theme (a :root binding to a role var freezes at :root). A service tints the shimmer to * its surface (rule #44) without forking the keyframes. Default = hsl(var(--muted))."},{name:"--form-label-width",value:"max-content",description:"Width of the label column in horizontal/inline layout. A service theme sets * this once (e.g. 110px) to align every form to its design grid; the Form/ * FormField `labelWidth` prop overrides per form/field."},{name:"--form-label-gap",value:"var(--space-4)",description:"Column gap between the label and its control in horizontal/inline layout."},{name:"--legal-document-measure-max-width",value:"46rem",description:"Readable document measure. Caps the header, the section column and the footer so * a 1440px viewport still reads at ~75-90 characters per line."},{name:"--legal-document-column-gap",value:"var(--space-10)",description:"Rail \u2194 document column gutter (two-column mode only)."},{name:"--legal-document-toc-width",value:"15rem",description:"Sticky table-of-contents rail width (two-column mode only)."},{name:"--legal-document-toc-inset-block-start",value:"var(--space-6)",description:"Distance the pinned rail keeps from the top of the scrollport."},{name:"--legal-document-toc-max-height",value:"calc(100dvh - var(--space-12))",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-gap",value:"var(--space-1)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-padding",value:"var(--space-1) var(--space-2)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-item-radius",value:"var(--radius)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-title-font-size",value:"var(--font-size-2xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-font-size",value:"var(--font-size-xs)",description:"Cap for a long contents list so the rail never outgrows the viewport."},{name:"--legal-document-toc-marker-width",value:"2px",description:"Leading marker on the active contents entry \u2014 a non-colour affordance so the * active state is never colour-only (WCAG 1.4.1)."},{name:"--legal-document-nav-gap",value:"var(--space-4)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-header-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-gap",value:"var(--space-2)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-meta-font-size",value:"var(--font-size-xs)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-gap",value:"var(--space-10)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-section-title-gap",value:"var(--space-3)",description:"Gap between the `documentNavigation` slot and the contents list inside the rail."},{name:"--legal-document-body-line-height",value:"1.8",description:"Long-form leading \u2014 legal prose reads looser than UI text."},{name:"--legal-document-scroll-offset",value:"var(--space-6)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-footer-gap",value:"var(--space-4)",description:"`scroll-margin-block-start` on every section: the offset a hash jump / anchor * activation leaves above the heading so it is not hidden under sticky chrome."},{name:"--legal-document-toc-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-header-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-footer-border",value:"none",description:"\u2500\u2500 Chrome \u2014 quiet by default (rule #44) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-meta-foreground",value:"initial",description:"\u2500\u2500 Role-mirror knobs \u2014 `initial` here, role default at the call site \u2500\u2500\u2500\u2500\u2500\u2500\u2500"},{name:"--legal-document-summary-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-foreground",value:"initial",description:"default = hsl(var(--muted-foreground))"},{name:"--legal-document-toc-active-background",value:"initial",description:"default = hsl(var(--foreground))"},{name:"--legal-document-toc-marker-color",value:"initial",description:"default = hsl(var(--accent))"},{name:"--list-row-padding-y",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-padding-x",value:"var(--space-4)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-gap",value:"var(--space-3)",description:"ListRow component tokens \u2014 a single-line entity row for short lists inside a Card * (sessions / API tokens / linked accounts / passkeys / notifications \u2026). Sits in a flush * CardContent; rows separate with a quiet divider (#44 \u2014 chrome defaults to the calm * semantic border)."},{name:"--list-row-border",value:"initial",description:"Row divider \u2014 `initial` so the --border default re-resolves at the call site under a scoped * theme (a :root binding to a role var freezes at :root). Default = 1px solid hsl(var(--border))."},{name:"--list-row-body-min-width",value:"12rem",description:"Inline size the content column keeps before the trailing actions wrap onto their own line * (#45 \u2014 a service retunes the stack threshold to its grid). Clamped with min(\u2026, 100%) at the * call site, so a container narrower than the knob can never force page-root overflow (#224)."},{name:"--list-row-trailing-gap",value:"var(--space-2)",description:"Gap BETWEEN trailing actions \u2014 they also wrap among themselves at narrow widths (#224)."},{name:"--list-row-read-background",value:"initial",description:"Read (default) row surface \u2014 `initial`, documented default = transparent, i.e. the Card * surface shows through untouched (#44 \u2014 chrome defaults to the quietest state)."},{name:"--list-row-unread-background",value:"initial",description:"Unread emphasis surface (`unread` rows) \u2014 `initial` so the --muted default re-resolves at the * call site under a scoped theme. Documented default = hsl(var(--muted)), the same quiet * emphasis role the table header uses; it keeps the xs muted description line at WCAG AA * (4.63:1 light / 5.47:1 dark), which hsl(var(--accent)) would not (4.23:1 light)."},{name:"--list-row-indicator-color",value:"initial",description:"Unread indicator dot fill \u2014 `initial` so the --primary default re-resolves at the call site * under a scoped theme. Documented default = hsl(var(--primary))."},{name:"--list-row-indicator-size",value:"var(--space-2)",description:"Diameter of the read/unread indicator dot; the gutter is reserved on read rows too so the * titles of a mixed list stay on one optical axis."},{name:"--logo-radius",value:"var(--radius)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-xs",value:"1.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-sm",value:"1.5rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-md",value:"1.75rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-size-lg",value:"2.25rem",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-xs",value:"var(--font-size-2xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-sm",value:"var(--font-size-xs)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-md",value:"var(--font-size-sm)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-font-size-lg",value:"var(--font-size-base)",description:"Logo component tokens \u2014 the product brand-mark box (a glyph on the primary fill), used in the * app shell header, auth screens, and topbars. Size + radius + per-tier font-size are knobs so a * service theme retunes the mark without forking CSS (rules #44/#45). Colours read the primary * role tokens directly, so a re-themed --primary re-tints the mark automatically."},{name:"--logo-success-background",value:"initial",description:'Semantic identity fill is deliberately independent from --primary so a green brand mark never * recolours primary buttons or links. Services can retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --success actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--success)) * --logo-success-foreground = hsl(var(--success-foreground)) * --logo-godx-color = hsl(var(--success))'},{name:"--logo-success-foreground",value:"initial",description:'Semantic identity fill is deliberately independent from --primary so a green brand mark never * recolours primary buttons or links. Services can retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --success actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--success)) * --logo-success-foreground = hsl(var(--success-foreground)) * --logo-godx-color = hsl(var(--success))'},{name:"--logo-godx-size",value:"2rem",description:'Semantic identity fill is deliberately independent from --primary so a green brand mark never * recolours primary buttons or links. Services can retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --success actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--success)) * --logo-success-foreground = hsl(var(--success-foreground)) * --logo-godx-color = hsl(var(--success))'},{name:"--logo-godx-color",value:"initial",description:'Semantic identity fill is deliberately independent from --primary so a green brand mark never * recolours primary buttons or links. Services can retune the role once for every Logo. * ROLE-MIRROR KNOBS \u2014 declared `initial` here with the role default at the CALL SITE * (logo-layout.css), so a scoped `.dark` / `[data-tenant]` override of --success actually * reaches the mark instead of freezing at the :root value (docs/TOKENS.md \xB7 * "Role-mirror knobs MUST be `initial`"). Documented defaults: * --logo-success-background = hsl(var(--success)) * --logo-success-foreground = hsl(var(--success-foreground)) * --logo-godx-color = hsl(var(--success))'},{name:"--logo-wordmark-gap",value:"var(--space-2)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-xs",value:"var(--font-size-xs)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-sm",value:"var(--font-size-sm)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-md",value:"var(--font-size-base)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-size-lg",value:"var(--font-size-lg)",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-weight",value:"700",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-letter-spacing",value:"-0.01em",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-font-family",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--logo-wordmark-color",value:"initial",description:'\u2500\u2500 Wordmark / lockup (`<Logo wordmark="GoDX" />`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 * The readable product name set beside the mark. The package ships NO wordmark artwork: the * wordmark is typeset in the design-system display face, so a service retunes face, weight, * tracking, size and the mark\u2194wordmark gap here instead of writing page CSS. The colour is a * role-mirror knob whose default is the IDENTITY role (never --primary): the GoDX/`success` * lockup is brand-green out of the box, and every other lockup reads --foreground. * Documented defaults: --logo-wordmark-font-family = var(--font-family-display); * --logo-wordmark-color = hsl(var(--foreground)), or hsl(var(--success)) on the * godx / tone="success" lockup.'},{name:"--pagination-gap",value:"var(--space-inline-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-item-gap",value:"var(--space-inline-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-size-width",value:"5.5rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--pagination-total-font-size",value:"var(--font-size-sm)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-gap",value:"var(--space-3)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-bar-padding-y",value:"var(--space-2)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-label-font-size",value:"var(--font-size-xs)",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-sm",value:"11rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--filter-picker-width-md",value:"14rem",description:"Navigation primitive tokens: pagination, filters, compact pickers."},{name:"--app-setting-picker-compact-control-height",value:"var(--control-height-sm)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-padding-x",value:"var(--space-2)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-gap",value:"var(--space-1)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--app-setting-picker-compact-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--breadcrumb-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--menubar-shortcut-font-size",value:"var(--font-size-xs)",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-max-inline-size",value:"100%",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--tabs-list-overflow",value:"auto",description:"AppSettingPicker `compact` (gh#217) \u2014 the small, content-hugging labelled trigger used in an * auth/legal footer, where the square icon-only default reads as a stray button and the full * labelled trigger is too tall. Box height comes from the official --control-height-sm tier (never * a literal / ad-hoc calc), and every other knob is themeable (rule #45)."},{name:"--menubar-item-hover-background",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--menubar-item-hover-foreground",value:"initial",description:"Menu item hover/highlight tint \u2014 `initial` so the --accent default re-resolves at the call site * under a scoped theme (a :root binding to a role var freezes at :root). * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--accent-foreground)) text."},{name:"--filter-bar-sticky-offset",value:"0px",description:"Sticky offset for the pinned filter strip (Toolbar `sticky`, #197). Default 0 (chrome, * rule #44); raise it so the strip parks below a fixed topbar."},{name:"--filter-bar-sticky-background",value:"initial",description:"Fill painted only when the filter strip is pinned. Role-mirror knob (rule #45): `initial` * so --background re-resolves at the call site under a scoped [data-tenant]/.dark theme."},{name:"--filter-bar-scroll-padding-y",value:"var(--space-1)",description:'Scrollbar gutter reserved under a `overflow="scroll"` filter strip (#216) so the inline * scrollbar never overlaps the controls. Rule #45 \u2014 a service theme retunes it to its grid * (0 on an overlay-scrollbar platform).'},{name:"--sheet-responsive-breakpoint-width",value:"48rem",description:'Viewport width at and below which `SheetContent responsive="auto"` renders the mobile bottom * sheet instead of the desktop side panel. Read at runtime by useSheetResponsiveMode() (a CSS * @media cannot resolve a custom property), so a service moves the drawer breakpoint from ONE * knob for every overlay that opts into the responsive contract \u2014 Sheet and OrgSwitcher alike. * 48rem = 768px mirrors the library\'s canonical mobile line (useIsMobile). Accepts px/rem/em.'},{name:"--sheet-bottom-max-height",value:"85dvh",description:'Block size cap for the responsive BOTTOM presentation only (never for a plain * `side="bottom"` sheet, which stays content-sized). Keeps the sheet below the viewport so the * SheetBody scrolls and the page behind it stays visible/dismissable.'},{name:"--sidebar-section-label-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-product-tenant-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-badge-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-role-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--org-switcher-meta-foreground",value:"var(--muted-foreground)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-nav-sub-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-flyout-title-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--topbar-chip-icon-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--kbd-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-logo-mark-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-avatar-font-size",value:"var(--font-size-2xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-user-name-font-size",value:"var(--font-size-xs)",description:"Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."},{name:"--sidebar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--topbar-gradient",value:"none",description:"Brand-chrome gradient hooks \u2014 opt-in, invisible by default. A service paints * the sidebar/topbar surface by setting these to a gradient (no-op = none)."},{name:"--app-shell-bar-height",value:"3rem",description:"DXS application-shell geometry. These defaults mirror the checked-in * Admin/Console hi-fi source while remaining themeable by consumers."},{name:"--app-shell-bar-inset",value:"var(--space-4)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-bar-inset-compact",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-bar-gap",value:"var(--space-3)",description:"Inline inset and slot gap of the AppShell top bar. `-compact` applies below the shell * breakpoint (the docked sidebar is gone, so the bar tightens against the viewport edge)."},{name:"--app-shell-sidebar-width",value:"16rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-rail-width",value:"4rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-page-max-width",value:"80rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-main-background",value:"hsl(var(--muted) / 0.4)",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-width",value:"22.5rem",description:"Docked navigation rail widths \u2014 the SINGLE most-retuned shell constant (rule #45). A service * that designs on a different grid sets `--app-shell-sidebar-width: 15.9375rem` (255px) once * instead of forking `.app-root`. `-rail-` is the icon-only width used at * `<AppShell sidebarCollapsed>`."},{name:"--app-shell-mobile-nav-background",value:"initial",description:"Mobile-drawer scrim \u2014 `initial` so the shared --overlay-background default re-resolves at the * CALL SITE under a scoped [data-tenant]/.dark theme (a :root binding freezes at :root). Default = * var(--app-shell-mobile-nav-alpha) of the global scrim, i.e. rgb(0 0 0 / 0.2) out of the box."},{name:"--app-shell-mobile-nav-alpha",value:"40%",description:"Share of the global --overlay-background this drawer's scrim uses. A slide-in drawer washes the * page more lightly than a modal dialog; keeping it a RATIO means a service retints the whole * system with one --overlay-background and every overlay keeps its calibrated depth."},{name:"--app-shell-mobile-nav-inset",value:"var(--space-1)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-brand-mark-size",value:"1.375rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-height",value:"2rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-font-size",value:"0.8125rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-icon-size",value:"1rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-gap",value:"0.625rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-item-padding-x",value:"0.625rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-gap",value:"2px",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-nav-scroll-padding",value:"var(--space-3) var(--space-2)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-section-gap",value:"var(--space-4)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-section-label-padding-x",value:"var(--space-2)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--sidebar-section-label-padding-bottom",value:"var(--space-1)",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--topbar-search-max-width",value:"26.25rem",description:"Inline inset of the mobile drawer's scrollable nav body. Near-zero by design: the drawer nav * (the <Sidebar> node by default) owns its own inset via --sidebar-nav-scroll-padding, so the * generic sheet chrome inset must NOT stack on top of it (gh#211). Set it to var(--space-6) for * a custom mobileNav node that wants the full sheet chrome inset."},{name:"--topbar-height",value:"auto",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-inset",value:"0px",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--topbar-gap",value:"var(--space-2)",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-trigger-height",value:"2.75rem",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-trigger-padding-x",value:"var(--space-2)",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-avatar-size",value:"1.75rem",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-menu-width",value:"16rem",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-sheet-max-height",value:"75dvh",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--org-switcher-state-min-height",value:"8rem",description:"Standalone <Topbar> geometry (rule #45). Topbar is a pure slot bar, so its box is intentionally * QUIET by default: `auto` height and no inline inset, i.e. exactly what it did before these knobs * existed \u2014 inside AppShell the `.app-topbar` grid row still owns the height. A service that * places Topbar directly on a page sets `--topbar-height: 3.5rem` / `--topbar-inset: var(--space-4)` * once instead of writing an app-local class. `--topbar-gap` is the gap BETWEEN the start/center/end * clusters and INSIDE each of them, so one knob re-rhythms the whole bar."},{name:"--sidebar-item-active-color",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-tint",value:"initial",description:"Sidebar active-item tint/marker \u2014 `initial` so the role defaults re-resolve at the call site * under a scoped theme (a :root binding to a role var freezes at :root; a scoped role override * never reaches it). A service re-tunes the active sub-item accent without forking CSS. * Defaults = hsl(var(--primary)) marker/tint."},{name:"--sidebar-item-active-background",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--sidebar-item-active-foreground",value:"initial",description:"Main nav-item active row \u2014 defaults mirror the hover state (accent bg, foreground text); a * service overrides these to brand the selected row (e.g. a gold tint + gold text on a navy * sidebar). `initial` so the defaults re-resolve under a scoped theme. * Defaults = hsl(var(--accent)) fill \xB7 hsl(var(--foreground)) text."},{name:"--auth-shell-control-height",value:"var(--control-height-comfortable)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-heading-size",value:"var(--font-size-2xl)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-card-max-width",value:"24rem",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-bar-padding",value:"var(--space-5) var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-main-padding",value:"var(--space-6)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:"AuthShell \u2014 centred auth/login page shell. Comfortable control density (44px, WCAG touch floor) * + a larger auth heading, scoped to the shell; a service re-tunes the auth card width, insets * and heading size without forking."},{name:"--auth-shell-canonical-control-height",value:"2.25rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-card-max-width",value:"22.5rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-heading-size",value:"var(--font-size-base)",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding",value:"1rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-canonical-main-padding-mobile",value:"0.9375rem",description:"Canonical DXS auth preset. These are public theme knobs: consumers select the preset once on * AuthShell and may retune the product theme here rather than overriding individual fields."},{name:"--auth-shell-compact-card-inset",value:"var(--space-6)",description:"INLINE (start/end) inset of the compact auth card \u2014 the column the title, fields and actions * align to. Independent of the block axis below (gh#232)."},{name:"--auth-shell-card-padding-block-compact",value:"initial",description:"BLOCK (top/bottom) padding of the compact auth card \u2014 the knob that tunes canonical Login card * HEIGHT without narrowing the column. Bound to the card's --card-space-shell-y, so it really * reaches CardContent's block edges (before gh#232 it was wired only to --card-space-body-y and * the rendered body kept the inset on both block edges \u2014 the documented token did nothing). * Declared `initial` so the default mirrors the LIVE --auth-shell-compact-card-inset resolved at * the CALL SITE: the canonical card renders exactly as before (24px block = 24px inline), and a * service that re-tunes the inset inside a scoped `[data-tenant]` still drags the block default * with it (a :root binding would freeze at the :root inset \u2014 docs/TOKENS.md)."},{name:"--auth-shell-card-body-gap-compact",value:"var(--space-3)",description:"Header\u2194body gap inside the compact auth card (the breathing room under the title before the * first field). Its own knob since gh#232 \u2014 it used to ride on * --auth-shell-card-padding-block-compact, which conflated the section gap with the card's block * padding and made the block knob un-tunable. Default is the pre-gh#232 12px rhythm."},{name:"--auth-shell-card-gap-compact",value:"var(--space-3)",description:"IN-SLOT stack gap of the compact auth card (title \u2195 description inside the header)."},{name:"--auth-shell-main-align",value:"center",description:"Main-axis alignment of the auth column. Default `center` = today's vertically-centred card; * a flow whose intro + card + footnote stack is tall opts into `start` via this knob."},{name:"--auth-shell-card-stack-gap",value:"0px",description:"Gap between the direct sections of the auth column (intro \xB7 card \xB7 remember row). Default 0 * (quiet, rule #44) so an existing single-card consumer is untouched; presets opt in."},{name:"--auth-shell-device-card-max-width",value:"23.75rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding",value:"1rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-device-main-padding-mobile",value:"0.9375rem 0.3125rem",description:"Device-authorization preset \u2014 380px card measure, 15px block \xB7 5px inline mobile gutter * (at a 390px viewport the card is x=5px, width=380px)."},{name:"--auth-shell-context-card-max-width",value:"25rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding",value:"var(--space-6)",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-main-padding-mobile",value:"var(--space-6) 0",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-context-card-stack-gap",value:"1rem",description:"Context-selection preset \u2014 25rem card measure on desktop/tablet, edge-to-edge on mobile * (0 inline gutter) with a 16px rhythm between the intro, the card and the remember row."},{name:"--auth-shell-recovery-card-max-width",value:"27rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding",value:"1rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-recovery-main-padding-mobile",value:"0.9375rem",description:"Account-recovery preset (gh#233) \u2014 the SCR-008 measure shared by the password-recovery panel * (request \xB7 sent \xB7 new-password \xB7 expired) and the sign-in MFA challenge panel (OTP \xB7 * recovery-code \xB7 passkey-failure). Both canonical desktop panels measure w=432 at 1440, so ONE * preset owns them; the 360px canonical Login measure is untouched. * The mobile gutter is a DECIDED contract, not a traced artboard: the supplied 390 reference is a * desktop 2x2 composite that crops horizontally and cannot be measured. 15px inline at 390 \u21D2 the * panel is x=15, width=360 \u2014 the same page rhythm as the canonical Login flow, so a user moving * from Login to Recovery on a phone never sees the surface jump."},{name:"--auth-shell-divider-gap",value:"0.625rem",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-label-font-size",value:"0.6875rem",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-rule-color",value:"var(--border)",description:"Labelled auth divider geometry."},{name:"--auth-shell-divider-label-color",value:"var(--muted-foreground)",description:"Labelled auth divider geometry."},{name:"--auth-identity-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-icon-size",value:"1rem",description:"Labelled auth divider geometry."},{name:"--auth-requester-glyph-size",value:"0.625rem",description:"Labelled auth divider geometry."},{name:"--auth-footer-content-gap",value:"0.375rem",description:"Labelled auth divider geometry."},{name:"--auth-footer-text-font-size",value:"0.6875rem",description:"Labelled auth divider geometry."},{name:"--auth-stack-gap",value:"var(--space-3)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-gap",value:"var(--space-1)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-font-size",value:"var(--font-size-xs)",description:"Labelled auth divider geometry."},{name:"--auth-shell-field-label-line-height",value:"1.25",description:"Labelled auth divider geometry."},{name:"--centered-shell-bar-height",value:"var(--app-shell-bar-height)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-bar-padding-x",value:"var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-main-padding",value:"var(--space-6)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-footer-padding",value:"var(--space-3) var(--space-6) var(--space-4)",description:'CenteredShell \u2014 authenticated, no-sidebar, centred-column page shell (hosted-ID "My Page", * account, standalone settings). The bar mirrors AppShell\'s `.app-topbar` chrome (fixed height + * inline padding); the column max-width has three tiers, all wider than the 24rem auth card. A * service retunes the bar inset, block padding and each width tier without forking CSS.'},{name:"--centered-shell-column-offset-block",value:"0",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-sm",value:"32rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-md",value:"46rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--centered-shell-width-lg",value:"64rem",description:'Block offset of the centred column inside the 100dvh shell. `0` (default, the quietest state \u2014 * rule #44) keeps the top-aligned flowing page; `auto` centres the column in the viewport, which * is what CenteredShell `align="center"` sets for a SYSTEM-level standalone surface (500/503 * error page, maintenance notice). Auto block offsets collapse to 0 when the content is taller * than the viewport, so a long localized message scrolls from the top instead of clipping.'},{name:"--sidebar-nav-item-foreground",value:"initial",description:"Resting nav row + label (also the resting sub-row). Default = hsl(var(--muted-foreground))."},{name:"--sidebar-nav-item-hover-foreground",value:"initial",description:"Hovered nav row + label. Default = hsl(var(--foreground))."},{name:"--sidebar-nav-item-disabled-foreground",value:"initial",description:"`aria-disabled` nav row + label. Default = the resting row colour."},{name:"--sidebar-nav-icon-foreground",value:"initial",description:"Nav ICON (`.sb-icon`, incl. the collapsed rail and group triggers). Default = currentColor, * i.e. the row colour \u2014 the pre-gh#228 look. Set this alone to get canonical dark icons next to * muted labels."},{name:"--sidebar-nav-icon-hover-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-active-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--sidebar-nav-icon-disabled-foreground",value:"initial",description:"Icon on a hovered / active / disabled row. Each falls back to --sidebar-nav-icon-foreground, * then to currentColor, so setting the base icon knob alone themes every state."},{name:"--table-row-height-compact",value:"1.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-default",value:"2rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height-comfortable",value:"2.75rem",description:"Table component tokens: row height, cell padding."},{name:"--table-row-height",value:"calc(var(--table-row-height-default) * var(--scaling))",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-padding-y",value:"var(--space-2)",description:"Table component tokens: row height, cell padding."},{name:"--table-cell-space-x",value:"var(--control-padding-x)",description:"Table component tokens: row height, cell padding."},{name:"--table-head-font-size",value:"var(--font-size-xs)",description:"Table component tokens: row height, cell padding."},{name:"--table-header-background",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-header-foreground",value:"initial",description:"Header band \u2014 its OWN bg + fg knobs (decoupled from --secondary). Declared `initial` so the * default re-resolves to the LIVE --muted / --muted-foreground roles at the call site: a :root * binding to a role var freezes at the :root value and a scoped [data-tenant] role override never * reaches it. A brand sets both header tokens together to keep band/text contrast. * Defaults = hsl(var(--muted)) band \xB7 hsl(var(--muted-foreground)) text."},{name:"--table-pin-shadow",value:"-6px 0 6px -5px hsl(var(--foreground) / 0.12)",description:"Inline-end shadow that lifts a pinned (sticky) action column off the body it scrolls over."},{name:"--table-row-striped-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-hover-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."},{name:"--table-row-selected-background",value:"initial",description:"Row-state tint washes \u2014 translucent muted over the opaque base. `initial` so the --muted * default re-resolves under a scoped theme; a service retints by reading another role (e.g. * --primary). Defaults = hsl(var(--muted) / 0.4 striped \xB7 0.5 hover \xB7 0.3 selected)."}];var p={issue:"https://github.com/godx-jp/godxjp-ui/issues/163",standard:"docs/FRAME-COVERAGE-STANDARD.md",ledger:"preview/frame-coverage.ledger.json",recordedAt:"2026-07-30",warning:"UNTESTED means no executed case demonstrates this dimension. It is NOT a pass. Do not infer support from a happy-path example, a similarly-named frame, or a rendered screenshot.",verdicts:{covered:"An executed case proves this dimension.",untested:"No executed case. NOT a pass \u2014 report it as UNTESTED to the user.","not-applicable":"The dimension cannot exist for this export; the ledger carries the written reason."},requiredViewports:[320,375,390,768,1024,1280,1440,1920],containerWidths:[240,320,480,640,960],totals:{exports:273,dimensionCells:3822,covered:6,untested:2144,notApplicable:1672,exportsWithoutFrame:0},dimensions:[{id:"variants",title:"Variants",axis:"visual"},{id:"tones",title:"Tones",axis:"visual"},{id:"sizes",title:"Sizes",axis:"visual"},{id:"shapes",title:"Shapes",axis:"visual"},{id:"density",title:"Spacing / density",axis:"visual"},{id:"ownership",title:"Controlled / uncontrolled / reset",axis:"ownership"},{id:"states",title:"Disabled / read-only / loading / empty / error / success",axis:"state"},{id:"async",title:"Async: loading / reject / retry / cancel / offline",axis:"async"},{id:"responsive",title:"Responsive viewport matrix",axis:"responsive"},{id:"rtl",title:"RTL / direction",axis:"international"},{id:"contentStress",title:"Long / localized content stress",axis:"contentStress"},{id:"keyboard",title:"Keyboard / focus order / focus return",axis:"accessibility"},{id:"accessibleName",title:"Accessible name / description / error association",axis:"accessibility"},{id:"reducedMotion",title:"Preferences: reduced motion / forced colors / 200% zoom / coarse touch",axis:"preferences"}],knownGaps:[{id:"button-contract-gaps",targets:["Button"],dimensions:["states","contentStress","rtl","keyboard","reducedMotion"],cases:["implicit form `type` inside a form","loading combined with asChild","count / overflowCount / showZero overflow","long Japanese and Vietnamese labels","RTL","coarse touch target"]},{id:"card-contract-gaps",targets:["Card"],dimensions:["variants","density","rtl","accessibleName"],cases:["`variant` \xD7 `accent` \xD7 `size` matrix",'`density="tight"` spacing mode',"nested heading levels","RTL accent rail"]},{id:"card-subcomponent-contract-gaps",targets:["CardBar","CardContent","CardFooter","CardHeader"],dimensions:["rtl","accessibleName","contentStress"],cases:["CardBar `extra` slot","CardContent `tight` / `flush` / `solo`","CardFooter `flush` / `separated`","CardHeader `banded`","RTL"]},{id:"empty-state-contract-gaps",targets:["EmptyState"],dimensions:["variants","contentStress","accessibleName"],cases:["`section` variant","`compact` variant","without icon / description / action","heading level (`titleAs` / `titleLevel`) and live-region semantics"]},{id:"progress-contract-gaps",targets:["Progress"],dimensions:["tones","contentStress"],cases:["destructive tone","`over={true}` (value beyond 100%)"]},{id:"carousel-contract-gaps",targets:["Carousel"],dimensions:["ownership","contentStress","keyboard","reducedMotion","rtl"],cases:["single slide","plugins / setApi","autoplay pause","arrow-key traversal alongside tabs","touch / swipe","RTL"]},{id:"data-table-contract-gaps",targets:["DataTable"],dimensions:["states","async","keyboard","accessibleName","density"],cases:["error / prerequisite / background refresh","server and manual modes (manualPagination / manualSorting / manualFiltering)","zero-page and single-page pagination","accessible table and action-column labels","invalid row ids (getRowId)","keyboard behaviour","virtualization"]},{id:"charts-contract-gaps",targets:["LineChart","BarChart","AreaChart","PieChart"],dimensions:["async","reducedMotion","keyboard","contentStress","responsive","rtl"],cases:["loading / error","reduced motion","keyboard datum navigation","invalid / null / NaN data","mobile axis collision","RTL"]},{id:"locale-stress-gaps",targets:["Timeline","TreeList","PasswordStrength"],dimensions:["contentStress","accessibleName"],cases:["long localized Japanese / Vietnamese content","hard-coded screen-reader strings that never reach `t()`"]}]},z=[{name:"Accordion",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionContent",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AccordionItem",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AccordionTrigger",group:"data-display",frame:"docs/data-display/accordion.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Alert",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones","variants"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"AlertActions",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertContent",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDescription",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states","variants"],notApplicable:["density","shapes","sizes","tones"]},{name:"AlertDialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertDialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"AlertDialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AlertDialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AlertMutationFeedback",group:"query",frame:"docs/query/mutation-feedback.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertQueryError",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"AlertTitle",group:"feedback",frame:"docs/feedback/alert.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AppSettingPicker",group:"navigation",frame:"docs/navigation/app-setting-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"AppShell",group:"layout",frame:"docs/layout/app-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AreaChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"AspectRatio",group:"layout",frame:"docs/layout/aspect-ratio.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthDivider",group:"layout",frame:"docs/layout/auth-divider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthFooter",group:"layout",frame:"docs/layout/auth-footer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthIdentity",group:"layout",frame:"docs/layout/auth-identity.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AuthShell",group:"layout",frame:"docs/layout/auth-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","density","keyboard","reducedMotion","responsive","rtl","variants"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"AuthStack",group:"layout",frame:"docs/layout/auth-stack.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Avatar",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AvatarFallback",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"AvatarImage",group:"data-display",frame:"docs/data-display/avatar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Badge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","shapes","tones","variants"],notApplicable:["density","ownership","sizes","states"]},{name:"BarChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Breadcrumb",group:"layout",frame:"docs/navigation/breadcrumb.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Button",group:"general",frame:"docs/general/button/index.tsx",covered:["shapes","sizes","variants"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","tones"]},{name:"ButtonRefetch",group:"query",frame:"docs/query/button-refetch.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","shapes","sizes","states","variants"],notApplicable:["density","ownership","tones"]},{name:"Calendar",group:"data-entry",frame:"docs/data-entry/calendar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Card",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","density","keyboard","reducedMotion","responsive","rtl","sizes","variants"],notApplicable:["ownership","shapes","states","tones"]},{name:"CardAction",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardBar",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardContent",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardCover",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardDescription",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardFooter",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardHeader",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CardTitle",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Carousel",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CarouselContent",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselDots",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselItem",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CarouselNext",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CarouselPrevious",group:"data-display",frame:"docs/data-display/carousel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Cascader",group:"data-entry",frame:"docs/data-entry/cascader.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CenteredShell",group:"layout",frame:"docs/layout/centered-shell.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Checkbox",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CheckboxGroup",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Collapsible",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CollapsibleContent",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CollapsibleTrigger",group:"data-display",frame:"docs/data-display/collapsible.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ColorPicker",group:"data-entry",frame:"docs/data-entry/color-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Command",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandEmpty",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandGroup",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandInput",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"CommandItem",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CommandList",group:"data-entry",frame:"docs/data-entry/command.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"CommandPalette",group:"data-entry",frame:"docs/data-entry/command-palette.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"CompactBarTrend",group:"charts",frame:"docs/charts/compact-bar-trend.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ContextMenu",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuCheckboxItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states","variants"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"ContextMenuLabel",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuPortal",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioGroup",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuRadioItem",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ContextMenuSeparator",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuShortcut",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSub",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubContent",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ContextMenuSubTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ContextMenuTrigger",group:"navigation",frame:"docs/navigation/context-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"CredentialReveal",group:"data-display",frame:"docs/data-display/credential-reveal.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","tones"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"DataState",group:"query",frame:"docs/query/data-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DataTable",group:"data-display",frame:"docs/data-display/data-table/index.tsx",covered:[],untested:["accessibleName","async","contentStress","density","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["shapes","sizes","tones","variants"]},{name:"DatePicker",group:"data-entry",frame:"docs/data-entry/date-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DateRangePicker",group:"data-entry",frame:"docs/data-entry/date-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Descriptions",group:"data-display",frame:"docs/data-display/descriptions.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Dialog",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DialogAction",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogBody",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogCancel",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogClose",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DialogContent",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogDescription",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogFooter",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogHeader",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"DialogOverlay",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogPortal",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTitle",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DialogTrigger",group:"feedback",frame:"docs/feedback/dialog.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenu",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuCheckboxItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states","variants"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"DropdownMenuLabel",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuPortal",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioGroup",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuRadioItem",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"DropdownMenuSeparator",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuShortcut",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSub",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubContent",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"DropdownMenuSubTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"DropdownMenuTrigger",group:"navigation",frame:"docs/navigation/dropdown-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"EmptyState",group:"data-display",frame:"docs/data-display/empty-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones","variants"],notApplicable:["density","ownership","shapes","sizes","states"]},{name:"Field",group:"data-entry",frame:"docs/data-entry/checkbox.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"FilterBarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Flex",group:"layout",frame:"docs/layout/flex.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Form",group:"data-entry",frame:"docs/data-entry/form.tsx",covered:["density"],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["ownership","shapes","sizes","states","tones","variants"]},{name:"FormField",group:"data-entry",frame:"docs/data-entry/form-field/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Heading",group:"general",frame:"docs/general/typography.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"HoverCard",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"HoverCardContent",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"HoverCardTrigger",group:"data-display",frame:"docs/data-display/hover-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InfiniteQueryState",group:"query",frame:"docs/query/infinite-query-state.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"Input",group:"data-entry",frame:"docs/data-entry/input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"InputOTP",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPGroup",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSeparator",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"InputOTPSlot",group:"data-entry",frame:"docs/data-entry/input-otp.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Label",group:"data-entry",frame:"docs/data-entry/label.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LegalDocumentShell",group:"layout",frame:"docs/layout/legal-document-shell/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"LineChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"ListRow",group:"data-display",frame:"docs/data-display/list-row.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Logo",group:"general",frame:"docs/general/logo.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","tones"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"MasterDetail",group:"layout",frame:"docs/layout/master-detail.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Menubar",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarCheckboxItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states","variants"],notApplicable:["density","ownership","shapes","sizes","tones"]},{name:"MenubarLabel",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarMenu",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioGroup",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarRadioItem",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MenubarSeparator",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarShortcut",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSub",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"MenubarSubContent",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MenubarSubTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"MenubarTrigger",group:"navigation",frame:"docs/navigation/menubar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"MonthPicker",group:"data-entry",frame:"docs/data-entry/month-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"MonthRangePicker",group:"data-entry",frame:"docs/data-entry/month-range-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"NavigationMenu",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuContent",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuIndicator",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuItem",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuLink",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuList",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuTrigger",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NavigationMenuViewport",group:"navigation",frame:"docs/navigation/navigation-menu.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"NumberInput",group:"data-entry",frame:"docs/data-entry/number-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes","states"],notApplicable:["density","shapes","tones","variants"]},{name:"OrgSwitcher",group:"layout",frame:"docs/layout/org-switcher.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PageContainer",group:"layout",frame:"docs/layout/page-container.tsx",covered:[],untested:["accessibleName","async","contentStress","density","keyboard","reducedMotion","responsive","rtl","variants"],notApplicable:["ownership","shapes","sizes","states","tones"]},{name:"Pagination",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationContent",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationEllipsis",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PaginationItem",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PaginationLink",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationNext",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PaginationPrevious",group:"navigation",frame:"docs/navigation/pagination.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"PasswordInput",group:"data-entry",frame:"docs/data-entry/password-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PasswordStrength",group:"data-entry",frame:"docs/data-entry/password-strength.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PieChart",group:"charts",frame:"docs/data-display/charts.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","ownership","shapes","states","tones","variants"]},{name:"Popover",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"PopoverAnchor",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverContent",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverDescription",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverHeader",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTitle",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PopoverTrigger",group:"data-display",frame:"docs/data-display/popover.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"PrefetchLink",group:"query",frame:"docs/query/prefetch-link.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Progress",group:"data-display",frame:"docs/data-display/progress.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","tones"],notApplicable:["density","shapes","sizes","states","variants"]},{name:"QrCode",group:"data-display",frame:"docs/data-display/qr-code.tsx",covered:["sizes"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Radio",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroup",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioGroupRoot",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"RadioItem",group:"data-entry",frame:"docs/data-entry/radio-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Rating",group:"data-entry",frame:"docs/data-entry/rating.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"ResizableHandle",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanel",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResizablePanelGroup",group:"layout",frame:"docs/layout/resizable-panel.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"ResponsiveGrid",group:"layout",frame:"docs/layout/responsive-grid.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Reveal",group:"general",frame:"docs/general/reveal.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollArea",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ScrollBar",group:"data-display",frame:"docs/data-display/scroll-area.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SearchInput",group:"data-entry",frame:"docs/data-entry/search-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Select",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectContent",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectGroup",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectItem",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SelectLabel",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollDownButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectScrollUpButton",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectSeparator",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SelectTrigger",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes","states"],notApplicable:["density","shapes","tones","variants"]},{name:"SelectValue",group:"data-entry",frame:"docs/data-entry/select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"Separator",group:"layout",frame:"docs/layout/separator.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceCatalogCta",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCard",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ServiceLauncherCardSkeleton",group:"data-display",frame:"docs/data-display/service-launcher-card.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Sheet",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"SheetBody",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetClose",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SheetContent",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetDescription",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetFooter",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetHeader",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","tones"],notApplicable:["density","ownership","shapes","sizes","states","variants"]},{name:"SheetOverlay",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetPortal",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTitle",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SheetTrigger",group:"feedback",frame:"docs/feedback/sheet.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Sidebar",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarHeader",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SidebarItem",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","states"],notApplicable:["density","ownership","shapes","sizes","tones","variants"]},{name:"SidebarSection",group:"layout",frame:"docs/layout/sidebar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Skeleton",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonDetail",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonRows",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonStat",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"SkeletonTable",group:"feedback",frame:"docs/feedback/skeleton.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Slider",group:"data-entry",frame:"docs/data-entry/slider.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"SplitPane",group:"layout",frame:"docs/layout/split-pane.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"StatCard",group:"data-display",frame:"docs/data-display/card/index.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","shapes","states","tones","variants"]},{name:"StatusBadge",group:"data-display",frame:"docs/data-display/badge.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","shapes","tones","variants"],notApplicable:["density","ownership","sizes","states"]},{name:"Steps",group:"navigation",frame:"docs/navigation/steps.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes"],notApplicable:["density","shapes","states","tones","variants"]},{name:"Switch",group:"data-entry",frame:"docs/data-entry/switch.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes","states"],notApplicable:["density","shapes","tones","variants"]},{name:"Table",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableBody",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableCell",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHead",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableHeader",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TableRow",group:"data-display",frame:"docs/data-display/table.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tabs",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:["variants"],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsContent",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TabsList",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","variants"],notApplicable:["density","shapes","sizes","states","tones"]},{name:"TabsTrigger",group:"navigation",frame:"docs/navigation/tabs.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TagInput",group:"data-entry",frame:"docs/data-entry/tag-input.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Text",group:"general",frame:"docs/general/typography.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","tones"],notApplicable:["density","ownership","shapes","states","variants"]},{name:"Textarea",group:"data-entry",frame:"docs/data-entry/textarea.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Timeline",group:"data-display",frame:"docs/data-display/timeline.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","variants"],notApplicable:["density","ownership","shapes","sizes","states","tones"]},{name:"TimePicker",group:"data-entry",frame:"docs/data-entry/time-picker.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Toaster",group:"feedback",frame:"docs/feedback/toast.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Toggle",group:"data-entry",frame:"docs/data-entry/toggle.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl","sizes","variants"],notApplicable:["density","ownership","shapes","states","tones"]},{name:"ToggleGroup",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes","states","variants"],notApplicable:["density","shapes","tones"]},{name:"ToggleGroupItem",group:"data-entry",frame:"docs/data-entry/toggle-group.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","sizes","variants"],notApplicable:["density","shapes","states","tones"]},{name:"Toolbar",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"ToolbarGroup",group:"navigation",frame:"docs/navigation/toolbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Tooltip",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl"],notApplicable:["density","shapes","sizes","states","tones","variants"]},{name:"TooltipContent",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipProvider",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TooltipTrigger",group:"feedback",frame:"docs/feedback/tooltip.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Topbar",group:"layout",frame:"docs/layout/topbar.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"Transfer",group:"data-entry",frame:"docs/data-entry/transfer.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TreeList",group:"data-display",frame:"docs/data-display/tree-list.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","reducedMotion","responsive","rtl"],notApplicable:["density","ownership","shapes","sizes","states","tones","variants"]},{name:"TreeSelect",group:"data-entry",frame:"docs/data-entry/tree-select.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"TwoFactorSetup",group:"feedback",frame:"docs/feedback/two-factor-setup.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states"],notApplicable:["density","shapes","sizes","tones","variants"]},{name:"Upload",group:"data-entry",frame:"docs/data-entry/upload.tsx",covered:[],untested:["accessibleName","async","contentStress","keyboard","ownership","reducedMotion","responsive","rtl","states","variants"],notApplicable:["density","shapes","sizes","tones"]}],le=new Map(z.map(t=>[t.name.toLowerCase(),t]));function M(t){return le.get(String(t??"").trim().toLowerCase())}var h=[{number:1,title:"Storybook is mandatory",body:"Every primitive / shell / composite has a paired story under `src/stories/<group>/<Name>.stories.tsx` covering every variant + state on light + dark."},{number:2,title:"Tokens, not utilities",body:"Visual values come from CSS custom properties in `src/tokens/` + `src/styles/theme.css`. Token-named Tailwind utilities (`bg-background`) are fine; raw value utilities (`bg-blue-500`) are forbidden. (ADR-0003)"},{number:3,title:"Radix for interactive primitives",body:"Anything with keyboard / ARIA / portal wraps the relevant Radix primitive. (ADR-0001)"},{number:4,title:"shadcn-style ownership",body:"Primitives are thin wrappers; consumers can fork the source in place. (ADR-0002)"},{number:5,title:"One i18next singleton",body:"`initI18n()` in `src/i18n/index.ts` is THE instance; consumers extend via `addResourceBundle`. (ADR-0004)"},{number:6,title:"WCAG 2.1 AA baseline",body:"Every interactive primitive passes axe-core (keyboard nav, ARIA, focus-visible, 4.5:1 contrast, `prefers-reduced-motion`). Stories double as a11y test surfaces."},{number:7,title:"SemVer 2.0 + Keep a Changelog 1.1",body:"Every release-worthy change updates `CHANGELOG.md` under `## Unreleased` in the same PR."},{number:8,title:"Inclusive naming",body:"`allowlist` / `denylist`, `main` / `primary` / `replica` / `secondary`, `they/them`. Never `whitelist` / `blacklist` / `master` / `slave`. Lint-enforced."},{number:9,title:"No marketing speak",body:'Banned: "powerful", "robust", "blazing fast", "best-in-class", "seamless", "enterprise-grade". State what it does.'},{number:10,title:"English is canonical for docs",body:"Localised docs at `docs/i18n/<bcp47>/`; front-matter tracks staleness."},{number:11,title:"Submodule discipline",body:"Two-PR workflow: (1) submodule PR \u2192 `main`, (2) downstream PR \u2192 bump pin. Never push a pin to a SHA not on the submodule remote."},{number:12,title:"Branch + PR workflow",body:"`feat/<scope>` / `fix/<scope>` \u2192 submodule `main`. CI green + squash-merge. No direct push to `main`. `--no-verify` forbidden."},{number:13,title:"TypeScript strict",body:"Explicit types on every export. `forwardRef` for components; `ComponentPropsWithoutRef` for extension. No `any`. No `@ts-ignore` without comment + issue link."},{number:14,title:"Every third-party library is shadcn / Radix-recommended",body:"Locked stack: Radix UI, cmdk, sonner, lucide-react, react-aria-components + `@internationalized/date`, i18next + react-i18next, class-variance-authority + clsx + tailwind-merge. New peer \u2192 ADR documenting why it's the canonical choice."},{number:15,title:"No `@apply` re-encoding tokens",body:"Inside a primitive `.tsx`, don't `@apply` a Tailwind utility that re-encodes a token \u2014 reference the canonical CSS class from `tokens.css` instead. Composite token-named utilities remain fine."},{number:16,title:"CSS source-of-truth is `src/tokens/` + `src/styles/theme.css`",body:"A primitive that needs a new color / spacing / radius adds it there FIRST, then references it."},{number:17,title:"`src/stories/` \u2194 `src/components/` parity",body:"Story set matches primitive set under each group. CI-checked via `scripts/check-stories-parity.mjs`."},{number:18,title:"`docs/reference/<group>/` \u2194 `src/components/<group>/` parity",body:"Every primitive has a reference page; every page maps to a primitive. CI-checked via `scripts/check-docs-parity.mjs`."},{number:19,title:"No service-specific anything",body:'`me-service`, `forge-service`, `admin-service` never appear in source / comments / prop names. Per-deployment brand color lives at `[data-accent="<palette>"]`.'},{number:20,title:'No "platform-only" exports',body:"Every primitive ships via `package.json::exports`. Internal-only helpers stay un-exported."},{number:21,title:"Every component honours every theme axis",body:"`data-theme` (light / dark), `data-accent` (6 palettes), `data-density` (compact / default / comfortable), `data-font-size` (sm / base / lg / xl). Read from tokens, never hardcode values. Verify every PR via the Storybook toolbar sweep."},{number:22,title:"100% match to the design canon",body:'Every visual literal comes from `design-handoff/ui-system/<latest-bundle>/`. Token-pin canon literals; never substitute "close enough". If the bundle doesn\'t cover a case \u2014 STOP, ask the user to mock it.'},{number:23,title:"Concept-first prop API",body:"One concept per prop. Reuse shared vocabulary (`size`, `variant`, `color`, `tone`, `accent`, `padding`, `density`, `orientation`, `placement`, `current`, `value` / `defaultValue` / `onValueChange`, `open` / `defaultOpen` / `onOpenChange`, `justify`, `sticky`, `offset`). Before adding a new prop or token: grep for an existing one."},{number:24,title:"Mobile-first",body:"Defaults target `xs` (\u22650px); progressive enhancement via `sm:` / `md:` / `lg:` / `xl:` / `2xl:`. Touch targets \u2265 44 \xD7 44 px (`--touch-target-min`, does NOT scale with density). Runtime viewport via `useBreakpoint`, never `window.innerWidth`. Stories render at narrow viewport first."},{number:25,title:"Stories are docs; UI is the primitive",body:"When a story looks wrong, fix the primitive / CSS / token. Never paper over with a story tweak. Story-only diff without a paired primitive / CSS / token diff is rejected."},{number:26,title:"Library isolation",body:"`dist/` ships only the consumer surface. Storybook, tests, scripts, design-handoff, `dev-probe/` stay out of npm. Every `dependencies` entry is `external` in `tsup`. Verification via `pnpm pack` + grep of `dist/`."},{number:27,title:"Per-group folder structure",body:"Primitives at `src/components/<group>/<Name>.tsx`; six canonical groups (general, layout, data-display, data-entry, feedback, navigation). Barrel = `src/components/primitives.ts` (single file). Stories + reference docs mirror the same group hierarchy."},{number:28,title:"`src/` folder taxonomy",body:"Three classes: consumer surface (matched by `tsup` entry + `package.json::exports`), Storybook-only (`src/stories/`), build-input-only (`cn.ts`, per-group sources consumed via the barrel). No `src/lib/`, `src/utils/`, `src/internal/`, `src/clients/`, `src/screens/`. Service clients live with the composite that uses them."},{number:29,title:"Stories consume framework primitives only",body:"No raw `<button>` / `<input>` / hand-rolled chips when a primitive exists. HTML semantics (`<section>`, `<article>`, \u2026) for structure are fine. Inline `style={{}}` limited to layout / positioning; no colour / radius / typography overrides."},{number:30,title:"Story `render` returns JSX directly",body:"No opaque `<XyzDemo />` wrapper components, no zero-arg `Demo` helpers. Use `render: function StoryName() { \u2026 }` so Storybook's source panel shows runnable JSX, not `<XyzDemo />`."},{number:31,title:"No nested wrapper / convenience primitives",body:"One Radix base = one framework primitive. `<SimpleX>` over `<X>` is forbidden; add a prop to `<X>` instead. Composites under `src/components/composites/` that combine multiple primitives are NOT wrappers."},{number:32,title:"No redundant props",body:"Before adding a prop / item field / variant, grep the existing surface; if a field already covers the concept, use it. Top-level prop that re-expresses an item field (Timeline `pending` \u2194 `items[i].animate`) is rejected."},{number:33,title:"Stories / source / docs name-synchronized",body:"No two names for the same export across the framework surface; no legacy aliases in stories / docs (source may keep an alias for a deprecation cycle, but the marketing surfaces use the canonical name only). Rename PR runs `grep -rn '<oldName>' src docs` and clears it."},{number:34,title:"Storybook source panel = real, copy-paste-ready code",body:'Storybook\'s react-docgen serializer strips every function value (`cell: ({row}) => <JSX/>`, `render: ({field}) => <Input/>`, `rowClassName`, `renderItem`, \u2026) to `() => {}`. Any story whose `render` passes a function-valued prop, references a module-level helper (`Badge`, `EMPLOYEE_COLUMNS`, etc.), or uses a render-prop pattern MUST override `parameters.docs.source.code` with the literal copy-paste-ready snippet \u2014 type aliases, helper functions spelled out, column definitions with cell JSX visible, inline data array. The `render()` callback stays as-is (module-level constants are fine for runtime performance); `source.code` is the marketing surface. Skip ONLY for stories whose JSX is purely static primitives Storybook can serialize verbatim (`<Button variant="primary">Click</Button>`). The exemplar is `Table.Default` in `src/stories/data-display/Table.stories.tsx`.'},{number:35,title:"Status chips never wrap",body:"A `Badge` / `Badge` reads as one atomic unit. Its label must never break across lines \u2014 pin `white-space: nowrap` on the chip (done in `badge-layout.css`), especially inside narrow `DataTable` cells (\u30B9\u30B3\u30FC\u30D7 / \u30B9\u30C6\u30FC\u30BF\u30B9 columns). If a cell is too tight, widen the column or shorten the label; never let the chip wrap."},{number:36,title:"Badge tone/icon are the colour escape hatch",body:"`Badge` auto-maps a fixed set of English lifecycle keys (active, draft, pending, scheduled, cancelled, failed, \u2026) to tone + icon. For ANY other value \u2014 localized labels (\u516C\u958B\u4E2D, \u30A2\u30AF\u30C6\u30A3\u30D6) or categorical tiers (\u4F1A\u54E1\u30E9\u30F3\u30AF, \u5951\u7D04\u30D7\u30E9\u30F3) \u2014 pass `tone` explicitly (success | warning | destructive | info | neutral) and, for non-lifecycle tiers, `icon={null}` to drop the misleading glyph. Don't let domain labels fall back to neutral grey + \u25CB. Map domain\u2192tone in the CONSUMER layer; the framework only provides the props."},{number:37,title:"DataTable is full-width \u2014 never inside a narrow grid column",body:"A multi-column `DataTable` occupies its OWN row at the page's full width: `<Card><CardContent flush><DataTable \u2026/></CardContent></Card>`. Never nest it in a `lg:col-span-2` of a `ResponsiveGrid columns={3}` beside a chart \u2014 the columns get squeezed until CJK text collapses to one character per line. Charts / KPI cards go in their own row ABOVE the table. (See the `inertia-list-page` pattern.)"},{number:38,title:"FilterBar stays OUT of CardContent flush",body:"`CardContent flush` strips horizontal padding for edge-to-edge tables. A `FilterBar` placed inside it loses all padding and sticks to the card edge. Render `FilterBar` as a STANDALONE block above the table card; wrap ONLY the `DataTable` / `EmptyState` in the `Card` + `CardContent flush`. Order on a list page: KPIs \u2192 FilterBar \u2192 table card."},{number:39,title:"Long text columns get an explicit width",body:"For columns whose value can be long (name / title / segment / address), set `col.width` to a Tailwind width class (e.g. `w-64`, `w-48`) so the column reserves space instead of shrinking and wrapping to many lines; leave numeric / status columns auto. Table cells default to `white-space: nowrap`, so an over-tight table scrolls horizontally rather than crushing \u2014 give the important columns real widths so the default layout reads well before any scroll."},{number:40,title:"Pages are mobile-first",body:'Author and verify every page at 320\u2013390px FIRST. Spacing comes only from `Flex` `gap` (vertical rhythm = `Flex direction="col"`, control rows = the default `direction="row"`) + `ResponsiveGrid columns={2|3|4}` (which collapse to a single column on narrow screens) \u2014 never raw `p-*` / `gap-*` / `space-*` utilities for page layout. Wide tables scroll horizontally on small screens (don\'t force-fit them); dialogs and sheets are full-height on mobile. Touch targets \u2265 44\xD744px.'},{number:41,title:"Drawer & dialog footer layout",body:'Sheet/Dialog/AlertDialog footers are a pinned action bar (Ant Design Drawer footer): the footer sticks to the bottom, SheetFooter draws a full-bleed top border, and actions are RIGHT-aligned with the PRIMARY button rightmost (Cancel/secondary to its left). A destructive / clear / reset action goes far-LEFT \u2014 give that button `className="mr-auto"`. NEVER stack footer buttons full-width or center them.'},{number:42,title:"Props & Tokens Before Customization",body:"Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists. If a real need has NO prop/token/primitive, that is a library GAP \u2014 file it (draft_bug_report), don't paper over it with ad-hoc Tailwind."},{number:43,title:"Every form control goes through FormField",body:"Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."},{number:44,title:"Chrome is a token, default quiet",body:"Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`. Born from real consumption: PageContainer's header divider was undisableable until tokenised."},{number:45,title:"Every service-tunable constant gets a knob",body:'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app. Born from real consumption: `--form-label-width` / `--form-label-gap` (design spec said 110px/8px; the values were prop-only and hard-coded `--space-4`).'},{number:46,title:"Typography is tokens, default is base",body:"A UI framework gives consumers knobs: every font-size in `src/styles/*.css` MUST reference a token \u2014 the global modular scale `var(--font-size-{2xs|xs|sm|base|lg|xl|2xl})` or a per-component `var(--{component}-\u2026-font-size)` knob (rule #45) \u2014 never a hard-coded literal (`font-size: 12px` can't be re-themed). The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync). Enforced by `check:typography`."}];function T(t){return h.find(a=>a.number===t)}var b=[{name:"common-fixes",tagline:"Fix the most common @godxjp/ui consumer mistakes & visual bugs (StatCard double-border, grey Badge, crushed/empty table headers, washed-out sidebar footer, Inertia layout crash, SSR hydration). Before \u2192 after.",tags:["fixes","migration","bug","cardstat","statusbadge","datatable","sidebar","gotcha","review"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1634
1822
  // 0) \u2605 MOST COMMON: <Card> body has NO padding (content is flush against the edges)
1635
1823
  // Cause: the bare <Card> has ZERO inner padding \u2014 it MUST contain <CardContent>.
1636
1824
  // Don't hand-roll padding with className="p-4" on the Card either.
@@ -1818,7 +2006,132 @@ export function GeneralSettingsSection({
1818
2006
  // use route-backed local navigation so every area is deep-linkable and back/forward works.
1819
2007
  // \u26D4 Do NOT wrap the whole settings page in a Card, and do NOT make desktop a horizontal tab bar
1820
2008
  // \u2014 persistent local nav scales to many sections; a tab strip does not.
1821
- // \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"confirm-destructive",tagline:'Type-to-confirm destructive dialog \u2014 Dialog mode="confirm" + Input gate + toast (real @godxjp/ui API).',tags:["dialog","confirm","destructive","delete"],code:`import { useState } from "react";
2009
+ // \u26D4 Do NOT invent CSS class names for the shell/nav/content \u2014 compose Flex + semantic tokens.`},{name:"settings-section-rows",aliases:["settings-section","settings-row","danger-zone","billing-handoff","preferences-rows","account-identity"],tagline:"The four canonical settings BLOCKS \u2014 identity \xB7 preference rows \xB7 billing handoff \xB7 danger zone \u2014 composed from Card + ListRow + Descriptions + StatusBadge + AlertDialog. A COMPOSITION pattern: godx-ui ships no SettingsSection/SettingsRow/DangerZone component and never will.",tags:["settings","account","billing","danger","preferences","listrow","card","admin"],code:`// gh#216. Gate 0 (docs/COMPOSITION-VS-COMPONENT.md): a "SettingsSection"/"SettingsRow"/
2010
+ // "DangerZone" FAILS the Framework-Component Test \u2014 it owns no behavior (C2), is fully
2011
+ // expressible from existing primitives + tokens (C3), and its API would be screen-shaped (C4).
2012
+ // So compose it. Live reference screen: the "settings account sections" page in the preview
2013
+ // showcase gallery (docs/showcase/, served at /showcase/<id>).
2014
+ import { ExternalLink, ShieldAlert } from "lucide-react";
2015
+ import { Button, Text } from "@godxjp/ui/general";
2016
+ import {
2017
+ Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
2018
+ Descriptions, ListRow, StatusBadge,
2019
+ } from "@godxjp/ui/data-display";
2020
+ import { Switch } from "@godxjp/ui/data-entry";
2021
+ import { AlertDialog } from "@godxjp/ui/feedback";
2022
+ import { Flex } from "@godxjp/ui/layout";
2023
+
2024
+ // 1) IDENTITY \u2014 metadata is a description list, so it is Descriptions (a real <dl>), never a
2025
+ // two-column flex of styled divs. One header action goes in CardAction.
2026
+ <Card>
2027
+ <CardHeader>
2028
+ <CardTitle level={2}>\u30A2\u30AB\u30A6\u30F3\u30C8\u60C5\u5831</CardTitle>
2029
+ <CardDescription>\u8ACB\u6C42\u66F8\u30FB\u76E3\u67FB\u30ED\u30B0\u306B\u8868\u793A\u3055\u308C\u308B\u6C0F\u540D\u3068\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3067\u3059\u3002</CardDescription>
2030
+ <CardAction><Button variant="outline" size="sm">\u7DE8\u96C6</Button></CardAction>
2031
+ </CardHeader>
2032
+ <CardContent>
2033
+ <Descriptions columns={2}>
2034
+ <Descriptions.Item label="\u8868\u793A\u540D">\u85E4\u539F \u6167</Descriptions.Item>
2035
+ <Descriptions.Item label="\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9">satoshi@example.co.jp</Descriptions.Item>
2036
+ <Descriptions.Item label="\u5F93\u696D\u54E1\u756A\u53F7" mono>EMP-1042-JP</Descriptions.Item>
2037
+ <Descriptions.Item label="\u5229\u7528\u958B\u59CB\u65E5">
2038
+ {/* ISO-8601 in the machine attribute, Intl for the human text. */}
2039
+ <time dateTime="2023-04-01">
2040
+ {new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" }).format(since)}
2041
+ </time>
2042
+ </Descriptions.Item>
2043
+ </Descriptions>
2044
+ </CardContent>
2045
+ </Card>
2046
+
2047
+ // 2) PREFERENCE ROWS \u2014 THE setting row pattern: CardContent flush + ListRow, whose \`trailing\`
2048
+ // slot IS the control. align="start" + overflow="wrap" keep long JA/VI titles readable and let
2049
+ // the control drop to its own line at 390px (--list-row-body-min-width).
2050
+ // \u26D4 NEVER: <div className="flex items-center justify-between border-b py-3">\u2026</div>
2051
+ <Card>
2052
+ <CardHeader><CardTitle level={2}>\u901A\u77E5\u306E\u74B0\u5883\u8A2D\u5B9A</CardTitle></CardHeader>
2053
+ <CardContent flush>
2054
+ <ListRow
2055
+ align="start"
2056
+ overflow="wrap"
2057
+ title={<label htmlFor="pref-digest">\u9031\u6B21\u30B5\u30DE\u30EA\u30FC\u3092\u30E1\u30FC\u30EB\u3067\u53D7\u3051\u53D6\u308B</label>}
2058
+ description="\u6BCE\u9031\u6708\u66DC\u65E5\u306E\u671D\u306B\u672A\u51E6\u7406\u306E\u627F\u8A8D\u3092\u307E\u3068\u3081\u3066\u9001\u4FE1\u3057\u307E\u3059\u3002"
2059
+ trailing={<Switch id="pref-digest" checked={digest} onCheckedChange={setDigest} />}
2060
+ />
2061
+ </CardContent>
2062
+ </Card>
2063
+
2064
+ // 3) BILLING HANDOFF \u2014 the subscription state is a CANONICAL StatusBadge; the tone + icon come
2065
+ // from the library's shared domain-status map, so a page NEVER keeps its own status\u2192colour
2066
+ // table. Money/seats/dates go through Intl (ISO 4217 minor units come from the formatter).
2067
+ // \u26D4 NEVER: const STATUS_COLORS = { past_due: "red", trialing: "blue" }
2068
+ <Card>
2069
+ <CardHeader>
2070
+ <CardTitle level={2}>\u8ACB\u6C42\u3068\u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3</CardTitle>
2071
+ <CardDescription>\u652F\u6255\u3044\u65B9\u6CD5\u3068\u8ACB\u6C42\u66F8\u306F\u5916\u90E8\u306E\u8ACB\u6C42\u30DD\u30FC\u30BF\u30EB\u3067\u7BA1\u7406\u3057\u307E\u3059\u3002</CardDescription>
2072
+ <CardAction><StatusBadge status="past_due" variant="outline" /></CardAction>
2073
+ </CardHeader>
2074
+ <CardContent>
2075
+ <Descriptions columns={2}>
2076
+ <Descriptions.Item label="\u5951\u7D04\u30D7\u30E9\u30F3">\u30D3\u30B8\u30CD\u30B9\uFF08\u5E74\u984D\uFF09</Descriptions.Item>
2077
+ <Descriptions.Item label="\u6B21\u56DE\u8ACB\u6C42\u4E88\u5B9A\u984D">
2078
+ {new Intl.NumberFormat(locale, { style: "currency", currency: "JPY" }).format(1536000)}
2079
+ </Descriptions.Item>
2080
+ </Descriptions>
2081
+ </CardContent>
2082
+ <CardFooter separated>
2083
+ <Button variant="outline" asChild>
2084
+ <a href={portalUrl} target="_blank" rel="noreferrer">
2085
+ <ExternalLink aria-hidden="true" />
2086
+ \u8ACB\u6C42\u30DD\u30FC\u30BF\u30EB\u3092\u958B\u304F
2087
+ {/* Never signal "leaves the app" with the icon alone (WCAG 1.4.1 / 2.4.4). */}
2088
+ <span className="sr-only">\uFF08\u65B0\u3057\u3044\u30BF\u30D6\u3067\u958B\u304D\u307E\u3059\uFF09</span>
2089
+ </a>
2090
+ </Button>
2091
+ </CardFooter>
2092
+ </Card>
2093
+
2094
+ // 4) DANGER ZONE \u2014 Card accent="destructive" paints the semantic leading rail from --destructive
2095
+ // through the existing --card-accent-rail-width token. NO new component, NO new token, and no
2096
+ // baked red. Colour never carries the meaning alone: the heading says "irreversible", each row
2097
+ // says what is lost, and delete is gated behind type-to-confirm friction.
2098
+ <Card accent="destructive">
2099
+ <CardHeader>
2100
+ <CardTitle level={2} className="text-destructive">\u53D6\u308A\u6D88\u305B\u306A\u3044\u64CD\u4F5C</CardTitle>
2101
+ <CardDescription>\u4EE5\u4E0B\u306E\u64CD\u4F5C\u306F\u5143\u306B\u623B\u305B\u307E\u305B\u3093\u3002</CardDescription>
2102
+ </CardHeader>
2103
+ <CardContent flush>
2104
+ <ListRow
2105
+ align="start"
2106
+ overflow="wrap"
2107
+ leading={<ShieldAlert className="text-destructive size-4" aria-hidden="true" />}
2108
+ title="\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3068\u5168\u30C7\u30FC\u30BF\u3092\u5B8C\u5168\u306B\u524A\u9664\u3059\u308B"
2109
+ description="\u52E4\u6020\u8A18\u9332\u30FB\u7533\u8ACB\u5C65\u6B74\u30FB\u76E3\u67FB\u30ED\u30B0\u304C\u3059\u3079\u3066\u524A\u9664\u3055\u308C\u307E\u3059\u3002"
2110
+ trailing={
2111
+ <Button variant="destructive" size="sm" onClick={() => setConfirmOpen(true)}>\u524A\u9664</Button>
2112
+ }
2113
+ />
2114
+ </CardContent>
2115
+ </Card>
2116
+
2117
+ <AlertDialog
2118
+ open={confirmOpen}
2119
+ onOpenChange={setConfirmOpen}
2120
+ title="\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u3092\u5B8C\u5168\u306B\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F"
2121
+ description="\u3053\u306E\u64CD\u4F5C\u306F\u53D6\u308A\u6D88\u305B\u307E\u305B\u3093\u3002\u78BA\u8A8D\u306E\u305F\u3081\u8B58\u5225\u5B50\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
2122
+ challenge={workspaceSlug}
2123
+ variant="destructive"
2124
+ onConfirm={destroy}
2125
+ />
2126
+
2127
+ // \u2500\u2500 DO / DON'T \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2128
+ // \u2705 Sections stack in a bounded column: <Flex direction="col" gap="lg" className="max-w-3xl">.
2129
+ // \u2705 Every setting row is a ListRow; its control lives in \`trailing\`, and the visible row title is
2130
+ // a <label htmlFor> pointing at that control so the switch/select is named by what you can read.
2131
+ // \u26D4 Do NOT add SettingsSection / SettingsRow / DangerZone to src/components \u2014 they fail Gate 0.
2132
+ // \u26D4 Do NOT wrap the WHOLE settings page in one Card (route-backed local nav owns the IA \u2014 see the
2133
+ // responsive settings page pattern); each block is its own Card.
2134
+ // \u26D4 Do NOT hand-roll padding (p-4) on a Card, a status colour map, or a <dl> replacement.`},{name:"confirm-destructive",tagline:'Type-to-confirm destructive dialog \u2014 Dialog mode="confirm" + Input gate + toast (real @godxjp/ui API).',tags:["dialog","confirm","destructive","delete"],code:`import { useState } from "react";
1822
2135
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@godxjp/ui/feedback";
1823
2136
  import { Input } from "@godxjp/ui/data-entry";
1824
2137
  import { Button } from "@godxjp/ui/general";
@@ -2027,7 +2340,111 @@ export const withCrmLayout = [CrmLayout] // \u2705 array \u2192 Inertia passes
2027
2340
  // argless new Date() during render (e.g. fabricating chart/demo numbers) \u2192 React
2028
2341
  // hydration mismatch ("server rendered text didn't match the client"). Seed
2029
2342
  // deterministically by index, or compute inside an event handler:
2030
- const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; return x - Math.floor(x) }`},{name:"badge-coloring",tagline:"Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",tags:["statusbadge","badge","tone","color","status","tier","table"],code:`import { Badge } from "@godxjp/ui/data-display"
2343
+ const seeded = (n: number) => { const x = Math.sin((n + 1) * 99.71) * 1e4; return x - Math.floor(x) }`},{name:"error-pages",aliases:["error-surface","errorsurface","403","404","500","503","exception-page"],tagline:'403 / 404 / 500 / 503 exception pages. There is NO ErrorSurface component \u2014 compose EmptyState + Text + Button inside the shell the page already renders (403/404 keep AppShell; 500/503 use CenteredShell align="center").',tags:["error","errorsurface","403","404","500","503","maintenance","inertia","ssr","layout","emptystate","centeredshell"],code:`// \u26A0\uFE0F There is NO <ErrorSurface mode=\u2026 status=\u2026/> in @godxjp/ui, and no \`mode\` prop to set.
2344
+ // The mode IS THE SHELL you render (gh#221 \u2014 the surface fails the Framework-Component Test:
2345
+ // it owns no behaviour and is expressible from existing primitives + tokens):
2346
+ // 403 / 404 \u2192 the page's EXISTING <AppShell> is PRESERVED (nav + topbar survive)
2347
+ // 500 / 503 \u2192 <CenteredShell align="center" width="sm"> (viewport-centred system surface)
2348
+ // Docs page: docs/layout/error-surface/
2349
+
2350
+ import { EmptyState } from "@godxjp/ui/data-display";
2351
+ import { Button, Text } from "@godxjp/ui/general";
2352
+ import { CenteredShell, Flex, PageContainer } from "@godxjp/ui/layout";
2353
+ import { SearchX, ServerCrash, ShieldAlert, Wrench } from "lucide-react";
2354
+
2355
+ // \u2500\u2500 THE CANONICAL BODY \u2014 identical for all four statuses \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2356
+ // status code (compact, mono/tabular) \u2192 EmptyState(icon \xB7 tone \xB7 title \xB7 description \xB7 ONE action)
2357
+ // \u2192 optional metadata (request ID \xB7 maintenance window).
2358
+ // EXACTLY ONE recovery action is STRUCTURAL: EmptyState.action is a single slot, so a second
2359
+ // CTA has nowhere to go. Every string comes from the APP's t() \u2014 the library owns no product copy.
2360
+ const META = {
2361
+ 403: { icon: ShieldAlert, tone: "warning" },
2362
+ 404: { icon: SearchX, tone: "muted" },
2363
+ 500: { icon: ServerCrash, tone: "destructive" },
2364
+ 503: { icon: Wrench, tone: "warning" },
2365
+ } as const;
2366
+
2367
+ function ErrorBody({ status, requestId, maintenance, inShell }: {
2368
+ status: 403 | 404 | 500 | 503;
2369
+ requestId?: string;
2370
+ maintenance?: { start: string; end: string; timeZone: string }; // ISO-8601 + IANA
2371
+ inShell: boolean; // true \u21D2 a page <h1> exists above
2372
+ }) {
2373
+ const meta = META[status];
2374
+ return (
2375
+ <Flex direction="col" align="center" gap="sm">
2376
+ <Text as="p" size="sm" tone="muted" weight="medium" mono tabular>{status}</Text>
2377
+ <EmptyState
2378
+ icon={meta.icon}
2379
+ tone={meta.tone}
2380
+ titleLevel={inShell ? 2 : 1} {/* keep the outline valid, never for size */}
2381
+ title={t(\`errors.\${status}.title\`)}
2382
+ description={t(\`errors.\${status}.description\`)}
2383
+ action={<Button onClick={recover}>{t(\`errors.\${status}.action\`)}</Button>}
2384
+ />
2385
+ {requestId && (
2386
+ <Text as="p" size="xs" tone="muted" mono>{t("errors.requestId")}: {requestId}</Text>
2387
+ )}
2388
+ {maintenance && (
2389
+ <Text as="p" size="sm" tone="muted">
2390
+ {t("errors.maintenance")}:{" "}
2391
+ {new Intl.DateTimeFormat(locale, {
2392
+ dateStyle: "medium", timeStyle: "short", timeZone: maintenance.timeZone,
2393
+ }).formatRange(new Date(maintenance.start), new Date(maintenance.end))}
2394
+ </Text>
2395
+ )}
2396
+ </Flex>
2397
+ );
2398
+ }
2399
+
2400
+ // \u2500\u2500 403 / 404 \u2014 APPLICATION mode: PRESERVE the authenticated shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2401
+ // Render the body as the page BODY. The route's own <AppShell> (persistent layout) stays
2402
+ // mounted, so the sidebar, topbar and breadcrumb are never reconstructed by the error page.
2403
+ export function ForbiddenPage() {
2404
+ return (
2405
+ <PageContainer title={t("reports.title")} breadcrumb={[{ label: t("nav.home"), to: "/" }, { label: t("reports.title") }]}>
2406
+ <ErrorBody status={403} inShell />
2407
+ </PageContainer>
2408
+ );
2409
+ }
2410
+
2411
+ // \u2500\u2500 500 / 503 \u2014 SYSTEM mode: package-owned viewport-centred geometry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2412
+ // align="center" sets --centered-shell-column-offset-block: auto \u2192 the column centres in the
2413
+ // 100dvh shell at 1440 / 1024 / 390. NO className, NO min-h-dvh, NO media query. Tall copy
2414
+ // still scrolls from the top (auto offsets collapse), so a long JA/VI message is never clipped.
2415
+ export function MaintenancePage() {
2416
+ return (
2417
+ <CenteredShell align="center" width="sm" footer={<Text size="xs" tone="muted">2026 GodX</Text>}>
2418
+ <ErrorBody
2419
+ status={503}
2420
+ inShell={false}
2421
+ maintenance={{ start: "2026-08-02T18:00:00Z", end: "2026-08-02T20:00:00Z", timeZone: "Asia/Tokyo" }}
2422
+ />
2423
+ </CenteredShell>
2424
+ );
2425
+ }
2426
+
2427
+ // \u2500\u2500 INERTIA / SSR \u2014 one Error page component, status switch, zero client state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2428
+ // Laravel: Handler::render() \u2192 Inertia::render('Error', ['status' => $response->getStatusCode()])
2429
+ //
2430
+ // export default function Error({ status, requestId }) {
2431
+ // const body = <ErrorBody status={status} requestId={requestId} inShell={status < 500} />
2432
+ // return status < 500 ? body : <CenteredShell align="center" width="sm">{body}</CenteredShell>
2433
+ // }
2434
+ // // persistent layout \u21D2 403/404 keep the authenticated shell; 500/503 own the page
2435
+ // Error.layout = (page) => (page.props.status < 500 ? <CrmLayout>{page}</CrmLayout> : page)
2436
+ //
2437
+ // SSR rules:
2438
+ // \u2022 The pattern has NO client state / effects / portals \u2192 it renders fully server-side. An
2439
+ // exception page must be readable without hydration.
2440
+ // \u2022 Format the maintenance window from a SERVER-SENT ISO-8601 string + an explicit IANA
2441
+ // timeZone. An argless new Date() or the server's local zone \u21D2 hydration mismatch.
2442
+ // \u2022 A 500 page must NOT import the app's query client / data providers \u2014 the failure may be
2443
+ // inside them. EmptyState + CenteredShell need no provider.
2444
+ //
2445
+ // ANTI-PATTERNS: rebuilding nav on the 403 page \xB7 AuthShell for a system error (it is the
2446
+ // UNAUTHENTICATED root with auth-card geometry) \xB7 two CTAs \xB7 className="min-h-dvh flex \u2026" \xB7
2447
+ // a hand-built "18:00 - 20:00 JST" string.`},{name:"badge-coloring",tagline:"Colour a Badge for localized labels and tiers via tone + icon (escape-hatch props).",tags:["statusbadge","badge","tone","color","status","tier","table"],code:`import { Badge } from "@godxjp/ui/data-display"
2031
2448
 
2032
2449
  // Badge auto-colours a fixed set of English LIFECYCLE keys:
2033
2450
  // active/completed (success \u2713) \xB7 draft (neutral \u25CB) \xB7 pending/temporary (warning \u23F1)
@@ -2277,7 +2694,108 @@ export function OrganizationMemberships({
2277
2694
  );
2278
2695
  }
2279
2696
  // DON'T: passive rows with no action \xB7 identical decorative icons \xB7 unlabeled timestamps
2280
- // (label them: "2024\u5E74\u306B\u53C2\u52A0") \xB7 a large empty invitation Card shown permanently.`},{name:"account-recovery-settings",aliases:["recovery-email","backup-codes","security-settings","recovery-settings"],tagline:"Signed-in recovery: compact method status/action rows (email \xB7 phone \xB7 backup codes) + a separate password-change destination \u2014 capability-aware, risk-appropriate, NO stacked Info Alerts.",tags:["account","recovery","security","backup-codes","password","settings"],code:`// Research basis: Google Account recovery (email/phone + backup codes), Microsoft Security info,
2697
+ // (label them: "2024\u5E74\u306B\u53C2\u52A0") \xB7 a large empty invitation Card shown permanently.`},{name:"auth-recovery-panels",aliases:["password-recovery","forgot-password","reset-password","mfa-challenge","two-factor-challenge","2fa-challenge","otp-challenge","passkey-failure","scr-008"],tagline:'Signed-OUT password recovery + sign-in MFA challenge panels (SCR-008). There is NO PasswordRecoveryPanel and NO MfaChallengePanel \u2014 compose Card + AuthStack + FormField/InputOTP + Button inside AuthShell preset="account-recovery" (the 432px token-owned measure).',tags:["auth","authshell","recovery","password","mfa","2fa","otp","inputotp","passkey","layout","presentation"],code:`// \u26A0\uFE0F There is NO <PasswordRecoveryPanel state=\u2026/> and NO <MfaChallengePanel state=\u2026/> in
2698
+ // @godxjp/ui (gh#233 \u2014 both fail the Framework-Component Test: they own no behaviour, and a
2699
+ // \`state\` prop that swaps five incompatible bodies is the screen-shaped grab-bag anti-pattern,
2700
+ // the same call as ErrorSurface's \`mode\`). What the package owns is the MEASURE:
2701
+ // <AuthShell variant="canonical" preset="account-recovery"> \u2190 432px panel, 15px gutter at 390
2702
+ // Docs page: docs/layout/auth-recovery/
2703
+ //
2704
+ // PRESENTATION ONLY. No route, no reset semantics, no OTP verification, no recovery-code
2705
+ // consumption, no passkey authentication, no permissions. The consumer owns all logic and copy.
2706
+
2707
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
2708
+ import { FormField, Input, InputOTP, InputOTPGroup, InputOTPSlot, PasswordInput, PasswordStrength } from "@godxjp/ui/data-entry";
2709
+ import { Alert, AlertDescription, AlertTitle } from "@godxjp/ui/feedback";
2710
+ import { Button, Reveal } from "@godxjp/ui/general";
2711
+ import { AuthFooter, AuthShell, AuthStack, Flex } from "@godxjp/ui/layout";
2712
+
2713
+ // \u2500\u2500 THE CANONICAL PANEL ANATOMY \u2014 identical for ALL SEVEN states \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2714
+ // Card > CardHeader(CardTitle + CardDescription) \u2190 title/description INSIDE the surface
2715
+ // > CardContent > AuthStack
2716
+ // [notice] Alert tone="success|destructive" (only when the state has one)
2717
+ // [fields] FormField(s): email \xB7 password pair \xB7 ONE 6-slot InputOTP \xB7 one Input
2718
+ // [primary] Button fullWidth (spans the panel; exactly one)
2719
+ // [fallback] Flex justify="between" gap="sm" wrap (two ghost actions share ONE row)
2720
+ //
2721
+ // DON'T put <AuthIdentity> above the panel here \u2014 it always renders the hosted mark, and the
2722
+ // SCR-008 hierarchy puts the heading INSIDE the bordered surface.
2723
+ // DON'T reuse <TwoFactorSetup> \u2014 that is the ENROLLMENT Dialog (QR / manual key / recovery
2724
+ // output). A sign-in challenge has no secret to reveal and no dialog to dismiss: it IS the page.
2725
+
2726
+ // \u2500\u2500 SIGN-IN MFA CHALLENGE (otp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2727
+ // Focus order IS DOM order: code field \u2192 verify \u2192 fallback A \u2192 fallback B. No tabindex.
2728
+ // The 6-slot row is ONE field: InputOTP owns paste/arrows/backspace/caret and is a single tab
2729
+ // stop. Six <Input>s would put six stops in the ring and break paste of a 6-digit code.
2730
+ export function MfaChallengePage({ code, onCodeChange, error, pending, onVerify }) {
2731
+ return (
2732
+ <AuthShell
2733
+ variant="canonical"
2734
+ preset="account-recovery"
2735
+ brand={brand}
2736
+ footer={<AuthFooter product="GoDX ID" terms={termsLink} privacy={privacyLink} locale={localePicker} />}
2737
+ >
2738
+ <Reveal>
2739
+ <Card>
2740
+ <CardHeader>
2741
+ <CardTitle level={1}>{t("mfa.title")}</CardTitle>
2742
+ <CardDescription>{t("mfa.description")}</CardDescription>
2743
+ </CardHeader>
2744
+ <CardContent>
2745
+ <AuthStack>
2746
+ <FormField id="mfa-code" label={t("mfa.codeLabel")} error={error} required>
2747
+ <InputOTP maxLength={6} pattern="^[0-9]+$" value={code} onChange={onCodeChange}>
2748
+ <InputOTPGroup>
2749
+ {/* six <InputOTPSlot index={0..5} /> */}
2750
+ </InputOTPGroup>
2751
+ </InputOTP>
2752
+ </FormField>
2753
+ <Button fullWidth loading={pending} loadingText={t("common.verifying")} onClick={onVerify}>
2754
+ {t("mfa.verify")}
2755
+ </Button>
2756
+ <Flex justify="between" gap="sm" wrap>
2757
+ <Button variant="ghost" size="sm">{t("mfa.useRecoveryCode")}</Button>
2758
+ <Button variant="ghost" size="sm">{t("mfa.retryPasskey")}</Button>
2759
+ </Flex>
2760
+ </AuthStack>
2761
+ </CardContent>
2762
+ </Card>
2763
+ </Reveal>
2764
+ </AuthShell>
2765
+ );
2766
+ }
2767
+
2768
+ // \u2500\u2500 THE OTHER SIX STATES \u2014 same anatomy, only the notice/fields/copy change \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2769
+ // The STATE IS NOT A PROP: the server answers, and the route renders the matching body in the
2770
+ // SAME 432px panel. Nothing is reconstructed, because nothing was torn down.
2771
+ //
2772
+ // password recovery \xB7 request \u2014 fields: email (autoComplete="username")
2773
+ // password recovery \xB7 sent \u2014 <Alert tone="success"> + <Button fullWidth disabled> (cooldown)
2774
+ // password recovery \xB7 new-password \u2014 two <PasswordInput autoComplete="new-password"> + <PasswordStrength>
2775
+ // password recovery \xB7 expired \u2014 <Alert tone="destructive">, primary = request a new link
2776
+ // MFA \xB7 recovery-code \u2014 ONE <Input autoComplete="one-time-code">
2777
+ // MFA \xB7 passkey-failure \u2014 <Alert tone="destructive">, primary = retry passkey
2778
+ //
2779
+ // The masked address in \`sent\` ("h*****@example.co.jp") is produced by the SERVER \u2014 the panel
2780
+ // never reconstructs an identifier, it renders the string it is given.
2781
+
2782
+ // \u2500\u2500 STATES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2783
+ // error FormField error \u21D2 aria-invalid + aria-errormessage + role="alert". Never colour-only.
2784
+ // loading Button loading + loadingText \u21D2 aria-busy, the box does not resize, fallbacks disabled.
2785
+ // disabled a resend cooldown is <Button disabled> \u2014 keep it VISIBLE so the affordance is known.
2786
+
2787
+ // \u2500\u2500 RESPONSIVE CONTRACT (1440 / 1024 / 390) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2788
+ // 1440 \xB7 1024 panel 432px, centred, 16px page gutter; OTP one row (6 \xD7 36px = 216px)
2789
+ // 390 panel 360px at x=15 (15px inline gutter, --auth-shell-recovery-main-padding-mobile),
2790
+ // 310px content column, OTP still ONE row, primary still full-width, the fallback
2791
+ // row wraps to a stack only when the localized labels exceed the column.
2792
+ // \u26A0\uFE0F The 390 canonical reference supplied with gh#233 is a desktop 2\xD72 COMPOSITE that overflows
2793
+ // and crops horizontally. It is NOT a valid mobile source and was not traced; the row above is a
2794
+ // DECIDED contract, documented in docs/layout/auth-recovery/ and pinned by tests.
2795
+
2796
+ // ANTI-PATTERNS: a page-local --auth-shell-card-max-width override or a .recovery-panel{width:432px}
2797
+ // class \xB7 AuthIdentity above the panel \xB7 TwoFactorSetup as a challenge \xB7 six Inputs for the OTP row \xB7
2798
+ // re-scoping --control-height to widen the slots (use --otp-slot-size) \xB7 two primary actions.`},{name:"account-recovery-settings",aliases:["recovery-email","backup-codes","security-settings","recovery-settings"],tagline:"Signed-in recovery: compact method status/action rows (email \xB7 phone \xB7 backup codes) + a separate password-change destination \u2014 capability-aware, risk-appropriate, NO stacked Info Alerts.",tags:["account","recovery","security","backup-codes","password","settings"],code:`// Research basis: Google Account recovery (email/phone + backup codes), Microsoft Security info,
2281
2799
  // GitHub recovery codes. Signed-IN recovery SETTINGS (manage your methods) is a DIFFERENT surface
2282
2800
  // from the signed-OUT forgot-password JOURNEY \u2014 do not mix them on one page.
2283
2801
  import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
@@ -2347,7 +2865,121 @@ export function AccountRecoverySettings({ email, phone, backupCodesSupported, ba
2347
2865
  );
2348
2866
  }
2349
2867
  // The signed-OUT recovery journey (forgot password \u2192 email link \u2192 reset) is a separate flow on the
2350
- // auth screens (AuthShell), NOT part of these signed-in settings. Do not surface it here.`}];function I(a){let t=a.trim().toLowerCase();return g.find(e=>e.name===t||e.aliases?.includes(t))}function M(a){let t=a.trim().toLowerCase();return t===""?g:g.filter(e=>e.name.includes(t)||e.aliases?.some(o=>o.includes(t))||e.tagline.toLowerCase().includes(t)||e.tags.some(o=>o.includes(t)))}function F(a){return a.audience!=="core"}var k=[{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
2868
+ // auth screens (AuthShell), NOT part of these signed-in settings. Do not surface it here.`},{name:"transactional-email",aliases:["email-template","email-tokens","blade-email","html-email"],tagline:"Build a transactional HTML email (Blade/Twig/MJML/React) from @godxjp/ui/email \u2014 literal hex + px derived from the web tokens, the canonical GoDX brand mark, a bulletproof CTA, legal footer and mobile reflow. NEVER hand-copy hex or hand-draw the logo in a mail template.",tags:["email","transactional","blade","laravel","tokens","brand","mark","mjml","cta"],code:`// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2869
+ // WHY a separate export: an email is rendered by Blade/Twig/MJML and by clients that strip
2870
+ // <style>, ignore CSS custom properties and block remote images. So NEITHER the React components
2871
+ // NOR @godxjp/ui/tokens are reachable \u2014 every value must be a LITERAL inline style.
2872
+ // @godxjp/ui/email is that bridge. It is React-free and dependency-free, and every value is
2873
+ // DERIVED from src/tokens/ (HSL\u2192hex at module load), so it can never drift from the web palette.
2874
+ //
2875
+ // \u274C style="background:#0077c7" // hand-copied hex \u2192 drifts the first re-theme
2876
+ // \u274C style="padding: var(--space-8)" // no email client resolves var()
2877
+ // \u274C style="width: 30rem" // rem is unreliable; use literal px
2878
+ // \u274C <img src="https://cdn\u2026/logo.png"> // blocked by default in most clients
2879
+ // \u274C an emerald pill with no internal glyph // that is the INCOMPLETE mark
2880
+ // \u2705 every value from the token export + the embedded brand mark below
2881
+
2882
+ import {
2883
+ EMAIL_BRAND_MARK,
2884
+ EMAIL_COLORS,
2885
+ EMAIL_CTA,
2886
+ EMAIL_FOOTER,
2887
+ EMAIL_MOBILE,
2888
+ EMAIL_SHELL,
2889
+ EMAIL_TYPOGRAPHY,
2890
+ emailInlineStyle,
2891
+ } from "@godxjp/ui/email";
2892
+
2893
+ const c = EMAIL_COLORS;
2894
+
2895
+ // 1) THE 480px SHELL \u2014 a table, not a div: Outlook's Word renderer has no flex/grid.
2896
+ const page = emailInlineStyle({ padding: EMAIL_SHELL.pagePadding, backgroundColor: c.background });
2897
+ const card = emailInlineStyle({
2898
+ width: EMAIL_SHELL.width, // "480px" \u2014 EMAIL_SHELL.widthPx (480) for width="480"
2899
+ maxWidth: "100%",
2900
+ backgroundColor: c.surface, // \u2190 --card
2901
+ border: \`\${EMAIL_SHELL.borderWidth} solid \${c.border}\`,
2902
+ borderRadius: EMAIL_SHELL.radius,
2903
+ });
2904
+ const body = emailInlineStyle({
2905
+ padding: EMAIL_SHELL.padding, // content column = EMAIL_SHELL.contentWidth (416px)
2906
+ fontFamily: EMAIL_TYPOGRAPHY.fontFamily,
2907
+ fontSize: EMAIL_TYPOGRAPHY.bodyFontSize,
2908
+ lineHeight: EMAIL_TYPOGRAPHY.bodyLineHeight,
2909
+ color: c.foreground,
2910
+ });
2911
+
2912
+ // 2) THE BRAND MARK \u2014 emerald capsule + internal glyph, embedded, zero network fetch.
2913
+ // EMAIL_BRAND_MARK.svg inline <svg> (Apple Mail, iOS, Thunderbird)
2914
+ // EMAIL_BRAND_MARK.dataUri data: URL for <img src> (no request)
2915
+ // EMAIL_BRAND_MARK.tableHtml <table> fallback (renders everywhere)
2916
+ const mark = EMAIL_BRAND_MARK.svg;
2917
+
2918
+ // 3) THE CTA \u2014 line-height EQUALS height, so the label centres with no flexbox.
2919
+ const ctaCell = emailInlineStyle({
2920
+ height: EMAIL_CTA.height, // 44px = the coarse-pointer target
2921
+ borderRadius: EMAIL_CTA.radius,
2922
+ backgroundColor: c.primary,
2923
+ });
2924
+ const ctaLink = emailInlineStyle({
2925
+ display: "inline-block",
2926
+ padding: \`0 \${EMAIL_CTA.paddingX}\`,
2927
+ lineHeight: EMAIL_CTA.lineHeight,
2928
+ fontSize: EMAIL_CTA.fontSize,
2929
+ fontWeight: EMAIL_CTA.fontWeight,
2930
+ color: c.primaryForeground,
2931
+ textDecoration: "none",
2932
+ });
2933
+
2934
+ // 4) LEGAL FOOTER \u2014 quiet type over a hairline; links separated by a fixed-width spacer span
2935
+ // (margins between inline links are dropped by several clients).
2936
+ const footer = emailInlineStyle({
2937
+ borderTop: \`\${EMAIL_FOOTER.borderWidth} solid \${c.border}\`,
2938
+ paddingTop: EMAIL_FOOTER.paddingTop,
2939
+ fontSize: EMAIL_FOOTER.fontSize,
2940
+ lineHeight: EMAIL_FOOTER.lineHeight,
2941
+ color: c.mutedForeground,
2942
+ });
2943
+ const linkSpacer = emailInlineStyle({ display: "inline-block", width: EMAIL_FOOTER.linkGap });
2944
+
2945
+ export const html = \`
2946
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
2947
+ <tr><td align="center" style="\${page}">
2948
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0"
2949
+ width="\${EMAIL_SHELL.widthPx}" class="email-card" style="\${card}">
2950
+ <tr><td class="email-body" style="\${body}">
2951
+ \${mark}
2952
+ <h1 class="email-heading">Your subject line</h1>
2953
+ <p>Your body copy.</p>
2954
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
2955
+ <tr><td class="email-cta" align="center" style="\${ctaCell}">
2956
+ <a class="email-cta-link" href="https://your.app/action" style="\${ctaLink}">Continue</a>
2957
+ </td></tr>
2958
+ </table>
2959
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
2960
+ <tr><td style="\${footer}">
2961
+ <p>This mailbox is not monitored.</p>
2962
+ <p><a href="#">Help</a><span style="\${linkSpacer}">&#8203;</span><a href="#">Privacy</a></p>
2963
+ </td></tr>
2964
+ </table>
2965
+ </td></tr>
2966
+ </table>
2967
+ </td></tr>
2968
+ </table>\`;
2969
+
2970
+ // 5) MOBILE REFLOW \u2014 progressive enhancement ONLY. Every rule below has an inline equivalent
2971
+ // above, so a client that strips <style> still renders the desktop layout, never a broken one.
2972
+ export const mediaQuery = \`
2973
+ @media (max-width: \${EMAIL_MOBILE.maxWidth}) {
2974
+ .email-card { width: \${EMAIL_MOBILE.width} !important; }
2975
+ .email-body { padding: \${EMAIL_MOBILE.padding} !important; }
2976
+ .email-heading { font-size: \${EMAIL_MOBILE.headingFontSize} !important; }
2977
+ .email-cta, .email-cta-link { width: \${EMAIL_MOBILE.ctaWidth} !important; }
2978
+ }\`;
2979
+
2980
+ // 6) BLADE / TWIG / any non-JS engine \u2014 dump the same values once in the build step:
2981
+ // node --input-type=module -e 'import("@godxjp/ui/email").then(m => process.stdout.write(m.EMAIL_TOKENS_JSON))' > resources/design/email-tokens.json
2982
+ // then read the JSON from PHP. Same numbers, same source, no copy-paste.`}];function P(t){let a=t.trim().toLowerCase();return b.find(e=>e.name===a||e.aliases?.includes(a))}function j(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 L(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
2351
2983
  window.innerWidth \u2014 use useBreakpoint(). Stories render at narrow
2352
2984
  viewport first. Multi-column layouts: grid grid-cols-1 sm:grid-cols-N.
2353
2985
  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
@@ -2871,7 +3503,7 @@ Only after sign-off do you move to code. A wireframe skipped = a redesign reliti
2871
3503
  6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
2872
3504
  Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
2873
3505
  (2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
2874
- Do not advance a step until the prior artifact is signed off.`}]}];function T(a){return k.find(t=>t.id===a)}function B(a,t){return T(a)?.sections.find(e=>e.id===t)}function j(a,t){let e=a.toLowerCase(),o=[],n=(i,s,m,p,d)=>{i.some(f=>e.includes(f))&&o.push({skill:s,section:m,why:p,alsoSee:d})};n(["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"]),n(["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"]),n(["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"]),n(["workspace","notion","document","editorial","knowledge base"],"minimalist","palette","Editorial workspace = warm monochrome + spot pastels + serif headings.",["minimalist/typography","minimalist/bento-grids"]),n(["dashboard","data heavy","tabular","ops table"],"brutalist","principles","Data-heavy dashboards work with Brutalist (rigid grids, utilitarian color).",["taste/one-intent-per-screen"]),n(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),n(["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"]),n(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),n(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),n(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Use useBreakpoint()."),n(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),n(["gsap","scrolltrigger","scroll choreography","pinning"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),n(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),n(["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"]),n(["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"]),n(["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"]),n(["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"]),n(["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 r=t?.consumerOnly?o.filter(i=>{let s=T(i.skill);return!s||s.audience!=="core"}):o;return r.length===0?t?.consumerOnly?[{skill:"compose-a-screen",section:"pick-primitives",why:`No keyword match for "${a}". 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 "${a}". Default to the "taste" baseline \u2014 see whenToUse for sections.`}]:r}var D=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
3506
+ Do not advance a step until the prior artifact is signed off.`}]}];function D(t){return x.find(a=>a.id===t)}function U(t,a){return D(t)?.sections.find(e=>e.id===a)}function q(t,a){let e=t.toLowerCase(),o=[],n=(r,s,g,u,d)=>{r.some(y=>e.includes(y))&&o.push({skill:s,section:g,why:u,alsoSee:d})};n(["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"]),n(["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"]),n(["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"]),n(["workspace","notion","document","editorial","knowledge base"],"minimalist","palette","Editorial workspace = warm monochrome + spot pastels + serif headings.",["minimalist/typography","minimalist/bento-grids"]),n(["dashboard","data heavy","tabular","ops table"],"brutalist","principles","Data-heavy dashboards work with Brutalist (rigid grids, utilitarian color).",["taste/one-intent-per-screen"]),n(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),n(["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"]),n(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),n(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),n(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Use useBreakpoint()."),n(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),n(["gsap","scrolltrigger","scroll choreography","pinning"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),n(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),n(["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"]),n(["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"]),n(["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"]),n(["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"]),n(["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(r=>{let s=D(r.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 A=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
2875
3507
  linear gradient as hero background. Looks like every AI-generated
2876
3508
  SaaS landing page from 2023.`,fix:`Use the framework's accent palette (\`data-accent="blue"\` /
2877
3509
  "violet" / "cyan" / "green" / "orange" / "rose"). Solid surface
@@ -2979,143 +3611,143 @@ arrives.`,fix:`Use Skeleton placeholders matching the eventual content shape.
2979
3611
  The framework's \`<Form loading={{ kind: "skeleton" }}>\` cascades
2980
3612
  to every field; \`<Skeleton className="h-9 w-full rounded-md" />\`
2981
3613
  for individual blocks. Layout stays stable, perceived speed
2982
- improves.`}];function V(a){return D.filter(t=>t.category===a)}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 (#0A0A0A) / dark charcoal (#121212) / 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 subtle background imagery at low opacity (`/picsum.photos/seed/{name}/1920/1080`) OR ambient gradient at 0.02-0.05 opacity. 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."}],U=["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"],q=["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 H(a){return R.filter(t=>t.category===a)}var _="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",G=[{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-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 W(a){return a?G.filter(t=>t.category===a):G}var K="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)",$=[{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 Y(a){return a?$.filter(t=>t.category===a):$}var h={name:"@godxjp/ui-mcp",version:"18.4.0",godxUiCompatibility:"18.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"]};var J=[{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:"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 X(a,t){switch(a){case"list_skills":return ie();case"list_primitives":return Z(t.group);case"list_patterns":return ce();case"list_anti_ai_tells":return he(t.category);case"list_redesign_checks":return ge(t.category);case"list_audit_rules":return ue(t.category);case"list_visual_checks":return pe(t.category);case"get_anti_ai_tell":return me(String(t.name??""));case"get_redesign_check":return fe(String(t.symptom??""));case"get_skill_section":return ee(String(t.skill??""),String(t.section??""));case"get_component":return ve(String(t.name??""),t.verbose===!0);case"get_pattern":return we(String(t.name??""));case"get_rule":return ke(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Ce(t.name==null?void 0:String(t.name));case"get_tokens":return xe(t.category);case"list_consumer_skills":return re();case"get_consumer_skill":return se(String(t.skill??""),String(t.section??""));case"route_consumer_task":return Q(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return le(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return Q(String(t.task??""));case"suggest_primitive":return Se(String(t.use_case??""));case"search_components":return Te(String(t.query??""));case"lint_jsx":return De(String(t.jsx??""));default:return`Unknown tool: ${a}`}}function ie(){let a=`# Available skills (${k.length})
3614
+ improves.`}];function G(t){return A.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 (#0A0A0A) / dark charcoal (#121212) / 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 subtle background imagery at low opacity (`/picsum.photos/seed/{name}/1920/1080`) OR ambient gradient at 0.02-0.05 opacity. 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."}],H=["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"],_=["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 W(t){return R.filter(a=>a.category===t)}var K="node node_modules/@godxjp/ui/scripts/ui-audit.mjs (add --format json for machine output, --rules to print this catalog)",$=[{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-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 Y(t){return t?$.filter(a=>a.category===t):$}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:"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:"18.5.0",godxUiCompatibility:"18.5.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"]};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 fe(a.category);case"list_redesign_checks":return we(a.category);case"list_audit_rules":return ye(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 Ae(String(a.name??""));case"get_rule":return Ne(typeof a.number=="number"?a.number:void 0);case"get_vocab":return Oe(a.name==null?void 0:String(a.name));case"get_tokens":return Ie(a.category);case"list_consumer_skills":return pe();case"get_consumer_skill":return ue(String(a.skill??""),String(a.section??""));case"route_consumer_task":return Z(String(a.task??""),{consumerOnly:!0});case"draft_bug_report":return he(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 Pe(String(a.use_case??""));case"search_components":return Re(String(a.query??""));case"get_frame_coverage":return De(a.name===void 0?void 0:String(a.name));case"lint_jsx":return Ee(String(a.jsx??""));default:return`Unknown tool: ${t}`}}function ce(){let t=`# Available skills (${x.length})
2983
3615
 
2984
- `;a+="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",a+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let t of k)a+=`## ${t.id} \u2014 ${t.name} \`[${t.audience}]\`
2985
- `,a+=`**When to use:** ${t.whenToUse}
3616
+ `;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}]\`
3617
+ `,t+=`**When to use:** ${a.whenToUse}
2986
3618
 
2987
- `,a+=`**Sections:** ${t.sections.map(e=>`\`${e.id}\``).join(", ")}
3619
+ `,t+=`**Sections:** ${a.sections.map(e=>`\`${e.id}\``).join(", ")}
2988
3620
 
2989
- `;return a+=`
2990
- _Source: ${k.map(t=>t.source).filter((t,e,o)=>o.indexOf(t)===e).slice(0,3).join("; ")}, \u2026_`,a}function re(){let a=k.filter(F),t=`# Consumer skills (${a.length}) \u2014 building an app WITH @godxjp/ui
3621
+ `;return t+=`
3622
+ _Source: ${x.map(a=>a.source).filter((a,e,o)=>o.indexOf(a)===e).slice(0,3).join("; ")}, \u2026_`,t}function pe(){let t=x.filter(L),a=`# Consumer skills (${t.length}) \u2014 building an app WITH @godxjp/ui
2991
3623
 
2992
- `;t+='Core library-maintenance skills are hidden. Drill in with `get_consumer_skill skill="..." section="..."`, or route a task with `route_consumer_task`.\n\n';for(let e of a)t+=`## ${e.id} \u2014 ${e.name} \`[${e.audience}]\`
2993
- `,t+=`**When to use:** ${e.whenToUse}
3624
+ `;a+='Core library-maintenance skills are hidden. Drill in with `get_consumer_skill skill="..." section="..."`, or route a task with `route_consumer_task`.\n\n';for(let e of t)a+=`## ${e.id} \u2014 ${e.name} \`[${e.audience}]\`
3625
+ `,a+=`**When to use:** ${e.whenToUse}
2994
3626
 
2995
- `,t+=`**Sections:** ${e.sections.map(o=>`\`${o.id}\``).join(", ")}
3627
+ `,a+=`**Sections:** ${e.sections.map(o=>`\`${o.id}\``).join(", ")}
2996
3628
 
2997
- `;return t}function se(a,t){let e=T(a);return e&&!F(e)?`Skill "${a}" is CORE-only (building @godxjp/ui itself) and isn't served to app-devs. Use \`list_consumer_skills\` for consumer-facing guidance${a==="component-discipline"?" \u2014 the standards you need when composing/extending are folded into `compose-a-screen/state-and-a11y` and `design-to-page/verify`.":"."}`:ee(a,t)}function le(a){let t=b=>{let C=a[b];return typeof C=="string"?C.trim():""},e=t("summary");if(!e)return"Pass at least `summary` (one-line title). For a useful report also pass `repro`, `expected`, `actual`, and ideally `component` / `rule` / `version` / `env`. A vague report a maintainer can't reproduce is not enough.";let o=t("repro"),n=t("expected"),r=t("actual"),i=t("component"),s=typeof a.rule=="number"?a.rule:void 0,m=t("version"),p=t("env"),d=[];o||d.push("repro"),n||d.push("expected"),r||d.push("actual");let f=(b,C)=>C||`_(TODO: ${b} \u2014 required for a reproducible report)_`,A=`[bug] ${e}`,l=`## Summary
3629
+ `;return a}function ue(t,a){let e=D(t);return e&&!L(e)?`Skill "${t}" is CORE-only (building @godxjp/ui itself) and isn't served to app-devs. Use \`list_consumer_skills\` for consumer-facing guidance${t==="component-discipline"?" \u2014 the standards you need when composing/extending are folded into `compose-a-screen/state-and-a11y` and `design-to-page/verify`.":"."}`:oe(t,a)}function he(t){let a=f=>{let S=t[f];return typeof S=="string"?S.trim():""},e=a("summary");if(!e)return"Pass at least `summary` (one-line title). For a useful report also pass `repro`, `expected`, `actual`, and ideally `component` / `rule` / `version` / `env`. A vague report a maintainer can't reproduce is not enough.";let o=a("repro"),n=a("expected"),i=a("actual"),r=a("component"),s=typeof t.rule=="number"?t.rule:void 0,g=a("version"),u=a("env"),d=[];o||d.push("repro"),n||d.push("expected"),i||d.push("actual");let y=(f,S)=>S||`_(TODO: ${f} \u2014 required for a reproducible report)_`,N=`[bug] ${e}`,l=`## Summary
2998
3630
 
2999
3631
  ${e}
3000
3632
 
3001
3633
  `;l+=`## Affected
3002
3634
 
3003
- `,l+=i?`- Component: \`${i}\` (see \`get_component name="${i}"\`)
3635
+ `,l+=r?`- Component: \`${r}\` (see \`get_component name="${r}"\`)
3004
3636
  `:`- Component: _(n/a)_
3005
3637
  `,l+=s!==void 0?`- Cardinal rule: #${s} (see \`get_rule number=${s}\`)
3006
3638
  `:"",l+=`
3007
3639
  ## Reproduction (minimal)
3008
3640
 
3009
- ${f("repro",o)}
3641
+ ${y("repro",o)}
3010
3642
 
3011
3643
  `,l+=`## Expected
3012
3644
 
3013
- ${f("expected",n)}
3645
+ ${y("expected",n)}
3014
3646
 
3015
3647
  `,l+=`## Actual
3016
3648
 
3017
- ${f("actual",r)}
3649
+ ${y("actual",i)}
3018
3650
 
3019
3651
  `,l+=`## Environment
3020
3652
 
3021
- `,l+=`- @godxjp/ui version: ${m||"_(TODO: e.g. 12.1.0)_"}
3022
- `,l+=`- Env: ${p||"_(TODO: browser / OS / framework)_"}
3653
+ `,l+=`- @godxjp/ui version: ${g||"_(TODO: e.g. 12.1.0)_"}
3654
+ `,l+=`- Env: ${u||"_(TODO: browser / OS / framework)_"}
3023
3655
 
3024
3656
  `,l+=`## Proposed fix
3025
3657
 
3026
3658
  _(optional \u2014 what the library should do instead)_
3027
- `;let c=b=>`'${b.replace(/'/g,"'\\''")}'`,N=`gh issue create --repo godx-jp/godxjp-ui --label bug --title ${c(A)} --body ${c(l)}`,v=`# Draft bug report
3659
+ `;let c=f=>`'${f.replace(/'/g,"'\\''")}'`,E=`gh issue create --repo godx-jp/godxjp-ui --label bug --title ${c(N)} --body ${c(l)}`,w=`# Draft bug report
3028
3660
 
3029
- `;return d.length&&(v+=`> \u26A0\uFE0F Incomplete \u2014 fill ${d.map(b=>`\`${b}\``).join(", ")} before filing (a report a maintainer can't reproduce will bounce).
3661
+ `;return d.length&&(w+=`> \u26A0\uFE0F Incomplete \u2014 fill ${d.map(f=>`\`${f}\``).join(", ")} before filing (a report a maintainer can't reproduce will bounce).
3030
3662
 
3031
- `),v+=`**Title:** ${A}
3663
+ `),w+=`**Title:** ${N}
3032
3664
 
3033
- `,v+=`## Issue body (Markdown)
3665
+ `,w+=`## Issue body (Markdown)
3034
3666
 
3035
3667
  ${l}
3036
- `,v+=`## File it (copy-paste \u2014 this tool does NOT run gh)
3668
+ `,w+=`## File it (copy-paste \u2014 this tool does NOT run gh)
3037
3669
 
3038
3670
  \`\`\`sh
3039
- ${N}
3671
+ ${E}
3040
3672
  \`\`\`
3041
3673
 
3042
- `,v+="_Reminder: don't hand-roll a fake workaround to hide the bug \u2014 file this, then mark any minimal local workaround with `// TODO(godxui#<n>)`._\n",v}function de(a){let t=h.version,e=h.godxUiCompatibility,o=`# @godxjp/ui compatibility
3674
+ `,w+="_Reminder: don't hand-roll a fake workaround to hide the bug \u2014 file this, then mark any minimal local workaround with `// TODO(godxui#<n>)`._\n",w}function me(t){let a=m.version,e=m.godxUiCompatibility,o=`# @godxjp/ui compatibility
3043
3675
 
3044
- - MCP / catalog version (serverInfo.version): **${t}**
3676
+ - MCP / catalog version (serverInfo.version): **${a}**
3045
3677
  - Compatible @godxjp/ui range: **${e??"(unset)"}**
3046
- `,n=a?.trim();if(!n)return o+'\nProvide the installed version \u2014 `check_compatibility version="16.10.0"` \u2014 to get a match/mismatch verdict. Read it from the target project\'s `node_modules/@godxjp/ui/package.json` or `npm ls @godxjp/ui`.\n';let r=p=>{let d=/^(\d+)\.(\d+)\.(\d+)/.exec(p);return d?{major:d[1],minor:d[2]}:null},i=e?/^(\d+)\.(\d+)\.x$/.exec(e):null,s=r(n);return!s||e&&!i?o+`
3678
+ `,n=t?.trim();if(!n)return o+'\nProvide the installed version \u2014 `check_compatibility version="16.10.0"` \u2014 to get a match/mismatch verdict. Read it from the target project\'s `node_modules/@godxjp/ui/package.json` or `npm ls @godxjp/ui`.\n';let i=u=>{let d=/^(\d+)\.(\d+)\.(\d+)/.exec(u);return d?{major:d[1],minor:d[2]}:null},r=e?/^(\d+)\.(\d+)\.x$/.exec(e):null,s=i(n);return!s||e&&!r?o+`
3047
3679
  \u26A0\uFE0F Could not compare \u2014 installed="${n}" is not a plain x.y.z version, or the catalog range is malformed. Verify with \`npm ls @godxjp/ui\`.
3048
- `:(i?s.major===i[1]&&s.minor===i[2]:n===t)?o+`
3680
+ `:(r?s.major===r[1]&&s.minor===r[2]:n===a)?o+`
3049
3681
  \u2705 In lockstep \u2014 installed @godxjp/ui@${n} is described by this catalog. Prop / token / pattern guidance is safe to trust.
3050
3682
  `:o+`
3051
- \u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${n}, but this catalog describes ${e??t}. Props, defaults, tokens, and patterns it returns may not match the installed build. Align them before trusting output:
3052
- - upgrade the app: \`npm i @godxjp/ui@${t}\`, or
3683
+ \u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${n}, but this catalog describes ${e??a}. Props, defaults, tokens, and patterns it returns may not match the installed build. Align them before trusting output:
3684
+ - upgrade the app: \`npm i @godxjp/ui@${a}\`, or
3053
3685
  - point your agent at the matching MCP release: \`@godxjp/ui-mcp@${s.major}.${s.minor}\` (same minor as the app).
3054
- `}function Z(a){let t=a?E(a):y;if(t.length===0)return`No components${a?` in group "${a}"`:""}.`;let e=t.reduce((n,r)=>((n[r.group]??=[]).push(r),n),{}),o=`# @godxjp/ui primitives${a?` \u2014 ${a}`:""}
3686
+ `}function ae(t){let a=t?F(t):v;if(a.length===0)return`No components${t?` in group "${t}"`:""}.`;let e=a.reduce((n,i)=>((n[i.group]??=[]).push(i),n),{}),o=`# @godxjp/ui primitives${t?` \u2014 ${t}`:""}
3055
3687
 
3056
- ${t.length} components.
3688
+ ${a.length} components.
3057
3689
 
3058
- `;for(let[n,r]of Object.entries(e)){o+=`## ${n}
3690
+ `;for(let[n,i]of Object.entries(e)){o+=`## ${n}
3059
3691
 
3060
- `;for(let i of r)o+=`- **${i.name}** \u2014 ${i.tagline}
3692
+ `;for(let r of i)o+=`- **${r.name}** \u2014 ${r.tagline}
3061
3693
  `;o+=`
3062
- `}return o}function ce(){let a=`# Canonical patterns (${g.length})
3694
+ `}return o}function ge(){let t=`# Canonical patterns (${b.length})
3063
3695
 
3064
- `;for(let t of g)a+=`- **${t.name}** \u2014 ${t.tagline}
3065
- _tags: ${t.tags.join(", ")}_
3066
- `;return a}function pe(a){let t=Y(a),e=`# Runtime visual-audit checks${a?` \u2014 ${a}`:""} (${t.length})
3696
+ `;for(let a of b)t+=`- **${a.name}** \u2014 ${a.tagline}
3697
+ _tags: ${a.tags.join(", ")}_
3698
+ `;return t}function be(t){let a=J(t),e=`# Runtime visual-audit checks${t?` \u2014 ${t}`:""} (${a.length})
3067
3699
 
3068
3700
  `;e+=`_Run against the RUNNING app BEFORE a visual review (warnings, non-blocking). Needs a browser._
3069
3701
  `,e+=`\`\`\`
3070
- ${K}
3702
+ ${X}
3071
3703
  \`\`\`
3072
3704
 
3073
- `,e+="_Static `list_audit_rules` first (cheap, every save) \u2192 THIS (before review) \u2192 human eyes for taste._\n\n";for(let o of t)e+=`- **${o.id}** (${o.severity}, ${o.category}) \u2014 _${o.standard}_
3705
+ `,e+="_Static `list_audit_rules` first (cheap, every save) \u2192 THIS (before review) \u2192 human eyes for taste._\n\n";for(let o of a)e+=`- **${o.id}** (${o.severity}, ${o.category}) \u2014 _${o.standard}_
3074
3706
  ${o.fix}
3075
- `;return e}function ue(a){let t=W(a),e=`# Local UI-audit rules${a?` \u2014 ${a}`:""} (${t.length})
3707
+ `;return e}function ye(t){let a=Y(t),e=`# Local UI-audit rules${t?` \u2014 ${t}`:""} (${a.length})
3076
3708
 
3077
3709
  `;e+=`_Run BEFORE any visual review (warnings are agent guidance, non-blocking):_
3078
3710
  `,e+=`\`\`\`
3079
- ${_}
3711
+ ${K}
3080
3712
  \`\`\`
3081
3713
 
3082
- `;let o=t.reduce((n,r)=>((n[r.category]??=[]).push(r),n),{});for(let[n,r]of Object.entries(o)){e+=`## ${n}
3083
- `;for(let i of r)e+=`- **${i.id}** (${i.severity})${i.standard?` \u2014 _${i.standard}_`:""}
3084
- ${i.fix}
3714
+ `;let o=a.reduce((n,i)=>((n[i.category]??=[]).push(i),n),{});for(let[n,i]of Object.entries(o)){e+=`## ${n}
3715
+ `;for(let r of i)e+=`- **${r.id}** (${r.severity})${r.standard?` \u2014 _${r.standard}_`:""}
3716
+ ${r.fix}
3085
3717
  `;e+=`
3086
- `}return e}function he(a){let t=a?V(a):D,e=`# AI tells to AVOID${a?` \u2014 ${a}`:""} (${t.length})
3718
+ `}return e}function fe(t){let a=t?G(t):A,e=`# AI tells to AVOID${t?` \u2014 ${t}`:""} (${a.length})
3087
3719
 
3088
- `;e+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let o=t.reduce((n,r)=>((n[r.category]??=[]).push(r),n),{});for(let[n,r]of Object.entries(o)){e+=`## ${n}
3089
- `;for(let i of r)e+=`- ${i.name}
3720
+ `;e+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let o=a.reduce((n,i)=>((n[i.category]??=[]).push(i),n),{});for(let[n,i]of Object.entries(o)){e+=`## ${n}
3721
+ `;for(let r of i)e+=`- ${r.name}
3090
3722
  `;e+=`
3091
- `}return e}function me(a){let t=D.find(e=>e.name.toLowerCase()===a.trim().toLowerCase());if(!t){let e=`Anti-AI-tell "${a}" not found. Use \`list_anti_ai_tells\` to discover. Closest:
3723
+ `}return e}function ve(t){let a=A.find(e=>e.name.toLowerCase()===t.trim().toLowerCase());if(!a){let e=`Anti-AI-tell "${t}" not found. Use \`list_anti_ai_tells\` to discover. Closest:
3092
3724
 
3093
- `;for(let o of D.slice(0,8))e+=`- ${o.name} (${o.category})
3094
- `;return e}return`# ${t.name}
3725
+ `;for(let o of A.slice(0,8))e+=`- ${o.name} (${o.category})
3726
+ `;return e}return`# ${a.name}
3095
3727
 
3096
- **Category:** ${t.category}
3728
+ **Category:** ${a.category}
3097
3729
 
3098
3730
  ## Symptom
3099
3731
 
3100
- ${t.body}
3732
+ ${a.body}
3101
3733
 
3102
3734
  ## Fix
3103
3735
 
3104
- ${t.fix}
3105
- `}function ge(a){let t=a?H(a):R,e=`# Redesign audit${a?` \u2014 ${a}`:""} (${t.length} checks)
3736
+ ${a.fix}
3737
+ `}function we(t){let a=t?W(t):R,e=`# Redesign audit${t?` \u2014 ${t}`:""} (${a.length} checks)
3106
3738
 
3107
- `;a||(e+=`## Fix priority
3108
- ${U.map(n=>n).join(`
3739
+ `;t||(e+=`## Fix priority
3740
+ ${H.map(n=>n).join(`
3109
3741
  `)}
3110
3742
 
3111
3743
  `,e+=`## Rules
3112
- ${q.map(n=>`- ${n}`).join(`
3744
+ ${_.map(n=>`- ${n}`).join(`
3113
3745
  `)}
3114
3746
 
3115
- `),e+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let o=t.reduce((n,r)=>((n[r.category]??=[]).push(r),n),{});for(let[n,r]of Object.entries(o)){e+=`## ${n}
3116
- `;for(let i of r)e+=`- ${i.symptom}
3747
+ `),e+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let o=a.reduce((n,i)=>((n[i.category]??=[]).push(i),n),{});for(let[n,i]of Object.entries(o)){e+=`## ${n}
3748
+ `;for(let r of i)e+=`- ${r.symptom}
3117
3749
  `;e+=`
3118
- `}return e}function fe(a){let t=a.trim().toLowerCase();if(!t)return"Pass `symptom` \u2014 a fragment matching the audit check symptom (e.g. 'Inter everywhere' / '100vh' / 'Acme').";let e=R.filter(n=>n.symptom.toLowerCase().includes(t)||n.fix.toLowerCase().includes(t));if(!e.length)return`No redesign check matches "${a}". Use \`list_redesign_checks\` to see all.`;let o=`# Redesign checks matching "${a}" (${e.length})
3750
+ `}return e}function ke(t){let a=t.trim().toLowerCase();if(!a)return"Pass `symptom` \u2014 a fragment matching the audit check symptom (e.g. 'Inter everywhere' / '100vh' / 'Acme').";let e=R.filter(n=>n.symptom.toLowerCase().includes(a)||n.fix.toLowerCase().includes(a));if(!e.length)return`No redesign check matches "${t}". Use \`list_redesign_checks\` to see all.`;let o=`# Redesign checks matching "${t}" (${e.length})
3119
3751
 
3120
3752
  `;for(let n of e)o+=`## ${n.category}
3121
3753
 
@@ -3125,21 +3757,21 @@ ${q.map(n=>`- ${n}`).join(`
3125
3757
  ${n.uiNote?`
3126
3758
  _UI note:_ ${n.uiNote}
3127
3759
  `:""}
3128
- `;return o}function ee(a,t){let e=T(a);if(!e)return`Skill "${a}" not found. Use \`list_skills\` for available ids.`;if(!t){let n=`# ${e.name}
3760
+ `;return o}function oe(t,a){let e=D(t);if(!e)return`Skill "${t}" not found. Use \`list_skills\` for available ids.`;if(!a){let n=`# ${e.name}
3129
3761
 
3130
3762
  ${e.whenToUse}
3131
3763
 
3132
3764
  ## Sections
3133
- `;for(let r of e.sections)n+=`- \`${r.id}\` \u2014 ${r.tagline}
3134
- `;return n}let o=B(a,t);if(!o){let n=`Section "${t}" not in skill "${a}". Available:
3135
- `;for(let r of e.sections)n+=`- \`${r.id}\` \u2014 ${r.tagline}
3765
+ `;for(let i of e.sections)n+=`- \`${i.id}\` \u2014 ${i.tagline}
3766
+ `;return n}let o=U(t,a);if(!o){let n=`Section "${a}" not in skill "${t}". Available:
3767
+ `;for(let i of e.sections)n+=`- \`${i.id}\` \u2014 ${i.tagline}
3136
3768
  `;return n}return`# ${e.name} \u2192 ${o.title}
3137
3769
 
3138
3770
  ${o.tagline}
3139
3771
 
3140
3772
  ${o.body}
3141
3773
 
3142
- _Source: ${e.source}_`}var be={Badge:["badge"],Button:["button"],Toggle:["toggle"],TagInput:["tag-input"],Card:["card"],StatCard:["stat-card"],Table:["table"],DataTable:["table"],Dialog:["dialog"],AlertDialog:["dialog"],Sheet:["dialog"],Drawer:["dialog"],Alert:["alert"],EmptyState:["empty-state"],Skeleton:["skeleton"],Pagination:["pagination"],Toolbar:["filter"],Breadcrumb:["breadcrumb"],Menubar:["menubar"],Progress:["progress"],TreeSelect:["tree"],Timeline:["timeline"],PasswordStrength:["password-strength"],PasswordInput:["password-strength"],Checkbox:["checkbox"],Switch:["switch"],Slider:["slider"],ColorPicker:["color-picker"],Command:["command"],Radio:["choice"],RadioGroup:["choice"],Field:["choice"],AppShell:["sidebar","topbar"],Sidebar:["sidebar"],Topbar:["topbar"],Form:["form"],FormField:["form"],Input:["control"],Textarea:["control"],NumberInput:["control"],Select:["control","search-input"],Cascader:["control"],DatePicker:["control"],TimePicker:["control"],InputOTP:["control"]};function ye(a){let t=be[a]??[a.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()];return L.filter(e=>t.some(o=>e.name.startsWith(`--${o}-`)))}function ve(a,t=!1){let e=P(a);if(!e)return`Component "${a}" not found. Use \`list_primitives\` to discover.`;let o=`# ${e.name}
3774
+ _Source: ${e.source}_`}var xe={Badge:["badge"],Button:["button"],Toggle:["toggle"],TagInput:["tag-input"],Card:["card"],StatCard:["stat-card"],Table:["table"],DataTable:["table"],Dialog:["dialog"],AlertDialog:["dialog"],Sheet:["dialog"],Drawer:["dialog"],Alert:["alert"],EmptyState:["empty-state"],Skeleton:["skeleton"],Pagination:["pagination"],Toolbar:["filter"],Breadcrumb:["breadcrumb"],Menubar:["menubar"],Progress:["progress"],TreeSelect:["tree"],Timeline:["timeline"],PasswordStrength:["password-strength"],PasswordInput:["password-strength"],Checkbox:["checkbox"],Switch:["switch"],Slider:["slider"],ColorPicker:["color-picker"],Command:["command"],Radio:["choice"],RadioGroup:["choice"],Field:["choice"],AppShell:["app-shell","sidebar","topbar"],Sidebar:["sidebar"],Topbar:["topbar"],Form:["form"],FormField:["form"],Input:["control"],Textarea:["control"],NumberInput:["control"],Select:["control","search-input"],Cascader:["control"],DatePicker:["control"],TimePicker:["control"],InputOTP:["control"]};function Se(t){let a=xe[t]??[t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()];return V.filter(e=>a.some(o=>e.name.startsWith(`--${o}-`)))}function Ce(t,a=!1){let e=O(t);if(!e)return`Component "${t}" not found. Use \`list_primitives\` to discover.`;let o=`# ${e.name}
3143
3775
 
3144
3776
  **Group:** ${e.group}`,n=e.importPath??`@godxjp/ui/${e.group==="providers"?"app":e.group}`;o+=` \xB7 **Import:** \`import { ${e.name} } from "${n}"\`
3145
3777
 
@@ -3151,28 +3783,28 @@ _Source: ${e.source}_`}var be={Badge:["badge"],Button:["button"],Toggle:["toggle
3151
3783
 
3152
3784
  `,o+=`| Name | Type | Required | Default | Description |
3153
3785
  |---|---|---|---|---|
3154
- `;for(let i of e.props)o+=`| \`${i.name}\` | \`${i.type}\` | ${i.required?"\u2713":""} | ${i.defaultValue?`\`${i.defaultValue}\``:""} | ${i.description} |
3155
- `;let r=ye(e.name);if(r.length)if(o+=`
3786
+ `;for(let r of e.props)o+=`| \`${r.name}\` | \`${r.type}\` | ${r.required?"\u2713":""} | ${r.defaultValue?`\`${r.defaultValue}\``:""} | ${r.description} |
3787
+ `;let i=Se(e.name);if(i.length)if(o+=`
3156
3788
  ## Design tokens (theme knobs)
3157
3789
 
3158
3790
  Override these in a service \`theme.css\` to re-tune ONLY this component (never hard-code or fork CSS \u2014 rules #44/#45/#46):
3159
3791
 
3160
- `,t){o+=`| Token | Default | What it controls |
3792
+ `,a){o+=`| Token | Default | What it controls |
3161
3793
  |---|---|---|
3162
- `;for(let i of r)o+=`| \`${i.name}\` | \`${i.value}\` | ${i.description} |
3794
+ `;for(let r of i)o+=`| \`${r.name}\` | \`${r.value}\` | ${r.description} |
3163
3795
  `}else{o+=`| Token | Default |
3164
3796
  |---|---|
3165
- `;for(let i of r)o+=`| \`${i.name}\` | \`${i.value}\` |
3797
+ `;for(let r of i)o+=`| \`${r.name}\` | \`${r.value}\` |
3166
3798
  `;o+=`
3167
- _${r.length} knob${r.length>1?"s":""}. G\u1ECDi \`get_component name="${e.name}" verbose=true\` \u0111\u1EC3 xem m\u1ED7i token \u0111i\u1EC1u khi\u1EC3n g\xEC._
3799
+ _${i.length} knob${i.length>1?"s":""}. G\u1ECDi \`get_component name="${e.name}" verbose=true\` \u0111\u1EC3 xem m\u1ED7i token \u0111i\u1EC1u khi\u1EC3n g\xEC._
3168
3800
  `}if(e.usage&&e.usage.length){o+=`
3169
3801
  ## How to use it
3170
3802
 
3171
- `;for(let i of e.usage)o+=`- ${i}
3803
+ `;for(let r of e.usage)o+=`- ${r}
3172
3804
  `}if(e.useCases&&e.useCases.length){o+=`
3173
3805
  ## When to reach for it (use cases)
3174
3806
 
3175
- `;for(let i of e.useCases)o+=`- ${i}
3807
+ `;for(let r of e.useCases)o+=`- ${r}
3176
3808
  `}if(o+=`
3177
3809
  ## Example
3178
3810
 
@@ -3180,38 +3812,77 @@ _${r.length} knob${r.length>1?"s":""}. G\u1ECDi \`get_component name="${e.name}"
3180
3812
  ${e.example}
3181
3813
  \`\`\`
3182
3814
 
3183
- `,e.related&&e.related.length){o+=`## Related \u2014 don't confuse / don't reinvent
3815
+ `,o+=ne(M(e.name),e.name),e.related&&e.related.length){o+=`## Related \u2014 don't confuse / don't reinvent
3184
3816
 
3185
- `;for(let i of e.related)o+=`- ${i}
3817
+ `;for(let r of e.related)o+=`- ${r}
3186
3818
  `;o+=`
3187
3819
  `}if(e.docPath&&(o+=`**Reference doc:** \`docs/reference/${e.docPath}\`
3188
3820
 
3189
3821
  `),o+=`**Storybook:** \`src/stories/${e.storyPath}\`
3190
3822
 
3191
3823
  `,e.rules.length){o+=`**Cardinal rules:**
3192
- `;for(let i of e.rules){let s=S(i);o+=s?`- #${i} \u2014 ${s.title}
3193
- `:`- #${i}
3194
- `}}return o}function we(a){let t=I(a);if(!t){let e=M(a);if(e.length===0)return`Pattern "${a}" not found.`;let o=`Pattern "${a}" not found. Closest:
3824
+ `;for(let r of e.rules){let s=T(r);o+=s?`- #${r} \u2014 ${s.title}
3825
+ `:`- #${r}
3826
+ `}}return o}var Te=new Map(p.dimensions.map(t=>[t.id,t.title]));function ne(t,a){if(!t)return`
3827
+ ## Verified contract coverage (issue #163)
3828
+
3829
+ **UNTESTED.** \`${a}\` has no entry in the frame-coverage ledger, so NOTHING about its props, states, responsive behaviour, RTL, keyboard or async lifecycle is proven by an executed case. Treat the example above as an illustration, never as evidence of support.
3830
+
3831
+ `;let e=i=>i.map(r=>Te.get(r)??r).join(", "),o=`
3832
+ ## Verified contract coverage (issue #163)
3833
+
3834
+ `;o+=`> ${p.warning}
3835
+
3836
+ `,o+=t.frame?`Frame: \`${t.frame}\`
3837
+
3838
+ `:"**No `/frame/**` route exists for this export yet.**\n\n",o+=t.covered.length?`- \u2705 **covered** (an executed case proves it): ${e(t.covered)}
3839
+ `:`- \u2705 **covered:** none
3840
+ `,o+=t.untested.length?`- \u26A0\uFE0F **UNTESTED \u2014 not a pass, no executed case:** ${e(t.untested)}
3841
+ `:`- \u26A0\uFE0F **UNTESTED:** none
3842
+ `,t.notApplicable.length&&(o+=`- \u2796 **not applicable** (no such axis in this export's public API): ${e(t.notApplicable)}
3843
+ `);let n=p.knownGaps.filter(i=>i.targets.includes(t.name));for(let i of n)o+=`- \u{1F50E} **tracked known gap** \`${i.id}\`: ${i.cases.join(" \xB7 ")}
3844
+ `;return o+="\nSee `preview/frame-coverage.ledger.json` and `docs/FRAME-COVERAGE-LEDGER.md` for the written reason behind every cell.\n\n",o}function De(t){let a=p.totals;if(!t||!t.trim()){let o=`# Preview contract coverage (issue #163)
3845
+
3846
+ `;o+=`> ${p.warning}
3847
+
3848
+ `,o+=`- Public exports tracked: **${a.exports}** (${a.exportsWithoutFrame} with no frame at all)
3849
+ `,o+=`- Dimension cells: **${a.dimensionCells}** \u2014 \u2705 ${a.covered} covered \xB7 \u26A0\uFE0F ${a.untested} UNTESTED \xB7 \u2796 ${a.notApplicable} reasoned not-applicable
3850
+ `,o+=`- Required viewport matrix: ${p.requiredViewports.join(", ")} (container widths ${p.containerWidths.join(", ")})
3851
+ `,o+=`- Ledger recorded: ${p.recordedAt} \xB7 \`${p.ledger}\`
3852
+
3853
+ `,o+=`## Contract dimensions
3854
+
3855
+ `;for(let n of p.dimensions)o+=`- \`${n.id}\` \u2014 ${n.title}
3856
+ `;o+=`
3857
+ ## Tracked known gaps
3858
+
3859
+ `;for(let n of p.knownGaps)o+=`- \`${n.id}\` \u2014 ${n.targets.join(", ")}: ${n.cases.join(" \xB7 ")}
3860
+ `;return o+='\nCall `get_frame_coverage name="Button"` for one export. **Never** report a dimension as supported because an example renders it.\n',o}let e=M(t);if(!e){let o=z.filter(n=>n.name.toLowerCase().includes(t.trim().toLowerCase())).slice(0,8).map(n=>n.name);return`No frame-coverage entry for "${t}" \u2014 its contract is **UNTESTED** by definition.
3861
+ `+(o.length?`Did you mean: ${o.join(", ")}?
3862
+ `:"")+"Call `get_frame_coverage` with no name for the repo-wide summary.\n"}return`# ${e.name} \u2014 contract coverage
3863
+
3864
+ **Group:** ${e.group}
3865
+ ${ne(e,e.name)}`}function Ae(t){let a=P(t);if(!a){let e=j(t);if(e.length===0)return`Pattern "${t}" not found.`;let o=`Pattern "${t}" not found. Closest:
3195
3866
  `;for(let n of e)o+=`- ${n.name} \u2014 ${n.tagline}
3196
- `;return o}return`# Pattern: ${t.name}
3867
+ `;return o}return`# Pattern: ${a.name}
3197
3868
 
3198
- ${t.tagline}
3869
+ ${a.tagline}
3199
3870
 
3200
- **Tags:** ${t.tags.join(", ")}
3871
+ **Tags:** ${a.tags.join(", ")}
3201
3872
 
3202
3873
  \`\`\`tsx
3203
- ${t.code}
3874
+ ${a.code}
3204
3875
  \`\`\`
3205
- `}function ke(a){if(a!==void 0){let e=S(a);return e?`# Rule ${e.number} \u2014 ${e.title}
3876
+ `}function Ne(t){if(t!==void 0){let e=T(t);return e?`# Rule ${e.number} \u2014 ${e.title}
3206
3877
 
3207
3878
  ${e.body}
3208
- `:`Rule ${a} not found. Valid: 1-${u.length}.`}let t=`# Cardinal rules (${u.length})
3879
+ `:`Rule ${t} not found. Valid: 1-${h.length}.`}let a=`# Cardinal rules (${h.length})
3209
3880
 
3210
- `;for(let e of u)t+=`## ${e.number}. ${e.title}
3881
+ `;for(let e of h)a+=`## ${e.number}. ${e.title}
3211
3882
 
3212
3883
  ${e.body}
3213
3884
 
3214
- `;return t}function Ce(a){if(a){let e=z(a);if(!e)return`Vocab "${a}" not found.`;let o=`# ${e.name}
3885
+ `;return a}function Oe(t){if(t){let e=B(t);if(!e)return`Vocab "${t}" not found.`;let o=`# ${e.name}
3215
3886
 
3216
3887
  ${e.concept}
3217
3888
 
@@ -3220,49 +3891,49 @@ ${e.concept}
3220
3891
  `,o+=`**Used by:** ${e.usedBy.map(n=>`\`${n}\``).join(", ")}
3221
3892
 
3222
3893
  `,e.notes&&(o+=`**Notes:** ${e.notes}
3223
- `),o}let t=`# Prop vocabulary
3894
+ `),o}let a=`# Prop vocabulary
3224
3895
 
3225
- ${w.length} shared types.
3896
+ ${k.length} shared types.
3226
3897
 
3227
- `;for(let e of w)t+=`## ${e.name}
3898
+ `;for(let e of k)a+=`## ${e.name}
3228
3899
  ${e.concept}
3229
3900
 
3230
3901
  Values: ${e.values.map(o=>`\`${o}\``).join(" | ")}
3231
3902
 
3232
- `;return t}function xe(a){let t=a?O(a):x;if(t.length===0)return`No tokens${a?` in "${a}"`:""}.`;let e=`# Design tokens${a?` \u2014 ${a}`:""}
3903
+ `;return a}function Ie(t){let a=t?I(t):C;if(a.length===0)return`No tokens${t?` in "${t}"`:""}.`;let e=`# Design tokens${t?` \u2014 ${t}`:""}
3233
3904
 
3234
- `,o=t.reduce((n,r)=>((n[r.category]??=[]).push(r),n),{});for(let[n,r]of Object.entries(o)){e+=`## ${n}
3905
+ `,o=a.reduce((n,i)=>((n[i.category]??=[]).push(i),n),{});for(let[n,i]of Object.entries(o)){e+=`## ${n}
3235
3906
 
3236
3907
  | Name | Role | Tier |
3237
3908
  |---|---|---|
3238
- `;for(let i of r)e+=`| \`${i.name}\` | ${i.role} | ${i.tier} |
3909
+ `;for(let r of i)e+=`| \`${r.name}\` | ${r.role} | ${r.tier} |
3239
3910
  `;e+=`
3240
- `}return e}function Q(a,t){if(!a.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let e=j(a,t),o=`# Routing "${a}"${t?.consumerOnly?" (consumer)":""}
3911
+ `}return e}function Z(t,a){if(!t.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let e=q(t,a),o=`# Routing "${t}"${a?.consumerOnly?" (consumer)":""}
3241
3912
 
3242
3913
  `;for(let n of e)o+=`- **skill:** \`${n.skill}\`, **section:** \`${n.section}\`
3243
3914
  ${n.why}
3244
- `,n.alsoSee?.length&&(o+=` _Also see:_ ${n.alsoSee.map(r=>`\`${r}\``).join(", ")}
3245
- `);return o+='\nFetch with: `get_skill_section skill="X" section="Y"`',o}function Se(a){let t=a.trim().toLowerCase();if(!t)return"Describe your use case.";let e=[],o=(r,i,s,m=2)=>{r.some(p=>t.includes(p))&&e.push({component:i,rationale:s,score:m})};if(o(["form","submit","validation","register","sign up"],"Form + FormField","RHF + zod composition.",5),o(["table","rows","columns"],"DataTable / Table","DataTable for chrome (toolbar+pagination+batch). Table for slim primitive.",5),o(["modal","dialog","confirm"],"Dialog / AlertDialog","Radix Dialog. AlertDialog for destructive.",4),o(["drawer","side panel","sheet"],"Sheet","Side panel for filters/settings.",4),o(["toast","notification"],"toast / Toaster","Sonner-backed.",4),o(["loading","saving","spinner"],"Spinner / Form loading prop","Spinner=active work, Skeleton=init fetch.",3),o(["alert","banner"],"Alert","5 semantic colors \xD7 outlined/banner.",3),o(["select","dropdown"],"Select / AutoComplete","Select=discrete options, AutoComplete=free-text+suggestions.",3),o(["filter"],"Toolbar/ToolbarGroup + pattern 'filter-bar' (\u2192 data-table-page)","Standalone Toolbar filter bar above a table; see the data-table-page pattern.",4),o(["delete","destructive"],"Pattern 'confirm-destructive'","Card accent='destructive' + typed-name confirm.",4),!e.length)return`No direct match for "${a}". Try \`list_primitives\` or \`search_components\`.`;e.sort((r,i)=>i.score-r.score);let n=`# Suggestions for "${a}"
3915
+ `,n.alsoSee?.length&&(o+=` _Also see:_ ${n.alsoSee.map(i=>`\`${i}\``).join(", ")}
3916
+ `);return o+='\nFetch with: `get_skill_section skill="X" section="Y"`',o}function Pe(t){let a=t.trim().toLowerCase();if(!a)return"Describe your use case.";let e=[],o=(i,r,s,g=2)=>{i.some(u=>a.includes(u))&&e.push({component:r,rationale:s,score:g})};if(o(["form","submit","validation","register","sign up"],"Form + FormField","RHF + zod composition.",5),o(["table","rows","columns"],"DataTable / Table","DataTable for chrome (toolbar+pagination+batch). Table for slim primitive.",5),o(["modal","dialog","confirm"],"Dialog / AlertDialog","Radix Dialog. AlertDialog for destructive.",4),o(["drawer","side panel","sheet"],"Sheet","Side panel for filters/settings.",4),o(["toast","notification"],"toast / Toaster","Sonner-backed.",4),o(["loading","saving","spinner"],"Spinner / Form loading prop","Spinner=active work, Skeleton=init fetch.",3),o(["alert","banner"],"Alert","5 semantic colors \xD7 outlined/banner.",3),o(["select","dropdown"],"Select / AutoComplete","Select=discrete options, AutoComplete=free-text+suggestions.",3),o(["filter"],"Toolbar/ToolbarGroup + pattern 'filter-bar' (\u2192 data-table-page)","Standalone Toolbar filter bar above a table; see the data-table-page pattern.",4),o(["delete","destructive"],"Pattern 'confirm-destructive'","Card accent='destructive' + typed-name confirm.",4),!e.length)return`No direct match for "${t}". Try \`list_primitives\` or \`search_components\`.`;e.sort((i,r)=>r.score-i.score);let n=`# Suggestions for "${t}"
3246
3917
 
3247
- `;for(let r of e)n+=`- **${r.component}** \u2014 ${r.rationale}
3248
- `;return n}function Te(a){let t=a.trim().toLowerCase();if(!t)return Z();let e=t.split(/\s+/).filter(i=>i.length>=2),o=e.length?e:[t],n=y.map(i=>{let s=i.name.toLowerCase(),m=i.tagline.toLowerCase(),p=(i.useCases??[]).join(" ").toLowerCase(),d=(i.usage??[]).join(" ").toLowerCase(),f=(i.related??[]).join(" ").toLowerCase(),A=i.props.map(c=>c.name.toLowerCase()),l=0;s===t&&(l+=100);for(let c of o)s.includes(c)&&(l+=5),m.includes(c)&&(l+=3),p.includes(c)&&(l+=2),d.includes(c)&&(l+=1),f.includes(c)&&(l+=1),i.group.includes(c)&&(l+=1),A.some(N=>N.includes(c))&&(l+=1);return{c:i,score:l}}).filter(i=>i.score>0).sort((i,s)=>s.score-i.score).slice(0,12);if(!n.length)return`No matches for "${a}". Try \`list_primitives\` or a broader term (e.g. a use-case word like "date", "select", "confirm").`;let r=`# Search "${a}" \u2014 ${n.length} match${n.length>1?"es":""}
3918
+ `;for(let i of e)n+=`- **${i.component}** \u2014 ${i.rationale}
3919
+ `;return n}function Re(t){let a=t.trim().toLowerCase();if(!a)return ae();let e=a.split(/\s+/).filter(r=>r.length>=2),o=e.length?e:[a],n=v.map(r=>{let s=r.name.toLowerCase(),g=r.tagline.toLowerCase(),u=(r.useCases??[]).join(" ").toLowerCase(),d=(r.usage??[]).join(" ").toLowerCase(),y=(r.related??[]).join(" ").toLowerCase(),N=r.props.map(c=>c.name.toLowerCase()),l=0;s===a&&(l+=100);for(let c of o)s.includes(c)&&(l+=5),g.includes(c)&&(l+=3),u.includes(c)&&(l+=2),d.includes(c)&&(l+=1),y.includes(c)&&(l+=1),r.group.includes(c)&&(l+=1),N.some(E=>E.includes(c))&&(l+=1);return{c:r,score:l}}).filter(r=>r.score>0).sort((r,s)=>s.score-r.score).slice(0,12);if(!n.length)return`No matches for "${t}". Try \`list_primitives\` or a broader term (e.g. a use-case word like "date", "select", "confirm").`;let i=`# Search "${t}" \u2014 ${n.length} match${n.length>1?"es":""}
3249
3920
 
3250
- `;for(let{c:i,score:s}of n)r+=`- **${i.name}** (${i.group}, ${s}) \u2014 ${i.tagline}
3251
- `;return r}function De(a){let t=[],e=(n,r)=>{n.test(a)&&t.push(r)};if(e(/<button[\s>]/,"Use `<Button>` instead of raw `<button>` (rule 29)."),e(/<input[\s>]/,"Use `<Input>` instead of raw `<input>` (rule 29)."),e(/<select[\s>]/,"Use `<Select>` instead of raw `<select>` (rule 29)."),e(/<textarea[\s>]/,"Use `<Textarea>` instead of raw `<textarea>` (rule 29)."),e(/<(table|thead|tbody)[\s>]/,"Use `<DataTable>` instead of a hand-rolled `<table>` (rule 29)."),e(/bg-(red|blue|green|yellow|gray|slate|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/,"Use semantic token utilities (`bg-primary`/`bg-destructive`) not raw color scales (rule 2)."),e(/\b(?:ml|mr|pl|pr|left|right)-(?:\d|\[|auto|px|full|screen)|\b(?:rounded-[lr]|border-[lr]|text-(?:left|right))\b/,"Physical direction class \u2014 use logical CSS (`ms-/me-`, `ps-/pe-`, `start-/end-`, `rounded-s/e`, `text-start/end`) so the UI flips correctly under RTL (rule: logical CSS)."),e(/size=["']default["']/,'`size="default"` is not in the controlled vocabulary \u2014 use `size` \u2208 xs|sm|md|lg.'),e(/\btext-\[[0-9.]+px\]/,"Arbitrary text size `text-[Npx]` bypasses the golden type scale \u2014 use `<Text size>` / `<Heading level>` (rule 42)."),e(/<Tag[\s\S]*?color=["']error["']/i,'Tag `color="error"` \u2192 `"destructive"` (v5.0, PR #60).'),e(/<Badge[\s\S]*?variant=["']error["']/i,'Badge `variant="error"` \u2192 `"destructive"` (v5.0, PR #63).'),e(/(Flex|Space|Grid|Masonry)[\s\S]*?(gap|size)=["']middle["']/i,'`"middle"` \u2192 `"default"` for Flex/Space/Grid/Masonry (v5.0).'),e(/<IconButton[\s\S]*?size=["']default["']/i,'IconButton `size="default"` \u2192 `"md"` (v5.0).'),e(/<SegmentedControl[\s\S]*?size=["']sm["']/i,'SegmentedControl `size="sm"` \u2192 `"small"` (v5.0).'),e(/<PageContent[\s\S]*?padding=["'](compact|comfortable)["']/i,'PageContent `padding="compact"/"comfortable"` \u2192 `"tight"/"cozy"` (v5.0).'),e(/<Pagination[\s\S]*?justify=["']between["']/i,'Pagination `justify="between"` \u2192 `"space-between"` (v5.0).'),/<IconButton(?![^>]*aria-label)/i.test(a)&&!/asChild/i.test(a)&&t.push("`<IconButton>` should have `aria-label` (rule 6 \u2014 WCAG)."),/cell:\s*\(\{?\s*row\s*\}?\)\s*=>/i.test(a)&&/export\s+const\s+\w+\s*:\s*Story/i.test(a)&&(/parameters[\s\S]{0,200}source[\s\S]{0,100}code:/i.test(a)||t.push("Stories with function-valued cell renderers MUST override `parameters.docs.source.code` (rule 34).")),/text-(red|blue|green|yellow)-\d{2,3}\b/.test(a)&&t.push("Hard-coded color scales \u2014 use semantic tokens. Tells AI-slop palette (rule 2 + anti-AI-tells.visual.rainbow-chip-wall)."),/h-\[?100vh\]?/.test(a)&&t.push("`100vh` causes iOS Safari viewport jump \u2014 use `min-h-[100dvh]` (redesign.layout / soft.absolute-zero)."),/className=["'][^"']*(?:shadow-md|shadow-lg|shadow-xl)["']/.test(a)&&t.push("Tailwind heavy shadows are an AI tell \u2014 use ultra-diffuse low-opacity (< 0.05) or tinted shadows (soft.absolute-zero, minimalist)."),/\b(?:Inter|Roboto|Helvetica|Open\s+Sans)\b/i.test(a)&&t.push("Banned default fonts (Inter/Roboto/Helvetica/Open Sans). Use Geist/Clash Display/PP Editorial New (soft.absolute-zero, minimalist.negative-constraints)."),/Acme|NovaCore|Flowbit|Quantix|VeloPay|John\s+Doe|Jane\s+Smith|Lorem\s+Ipsum/i.test(a)&&t.push("Generic placeholder content (Acme/NovaCore/John Doe/Lorem Ipsum). Use believable real-sounding names (anti-AI-tells.copy)."),t.length===0)return"\u2705 No issues found against the heuristic checks.";let o=`# Lint findings \u2014 ${t.length} issue${t.length===1?"":"s"}
3921
+ `;for(let{c:r,score:s}of n)i+=`- **${r.name}** (${r.group}, ${s}) \u2014 ${r.tagline}
3922
+ `;return i}function Ee(t){let a=[],e=(n,i)=>{n.test(t)&&a.push(i)};if(e(/<button[\s>]/,"Use `<Button>` instead of raw `<button>` (rule 29)."),e(/<input[\s>]/,"Use `<Input>` instead of raw `<input>` (rule 29)."),e(/<select[\s>]/,"Use `<Select>` instead of raw `<select>` (rule 29)."),e(/<textarea[\s>]/,"Use `<Textarea>` instead of raw `<textarea>` (rule 29)."),e(/<(table|thead|tbody)[\s>]/,"Use `<DataTable>` instead of a hand-rolled `<table>` (rule 29)."),e(/bg-(red|blue|green|yellow|gray|slate|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/,"Use semantic token utilities (`bg-primary`/`bg-destructive`) not raw color scales (rule 2)."),e(/\b(?:ml|mr|pl|pr|left|right)-(?:\d|\[|auto|px|full|screen)|\b(?:rounded-[lr]|border-[lr]|text-(?:left|right))\b/,"Physical direction class \u2014 use logical CSS (`ms-/me-`, `ps-/pe-`, `start-/end-`, `rounded-s/e`, `text-start/end`) so the UI flips correctly under RTL (rule: logical CSS)."),e(/size=["']default["']/,'`size="default"` is not in the controlled vocabulary \u2014 use `size` \u2208 xs|sm|md|lg.'),e(/\btext-\[[0-9.]+px\]/,"Arbitrary text size `text-[Npx]` bypasses the golden type scale \u2014 use `<Text size>` / `<Heading level>` (rule 42)."),e(/<Tag[\s\S]*?color=["']error["']/i,'Tag `color="error"` \u2192 `"destructive"` (v5.0, PR #60).'),e(/<Badge[\s\S]*?variant=["']error["']/i,'Badge `variant="error"` \u2192 `"destructive"` (v5.0, PR #63).'),e(/(Flex|Space|Grid|Masonry)[\s\S]*?(gap|size)=["']middle["']/i,'`"middle"` \u2192 `"default"` for Flex/Space/Grid/Masonry (v5.0).'),e(/<IconButton[\s\S]*?size=["']default["']/i,'IconButton `size="default"` \u2192 `"md"` (v5.0).'),e(/<SegmentedControl[\s\S]*?size=["']sm["']/i,'SegmentedControl `size="sm"` \u2192 `"small"` (v5.0).'),e(/<PageContent[\s\S]*?padding=["'](compact|comfortable)["']/i,'PageContent `padding="compact"/"comfortable"` \u2192 `"tight"/"cozy"` (v5.0).'),e(/<Pagination[\s\S]*?justify=["']between["']/i,'Pagination `justify="between"` \u2192 `"space-between"` (v5.0).'),/<IconButton(?![^>]*aria-label)/i.test(t)&&!/asChild/i.test(t)&&a.push("`<IconButton>` should have `aria-label` (rule 6 \u2014 WCAG)."),/cell:\s*\(\{?\s*row\s*\}?\)\s*=>/i.test(t)&&/export\s+const\s+\w+\s*:\s*Story/i.test(t)&&(/parameters[\s\S]{0,200}source[\s\S]{0,100}code:/i.test(t)||a.push("Stories with function-valued cell renderers MUST override `parameters.docs.source.code` (rule 34).")),/text-(red|blue|green|yellow)-\d{2,3}\b/.test(t)&&a.push("Hard-coded color scales \u2014 use semantic tokens. Tells AI-slop palette (rule 2 + anti-AI-tells.visual.rainbow-chip-wall)."),/h-\[?100vh\]?/.test(t)&&a.push("`100vh` causes iOS Safari viewport jump \u2014 use `min-h-[100dvh]` (redesign.layout / soft.absolute-zero)."),/className=["'][^"']*(?:shadow-md|shadow-lg|shadow-xl)["']/.test(t)&&a.push("Tailwind heavy shadows are an AI tell \u2014 use ultra-diffuse low-opacity (< 0.05) or tinted shadows (soft.absolute-zero, minimalist)."),/\b(?:Inter|Roboto|Helvetica|Open\s+Sans)\b/i.test(t)&&a.push("Banned default fonts (Inter/Roboto/Helvetica/Open Sans). Use Geist/Clash Display/PP Editorial New (soft.absolute-zero, minimalist.negative-constraints)."),/Acme|NovaCore|Flowbit|Quantix|VeloPay|John\s+Doe|Jane\s+Smith|Lorem\s+Ipsum/i.test(t)&&a.push("Generic placeholder content (Acme/NovaCore/John Doe/Lorem Ipsum). Use believable real-sounding names (anti-AI-tells.copy)."),a.length===0)return"\u2705 No issues found against the heuristic checks.";let o=`# Lint findings \u2014 ${a.length} issue${a.length===1?"":"s"}
3252
3923
 
3253
- `;for(let n of t)o+=`- ${n}
3924
+ `;for(let n of a)o+=`- ${n}
3254
3925
  `;return o+=`
3255
3926
  Note: heuristic only \u2014 not a substitute for the full CI gate.
3256
- `,o}var te=u.length,ae=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${te})`,description:`The ${te} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function oe(a){if(a==="godx-ui://compatibility")return JSON.stringify({mcpVersion:h.version,serverVersion:h.version,compatibleUi:h.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(a==="godx-ui://components")return JSON.stringify(y,null,2);if(a.startsWith("godx-ui://components/")){let t=a.slice(21),e=P(t);if(!e)throw new Error(`Component not found: ${t}`);return Ae(e)}if(a==="godx-ui://prop-vocabulary")return JSON.stringify(w,null,2);if(a==="godx-ui://tokens")return JSON.stringify(x,null,2);if(a.startsWith("godx-ui://tokens/")){let t=a.slice(17);return JSON.stringify(O(t),null,2)}if(a==="godx-ui://rules"){let t=`# Cardinal rules (${u.length})
3927
+ `,o}var re=h.length,ie=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${re})`,description:`The ${re} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function se(t){if(t==="godx-ui://compatibility")return JSON.stringify({mcpVersion:m.version,serverVersion:m.version,compatibleUi:m.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(t==="godx-ui://components")return JSON.stringify(v,null,2);if(t.startsWith("godx-ui://components/")){let a=t.slice(21),e=O(a);if(!e)throw new Error(`Component not found: ${a}`);return ze(e)}if(t==="godx-ui://prop-vocabulary")return JSON.stringify(k,null,2);if(t==="godx-ui://tokens")return JSON.stringify(C,null,2);if(t.startsWith("godx-ui://tokens/")){let a=t.slice(17);return JSON.stringify(I(a),null,2)}if(t==="godx-ui://rules"){let a=`# Cardinal rules (${h.length})
3257
3928
 
3258
- `;for(let e of u)t+=`## ${e.number}. ${e.title}
3929
+ `;for(let e of h)a+=`## ${e.number}. ${e.title}
3259
3930
 
3260
3931
  ${e.body}
3261
3932
 
3262
- `;return t}if(a.startsWith("godx-ui://rules/")){let t=Number(a.slice(16)),e=S(t);if(!e)throw new Error(`Rule not found: ${t}`);return`# Rule ${e.number} \u2014 ${e.title}
3933
+ `;return a}if(t.startsWith("godx-ui://rules/")){let a=Number(t.slice(16)),e=T(a);if(!e)throw new Error(`Rule not found: ${a}`);return`# Rule ${e.number} \u2014 ${e.title}
3263
3934
 
3264
3935
  ${e.body}
3265
- `}if(a==="godx-ui://patterns")return JSON.stringify(g.map(({name:t,tagline:e,tags:o})=>({name:t,tagline:e,tags:o})),null,2);if(a.startsWith("godx-ui://patterns/")){let t=a.slice(19),e=I(t);if(!e)throw new Error(`Pattern not found: ${t}`);return`# ${e.name}
3936
+ `}if(t==="godx-ui://patterns")return JSON.stringify(b.map(({name:a,tagline:e,tags:o})=>({name:a,tagline:e,tags:o})),null,2);if(t.startsWith("godx-ui://patterns/")){let a=t.slice(19),e=P(a);if(!e)throw new Error(`Pattern not found: ${a}`);return`# ${e.name}
3266
3937
 
3267
3938
  ${e.tagline}
3268
3939
 
@@ -3271,21 +3942,21 @@ ${e.tagline}
3271
3942
  \`\`\`tsx
3272
3943
  ${e.code}
3273
3944
  \`\`\`
3274
- `}throw new Error(`Unknown resource: ${a}`)}function Ae(a){let t=`# ${a.name}
3945
+ `}throw new Error(`Unknown resource: ${t}`)}function ze(t){let a=`# ${t.name}
3275
3946
 
3276
- **Group:** ${a.group}
3947
+ **Group:** ${t.group}
3277
3948
 
3278
- ${a.tagline}
3949
+ ${t.tagline}
3279
3950
 
3280
- `;t+=`## Props
3951
+ `;a+=`## Props
3281
3952
 
3282
- `,t+=`| Name | Type | Required | Default | Description |
3953
+ `,a+=`| Name | Type | Required | Default | Description |
3283
3954
  |---|---|---|---|---|
3284
- `;for(let e of a.props)t+=`| \`${e.name}\` | \`${e.type}\` | ${e.required?"\u2713":""} | ${e.defaultValue?`\`${e.defaultValue}\``:""} | ${e.description} |
3285
- `;return t+=`
3955
+ `;for(let e of t.props)a+=`| \`${e.name}\` | \`${e.type}\` | ${e.required?"\u2713":""} | ${e.defaultValue?`\`${e.defaultValue}\``:""} | ${e.description} |
3956
+ `;return a+=`
3286
3957
  ## Example
3287
3958
 
3288
3959
  \`\`\`tsx
3289
- ${a.example}
3960
+ ${t.example}
3290
3961
  \`\`\`
3291
- `,t}async function Ee(){let a=new Pe({name:"godx-ui-mcp",version:h.version},{capabilities:{tools:{},resources:{}}});a.setRequestHandler(Ne,async()=>({tools:J})),a.setRequestHandler(Ie,async e=>{let{name:o,arguments:n}=e.params;return{content:[{type:"text",text:await X(o,n??{})}]}}),a.setRequestHandler(Re,async()=>({resources:ae})),a.setRequestHandler(Fe,async e=>{let{uri:o}=e.params,n=await oe(o);return{contents:[{uri:o,mimeType:o.endsWith(".json")?"application/json":"text/markdown",text:n}]}});let t=new Oe;await a.connect(t),console.error("[godx-ui-mcp] connected (stdio)")}Ee().catch(a=>{console.error("[godx-ui-mcp] fatal:",a),process.exit(1)});
3962
+ `,a}async function Ue(){let t=new Me({name:"godx-ui-mcp",version:m.version},{capabilities:{tools:{},resources:{}}});t.setRequestHandler(Ve,async()=>({tools:ee})),t.setRequestHandler(Fe,async e=>{let{name:o,arguments:n}=e.params;return{content:[{type:"text",text:await te(o,n??{})}]}}),t.setRequestHandler(Be,async()=>({resources:ie})),t.setRequestHandler(je,async e=>{let{uri:o}=e.params,n=await se(o);return{contents:[{uri:o,mimeType:o.endsWith(".json")?"application/json":"text/markdown",text:n}]}});let a=new Le;await t.connect(a),console.error("[godx-ui-mcp] connected (stdio)")}Ue().catch(t=>{console.error("[godx-ui-mcp] fatal:",t),process.exit(1)});