@godxjp/ui-mcp 27.0.0 → 27.1.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 (2) hide show
  1. package/dist/index.js +7 -7
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import{Server as je}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Ge}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as qe,ListResourcesRequestSchema as We,ListToolsRequestSchema as _e,ReadResourceRequestSchema as Ke}from"@modelcontextprotocol/sdk/types.js";var v=[{name:"inertiaUpload",group:"data-entry",importPath:"@godxjp/ui/inertia",tagline:"Bridge Inertia multipart visits to Upload progress, cancellation, retry and error recovery.",props:[{name:"send",type:"(file: File, callbacks: InertiaUploadCallbacks) => void",description:"Start a router.post visit with the supplied callbacks and file."},{name:"errorMessage",type:"string",description:"Localized fallback for network or HTTP failures."}],usage:["Pass the returned callback to Upload.onUpload. Spread callbacks into router.post options; they enable multipart and independent async requests.","Keep failed items in controlled value so the user can retry; remove only done items when server props provide the saved list."],example:`import { inertiaUpload } from "@godxjp/ui/inertia";
3
3
  const upload = inertiaUpload((file, callbacks) => router.post(endpoint, { file }, callbacks), t("Upload failed"));
4
- <Upload onUpload={upload} />;`,docPath:"FORMS.md",storyPath:"data-entry/Upload.stories.tsx",rules:[1],related:["Upload","FormRoot"]},{name:"FormRoot",tagline:"Client or server-adapted forms with shared layout, validation, submission, reset and error recovery.",props:[{name:"form",type:"UseFormReturn<T>",description:"RHF form returned by useZodForm; use either form or adapter."},{name:"adapter",type:"FormStateAdapter",description:"External store with getValue/setValue/getError/getValues/isSubmitting and optional reset."},{name:"onSubmit",type:"(values: T) => void | Promise<void>",description:"Validated submit callback."},{name:"onSubmitFailed",type:"(errors: FieldErrors<T>) => void",description:"Validation failure callback."},{name:"onSubmitError",type:"(error: unknown) => void",description:"Handle rejected submission."},{name:"onReset",type:"() => void",description:"Called after values reset."},{name:"scrollToFirstError",type:"boolean",description:"Scroll the first invalid field into view."},{name:"disabled",type:"boolean",description:"Disable field mutations."},{name:"layout",type:"FormLayoutProp",description:"Shared Form layout."},{name:"labelWidth",type:"WidthProp",description:"Shared label width."},{name:"controlWidth",type:"WidthProp",description:"Shared control width."},{name:"labelAlign",type:'"start" | "end"',description:"Shared label alignment."},{name:"collapseBelow",type:"BreakpointProp | false",description:"Responsive stacking breakpoint."},{name:"density",type:"DensityProp",description:"Control density."},{name:"errors",type:"ErrorBagProp",description:"Server error bag."},{name:"requiredMark",type:'boolean | "optional"',description:"Required/optional marking policy."},{name:"columns",type:"ResponsiveGridColumnsProp",description:"Responsive field grid rendered inside the form element."}],example:`import { FormRoot, FormFieldControl, useZodForm } from "@godxjp/ui/form";
4
+ <Upload onUpload={upload} />;`,docPath:"FORMS.md",storyPath:"data-entry/Upload.stories.tsx",rules:[1],related:["Upload","FormRoot"]},{name:"FormRoot",tagline:"Client or server-adapted forms with shared layout, validation, submission, reset and error recovery.",props:[{name:"form",type:"UseFormReturn<T>",description:"RHF form returned by useZodForm; use either form or adapter."},{name:"adapter",type:"FormStateAdapter",description:"External store with getValue/setValue/getError/getValues/isSubmitting and optional reset."},{name:"onSubmit",type:"(values: T) => void | Promise<void>",description:"Validated submit callback."},{name:"onSubmitFailed",type:"(errors: FieldErrors<T>) => void",description:"Validation failure callback."},{name:"onSubmitError",type:"(error: unknown) => void",description:"Handle rejected submission."},{name:"submitFailedMessage",type:"ReactNode | false",description:"Banner shown when onSubmit rejects. Default: localized dataEntry.form.submitFailed text; a node replaces it; false never shows it. Skipped by default for a validation rejection (400/422) once the `errors` bag holds a message."},{name:"onReset",type:"() => void",description:"Called after values reset."},{name:"scrollToFirstError",type:"boolean",description:"Scroll the first invalid field into view."},{name:"disabled",type:"boolean",description:"Disable field mutations."},{name:"layout",type:"FormLayoutProp",description:"Shared Form layout."},{name:"labelWidth",type:"WidthProp",description:"Shared label width."},{name:"controlWidth",type:"WidthProp",description:"Shared control width."},{name:"labelAlign",type:'"start" | "end"',description:"Shared label alignment."},{name:"collapseBelow",type:"BreakpointProp | false",description:"Responsive stacking breakpoint."},{name:"density",type:"DensityProp",description:"Control density."},{name:"errors",type:"ErrorBagProp",description:"Server error bag."},{name:"requiredMark",type:'boolean | "optional"',description:"Required/optional marking policy."},{name:"columns",type:"ResponsiveGridColumnsProp",description:"Responsive field grid rendered inside the form element."}],example:`import { FormRoot, FormFieldControl, useZodForm } from "@godxjp/ui/form";
5
5
  import { Input } from "@godxjp/ui/data-entry";
6
6
  import { z } from "zod";
7
7
  const schema = z.object({ email: z.string().email() });
8
8
  // Inside your component:
9
9
  const form = useZodForm(schema, { defaultValues: { email: "" } });
10
- <FormRoot form={form} onSubmit={save} layout="horizontal"><FormFieldControl name="email" label="Email">{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl></FormRoot>;`,docPath:"docs/data-entry/form-root.tsx",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"FormFieldControl",tagline:"Bind a typed field to RHF or a server adapter, with shared FormField presentation and validation.",props:[{name:"name",type:"FieldPath<T>",description:"Typed field path, including nested list rows."},{name:"label",type:"React.ReactNode",description:"Visible field label."},{name:"dependencies",type:"FieldPath<T>[]",description:"Dependent fields that trigger revalidation."},{name:"getValueFromEvent",type:"(...args: unknown[]) => unknown",description:"Extract the control value."},{name:"normalize",type:"(value: unknown, previous: unknown) => unknown",description:"Normalize before storage."},{name:"preserve",type:"boolean",description:"Keep value after unmount, default true."},{name:"help",type:"React.ReactNode",description:"Override validation error text."},{name:"disabled",type:"boolean",description:"Field-level disabled state."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation feedback state."},{name:"hasFeedback",type:"boolean",description:"Show accessible validation feedback."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback icon/content."},{name:"children",type:"(field) => React.ReactNode",description:"Render a godx-ui control bound to the field."},{name:"id",type:"string",description:"Optional DOM identity; defaults to a unique id even across sibling forms."},{name:"layout",type:"FormLayoutProp",description:"Per-field layout override."},{name:"labelWidth",type:"WidthProp",description:"Per-field label width."},{name:"controlWidth",type:"WidthProp",description:"Per-field control width."},{name:"labelAddon",type:"React.ReactNode",description:"Label help or action. In a horizontal/inline layout it wraps under the label inside the label column when it does not fit beside it."},{name:"colSpan",type:"number",description:"Grid column span."}],example:`import { FormFieldControl } from "@godxjp/ui/form";
10
+ <FormRoot form={form} onSubmit={save} layout="horizontal"><FormFieldControl name="email" label="Email">{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl></FormRoot>;`,docPath:"docs/data-entry/form-root.tsx",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state.",'SUBMIT-FAILED BANNER: when `onSubmit` rejects, FormRoot shows a destructive banner (`submitFailedMessage`, default the localized `dataEntry.form.submitFailed`). It is SKIPPED for a validation rejection (`classifyQueryError(error).category === "validation"`: 400/422) once the `errors` bag holds at least one message \u2014 the fields / `<FormErrors />` show it, as antd shows field errors instead of a form banner \u2014 and stays skipped for that failure if the app later clears the bag. A validation rejection with an empty or message-less bag, and every 5xx / network / unknown rejection, still shows it, so a failure is never silently swallowed. Errors mapped with react-hook-form `setError` instead of `errors` are not detected: pass `submitFailedMessage={false}` there. `submitFailedMessage={false}` never shows the banner; a node replaces its text.','CANONICAL SERVER-VALIDATION FORM (gh#698) \u2014 a 422 renders EXACTLY ONCE: `const m = useMutation({ mutationFn: save }); <FormRoot form={form} onSubmit={(v) => m.mutateAsync(v)} errors={serverErrors(m.error)}><FormErrors /><AlertMutationFeedback mutation={m} /><FormFieldControl name="code" label="Code">{(field) => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl></FormRoot>` (`serverErrors` = your API client\'s mapper from its error to the Laravel-style `{ key: string[] }` bag). Each message appears once: under its field (claimed key) or in `<FormErrors />` (unclaimed key); `AlertMutationFeedback` skips the validation error (gh#690) and FormRoot shows no `submitFailed` banner. A 5xx / network rejection still shows both the FormRoot banner and the AlertMutationFeedback alert \u2014 pass `submitFailedMessage={false}` to keep only the latter.',"DON'T hand-guard the banner or wrap `mutateAsync` in try/catch just to hide a 422 \u2014 pass the bag to `errors` and the form renders it once."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"FormFieldControl",tagline:"Bind a typed field to RHF or a server adapter, with shared FormField presentation and validation.",props:[{name:"name",type:"FieldPath<T>",description:"Typed field path, including nested list rows."},{name:"label",type:"React.ReactNode",description:"Visible field label."},{name:"dependencies",type:"FieldPath<T>[]",description:"Dependent fields that trigger revalidation."},{name:"getValueFromEvent",type:"(...args: unknown[]) => unknown",description:"Extract the control value."},{name:"normalize",type:"(value: unknown, previous: unknown) => unknown",description:"Normalize before storage."},{name:"preserve",type:"boolean",description:"Keep value after unmount, default true."},{name:"help",type:"React.ReactNode",description:"Override validation error text."},{name:"disabled",type:"boolean",description:"Field-level disabled state."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation feedback state."},{name:"hasFeedback",type:"boolean",description:"Show accessible validation feedback."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback icon/content."},{name:"children",type:"(field) => React.ReactNode",description:"Render a godx-ui control bound to the field."},{name:"id",type:"string",description:"Optional DOM identity; defaults to a unique id even across sibling forms."},{name:"layout",type:"FormLayoutProp",description:"Per-field layout override."},{name:"labelWidth",type:"WidthProp",description:"Per-field label width."},{name:"controlWidth",type:"WidthProp",description:"Per-field control width."},{name:"labelAddon",type:"React.ReactNode",description:"Label help or action. In a horizontal/inline layout it wraps under the label inside the label column when it does not fit beside it."},{name:"colSpan",type:"number",description:"Grid column span."}],example:`import { FormFieldControl } from "@godxjp/ui/form";
11
11
  import { Input } from "@godxjp/ui/data-entry";
12
- <FormFieldControl name="email" label="Email" required>{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl>;`,docPath:"docs/data-entry/form-root.tsx",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"FormFieldArray",tagline:"Dynamic typed field collections with stable keys, nested validation and append/remove/reorder operations.",props:[{name:"name",type:"FieldArrayPath<T>",description:"Array field path in the surrounding RHF FormRoot."},{name:"children",type:"(collection) => React.ReactNode",description:"Receives fields with stable key/name/index, append/prepend/insert/remove/move/swap/replace, array error and disabled state."}],example:'import { FormFieldArray, FormFieldControl } from "@godxjp/ui/form";\nimport { Input } from "@godxjp/ui/data-entry";\n<FormFieldArray name="contacts">{({fields}) => fields.map(row => <FormFieldControl key={row.key} name={`${row.name}.email`} label="Email">{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl>)}</FormFieldArray>;',docPath:"docs/FORMS.md",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"useZodForm",tagline:"Create a typed React Hook Form instance with Zod validation; reuse reset, setValue, trigger, formState and field errors.",props:[{name:"schema",type:"z.ZodType<T>",description:"Zod schema, including async refinements."},{name:"options",type:"UseZodFormOptionsProp<T>",description:"RHF options such as defaultValues, mode, reValidateMode, criteriaMode and shouldUnregister."}],example:`import { useZodForm } from "@godxjp/ui/form";
12
+ <FormFieldControl name="email" label="Email" required>{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl>;`,docPath:"docs/data-entry/form-root.tsx",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state.",'RENDER-PROP BAG: `{ id, name, value, onChange, onValueChange, onBlur, ref, disabled? }`. `ref` is `RefCallback<HTMLElement>` (gh#698), so `{...field}` spreads onto Input, Textarea, Select, NumberInput and DatePicker with NO cast \u2014 never cast or drop the ref (react-hook-form focuses the first invalid field through it). `value` is `unknown`: narrow it per control (`String(field.value ?? "")`, `typeof field.value === "number" ? field.value : null`).',"SERVER VALIDATION: a `FormField name` under `FormRoot errors={\u2026}` claims its bag key, so the 422 message renders once under the field \u2014 see FormRoot's canonical server-validation composition (FormRoot + FormErrors + AlertMutationFeedback renders a 422 exactly once)."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"FormFieldArray",tagline:"Dynamic typed field collections with stable keys, nested validation and append/remove/reorder operations.",props:[{name:"name",type:"FieldArrayPath<T>",description:"Array field path in the surrounding RHF FormRoot."},{name:"children",type:"(collection) => React.ReactNode",description:"Receives fields with stable key/name/index, append/prepend/insert/remove/move/swap/replace, array error and disabled state."}],example:'import { FormFieldArray, FormFieldControl } from "@godxjp/ui/form";\nimport { Input } from "@godxjp/ui/data-entry";\n<FormFieldArray name="contacts">{({fields}) => fields.map(row => <FormFieldControl key={row.key} name={`${row.name}.email`} label="Email">{field => <Input {...field} value={String(field.value ?? "")} />}</FormFieldControl>)}</FormFieldArray>;',docPath:"docs/FORMS.md",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"useZodForm",tagline:"Create a typed React Hook Form instance with Zod validation; reuse reset, setValue, trigger, formState and field errors.",props:[{name:"schema",type:"z.ZodType<T>",description:"Zod schema, including async refinements."},{name:"options",type:"UseZodFormOptionsProp<T>",description:"RHF options such as defaultValues, mode, reValidateMode, criteriaMode and shouldUnregister."}],example:`import { useZodForm } from "@godxjp/ui/form";
13
13
  import { z } from "zod";
14
14
  const form = useZodForm(z.object({email: z.string().email()}), {defaultValues: {email: ""}, mode: "onBlur"});`,docPath:"docs/FORMS.md",group:"data-entry",importPath:"@godxjp/ui/form",storyPath:"data-entry/Form.stories.tsx",rules:[23,31],usage:["Use inside the documented form composition; do not nest native form elements.","Use godx-ui controls and preserve field names, errors and disabled state."],useCases:["Validated settings forms","Nested repeating data entry"],related:["Form","FormField","FormRoot","FormFieldControl"]},{name:"CardBar",group:"data-display",tagline:"Inline card toolbar with scoped inset and divider edges.",props:[{name:"pad",type:"PadProp",description:"Instance padding on the token scale."},{name:"padRaw",type:"PadRawProp",description:"Measured padding escape."},{name:"gap",type:"GapProp",description:"Spacing between main and extra slots."},{name:"surface",type:'"muted"',description:"Optional muted ground."},{name:"border",type:'"none" | "block-start" | "block-end" | "both"',description:"Override positional divider edges for stacked bars."}],storyPath:"data-display/Card.stories.tsx",docPath:"docs/data-display/card/index.tsx",rules:[9],usage:["Compose inside Card. Unset border follows its position; explicit border prevents double rules in stacked bars."],related:["Card","Flex"],useCases:["Composer tool strips and view tabs."],example:'<Card><CardBar pad={{block:2,inline:3}} border="block-start">Tools</CardBar></Card>'},{name:"TimeRangePicker",group:"data-entry",tagline:"Ordered time range with optional endpoints and canonical native fields.",props:[{name:"value",type:"[string,string]",description:"Controlled canonical times."},{name:"defaultValue",type:"[string,string]",description:"Uncontrolled initial range."},{name:"onValueChange",type:"(value: [string,string]) => void",description:"Reports edited range."},{name:"order",type:"boolean",description:"Sort times automatically; false allows overnight ranges."},{name:"allowEmpty",type:"[boolean,boolean]",description:"Permitted empty endpoints, default [true,true]."},{name:"name",type:"string",description:"Native names are name_from and name_to."},{name:"format",type:"string",description:"TimePicker display format."},{name:"disabledTime",type:"TimePickerDisabledTimeProp",description:"Shared time constraints."},{name:"showSeconds",type:"boolean",description:"Enable second precision."},{name:"minuteStep",type:"number",description:"Minute column step."},{name:"hourStep",type:"number",description:"Hour column step."},{name:"secondStep",type:"number",description:"Second column step."},{name:"allowClear",type:"boolean",description:"Permit clear only for an endpoint also allowed empty."}],usage:["Use for a start/end time pair. Set order=false for overnight intervals.","Each endpoint follows the one-trailing-icon rule; allowEmpty=false suppresses its clear action."],useCases:["Shift scheduling and reception hours."],related:["TimePicker","DatePicker"],example:`import { TimeRangePicker } from "@godxjp/ui/data-entry";
15
15
  <TimeRangePicker aria-label="Shift" defaultValue={["09:00", "18:00"]} />`,storyPath:"data-entry/time-range-picker.tsx",docPath:"docs/data-entry/time-range-picker.tsx",rules:[9]},{name:"VisuallyHidden",group:"general",tagline:"Accessible supporting text without a visible layout box.",props:[{name:"children",type:"ReactNode",description:"Text available to assistive technology."}],example:"<VisuallyHidden>Unread</VisuallyHidden>",docPath:"docs/general/typography.tsx",storyPath:"general/typography.tsx",rules:[],usage:["Use for supplementary accessible labels. Do not hide controls that remain focusable."]},{name:"RangeTimeline",group:"data-display",tagline:"Horizontal intervals with token-owned geometry and optional endpoint editing.",props:[{name:"label",type:"string",required:!0,description:"Localized accessible name for the scrollable schedule."},{name:"columns",type:"{ label: string; units: number }[]",required:!0,description:"Positive unit counts determine proportional column widths. Column count determines the minimum canvas width, so coarse grouping zooms out."},{name:"bands",type:"{ label: string; units: number }[]",description:"Optional grouped axis labels above the ticks, for example months above days. Counts use the same units as columns and cover the same range."},{name:"rows",type:"RangeTimelineRow[]",required:!0,description:"Each row has id, label, inclusive start/end unit offsets, and localized startLabel/endLabel including current values."},{name:"today",type:"number | null",description:"Optional current unit marker."},{name:"onRangeChange",type:'(id: string, edge: "start" | "end", delta: number) => void',description:"Committed endpoint movement in units. Omit for read-only. Dates remain consumer data; do not replace true anchors with clipped positions."}],example:'<RangeTimeline label="Schedule" columns={[{ label: "Week", units: 7 }]} rows={[{ id: "task", label: "Task", start: 0, end: 6, startLabel: "Start: day 1", endLabel: "End: day 7" }]} />',docPath:"docs/data-display/timeline.tsx",storyPath:"data-display/timeline.tsx",rules:[],usage:["Provide a precise non-drag editor in each row label when enabling changes. Clipped endpoints and short intervals omit grips; labels and their editors remain available.","Use TimelineGrid for time-of-day columns; RangeTimeline is a horizontal range axis."]},{name:"PageContainer",group:"layout",tagline:"Mandatory page shell \u2014 EVERY page wraps its content in PageContainer (title/subtitle/extra/footer/breadcrumb).",props:[{name:"footerPad",type:"PadProp",description:"Instance footer inset; omitted preserves the theme."},{name:"toolbarPad",type:"PadProp",description:"Instance toolbar inset; omitted preserves the theme."},{name:"breadcrumbLabel",type:"string",description:"T\xEAn kh\u1EA3 truy c\u1EADp c\u1EE7a landmark <nav> breadcrumb. M\u1EB7c \u0111\u1ECBnh l\xE0 chu\u1ED7i Breadcrumb \u0111\xE3 d\u1ECBch."},{name:"title",type:"string",required:!0,description:"Page heading rendered as <h1>."},{name:"subtitle",type:"string",description:"Secondary line beneath the title."},{name:"status",type:"ReactNode",description:'Status/meta band beside the title (StatusBadge, environment tag, "updated \u2026" meta). Sits on the title line at the token-owned --page-header-status-gap and wraps UNDER the title on compact viewports. Part of the canonical page-header contract \u2014 never hand-lay a badge next to the <h1>.'},{name:"extra",type:"ReactNode",description:"Action buttons / controls rendered right of the title row."},{name:"toolbar",type:"ReactNode",description:'FIXED chrome band between the page header and the body \u2014 a filter strip, a status bar, a "channel workflow" rail. It is a SIBLING of the body, not content inside it: with `fill` the body is the scroll viewport, so the band is `flex: none` OUTSIDE the scroller and never scrolls away or gets slid under. Shares the page gutters and the `measure` cap with the header and the body (the three bands line up on both edges), goes full-bleed under variant="flush" (wrap padded strips in PageContainer.Inset), and renders NOTHING when omitted \u2014 no wrapper, no gap. It sits FLUSH against the header above and the body below: chrome is attached, not a third page section floating between two voids, so the band cancels the container gap from itself and its ONLY breathing room is its own inset.'},{name:"footer",type:"ReactNode",description:'Content area pinned below the page body. The BAND (its top rule and, with `stickyFooter`, its background) always spans the page; its CONTENT is laid out in the same column as the header and body (gh#682). With a cap \u2014 `measure="narrow" | "medium"` or a service-wide `--app-shell-page-max-width` inside AppShell \u2014 an end-aligned Save/Cancel bar ends on the body\'s end edge and a full-row composer (`<Flex grow>`) spans exactly the body\'s content column; with no cap it is unchanged. The footer\'s children stay its direct children (no wrapper element), and an instance `footerPad` end inset composes with the cap. Never add a call-site `max-w-*` to line it up.'},{name:"children",type:"ReactNode",description:'The page sections. Every direct child of the body is spaced from the previous one by --page-body-gap (the section step): drop your Cards straight in \u2014 do NOT wrap them in a Flex to space them, do NOT add gap-*/mt-*. Group items INSIDE a section with <Flex direction="col" gap> or <ResponsiveGrid>.'},{name:"breadcrumb",type:"BreadcrumbItemProp[]",description:"Ordered trail of { label, to? } segments above the title."},{name:"breadcrumbAriaLabel",type:"string",description:`Override the breadcrumb nav landmark's accessible name (defaults to a localized "Breadcrumb"). Required when more than one PageContainer (each with its own breadcrumb) renders on the same page/view \u2014 two nav landmarks sharing one name/role fail landmark-unique.`},{name:"variant",type:'"default" | "narrow" | "flush" | "ghost"',defaultValue:'"default"',description:"Page shell layout; flush removes padding for full-bleed content."},{name:"density",type:'"compact" | "default" | "comfortable"',defaultValue:'"default"',description:"Spacing density across the page subtree."},{name:"preset",type:'"default" | "admin-collection"',defaultValue:'"default"',description:'Whole-page semantic composition. "admin-collection" owns header-to-toolbar rhythm, collection search measure, control height and table density for the subtree through themeable tokens.'},{name:"headerLayout",type:'"stack" | "responsive-inline"',defaultValue:'"stack"',description:'How the title band and `extra` share the header row BELOW the 640px step. "stack" (default) drops `extra` onto its own full-width line under the subtitle. "responsive-inline" keeps it beside the title at the token-owned --page-header-extra-measure (11rem) and lets the title/subtitle wrap \u2014 use it for ONE compact control (a search field, a single primary action) that must stay on the title row at 390px. At >=640px the two arrangements are identical.'},{name:"headerScale",type:'"document" | "chrome"',defaultValue:'"document"',description:"What the page's top row IS, which decides the `<h1>`'s type step. \"document\" (default) = the row is the page's TITLE (a record, a form, a collection, a report): --page-title-font-size (h1, 20px) with the existing responsive step down at 720px; no attribute is emitted, so an existing page is byte-identical. \"chrome\" = the row is the surface's own furniture \u2014 a chat channel name, a mail subject line, an IDE tab \u2014 naming the thing the user is already INSIDE instead of announcing a document; the h1 takes --page-title-font-size-chrome (--heading-h3 = the 14px body step) at EVERY width, including below 720px where the document-scale step would otherwise pull it back UP. The heading stays an `<h1>` either way \u2014 this moves the type step only, never the element, so the screen-reader outline is untouched."},{name:"measure",type:'"default" | "narrow" | "medium"',defaultValue:'"default"',description:'Bounded page MEASURE shared by the header AND the body \u2014 a third axis, ORTHOGONAL to `variant` (chrome) and `headerLayout` (arrangement). "narrow" (--page-measure-narrow, 42rem outer \u2192 624px visible surface) and "medium" (--page-measure-medium, 48rem outer \u2192 720px visible surface) cap BOTH bands, so a header `extra` action ends flush with the body surface instead of stranded at the page edge \u2014 unlike variant="narrow", which caps only the body. The package-owned page gutters sit INSIDE the cap, and it is a max, so a 390px viewport stays fluid (358px surface at the 16px compact gutter). The footer BAND is intentionally not capped (its border/background is page chrome when `stickyFooter` pins it), but its CONTENT follows the same measure (gh#682): an end-aligned footer action ends flush with the body too, instead of stranded at the band edge. Inside AppShell the page is fluid by default; a service-wide `--app-shell-page-max-width` caps the same header/toolbar/body bands and the footer CONTENT (never the footer band), and `measure` overrides it on the page that sets it.'},{name:"stickyFooter",type:"boolean",defaultValue:"false",description:'Pin footer to viewport bottom on scroll \u2014 pairs with variant="narrow".'},{name:"footerReveal",type:'"always" | "onScroll"',defaultValue:'"always"',description:'When the footer is sticky, control WHEN it shows. "always" keeps it pinned the whole time; "onScroll" hides it until the header scrolls out of view then slides it up \u2014 the standard edit/create save bar. Stays mounted (no reflow \u2192 no jitter).'},{name:"fill",type:"boolean",defaultValue:"false",description:"Grow the body to fill the remaining shell height. Default false = top-packed, content-height (short pages leave no stretched void). Enable for a full-height DataTable, SplitPane, or a chat surface."},{name:"headerLoading",type:"boolean",defaultValue:"false",description:"Skeletonise the TITLE BAND only while the page's record resolves (title/subtitle placeholders + aria-busy; the <h1> stays in the outline with an sr-only accessible name). Breadcrumbs and `extra` stay live \u2014 they come from the route, not the record. This is not a page-wide loading flag; use DataState for the body."},{name:"linkComponent",type:"React.ElementType",description:"Link component used for breadcrumb / header links (e.g. an Inertia or React Router `Link`). Defaults to a native `<a>`."}],usage:["DO: Always wrap every page's content in PageContainer \u2014 it is the mandatory page shell. Pass `title` (required, rendered as `<h1>`) for every page; omitting it leaves the page without an accessible heading.","CANONICAL PAGE-HEADER CONTRACT: PageContainer's embedded header IS the DXS `PageHeader` \u2014 there is deliberately NO separate PageHeader export, so the page header cannot be re-created or nested. It owns breadcrumbs (`breadcrumb`), title (`title`), subtitle/description (`subtitle`), status/meta (`status`), actions (`extra`) and responsive overflow (`headerLayout` + `measure`). Loading/error/denied are compositions of siblings, never hand-rolls: skeleton `title`/`subtitle` content or `SkeletonDetail` body while a detail loads; `ErrorSurface` (from @godxjp/ui/layout) REPLACES the page for denied (403) / not-found (404) / failed (5xx) whole-page states; `Alert.QueryError` / `DataState` own an in-body query failure.","DO: Use the `extra` prop (not a sibling div, not a wrapper) for action buttons or controls that sit right of the title row \u2014 e.g. `extra={<Button>\u65B0\u898F\u4F5C\u6210</Button>}`. Use the `footer` prop for a pinned action bar below the body (e.g. Save/Cancel on a form page); combine with `stickyFooter` to pin it to the viewport bottom on scroll.","DO: Use `toolbar` for any FIXED strip that belongs between the page header and the page content \u2014 a filter/segment bar, a status or connection band, a list's bulk-action rail. DON'T put it in `children` (with `fill` the body is the scroller, so it scrolls out of sight) and NEVER hand-lay it with `position: sticky` / a `top-0 z-10` wrapper at the call site: page chrome is the shell's job, and a sticky strip still lets content flow underneath it \u2014 the half-sliced row every hand-rolled version produces. It stays out of the scroll viewport, inherits the page gutters and the `measure` cap so it lines up with the title and the body, and is entirely absent from the DOM when the prop is omitted.","DO: Know the `toolbar` band draws NO bottom rule by default \u2014 `--page-toolbar-divider` is unset and falls back to `--page-header-divider` (itself `none`), so a service that opts into the page-header divider gets a consistent band rule in ONE declaration, and `--page-toolbar-divider: none` silences just the band. `variant='ghost'` keeps both quiet unless a theme opts one in explicitly, which it then lets through.","DO: Give the `toolbar` band its own SURFACE from the theme when the design separates it from the page ground \u2014 `--page-toolbar-background: hsl(var(--card));` declared once (`:root` or a scoped `[data-tenant]`) paints the whole band, page gutters and `measure` cap included, and stays re-themeable per tenant. It is the `background` shorthand, so a gradient works too. The default is `transparent`, i.e. the band looks exactly as it did before the knob existed (rule #44).","DO: Set `--page-toolbar-pad-block` in the SAME theme declaration that paints or rules the band. It is the band's ONLY breathing room: the band sits FLUSH against the header and the body (chrome is attached \u2014 a ruled, painted band adrift in two 16px voids divides nothing), so there is no outside space to tune. The default is `0` and stays `0`: a transparent band is not a surface and has no inside for an inset to breathe, and under `fill` every pixel of band height comes straight off the scroll viewport the slot exists to protect. Once the band is painted or ruled it DOES have an inside, and `--page-toolbar-pad-block: var(--space-2)` is where that inset belongs. The CALL SITE never sets it \u2014 a strip padded at the call site pads only the strip, not the band.","DO: Silence the `footer` band's top rule with `--page-footer-divider: none` when the footer content already carries its own frame \u2014 a chat composer is a bordered Card, and the shell's full-width rule then lands directly above it as a SECOND line (a pixel diff against a consumer chat design caught a 100%-wide rule at y=701 the design does not have). This is the ONE page-chrome divider whose default is a RULE rather than silence, deliberately: `footer` is the shared slot a form's Save/Cancel bar lands in, where that line separates the actions from the content. Unset is byte-identical to the literal the rule used to hard-code. All three page bands are now one contract: `--page-header-divider` / `--page-toolbar-divider` / `--page-footer-divider`, each read at the CALL SITE with a fallback, none of them a `border-*` utility at the call site.","DON'T: Style the `toolbar` band from the call site. `toolbar={<div className='bg-card py-1.5'>\u2026</div>}` is hand-laid page chrome: the utility paints the STRIP, not the band, so it stops at the content box instead of running the full page width (and full-bleed under `variant='flush'`); it is invisible to per-tenant theming; and it puts geometry the shell owns back into the app. The band's ground, inset and rule are `--page-toolbar-background` / `--page-toolbar-pad-block` / `--page-toolbar-divider` \u2014 three theme declarations, zero call-site classes.","DO: Set `headerScale='chrome'` when the page's top row is CHROME rather than a document title \u2014 a chat channel header, a mail thread's subject line, an IDE tab, a conversation view. The `<h1>` drops to the body type step (--page-title-font-size-chrome) at every width, so the header band stops eating the height the content needs: a consumer chat page measured a 61px band with a 24px channel name where the design asked for ~40px at the `sm` step. Pair it with `variant='ghost'` for the full quiet chrome header \u2014 ghost drops the header's bottom pad and lets no divider inherit in \u2014 and with `fill` + `toolbar` + `footer`/`stickyFooter` for the canonical chat surface. The heading stays an `<h1>`: this is a type step, never a heading-level downgrade. The same attribute also drops the page's top padding to `--page-pad-block-start-chrome` (0), so the band sits ON the frame instead of floating in a document's top margin \u2014 four consequences of ONE fact (this row is furniture), not four props a call site has to keep in lockstep: the subtitle drops to `--page-subtitle-font-size-chrome` (~11px) so the caption under a channel name stops matching the name's own size, and the `extra` cluster centres on the bar instead of top-packing against a heading that is no longer tall. If a design wants its chrome inset or a different caption step, the theme retunes those tokens once; never pad, negative-margin or `self-center` the page at the call site.","DON'T: Reach for `headerScale='chrome'` just because a title \"looks too big\" on an ordinary document page (a record detail, a form, a collection, a report) \u2014 the page title is the document's headline and the h1 step is the system's answer for it; shrinking it there only breaks the type rhythm the rest of the page is measured against. And NEVER override `--page-title-font-size` (or put a `text-sm` / `text-base` utility on the title) at the call site to fake it: that re-themes every page in the subtree, is invisible to the 720px responsive step, and puts page-chrome geometry back in the app. If a service wants a different chrome step, it retunes `--page-title-font-size-chrome` (or `--page-subtitle-font-size-chrome`) once in its theme. Same for the header actions: never hang `self-center` / `items-center` on the node you pass to `extra` to fix an off-centre icon row \u2014 that aligns one call site's box while every other chrome page keeps the document's top-packed row.","DO: Use `variant='flush'` when the page body contains a full-bleed component like DataTable. Inside a flush container, wrap any padded strips (Toolbar, intro text) in `<PageContainer.Inset>` to align them with the header. Never add manual `px-*` or `p-*` padding to compensate \u2014 use PageContainer.Inset.","DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.","DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.","DO: Use `preset='admin-collection'` for canonical Admin list pages. It owns the toolbar/search/control/table composition once at PageContainer level; do not repeat widths, heights, cell padding or media queries on child fields and rows.","DO: Use `subtitle` (not `description`) and `extra` (not `actions`) \u2014 those are the canonical page-header names. If you see `description` / `actions` in old code, migrate them.","DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`; or a page whose ENTIRE body is a `variant='page'` EmptyState, which then takes that height and centres in it (a zero-state that is the whole page is the one short page that must NOT top-pack \u2014 see EmptyState). DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",'DO: Reach for `headerLayout="responsive-inline"` when a SINGLE compact header control (a member search, one primary action) must stay beside the title at 390px instead of wrapping under the subtitle. Its measure is the token `--page-header-extra-measure` (11rem) \u2014 never a consumer `w-[176px]` or a media query in app CSS. Keep the default `stack` when `extra` holds a toolbar of several buttons; squeezing those into the compact measure only makes them wrap in a narrower box.',"DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title. `variant='ghost'` does NOT overrule the token: it blocks a divider from INHERITING in (so an unset token stays silent) but an explicit `--page-header-divider` still draws on a ghost page \u2014 the same shape as `--page-toolbar-divider` on the band. Ghost's real quiet half is the header's bottom pad, which it drops.",'DO: Bound a readable/feed page with `measure="medium"` (720px visible surface) or `measure="narrow"` (624px) \u2014 NEVER a page-local `max-w-[720px]`, a wrapper div, or a consumer CSS variable override. `measure` caps the HEADER and the BODY together, which is the whole point: with `variant="narrow"` the header action stays out at the page edge while the body is 624px, so the action and the card do not share an end edge. Retune the presets once in a service theme via `--page-measure-narrow` / `--page-measure-medium`.','DO: Compose the axes \u2014 `variant="ghost" measure="medium" headerLayout="responsive-inline"` is the canonical quiet notification/inbox feed: ghost owns the quiet header rhythm (no divider, no header bottom pad, tighter title\u2192body gap), `measure` owns the shared 720px measure, `headerLayout` keeps one compact control on the title row at 390px. They are independent props precisely so chrome and measure are no longer one variant axis. DON\'T stack `variant="narrow"` on top of `measure` \u2014 the measure rule simply wins on the body (verified in Chromium: variant="narrow" + measure="medium" resolves the body to 768px, not the intersection), so the `variant="narrow"` is dead weight that only misleads the next reader. `variant="narrow"` is the legacy body-only cap; `measure="narrow"` is the same 624px surface with the header included.'],useCases:["A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.","A detail / edit form page where the footer holds Save and Cancel buttons \u2014 use `footer={<Flex direction='row' justify='between' fill><Button variant='outline'>\u524A\u9664</Button><Button>\u4FDD\u5B58</Button></Flex>}` with `stickyFooter` + `footerReveal='onScroll'` so the save bar slides up only once the header (and its actions) scroll out of view \u2014 the canonical edit/create pattern.","A settings or narrow-form page (e.g. account profile, entity configuration) where `variant='narrow'` constrains content to a readable column width and `stickyFooter` pins the submit bar.","A dashboard page with KPI cards and chart sections \u2014 use `variant='default'` with `children={<Flex direction='col' gap='lg'>\u2026</Flex>}` to vertically stack multiple Card/StatCard sections beneath the page title.","Any deep-nav page in a multi-level admin (e.g. Accounting > Ledger > Journal Entry #42) where a 3-segment breadcrumb trail provides back-navigation without browser history dependence.","A high-density data reconciliation page where an analyst needs to see maximum rows \u2014 use `density='compact'` to tighten all spacing across the DataTable, Toolbar, and controls in a single prop.","A chat / messaging detail page where the message list should scroll inside the page and the composer stays pinned at the bottom \u2014 use `fill` so the body occupies the full shell height, with `footer={<Composer/>}` + `stickyFooter`. Without `fill` the page would top-pack and the composer would float mid-screen on a tall viewport.","A Slack-like chat channel, a mail thread, or an IDE-style tab view whose top row is the SURFACE's name rather than a document title \u2014 `headerScale='chrome'` (usually with `variant='ghost'`) puts the `<h1>` on the body type step so the header reads as a channel label and the band collapses to roughly the height of one control row, leaving the vertical space to the conversation.","A chat channel page where a fixed band (channel workflow / pinned-message / connection status) must sit between the page header and the scrolling transcript \u2014 `toolbar={<Toolbar>\u2026</Toolbar>}` with `fill` + `footer={<Composer/>}` + `stickyFooter`. The band is outside the scroller, so the transcript never travels under it and the composer stays pinned; a collection page uses the same slot for its filter strip above a full-bleed DataTable (`variant='flush'`)."],related:["PageContainer.Inset \u2014 use INSIDE a `variant='flush'` PageContainer to re-introduce horizontal padding for strips like Toolbar or intro text that should align with the page header, while the surrounding DataTable stays full-bleed. Not a standalone page shell.","PageContainer \u2014 always use PageContainer for new pages; it supports `children`, `toolbar`, `footer`, `variant`, `density`, `stickyFooter`, and `fill`. Legacy code using the old prop names (`description` \u2192 `subtitle`, `actions` \u2192 `extra`) should be migrated to PageContainer.","AppShell \u2014 the outer shell that owns the sidebar/topbar layout grid; PageContainer lives inside AppShell's `children` slot. Do not put AppShell inside PageContainer \u2014 the nesting order is AppShell \u2192 PageContainer.","SplitPane \u2014 use instead of PageContainer when the page body needs a fixed-width aside panel alongside main content (e.g. a detail drawer next to a list). PageContainer has no aside slot; SplitPane fills that gap and can itself be placed inside PageContainer's children."],example:`import { PageContainer, Flex } from "@godxjp/ui/layout";
@@ -873,7 +873,7 @@ import remarkGfm from "remark-gfm";
873
873
 
874
874
  <FormField id="coupon-name" label="\u30AF\u30FC\u30DD\u30F3\u540D" required error={errors.name} helper="\u6700\u592750\u6587\u5B57">
875
875
  <Input id="coupon-name" placeholder="\u6625\u306E\u82B1\u7C89\u75C7\u5BFE\u7B5615%OFF" value={name} onValueChange={(e) => setName(e.target.value)} />
876
- </FormField>`,storyPath:"data-entry/FormField.stories.tsx",rules:[23]},{name:"FormErrors",subParts:["FormErrorsProvider"],group:"data-entry",tagline:"The 'no field to stand on' error summary \u2014 renders the entries of the surrounding Form's server error bag that no mounted FormField name='\u2026' claims: validation errors on hidden/derived fields (action_mode, page, a source-record id) that would otherwise fail silently. Composed on Alert tone='destructive' (role='alert'); renders nothing while every entry is claimed or the bag is empty.",props:[{name:"errors",type:"Partial<Record<string, string | string[]>>",description:"Explicit error bag \u2014 overrides the surrounding Form's `errors`. Use when the component sits outside a Form (e.g. inside FormRoot); field claiming still applies when a Form provides the registry."},{name:"title",type:"ReactNode",description:"Heading above the messages. Defaults to the localized 'please review your input' title (dataEntry.formErrors.title)."},{name:"className",type:"string",description:"Root class override."}],usage:["DO pass the WHOLE bag to `<Form errors={form.errors}>` and place `<FormErrors />` at the top of the form \u2014 never hand-filter the bag per page. Fields with `name` claim their keys automatically; FormErrors shows only the remainder, so the consumer never maintains an except-list.","DO give every visible field its `name` when adopting `Form errors` on a screen. A field that keeps a manual `error={errors.x}` WITHOUT `name` does not claim its key, and FormErrors will show that message twice.","DON'T hand-roll a destructive Alert bound to `errors.hidden_key` per page \u2014 that is exactly the per-page listing this component exists to remove, and it goes stale the moment the server adds a new derived-field rule.","DON'T use FormErrors as a generic mutation-failure banner \u2014 that is `Alert.QueryError` / toast territory. FormErrors is scoped to the VALIDATION bag of the surrounding form.","ARRAY ENTRIES: a `string[]` bag value lists every message in the banner; a claimed field shows only the FIRST message of its array (Laravel `$errors->first()` semantics).","SIBLING FORMS: when the screen is split into several Card+Form sections, wrap the REGION in `<FormErrorsProvider errors={form.errors}>` and give NO `errors` to the section Forms \u2014 they join the shared registry and one `<FormErrors />` covers the whole screen. A nested Form WITH its own `errors` deliberately starts a separate (shadowed) registry."],useCases:["An Inertia edit screen whose Laravel FormRequest validates hidden/derived inputs (`action_mode`, `page`, `source_slip_cd`) \u2014 the user pressed save and previously saw NOTHING because those keys have no visible field.","A ported legacy screen where the server rejects a stale edit-lock or a missing source record under a key that only exists server-side.","A create form where a Laravel `RuleObject` attaches a cross-field error to a synthetic key (e.g. `combination`) rather than to one input."],related:["Form \u2014 provides the error bag (`errors`) and the claim registry FormErrors reads; FormErrors must sit inside it (or receive `errors` explicitly).","FormErrorsProvider \u2014 the shared registry for a REGION of sibling Forms (multi-Card edit screens): wrap the region with it, leave `errors` off the section Forms, and one FormErrors covers the whole screen.","FormField \u2014 `name` claims a bag key and self-binds its message; the claimed key never re-appears in FormErrors.","Alert \u2014 the underlying destructive banner; use Alert directly for non-validation notices."],example:`import { Form, FormErrors, FormField, Input } from "@godxjp/ui/data-entry";
876
+ </FormField>`,storyPath:"data-entry/FormField.stories.tsx",rules:[23]},{name:"FormErrors",subParts:["FormErrorsProvider"],group:"data-entry",tagline:"The 'no field to stand on' error summary \u2014 renders the entries of the surrounding Form's server error bag that no mounted FormField name='\u2026' claims: validation errors on hidden/derived fields (action_mode, page, a source-record id) that would otherwise fail silently. Composed on Alert tone='destructive' (role='alert'); renders nothing while every entry is claimed or the bag is empty.",props:[{name:"errors",type:"Partial<Record<string, string | string[]>>",description:"Explicit error bag \u2014 overrides the surrounding Form's `errors`. Use when the component sits outside a Form (e.g. inside FormRoot); field claiming still applies when a Form provides the registry."},{name:"title",type:"ReactNode",description:"Heading above the messages. Defaults to the localized 'please review your input' title (dataEntry.formErrors.title)."},{name:"className",type:"string",description:"Root class override."}],usage:["DO pass the WHOLE bag to `<Form errors={form.errors}>` and place `<FormErrors />` at the top of the form \u2014 never hand-filter the bag per page. Fields with `name` claim their keys automatically; FormErrors shows only the remainder, so the consumer never maintains an except-list.","DO give every visible field its `name` when adopting `Form errors` on a screen. A field that keeps a manual `error={errors.x}` WITHOUT `name` does not claim its key, and FormErrors will show that message twice.","DON'T hand-roll a destructive Alert bound to `errors.hidden_key` per page \u2014 that is exactly the per-page listing this component exists to remove, and it goes stale the moment the server adds a new derived-field rule.","DON'T use FormErrors as a generic mutation-failure banner \u2014 that is `Alert.QueryError` / toast territory. FormErrors is scoped to the VALIDATION bag of the surrounding form.","INSIDE FormRoot: `<FormRoot form={form} onSubmit={(v) => m.mutateAsync(v)} errors={serverErrors(m.error)}><FormErrors /><AlertMutationFeedback mutation={m} />\u2026</FormRoot>` \u2014 FormRoot mounts the claim registry, so `<FormErrors />` needs no `errors` of its own and shows only unclaimed keys. A 422 renders exactly once: claimed keys under their fields, unclaimed keys here, no AlertMutationFeedback alert (gh#690) and no FormRoot submitFailed banner (gh#698).","ARRAY ENTRIES: a `string[]` bag value lists every message in the banner; a claimed field shows only the FIRST message of its array (Laravel `$errors->first()` semantics).","SIBLING FORMS: when the screen is split into several Card+Form sections, wrap the REGION in `<FormErrorsProvider errors={form.errors}>` and give NO `errors` to the section Forms \u2014 they join the shared registry and one `<FormErrors />` covers the whole screen. A nested Form WITH its own `errors` deliberately starts a separate (shadowed) registry."],useCases:["An Inertia edit screen whose Laravel FormRequest validates hidden/derived inputs (`action_mode`, `page`, `source_slip_cd`) \u2014 the user pressed save and previously saw NOTHING because those keys have no visible field.","A ported legacy screen where the server rejects a stale edit-lock or a missing source record under a key that only exists server-side.","A create form where a Laravel `RuleObject` attaches a cross-field error to a synthetic key (e.g. `combination`) rather than to one input."],related:["Form \u2014 provides the error bag (`errors`) and the claim registry FormErrors reads; FormErrors must sit inside it (or receive `errors` explicitly).","FormErrorsProvider \u2014 the shared registry for a REGION of sibling Forms (multi-Card edit screens): wrap the region with it, leave `errors` off the section Forms, and one FormErrors covers the whole screen.","FormField \u2014 `name` claims a bag key and self-binds its message; the claimed key never re-appears in FormErrors.","Alert \u2014 the underlying destructive banner; use Alert directly for non-validation notices."],example:`import { Form, FormErrors, FormField, Input } from "@godxjp/ui/data-entry";
877
877
  import { useForm } from "@inertiajs/react";
878
878
 
879
879
  const form = useForm({ customer_nm: "", action_mode: "regist" });
@@ -1060,7 +1060,7 @@ export function BillingFields() {
1060
1060
  </FormField>
1061
1061
  </Flex>
1062
1062
  );
1063
- }`,storyPath:"data-entry/DatePicker.stories.tsx",rules:[3,6,13,31]},{name:"Dialog",subParts:["DialogAction","DialogBody","DialogCancel","DialogClose","DialogContent","DialogDescription","DialogFooter","DialogHeader","DialogOverlay","DialogPortal","DialogRoot","DialogTitle","DialogTrigger"],group:"feedback",tagline:"Compound modal. Controlled via open + onOpenChange. Parts available flat (DialogTrigger/DialogContent/\u2026) or as Dialog.Trigger/Dialog.Content. Rendered with role=dialog.",props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"defaultOpen",type:"boolean",description:"Initial open state when uncontrolled."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"variant",type:'"default" | "destructive"',defaultValue:'"default"',description:'How dangerous this dialog is. ONE prop, THREE results, because they always travel together: `destructive` renders `role="alertdialog"` instead of `role="dialog"`, stops an outside click from dismissing, and gives `DialogAction` the destructive emphasis (antd `okType="danger"`). It also defaults the corner \u2715 off, because a \u2715 is an accidental-dismiss affordance too. Escape still closes either way. Settable on the root (covers the tree) or on `DialogContent` (the nearer one wins). This is what replaces reaching for the separate `AlertDialog*` family \u2014 gh#567.'},{name:"modal",type:"boolean",defaultValue:"true",description:"Kept from the Radix era. react-aria's Modal always locks scroll and hides the background from assistive tech, so `false` no longer turns that off."}],usage:["Use `Dialog` for form-style or wizard-style modal flows that need freeform content and a close action.",'DO reach for `variant="destructive"` for a dangerous confirmation, INCLUDING one that needs a form inside it (a required reason, a typed challenge). It gives the alertdialog role and the non-dismissable scrim without giving up the freeform body. The separate `AlertDialog*` parts remain for existing code and still work, but they are the older way in.',"DO leave `variant` alone for everything that is not destructive. The prop is a danger level, not a colour knob: to tint only the header band use `DialogHeader tone`, which is a separate axis with seven values.","DO always control open state via `open` + `onOpenChange`. Dialog has no uncontrolled shortcut \u2014 omitting `open` means the trigger alone drives state, which is fine for simple trigger-only cases, but any async submission flow must use controlled state so you can hold the dialog open while `pending=true` and close it only on success.","DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. Radix requires an accessible title for screen readers; omitting it triggers a console warning and breaks a11y.","DO wrap tall/scrolling content in `DialogBody` (the ring-safe scroll slot, max-height ~60vh). It insets the content to match the dialog padding so a full-width control's focus ring never clips against the scroll container \u2014 mirror of SheetBody."],useCases:["Inline form dialog \u2014 create or edit a record (invoice line, supplier, coupon) without navigating away. Place `FormField`/`Input`/`Select` inside `DialogContent`, wire the submit button to your mutation, and hold `open` while `pending` to prevent double-submit.","Read-only detail popup \u2014 show a full transaction audit trail, attachment preview, or approval history in a modal without leaving the list page. Use `Dialog` with no `DialogFooter` action buttons, just a close trigger.","Wizard / multi-step flow \u2014 step through entity setup (legal entity \u2192 fiscal year \u2192 opening balances) using a single Dialog whose `DialogContent` conditionally renders different step panels. Control which step is shown in local state."],related:['AlertDialog \u2014 the flat confirm PRESET (title/description/challenge/step-up/pending, no markup of your own). Reach for the preset when it covers the case. When it does not, do NOT reach for the `AlertDialog*` compound parts: use `Dialog` with `variant="destructive"`, which is the same role and the same scrim with a freeform body.',"Sheet \u2014 use Sheet instead of Dialog when the content is a slide-in panel (filters, detail sidebar, settings drawer). Sheet uses `side` prop and is better suited for wide filter forms or contextual detail panels that don't demand full focus interruption.","Alert \u2014 use Alert for inline, non-modal status messages (validation errors, success banners on the page). Dialog is modal and focus-trapping; Alert is inline and never blocks interaction.","Popover \u2014 use Popover for lightweight non-modal overlays anchored to a trigger (quick-edit a single field, tooltip-style confirmation for low-stakes actions). Dialog is full-modal; Popover stays near its trigger and doesn't dim the page.","AlertMutationFeedback \u2014 use AlertMutationFeedback for toast/inline feedback after the Dialog closes, not inside it. Putting a success toast inside a Dialog that is about to unmount causes it to disappear immediately; emit the feedback after `onOpenChange(false)` resolves."],example:`import { useState } from "react";
1063
+ }`,storyPath:"data-entry/DatePicker.stories.tsx",rules:[3,6,13,31]},{name:"Dialog",subParts:["DialogAction","DialogBody","DialogCancel","DialogClose","DialogContent","DialogDescription","DialogFooter","DialogHeader","DialogOverlay","DialogPortal","DialogRoot","DialogTitle","DialogTrigger"],group:"feedback",tagline:"Compound modal. Controlled via open + onOpenChange. Parts available flat (DialogTrigger/DialogContent/\u2026) or as Dialog.Trigger/Dialog.Content. Rendered with role=dialog.",props:[{name:"open",type:"boolean",description:"Controlled open state."},{name:"defaultOpen",type:"boolean",description:"Initial open state when uncontrolled."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"variant",type:'"default" | "destructive"',defaultValue:'"default"',description:'How dangerous this dialog is. ONE prop, THREE results, because they always travel together: `destructive` renders `role="alertdialog"` instead of `role="dialog"`, stops an outside click from dismissing, and gives `DialogAction` the destructive emphasis (antd `okType="danger"`). It also defaults the corner \u2715 off, because a \u2715 is an accidental-dismiss affordance too. Escape still closes either way. Settable on the root (covers the tree) or on `DialogContent` (the nearer one wins). This is what replaces reaching for the separate `AlertDialog*` family \u2014 gh#567.'},{name:"modal",type:"boolean",defaultValue:"true",description:'`false` renders a NON-MODAL dialog (WAI-ARIA APG allows non-modal dialogs): the page behind stays interactive and in the accessibility tree (no inert/aria-hidden), no scroll lock, no scrim, an outside press does NOT close it, and there is no `aria-modal`. The dialog keeps role=dialog + its title as name, centred fixed placement, sizes and tokens; focus moves into it on open, Tab can leave it, Escape closes it while focus is inside, and focus returns to the trigger on close (only if focus was still inside). Ignored, with a dev warning, under `variant="destructive"` \u2014 an alertdialog is always modal. gh#696.'}],usage:["Use `Dialog` for form-style or wizard-style modal flows that need freeform content and a close action.",'DO reach for `variant="destructive"` for a dangerous confirmation, INCLUDING one that needs a form inside it (a required reason, a typed challenge). It gives the alertdialog role and the non-dismissable scrim without giving up the freeform body. The separate `AlertDialog*` parts remain for existing code and still work, but they are the older way in.',"DO leave `variant` alone for everything that is not destructive. The prop is a danger level, not a colour knob: to tint only the header band use `DialogHeader tone`, which is a separate axis with seven values.","DO always control open state via `open` + `onOpenChange`. Dialog has no uncontrolled shortcut \u2014 omitting `open` means the trigger alone drives state, which is fine for simple trigger-only cases, but any async submission flow must use controlled state so you can hold the dialog open while `pending=true` and close it only on success.","DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. Radix requires an accessible title for screen readers; omitting it triggers a console warning and breaks a11y.","DO wrap tall/scrolling content in `DialogBody` (the ring-safe scroll slot, max-height ~60vh). It insets the content to match the dialog padding so a full-width control's focus ring never clips against the scroll container \u2014 mirror of SheetBody.","DO set `modal={false}` when the user must keep working on the page behind an open dialog (edit a list while a payment or detail dialog stays open). Control `open` yourself: an outside press no longer closes it, so give it a visible close action. Escape closes it only while focus is inside the dialog."],useCases:["Inline form dialog \u2014 create or edit a record (invoice line, supplier, coupon) without navigating away. Place `FormField`/`Input`/`Select` inside `DialogContent`, wire the submit button to your mutation, and hold `open` while `pending` to prevent double-submit.","Read-only detail popup \u2014 show a full transaction audit trail, attachment preview, or approval history in a modal without leaving the list page. Use `Dialog` with no `DialogFooter` action buttons, just a close trigger.","Non-modal side task \u2014 `modal={false}` keeps a list or cart behind the dialog editable while the dialog stays open (take payment while the order lines can still change).","Wizard / multi-step flow \u2014 step through entity setup (legal entity \u2192 fiscal year \u2192 opening balances) using a single Dialog whose `DialogContent` conditionally renders different step panels. Control which step is shown in local state."],related:['AlertDialog \u2014 the flat confirm PRESET (title/description/challenge/step-up/pending, no markup of your own). Reach for the preset when it covers the case. When it does not, do NOT reach for the `AlertDialog*` compound parts: use `Dialog` with `variant="destructive"`, which is the same role and the same scrim with a freeform body.',"Sheet \u2014 use Sheet instead of Dialog when the content is a slide-in panel (filters, detail sidebar, settings drawer). Sheet uses `side` prop and is better suited for wide filter forms or contextual detail panels that don't demand full focus interruption.","Alert \u2014 use Alert for inline, non-modal status messages (validation errors, success banners on the page). Dialog is modal and focus-trapping; Alert is inline and never blocks interaction.","Popover \u2014 use Popover for lightweight non-modal overlays anchored to a trigger (quick-edit a single field, tooltip-style confirmation for low-stakes actions). Dialog is full-modal; Popover stays near its trigger and doesn't dim the page.","AlertMutationFeedback \u2014 use AlertMutationFeedback for toast/inline feedback after the Dialog closes, not inside it. Putting a success toast inside a Dialog that is about to unmount causes it to disappear immediately; emit the feedback after `onOpenChange(false)` resolves."],example:`import { useState } from "react";
1064
1064
  import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter } from "@godxjp/ui/feedback";
1065
1065
  import { Button } from "@godxjp/ui/general";
1066
1066
 
@@ -1125,7 +1125,7 @@ function ConfirmSettlement() {
1125
1125
  </AlertDialogPortal>
1126
1126
  </AlertDialogRoot>
1127
1127
  );
1128
- }`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"Sheet",subParts:["SheetBody","SheetClose","SheetContent","SheetDescription","SheetFooter","SheetHeader","SheetOverlay","SheetPortal","SheetTitle","SheetTrigger"],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). 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.'},{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, `DatePicker range`, 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";
1128
+ }`,storyPath:"feedback/AlertDialog.stories.tsx",rules:[23,3]},{name:"Sheet",subParts:["SheetBody","SheetClose","SheetContent","SheetDescription","SheetFooter","SheetHeader","SheetOverlay","SheetPortal","SheetTitle","SheetTrigger"],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:"modal",type:"boolean",defaultValue:"true",description:"On Sheet (root). `false` renders a NON-MODAL sheet (WAI-ARIA APG allows non-modal dialogs): the page behind stays interactive and in the accessibility tree (no inert/aria-hidden), no scroll lock, no scrim, an outside press does NOT close it, and there is no `aria-modal`. The panel keeps role=dialog + its title as name, and the same side placement, width, responsive presentation and tokens. Focus moves into it on open, Tab can leave it, Escape closes it while focus is inside, and focus returns to the trigger on close (only if focus was still inside). Same contract as Dialog `modal={false}`. gh#701."},{name:"width",type:"number | string",description:"On SheetContent (side left/right): desired panel width (number\u2192px). 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.'},{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.","DO set `modal={false}` on Sheet when the user must keep working on the page behind the open panel (edit a list while a detail panel stays open). Control `open` yourself: an outside press no longer closes it, so keep the \u2715 or a footer close action. Escape closes it only while focus is inside the panel.","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, `DatePicker range`, 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.","Non-modal side panel: `modal={false}` keeps a list behind the sheet editable while the panel stays open (change order lines while their running total stays visible in the panel).","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";
1129
1129
  import { Button } from "@godxjp/ui/general";
1130
1130
 
1131
1131
  <Sheet open={open} onOpenChange={setOpen}>
@@ -4429,7 +4429,7 @@ A block with no reason is IGNORED and the finding stands. An unclosed block runs
4429
4429
  The class-shaped rules (gap-*/p-*/m-*, bg-<palette>-*, w-[\u2026], pr-*, dark:*) only read class
4430
4430
  expressions \u2014 a className/class attribute, a class-named binding (\`baseClass\`, \`statusStyles\`,
4431
4431
  \`badgeVariants\`) or a cn()/clsx()/cva() call \u2014 so prose that merely spells a utility is not a
4432
- finding and needs no suppression.`,$=[{id:"dialog-needs-body",severity:"error",category:"composition",standard:null,fix:"Wrap a Dialog/Sheet's middle in <DialogBody>/<SheetBody>. The scroll lives on the body \u2014 the content box is `overflow: hidden` with no max-height \u2014 so an overlay without one clips long content at BOTH ends and takes the footer's buttons with it, leaving Escape as the only way out. It only shows on real data, never on demo data (gh#617)."},{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens. A read-only sample of a colour a USER chose is Swatch, which takes that value as a prop (gh#527)."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function J(e){return e?$.filter(t=>t.category===e):$}var ee="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)",Z=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function te(e){return e?Z.filter(t=>t.category===e):Z}var p={name:"@godxjp/ui-mcp",version:"27.0.0",godxUiCompatibility:"27.0.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var oe=[{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 ne(e,t){switch(e){case"list_skills":return ve();case"list_primitives":return ce(t.group);case"list_patterns":return xe();case"list_anti_ai_tells":return Ce(t.category);case"list_redesign_checks":return De(t.category);case"list_audit_rules":return Te(t.category);case"list_visual_checks":return Se(t.category);case"get_anti_ai_tell":return Ae(String(t.name??""));case"get_redesign_check":return Oe(String(t.symptom??""));case"get_skill_section":return he(String(t.skill??""),String(t.section??""));case"get_component":return Re(String(t.name??""),t.verbose===!0);case"get_pattern":return Pe(String(t.name??""));case"get_rule":return Le(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Fe(t.name==null?void 0:String(t.name));case"get_tokens":return Me(t.category);case"list_consumer_skills":return ye();case"get_consumer_skill":return we(String(t.skill??""),String(t.section??""));case"route_consumer_task":return ae(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return ke(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return ae(String(t.task??""));case"suggest_primitive":return Be(String(t.use_case??""));case"search_components":return Ue(String(t.query??""));case"get_frame_coverage":return ze(t.name===void 0?void 0:String(t.name));case"lint_jsx":return He(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function ve(){let e=`# Available skills (${S.length})
4432
+ finding and needs no suppression.`,$=[{id:"dialog-needs-body",severity:"error",category:"composition",standard:null,fix:"Wrap a Dialog/Sheet's middle in <DialogBody>/<SheetBody>. The scroll lives on the body \u2014 the content box is `overflow: hidden` with no max-height \u2014 so an overlay without one clips long content at BOTH ends and takes the footer's buttons with it, leaving Escape as the only way out. It only shows on real data, never on demo data (gh#617)."},{id:"no-utility-spacing",severity:"error",category:"composition",standard:null,fix:"Remove gap-*/p-*/m-* from your own markup; space siblings with <Flex gap> / <ResponsiveGrid>. Page sections are spaced by <PageContainer> (docs/CONSUMER-RULES.md \xA73)."},{id:"no-utility-layout",severity:"error",category:"composition",standard:null,fix:'Replace className="flex \u2026" / "grid \u2026" with <Flex> (row), <Flex direction="col"> (stack) or <ResponsiveGrid columns>.'},{id:"no-hand-rolled-surface",severity:"warn",category:"composition",standard:null,fix:"A rounded+border/bg div is a fake surface \u2014 use Card, Badge, Avatar, ListRow, Descriptions or EmptyState so height, padding and radius come from tokens. A read-only sample of a colour a USER chose is Swatch, which takes that value as a prop (gh#527)."},{id:"sibling-cards-need-flex",severity:"warn",category:"composition",standard:null,fix:'Wrap adjacent <Card>s in <Flex direction="col" gap="lg"> or <ResponsiveGrid>; direct children of PageContainer are spaced by the page already.'},{id:"no-raw-palette-color",severity:"error",category:"tokens",standard:null,fix:"Use semantic tokens (bg-primary, text-muted-foreground), never raw palette (bg-blue-500)."},{id:"no-arbitrary-hex",severity:"error",category:"tokens",standard:null,fix:"No hardcoded hex in className; read design-system color tokens."},{id:"no-arbitrary-spacing",severity:"error",category:"tokens",standard:null,fix:"No p-[13px]/gap-[7px]; use the token scale / <Flex gap> / <PageContainer>."},{id:"no-arbitrary-size",severity:"error",category:"tokens",standard:null,fix:"No w-[37px]/h-[260px]; use token sizes or a sizing prop (min-w-[\u2026] allowed)."},{id:"no-arbitrary-typography",severity:"error",category:"tokens",standard:null,fix:"No text-[20px]/leading-[1.7]; use the golden-ratio type-scale tokens."},{id:"no-arbitrary-radius",severity:"error",category:"tokens",standard:null,fix:"No rounded-[6px]; use rounded-sm/md/lg radius tokens."},{id:"no-off-scale-token-value",severity:"warn",category:"tokens",standard:null,fix:"A design-system knob you override takes a step (style={{ '--card-space-inset': 'var(--space-4)' }}) or a calc() from one (calc(var(--space-4) + 2px)), not a raw 13px. Only axes that HAVE a scale count: space/padding/gap/margin, font-size, radius, icon-size (width/height/size/offset have none yet, so a number there is fine). A value that is genuinely off the grid keeps its literal and says why in place, with a /* scale-exempt: 6px status dot, below --space-1 */ comment on that line or the one above."},{id:"no-dark-color-override",severity:"warn",category:"tokens",standard:null,fix:"Drop dark: color overrides \u2014 semantic tokens already adapt."},{id:"raw-white-black",severity:"warn",category:"tokens",standard:null,fix:"Prefer semantic tokens (text-primary-foreground, bg-background) over raw white/black."},{id:"no-domain-tracking-token",severity:"error",category:"tokens",standard:null,fix:"No package-tracking/domain tokens; use semantic tokens or app theme overrides."},{id:"no-space-xy",severity:"error",category:"tokens",standard:null,fix:"Use <Flex gap> instead of space-x/y-*."},{id:"no-raw-select",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Select> from @godxjp/ui, not a raw <select>."},{id:"no-raw-table",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use the <Table>/<DataTable> family, not a raw <table>."},{id:"no-raw-input",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Input> from @godxjp/ui, not a raw <input>."},{id:"no-raw-textarea",severity:"warn",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Textarea> from @godxjp/ui, not a raw <textarea>."},{id:"no-raw-button",severity:"error",category:"composition",standard:"HTML Living Standard (WHATWG)",fix:"Use <Button> from @godxjp/ui, not a raw <button>."},{id:"card-manual-padding",severity:"error",category:"composition",standard:null,fix:"Wrap the body in <CardContent>; don't hand-roll padding on <Card>."},{id:"card-needs-content",severity:"error",category:"composition",standard:null,fix:"<Card> body must be in <CardContent> (no padding otherwise); flush only for a full-bleed table."},{id:"bare-control-needs-formfield",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.3.1 \xB7 3.3.2 \xB7 @godxjp/ui FormField (cardinal rule 227)",fix:"Wrap a labelled control in <FormField label=\u2026> \u2014 it owns label\u2194control id wiring, aria/error, AND the field rhythm; never pair a bare <Label> with an <Input>."},{id:"manual-field-error",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 3.3.1",fix:"Use <FormField error=\u2026>, not a hand-rolled <p class='text-destructive'>."},{id:"manual-field-helper",severity:"warn",category:"composition",standard:null,fix:"Use <FormField helper=\u2026>, not a hand-rolled helper <p>."},{id:"status-tone-not-variant",severity:"error",category:"api",standard:null,fix:"Badge/Tag/StatCard status uses tone, not variant (variant is structural)."},{id:"value-callback-on-value-change",severity:"error",category:"api",standard:null,fix:"Abstract value components use onValueChange, not onChange."},{id:"icon-button-needs-name",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 4.1.2 \xB7 1.1.1 \xB7 WAI-ARIA 1.2",fix:"Add aria-label={t('\u2026')} to <Button size='icon'>; the glyph is aria-hidden."},{id:"img-needs-alt",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 1.1.1 \xB7 HTML Living Standard",fix:"Add alt to every <img> (alt='' if decorative); prefer <Avatar>/<AspectRatio>."},{id:"no-positive-tabindex",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.4.3 \xB7 WAI-ARIA APG",fix:"Use tabIndex 0 or -1 only; never positive \u2014 it breaks focus order."},{id:"hand-rolled-close-glyph",severity:"warn",category:"a11y",standard:"WAI-ARIA 1.2 (dialog) \xB7 WCAG 2.2 SC 4.1.2",fix:"Pass onDismiss to <Alert>, or use <Dialog>/<Sheet>'s built-in labelled close \u2014 not a bare \u2715."},{id:"no-emoji-in-ui",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"No emoji in product UI; quiet i18n copy + Lucide icon + Badge tone."},{id:"no-emoji-flag",severity:"warn",category:"i18n",standard:"ISO 3166-1 \xB7 ECMA-402 Intl.DisplayNames \xB7 Unicode UTS #51",fix:"Derive country names from Intl.DisplayNames; no emoji flags."},{id:"hardcoded-currency",severity:"warn",category:"i18n",standard:"ISO 4217 \xB7 ECMA-402 Intl.NumberFormat",fix:"Format money with Intl.NumberFormat({ style: 'currency', currency }), not \xA5{amount}."},{id:"raw-intl-date",severity:"warn",category:"i18n",standard:"ISO 8601 \xB7 IANA tz \xB7 ECMA-402 Intl.DateTimeFormat",fix:"Use formatDate from @godxjp/ui/datetime, not hand-built or locale-default dates."},{id:"no-physical-direction",severity:"warn",category:"rtl",standard:"W3C CSS Logical Properties L1 \xB7 WCAG 2.2 (1.3.2)",fix:"Use logical utilities (ms-/me-/ps-/pe-, start-/end-, text-start/end, border-s/e, rounded-s/e)."},{id:"no-em-dash-in-copy",severity:"warn",category:"copy",standard:"@godxjp/ui reference-design typography",fix:"No em-dash (\u2014) in copy; use a middot \xB7 or two calm sentences."}];function J(e){return e?$.filter(t=>t.category===e):$}var ee="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)",Z=[{id:"css-layers-missing",severity:"error",category:"layout",standard:"@godxjp/ui styles contract (styles / styles/core are the only entries)",fix:"Import `@godxjp/ui/styles` (or `styles/core` without fonts); never cherry-pick *-layout.css \u2014 a missing layer renders naked menus and unsized Select rows."},{id:"control-height-mismatch",severity:"error",category:"layout",standard:"@godxjp/ui control tier (--control-height) \xB7 Nielsen consistency heuristic",fix:"Every control in one row must share --control-height; replace hand-rolled pills with Avatar/Button/Badge, never restyle a control's height."},{id:"sibling-card-gap",severity:"error",category:"layout",standard:"@godxjp/ui spacing scale (docs/SPACING.md)",fix:'Adjacent Cards need one space step between them \u2014 <Flex direction="col" gap>, <ResponsiveGrid>, or direct children of PageContainer.'},{id:"row-content-starved",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 reflow",fix:`A sibling (a w-full SelectTrigger) takes the row's width and truncates its neighbours \u2014 give the Select width="auto" or move it out of the row.`},{id:"axe-violations",severity:"warn",category:"a11y",standard:"WCAG 2.2 A/AA \xB7 WAI-ARIA 1.2 (axe-core engine)",fix:"Fix each axe node \u2014 contrast (1.4.3), name/role/value (4.1.2), ARIA, landmarks. Runs on the REAL DOM, catching what static analysis cannot."},{id:"target-size-min",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.5.8 (24\xD724 AA) \xB7 2.5.5 (44\xD744 AAA)",fix:"Interactive targets must be \u226524\xD724 CSS px; size from the --control-height tier."},{id:"oversaturated-accent",severity:"warn",category:"color",standard:"@godxjp/ui reference-design \u6E0B\u307F (OKLCH chroma \u2264 0.18)",fix:"Desaturate brand/primary surfaces (OKLCH chroma \u2264 0.18); read --primary tokens, no raw vivid bars."},{id:"emoji-rendered",severity:"warn",category:"i18n",standard:"Unicode UTS #51 \xB7 WCAG 2.2 SC 1.1.1",fix:"Remove emoji from rendered product text; quiet i18n copy + Lucide icon + Badge tone."},{id:"alert-controls-misplaced",severity:"warn",category:"layout",standard:"@godxjp/ui Alert anatomy \xB7 WAI-ARIA 1.2 \xB7 WCAG 2.2 SC 4.1.2",fix:"Use <Alert>: one leading tone icon, <Alert.Actions> trailing-right normal width, onDismiss \xD7 top-right, one horizontal row."}];function te(e){return e?Z.filter(t=>t.category===e):Z}var p={name:"@godxjp/ui-mcp",version:"27.1.0",godxUiCompatibility:"27.1.x",description:"Model Context Protocol server for @godxjp/ui \u2014 gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit \u2014 token-efficient (list \u2192 drill-down).",type:"module",main:"./dist/index.js",module:"./dist/index.js",types:"./dist/index.d.ts",bin:{"godx-ui-mcp":"./dist/index.js"},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org/",access:"public"},repository:{type:"git",url:"git+https://github.com/godx-jp/godxjp-ui.git",directory:"mcp"},homepage:"https://github.com/godx-jp/godxjp-ui/tree/main/mcp#readme",license:"Apache-2.0",scripts:{build:"tsup",dev:"tsup --watch",start:"node dist/index.js",inspect:"npx @modelcontextprotocol/inspector node dist/index.js","type-check":"tsc --noEmit",test:"vitest run",prepublishOnly:"npm run build"},dependencies:{"@modelcontextprotocol/sdk":"^1.29.0",zod:"^4.4.3"},devDependencies:{"@types/node":"^22.10.0",tsup:"^8.5.1",typescript:"^6.0.3",vitest:"^4.1.6"},keywords:["mcp","model-context-protocol","godxjp","ui","design-system","react","claude","cursor"],author:"GoDX (https://godx.jp)",bugs:{url:"https://github.com/godx-jp/godxjp-ui/issues"}};var oe=[{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 ne(e,t){switch(e){case"list_skills":return ve();case"list_primitives":return ce(t.group);case"list_patterns":return xe();case"list_anti_ai_tells":return Ce(t.category);case"list_redesign_checks":return De(t.category);case"list_audit_rules":return Te(t.category);case"list_visual_checks":return Se(t.category);case"get_anti_ai_tell":return Ae(String(t.name??""));case"get_redesign_check":return Oe(String(t.symptom??""));case"get_skill_section":return he(String(t.skill??""),String(t.section??""));case"get_component":return Re(String(t.name??""),t.verbose===!0);case"get_pattern":return Pe(String(t.name??""));case"get_rule":return Le(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Fe(t.name==null?void 0:String(t.name));case"get_tokens":return Me(t.category);case"list_consumer_skills":return ye();case"get_consumer_skill":return we(String(t.skill??""),String(t.section??""));case"route_consumer_task":return ae(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return ke(t);case"check_compatibility":return de(t.version==null?void 0:String(t.version));case"route_task":return ae(String(t.task??""));case"suggest_primitive":return Be(String(t.use_case??""));case"search_components":return Ue(String(t.query??""));case"get_frame_coverage":return ze(t.name===void 0?void 0:String(t.name));case"lint_jsx":return He(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function ve(){let e=`# Available skills (${S.length})
4433
4433
 
4434
4434
  `;e+="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",e+='Use `get_skill_section skill="..." section="..."` to drill in.\n\n';for(let t of S)e+=`## ${t.id} \u2014 ${t.name} \`[${t.audience}]\`
4435
4435
  `,e+=`**When to use:** ${t.whenToUse}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui-mcp",
3
- "version": "27.0.0",
4
- "godxUiCompatibility": "27.0.x",
3
+ "version": "27.1.0",
4
+ "godxUiCompatibility": "27.1.x",
5
5
  "description": "Model Context Protocol server for @godxjp/ui — gives Claude Code / Codex CLI / Cursor / any MCP-aware agent live access to the component catalog, prop vocabulary, design tokens, 45 cardinal rules, copy-paste-ready patterns, 12 design / taste skills synthesised from Leonxlnx/taste-skill, 20+ anti-AI-tell patterns, and a 50-check redesign audit — token-efficient (list → drill-down).",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",