@godxjp/ui-mcp 26.2.0 → 26.3.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 +5 -5
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ 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:"Inline label help or action."},{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."],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
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";
13
13
  import { z } from "zod";
@@ -869,7 +869,7 @@ import remarkGfm from "remark-gfm";
869
869
  <FormField id="first" label="\u59D3"><Input id="first" /></FormField>
870
870
  <FormField id="last" label="\u540D"><Input id="last" /></FormField>
871
871
  <FormField id="address" label="\u4F4F\u6240" colSpan={2}><Input id="address" /></FormField>
872
- </Form>`,storyPath:"data-entry/Form.stories.tsx",rules:[23,24]},{name:"FormField",group:"data-entry",tagline:"Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) \u2014 overridable per field.",props:[{name:"field",type:"string",description:"T\xEAn tr\u01B0\u1EDDng c\u1EE7a form \u2014 d\xF9ng khi `id` kh\xF4ng \u0111\u1EE7 \u0111\u1EC3 n\u1ED1i control v\u1EDBi error/aria."},{name:"labelAddon",type:"ReactNode",description:"N\u1ED9i dung ph\u1EE5 c\u1EA1nh nh\xE3n: g\u1EE3i \xFD, badge b\u1EAFt bu\u1ED9c, n\xFAt tr\u1EE3 gi\xFAp. N\u1EB1m TRONG h\xE0ng nh\xE3n n\xEAn kh\xF4ng ph\xE1 nh\u1ECBp tr\u01B0\u1EDDng."},{name:"id",type:"string",required:!0,description:"Forwarded to Label htmlFor + builds helper/error ids."},{name:"name",type:"string",description:"Error-bag key of this field. When the surrounding Form carries `errors`, the field resolves its message from `errors[name]` automatically (an explicit `error` prop wins) and CLAIMS the key so <FormErrors /> does not repeat it. NOT injected into the child \u2014 pass `name` on the control itself for native form submission."},{name:"label",type:"ReactNode",required:!0,description:"Field label above the control."},{name:"required",type:"boolean",defaultValue:"false",description:"Red asterisk + aria-required on the child."},{name:"helper",type:"string",description:"Muted hint shown when there is no error."},{name:"helperPlacement",type:'"before" | "after"',defaultValue:'"after"',description:"Which side of the control the helper sits on. `before` puts it between the label and the input, for a hint the reader needs before answering (a bilingual form's second line, a unit or format note). Paint only \u2014 the helper keeps its id and stays on aria-describedby. Prefer it over stuffing a second line into `labelAddon` (inline, no wrap) or into a ReactNode `label` (which loses the string-label aria fallbacks)."},{name:"error",type:"string",description:"Destructive error message (role=alert); overrides helper."},{name:"layout",type:'"vertical" | "horizontal" | "inline"',description:"Override the parent Form's layout for this field only."},{name:"labelWidth",type:"number | string",description:"Override the Form's label width for this field."},{name:"controlWidth",type:"number | string",description:"Override the Form's control width for this field."},{name:"colSpan",type:"number",description:"Span N columns when inside a `columns` Form grid."},{name:"children",type:"ReactNode",description:"The single interactive control to render. Mutually exclusive with `staticText` \u2014 pass exactly one of the two."},{name:"staticText",type:"ReactNode",description:"Read-only VALUE instead of an interactive control \u2014 renders as plain text styled to match `Descriptions.Item`'s value typography byte-for-byte (`text-sm break-all`), skipping all of FormField's id/aria-* control wiring (there is nothing to label). Mutually exclusive with `children`. Use this to put a read-only field (name, email \u2014 anything immutable) on the SAME `<Form>` as editable fields, so it inherits the exact same layout/labelAlign/row-gap automatically instead of reaching for a separate `Descriptions` block that needs its own props reconciled to match."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation state; an actual error takes priority. Warnings do not mark values invalid."},{name:"hasFeedback",type:"boolean",description:"Show a localized accessible validation status beside its feedback icon."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback content; the localized status remains available."}],usage:["DO pass the same string to both `id` on `<FormField>` and `id` on the child control \u2014 the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.","DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` \u2014 if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.","COMPOSITE CHILD: when the single child is a layout wrapper \u2014 a `Flex` holding a range from/to pair or a \u5E74/\u6708 input+select combo \u2014 the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select/SearchSelect's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. \u958B\u59CB\u65E5/\u7D42\u4E86\u65E5) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).","DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form \u2014 e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and \u2014 because it IS a FormField reading the same Form context \u2014 it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.","WIDTH: a FormField FILLS its container in vertical/horizontal layout \u2014 like the conventional Form.Item (vertical \u2192 width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` \u2014 never constrain the FormField itself.","DO use the `error` prop (not a hand-rolled `<p>`) for validation messages \u2014 it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.","DO use `labelAddon` (a ReactNode rendered inline after the label text) for supplementary controls such as a tooltip trigger or a 'copy' icon button \u2014 never insert such controls as siblings outside FormField, which breaks layout.","DON'T wrap `Switch` in FormField \u2014 use `Field` instead, which already handles the label, hidden `<input name>` for HTML form submission, error, and helper internally.","DON'T use FormField for checkbox-beside-label or radio-beside-label patterns \u2014 use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.","CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div \u2014 Input/Textarea/NumberInput \u2192 the `<input>/<textarea>`; Select/SearchSelect/Cascader/TreeSelect \u2192 the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker \u2192 the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker \u2192 the `<input type=color>` swatch; SearchInput \u2192 the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup \u2192 `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer \u2192 `role=group` \u2014 per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) \u2014 do not reinvent it per control.","FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` \u2014 the field's stable MACHINE key, resolved as `field` \u2192 `name` \u2192 `id` \u2014 onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a \u5E74/\u6708 combo, a value beside a \u300C\u4E0D\u660E\u300D checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` \u2014 which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.","NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends \u2014 turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.","NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission \u2014 Input/Textarea/NumberInput/Select submit natively; SearchSelect submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.","ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."],useCases:["Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.","Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.","Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label\u2013control association.","Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.","Wrapping a `SearchSelect` or `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.","Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."],related:["Label \u2014 the bare Radix label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.","Field \u2014 a self-contained field for boolean toggles: it already includes its own label, hidden `<input name>` for HTML form submission, helper, and error. Never wrap a bare `Switch` in FormField.","Field \u2014 pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.","CheckboxGroup / RadioGroup \u2014 for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."],example:`import { FormField, Input } from "@godxjp/ui/data-entry";
872
+ </Form>`,storyPath:"data-entry/Form.stories.tsx",rules:[23,24]},{name:"FormField",group:"data-entry",tagline:"Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) \u2014 overridable per field.",props:[{name:"field",type:"string",description:"T\xEAn tr\u01B0\u1EDDng c\u1EE7a form \u2014 d\xF9ng khi `id` kh\xF4ng \u0111\u1EE7 \u0111\u1EC3 n\u1ED1i control v\u1EDBi error/aria."},{name:"labelAddon",type:"ReactNode",description:"N\u1ED9i dung ph\u1EE5 c\u1EA1nh nh\xE3n: g\u1EE3i \xFD, badge b\u1EAFt bu\u1ED9c, n\xFAt tr\u1EE3 gi\xFAp, action ch\u1EEF ng\u1EAFn. N\u1EB1m TRONG h\xE0ng nh\xE3n n\xEAn kh\xF4ng ph\xE1 nh\u1ECBp tr\u01B0\u1EDDng. \u1EDE layout horizontal/inline h\xE0ng nh\xE3n xu\u1ED1ng d\xF2ng: addon kh\xF4ng v\u1EEBa c\u1EA1nh nh\xE3n th\xEC r\u01A1i xu\u1ED1ng d\xF2ng ri\xEAng d\u01B0\u1EDBi nh\xE3n, trong c\u1ED9t nh\xE3n, kh\xF4ng tr\xE0n sang c\u1ED9t control."},{name:"id",type:"string",required:!0,description:"Forwarded to Label htmlFor + builds helper/error ids."},{name:"name",type:"string",description:"Error-bag key of this field. When the surrounding Form carries `errors`, the field resolves its message from `errors[name]` automatically (an explicit `error` prop wins) and CLAIMS the key so <FormErrors /> does not repeat it. NOT injected into the child \u2014 pass `name` on the control itself for native form submission."},{name:"label",type:"ReactNode",required:!0,description:"Field label above the control."},{name:"required",type:"boolean",defaultValue:"false",description:"Red asterisk + aria-required on the child."},{name:"helper",type:"string",description:"Muted hint shown when there is no error."},{name:"helperPlacement",type:'"before" | "after"',defaultValue:'"after"',description:"Which side of the control the helper sits on. `before` puts it between the label and the input, for a hint the reader needs before answering (a bilingual form's second line, a unit or format note). Paint only \u2014 the helper keeps its id and stays on aria-describedby. Prefer it over stuffing a second line into `labelAddon` (a label-row slot for a chip, help button or short action) or into a ReactNode `label` (which loses the string-label aria fallbacks)."},{name:"error",type:"string",description:"Destructive error message (role=alert); overrides helper."},{name:"layout",type:'"vertical" | "horizontal" | "inline"',description:"Override the parent Form's layout for this field only."},{name:"labelWidth",type:"number | string",description:"Override the Form's label width for this field."},{name:"controlWidth",type:"number | string",description:"Override the Form's control width for this field."},{name:"colSpan",type:"number",description:"Span N columns when inside a `columns` Form grid."},{name:"children",type:"ReactNode",description:"The single interactive control to render. Mutually exclusive with `staticText` \u2014 pass exactly one of the two."},{name:"staticText",type:"ReactNode",description:"Read-only VALUE instead of an interactive control \u2014 renders as plain text styled to match `Descriptions.Item`'s value typography byte-for-byte (`text-sm break-all`), skipping all of FormField's id/aria-* control wiring (there is nothing to label). Mutually exclusive with `children`. Use this to put a read-only field (name, email \u2014 anything immutable) on the SAME `<Form>` as editable fields, so it inherits the exact same layout/labelAlign/row-gap automatically instead of reaching for a separate `Descriptions` block that needs its own props reconciled to match."},{name:"validateStatus",type:'"success" | "warning" | "error" | "validating"',description:"Validation state; an actual error takes priority. Warnings do not mark values invalid."},{name:"hasFeedback",type:"boolean",description:"Show a localized accessible validation status beside its feedback icon."},{name:"feedback",type:"React.ReactNode",description:"Custom feedback content; the localized status remains available."}],usage:["DO pass the same string to both `id` on `<FormField>` and `id` on the child control \u2014 the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.","DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` \u2014 if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.","COMPOSITE CHILD: when the single child is a layout wrapper \u2014 a `Flex` holding a range from/to pair or a \u5E74/\u6708 input+select combo \u2014 the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select/SearchSelect's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. \u958B\u59CB\u65E5/\u7D42\u4E86\u65E5) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).","DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form \u2014 e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and \u2014 because it IS a FormField reading the same Form context \u2014 it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.","WIDTH: a FormField FILLS its container in vertical/horizontal layout \u2014 like the conventional Form.Item (vertical \u2192 width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` \u2014 never constrain the FormField itself.","DO use the `error` prop (not a hand-rolled `<p>`) for validation messages \u2014 it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.","DO use `labelAddon` (a ReactNode rendered after the label text, in the label row) for supplementary controls such as a tooltip trigger, a 'copy' icon button or a short text action ('Assign to myself'); in a horizontal/inline layout the label row wraps, so an addon that does not fit beside the label drops under it inside the label column rather than overlapping the control \u2014 never insert such controls as siblings outside FormField, which breaks layout.","DON'T wrap `Switch` in FormField \u2014 use `Field` instead, which already handles the label, hidden `<input name>` for HTML form submission, error, and helper internally.","DON'T use FormField for checkbox-beside-label or radio-beside-label patterns \u2014 use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.","CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div \u2014 Input/Textarea/NumberInput \u2192 the `<input>/<textarea>`; Select/SearchSelect/Cascader/TreeSelect \u2192 the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker \u2192 the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker \u2192 the `<input type=color>` swatch; SearchInput \u2192 the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup \u2192 `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer \u2192 `role=group` \u2014 per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) \u2014 do not reinvent it per control.","FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` \u2014 the field's stable MACHINE key, resolved as `field` \u2192 `name` \u2192 `id` \u2014 onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a \u5E74/\u6708 combo, a value beside a \u300C\u4E0D\u660E\u300D checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` \u2014 which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.","NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends \u2014 turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.","NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission \u2014 Input/Textarea/NumberInput/Select submit natively; SearchSelect submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.","ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."],useCases:["Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.","Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.","Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label\u2013control association.","Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.","Wrapping a `SearchSelect` or `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.","Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."],related:["Label \u2014 the bare Radix label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.","Field \u2014 a self-contained field for boolean toggles: it already includes its own label, hidden `<input name>` for HTML form submission, helper, and error. Never wrap a bare `Switch` in FormField.","Field \u2014 pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.","CheckboxGroup / RadioGroup \u2014 for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."],example:`import { FormField, Input } from "@godxjp/ui/data-entry";
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)} />
@@ -1135,7 +1135,7 @@ import { Button } from "@godxjp/ui/general";
1135
1135
  <SheetHeader><SheetTitle>\u30D5\u30A3\u30EB\u30BF\u30FC\u8A2D\u5B9A</SheetTitle></SheetHeader>
1136
1136
  {/* filter fields */}
1137
1137
  </SheetContent>
1138
- </Sheet>`,storyPath:"feedback/Sheet.stories.tsx",rules:[3]},{name:"Alert",subParts:["AlertActions","AlertBase","AlertContent","AlertDescription","AlertMutationFeedback","AlertQueryError","AlertTitle"],group:"feedback",tagline:"Inline alert banner with variant-aware icon + optional dismiss. Parts: Alert/AlertTitle/AlertDescription/AlertActions/AlertQueryError.",props:[{name:"variant",type:'"default" | "banner"',defaultValue:'"default"',description:'STRUCTURAL axis, orthogonal to `tone` (which owns colour + icon): "default" is the inline rounded card; "banner" is the full-bleed attention strip \u2014 prefer the `Banner` export, which fixes this axis.'},{name:"onDismiss",type:"() => void",description:"Renders an \xD7 dismiss button when provided."},{name:"icon",type:"LucideIcon | false",description:"Override or hide (false) the icon."},{name:"tone",type:'"success" | "warning" | "destructive" | "info" | "neutral"',description:"Semantic tone driving the colour + leading icon."}],usage:["ANATOMY (positions are fixed \u2014 never re-lay-them-out): Alert is ONE horizontal row \u2014 a SINGLE leading tone icon at the inline-start (top-aligned to the first text line, auto-selected by `tone`, never two icons), then the text body (Title/Description), then `<Alert.Actions>` in a trailing-RIGHT column (\u2265sm), and the dismiss \xD7 pinned to the TOP-RIGHT corner (rendered by `onDismiss`). DON'T stack these vertically, DON'T make the action a full-width bar under the text, DON'T center the \xD7 at the bottom, DON'T add a second icon \u2014 that hand-rolled vertical banner is the #1 Alert mistake.",'DO compose text as `<Alert.Title>` + `<Alert.Description>` \u2014 they stack vertically inside the body (the Example below is canonical). When you add `<Alert.Actions>`, the body becomes a two-column grid (text | actions) at \u2265sm; group multi-part text in `<Alert.Content>` so it occupies the text column as one block: `<Alert tone="destructive"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button \u2026/></Alert.Actions></Alert>`.',"DO use `Alert.QueryError` (alias `AlertQueryError`) for TanStack Query / API failure surfaces \u2014 it already renders humanError(error), an i18n title, and an optional Retry button. Never hand-roll that pattern.",'DON\'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` \u2014 the layout slot only activates correctly via the `data-slot="alert-actions"` wrapper.','DON\'T hand-roll a dismiss \u2715 button \u2014 pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label="Dismiss"`. The `onDismiss` handler may return a Promise.','DON\'T suppress the icon with `icon={false}` unless there is a deliberate design reason; the icon is the primary a11y cue for sighted users since the root already carries `role="alert"` for screen readers.',"DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."],useCases:['Page-level error banner after a form submission fails server-side validation \u2014 `tone="destructive"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.',"Inline warning at the top of an accounting invoice list when the OAuth token for the MF sync is about to expire \u2014 `tone=\"warning\"` with an `Alert.Actions` containing a 'Reconnect' Button.",'Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page \u2014 `tone="success"` with `Alert.Description` showing the record count imported.',"TanStack Query data-fetch failure inside a Card body \u2014 use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.","Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade \u2014 `tone=\"info\"` with a short description and an `Alert.Actions` 'Learn more' link.",'Dismissible billing-overdue notice at the top of the dashboard \u2014 `tone="destructive"` with `onDismiss` that sets a session flag so it does not reappear until the next login.'],related:["Toaster \u2014 use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.","AlertMutationFeedback \u2014 use when you want inline success/error feedback tightly coupled to a form mutation's state (renders inline below the submit button). Alert requires you to manage show/hide state yourself.","DataState \u2014 use for full query lifecycle (loading skeleton + empty state + error) inside a data-fetching section. Alert.QueryError is the error sub-component DataState uses internally; prefer DataState when you also need the loading/empty states.","EmptyState \u2014 use for the zero-data case inside a list or table section, not for errors or warnings. Alert is for status messages; EmptyState is for the absence of data."],example:`import { Alert, AlertTitle, AlertDescription } from "@godxjp/ui/feedback";
1138
+ </Sheet>`,storyPath:"feedback/Sheet.stories.tsx",rules:[3]},{name:"Alert",subParts:["AlertActions","AlertBase","AlertContent","AlertDescription","AlertMutationFeedback","AlertQueryError","AlertTitle"],group:"feedback",tagline:"Inline alert banner with variant-aware icon + optional dismiss. Parts: Alert/AlertTitle/AlertDescription/AlertActions/AlertQueryError.",props:[{name:"variant",type:'"default" | "banner"',defaultValue:'"default"',description:'STRUCTURAL axis, orthogonal to `tone` (which owns colour + icon): "default" is the inline rounded card; "banner" is the full-bleed attention strip \u2014 prefer the `Banner` export, which fixes this axis.'},{name:"onDismiss",type:"() => void",description:"Renders an \xD7 dismiss button when provided."},{name:"icon",type:"LucideIcon | false",description:"Override or hide (false) the icon."},{name:"tone",type:'"success" | "warning" | "destructive" | "info" | "neutral"',description:"Semantic tone driving the colour + leading icon."}],usage:["ANATOMY (positions are fixed \u2014 never re-lay-them-out): Alert is ONE horizontal row \u2014 a SINGLE leading tone icon at the inline-start (top-aligned to the first text line, auto-selected by `tone`, never two icons), then the text body (Title/Description), then `<Alert.Actions>` in a trailing-RIGHT column (\u2265sm), and the dismiss \xD7 pinned to the TOP-RIGHT corner (rendered by `onDismiss`). DON'T stack these vertically, DON'T make the action a full-width bar under the text, DON'T center the \xD7 at the bottom, DON'T add a second icon \u2014 that hand-rolled vertical banner is the #1 Alert mistake.",'DO compose text as `<Alert.Title>` + `<Alert.Description>` \u2014 they stack vertically inside the body (the Example below is canonical). When you add `<Alert.Actions>`, the body becomes a two-column grid (text | actions) at \u2265sm; group multi-part text in `<Alert.Content>` so it occupies the text column as one block: `<Alert tone="destructive"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button \u2026/></Alert.Actions></Alert>`.',"DO use `Alert.QueryError` (alias `AlertQueryError`) for TanStack Query / API failure surfaces \u2014 it already renders humanError(error), an i18n title, and an optional Retry button. Never hand-roll that pattern.",'`AlertMutationFeedback` props: `mutation`, `onRetry?`, `showRetry?` (default `true`), `pending?`, `ignoreValidationErrors?: boolean`, `className?`. `ignoreValidationErrors={true}` skips the alert for every error where `classifyQueryError(mutation.error).category === "validation"` (400/422). DEFAULT (omitted): skip a validation error only when rendered inside a `FormRoot`/`Form` whose `errors` bag holds at least one message (the fields show it, `<FormErrors />` shows unclaimed keys); outside such a form, or with an empty/absent bag, the alert renders. So DON\'T hand-guard `{!isValidation && <AlertMutationFeedback \u2026/>}` inside `FormRoot errors={\u2026}` \u2014 a 422 is not drawn twice, and a 5xx still renders. Pass `ignoreValidationErrors={false}` to force the alert in such a form.','DON\'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` \u2014 the layout slot only activates correctly via the `data-slot="alert-actions"` wrapper.','DON\'T hand-roll a dismiss \u2715 button \u2014 pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label="Dismiss"`. The `onDismiss` handler may return a Promise.','DON\'T suppress the icon with `icon={false}` unless there is a deliberate design reason; the icon is the primary a11y cue for sighted users since the root already carries `role="alert"` for screen readers.',"DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."],useCases:['Page-level error banner after a form submission fails server-side validation \u2014 `tone="destructive"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.',"Inline warning at the top of an accounting invoice list when the OAuth token for the MF sync is about to expire \u2014 `tone=\"warning\"` with an `Alert.Actions` containing a 'Reconnect' Button.",'Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page \u2014 `tone="success"` with `Alert.Description` showing the record count imported.',"TanStack Query data-fetch failure inside a Card body \u2014 use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.","Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade \u2014 `tone=\"info\"` with a short description and an `Alert.Actions` 'Learn more' link.",'Dismissible billing-overdue notice at the top of the dashboard \u2014 `tone="destructive"` with `onDismiss` that sets a session flag so it does not reappear until the next login.'],related:["Toaster \u2014 use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.","AlertMutationFeedback \u2014 use when you want inline success/error feedback tightly coupled to a form mutation's state (renders inline below the submit button). Alert requires you to manage show/hide state yourself.","DataState \u2014 use for full query lifecycle (loading skeleton + empty state + error) inside a data-fetching section. Alert.QueryError is the error sub-component DataState uses internally; prefer DataState when you also need the loading/empty states.","EmptyState \u2014 use for the zero-data case inside a list or table section, not for errors or warnings. Alert is for status messages; EmptyState is for the absence of data."],example:`import { Alert, AlertTitle, AlertDescription } from "@godxjp/ui/feedback";
1139
1139
 
1140
1140
  <Alert tone="warning">
1141
1141
  <AlertTitle>3 \u4EF6\u306E\u6253\u523B\u6F0F\u308C\u304C\u3042\u308A\u307E\u3059</AlertTitle>
@@ -2403,7 +2403,7 @@ const messages: ChatMessageProp[] = [
2403
2403
  user: { placement: "end", variant: "outlined" },
2404
2404
  system: { placement: "start", variant: "borderless", tone: "info", size: "sm" },
2405
2405
  }}
2406
- />`,docPath:"data-display/chat-bubble.tsx",storyPath:"data-display/ChatBubbleList.stories.tsx",rules:[6,23,44,45]},{name:"ChatComposer",group:"data-entry",tagline:"The message input of a conversation (Ant Design X Sender): an auto-growing Textarea plus exactly ONE trailing action \u2014 send, or cancel while a response streams. Enter/Shift+Enter is configurable and never fires during an IME conversion.",props:[{name:"value",type:"string",description:"Controlled draft text. Pair with onValueChange or the box freezes."},{name:"defaultValue",type:"string",description:"Uncontrolled initial draft text."},{name:"onValueChange",type:"(value: string) => void",description:"Draft-text change handler; fires on every keystroke, including during an IME conversion."},{name:"onSubmit",type:"(value: string) => void",description:"Send the draft. Never fires for empty or whitespace-only text, nor while loading/disabled/readOnly."},{name:"onCancel",type:"() => void",description:"Stop the in-flight response. Only reachable while loading."},{name:"loading",type:"boolean",defaultValue:"false",description:"A response is streaming: the trailing action BECOMES cancel. Send and cancel never render together."},{name:"submitType",type:'"enter" | "shiftEnter"',defaultValue:'"enter"',description:'"enter": Enter sends, Shift+Enter breaks the line. "shiftEnter": the inverse, for long deliberate drafts.'},{name:"placeholder",type:"string",description:"Empty-state text of the draft box \u2014 pass it through t() at the call site."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disable the composer and every action."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Show the draft without allowing an edit; still focusable."},{name:"header",type:"React.ReactNode",description:"Slot ABOVE the draft row \u2014 attachments, a reply-to banner, a model picker."},{name:"prefix",type:"React.ReactNode",description:"Slot at the inline START of the draft row \u2014 an attach Button, an Avatar."},{name:"footer",type:"React.ReactNode",description:"Slot BELOW the draft row \u2014 a hint line, a token counter."},{name:"actions",type:"React.ReactNode",description:"Extra trailing actions, rendered BEFORE the send/cancel action."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Height tier on the shared --control-height ladder; moves both auto-grow bounds together."},{name:"maxLength",type:"number",description:"Hard ceiling on the draft length, forwarded to the textarea."},{name:"status",type:'"error" | "warning"',description:"Validation state the frame paints. error also reports aria-invalid (colour alone fails WCAG 1.4.1)."},{name:"submitLabel",type:"string",description:"Accessible name override for the send action (localized default otherwise)."},{name:"cancelLabel",type:"string",description:"Accessible name override for the cancel action (localized default otherwise)."},{name:"onKeyDown",type:"React.KeyboardEventHandler<HTMLTextAreaElement>",description:"Keydown on the draft box \u2014 how ChatSuggestion drives its list. A handler that calls preventDefault() owns the key, and the composer will not treat it as a send."},{name:"name",type:"string",description:"Native form name, forwarded to the textarea."},{name:"id",type:"string",description:"DOM id of the textarea (the semantic focus target FormField labels)."}],usage:["DO pair a controlled `value` with `onValueChange` \u2014 a controlled value with no synchronised handler is the classic frozen-input bug, and it freezes the whole conversation.","DO wrap it in FormField when the composer is a labelled field; the label/helper/error contract lands on the <textarea>, which is the semantic focus target (ref goes there too).","DON'T hand-roll Enter-to-send. An IME conversion (ja/vi) fires a real Enter to ACCEPT a candidate; ChatComposer already guards compositionstart/compositionend, and skipping that guard makes Japanese and Vietnamese input impossible.","DON'T render your own stop button beside the send button \u2014 set `loading` and the trailing action becomes cancel. Exactly one trailing action exists at a time (the picker trailing-action discipline).","DO put a hint in `footer` (t('dataEntry.chatComposer.hintEnter')) when you flip `submitType` \u2014 the keystroke contract is invisible otherwise.","DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme."],useCases:["The message box of an AI assistant or support chat, under a ChatBubbleList feed.","A comment composer on a record detail screen (prefix = attach Button, footer = character counter).",'A long-form reply box where Enter must break the line: submitType="shiftEnter".',"A streaming answer the user can stop: loading + onCancel."],related:["Textarea \u2014 the primitive underneath. Use it directly for an ordinary multi-line form field with no send action.","ChatSuggestion \u2014 wraps ChatComposer to add trigger-character (/) autocomplete.","ChatBubbleList \u2014 the feed the composer sends into.","SearchInput \u2014 a single-line query field; a composer is multi-line and holds a draft."],example:['import { ChatComposer } from "@godxjp/ui/data-entry";',"",'const [draft, setDraft] = useState("");',"const [streaming, setStreaming] = useState(false);","","<ChatComposer"," value={draft}"," onValueChange={setDraft}",' onSubmit={(text) => { send(text); setDraft(""); }}'," loading={streaming}"," onCancel={() => setStreaming(false)}",' placeholder={t("chat.placeholder")}',' footer={t("dataEntry.chatComposer.hintEnter")}',"/>"].join(`
2406
+ />`,docPath:"data-display/chat-bubble.tsx",storyPath:"data-display/ChatBubbleList.stories.tsx",rules:[6,23,44,45]},{name:"ChatComposer",group:"data-entry",tagline:"The message input of a conversation (Ant Design X Sender): an auto-growing Textarea plus exactly ONE trailing action \u2014 send, or cancel while a response streams. Enter / Shift+Enter / \u2318-or-Ctrl+Enter is configurable and never fires during an IME conversion.",props:[{name:"value",type:"string",description:"Controlled draft text. Pair with onValueChange or the box freezes."},{name:"defaultValue",type:"string",description:"Uncontrolled initial draft text."},{name:"onValueChange",type:"(value: string) => void",description:"Draft-text change handler; fires on every keystroke, including during an IME conversion."},{name:"onSubmit",type:"(value: string) => void",description:'Send the draft. Never fires for empty or whitespace-only text (unless allowEmptySubmit, which passes ""), nor while loading/disabled/readOnly.'},{name:"onCancel",type:"() => void",description:"Stop the in-flight response. Only reachable while loading."},{name:"loading",type:"boolean",defaultValue:"false",description:"A response is streaming: the trailing action BECOMES cancel. Send and cancel never render together."},{name:"submitType",type:'"enter" | "shiftEnter" | "modEnter"',defaultValue:'"enter"',description:'"enter": Enter sends, Shift+Enter breaks the line. "shiftEnter": the inverse, for long deliberate drafts. "modEnter" (library extension; antd X Sender has only the first two): \u2318+Enter on Apple platforms, Ctrl+Enter elsewhere sends, while Enter and Shift+Enter both break the line \u2014 the record-comment convention (GitHub, Jira, Linear).'},{name:"allowEmptySubmit",type:"boolean",defaultValue:"false",description:'Let an empty or whitespace-only draft be sent when header/footer carry payload of their own (a status change on a record). The send button stays enabled and the button and keyboard submit call onSubmit(""). Still blocked while loading/disabled/readOnly.'},{name:"placeholder",type:"string",description:"Empty-state text of the draft box \u2014 pass it through t() at the call site."},{name:"disabled",type:"boolean",defaultValue:"false",description:"Disable the composer and every action."},{name:"readOnly",type:"boolean",defaultValue:"false",description:"Show the draft without allowing an edit; still focusable."},{name:"header",type:"React.ReactNode",description:"Slot ABOVE the draft row \u2014 attachments, a reply-to banner, a model picker."},{name:"prefix",type:"React.ReactNode",description:"Slot at the inline START of the draft row \u2014 an attach Button, an Avatar."},{name:"footer",type:"React.ReactNode",description:"Slot BELOW the draft row \u2014 a hint line, a token counter."},{name:"actions",type:"React.ReactNode",description:"Extra trailing actions, rendered BEFORE the send/cancel action."},{name:"size",type:'"xs" | "sm" | "md" | "lg"',defaultValue:'"md"',description:"Height tier on the shared --control-height ladder; moves both auto-grow bounds together."},{name:"maxLength",type:"number",description:"Hard ceiling on the draft length, forwarded to the textarea."},{name:"status",type:'"error" | "warning"',description:"Validation state the frame paints. error also reports aria-invalid (colour alone fails WCAG 1.4.1)."},{name:"submitLabel",type:"string",description:"Accessible name override for the send action (localized default otherwise)."},{name:"cancelLabel",type:"string",description:"Accessible name override for the cancel action (localized default otherwise)."},{name:"onKeyDown",type:"React.KeyboardEventHandler<HTMLTextAreaElement>",description:"Keydown on the draft box \u2014 how ChatSuggestion drives its list. A handler that calls preventDefault() owns the key, and the composer will not treat it as a send."},{name:"name",type:"string",description:"Native form name, forwarded to the textarea."},{name:"id",type:"string",description:"DOM id of the textarea (the semantic focus target FormField labels)."}],usage:["DO pair a controlled `value` with `onValueChange` \u2014 a controlled value with no synchronised handler is the classic frozen-input bug, and it freezes the whole conversation.","DO wrap it in FormField when the composer is a labelled field; the label/helper/error contract lands on the <textarea>, which is the semantic focus target (ref goes there too).","DON'T hand-roll Enter-to-send. An IME conversion (ja/vi) fires a real Enter to ACCEPT a candidate; ChatComposer already guards compositionstart/compositionend, and skipping that guard makes Japanese and Vietnamese input impossible.","DON'T render your own stop button beside the send button \u2014 set `loading` and the trailing action becomes cancel. Exactly one trailing action exists at a time (the picker trailing-action discipline).","DO put a hint in `footer` (t('dataEntry.chatComposer.hintEnter') / 'hintShiftEnter') when you flip `submitType` \u2014 the keystroke contract is invisible otherwise. For submitType=\"modEnter\" use t('dataEntry.chatComposer.hintModEnter', { modifier: isApplePlatform() ? '\u2318' : 'Ctrl' }) with isApplePlatform from @godxjp/ui/lib/utils \u2014 the same platform test the composer uses to pick metaKey vs ctrlKey.",'DO set `allowEmptySubmit` (not a hidden fake draft) when the composer also submits field changes from `header`/`footer`; your onSubmit receives "" and decides whether anything changed.',"DON'T size it with a className height: the box grows between --chat-composer-min-height and --chat-composer-max-height, both derived from the --control-height tier. Use `size`, or re-tune the two tokens in your theme."],useCases:["The message box of an AI assistant or support chat, under a ChatBubbleList feed.","A comment composer on a record detail screen (prefix = attach Button, footer = character counter).",'A long-form reply box where Enter must break the line: submitType="shiftEnter".','A comment bar on an issue/record where Enter breaks the line and \u2318/Ctrl+Enter posts, and a status change may be posted without text: submitType="modEnter" + allowEmptySubmit.',"A streaming answer the user can stop: loading + onCancel."],related:["Textarea \u2014 the primitive underneath. Use it directly for an ordinary multi-line form field with no send action.","ChatSuggestion \u2014 wraps ChatComposer to add trigger-character (/) autocomplete.","ChatBubbleList \u2014 the feed the composer sends into.","SearchInput \u2014 a single-line query field; a composer is multi-line and holds a draft."],example:['import { ChatComposer } from "@godxjp/ui/data-entry";',"",'const [draft, setDraft] = useState("");',"const [streaming, setStreaming] = useState(false);","","<ChatComposer"," value={draft}"," onValueChange={setDraft}",' onSubmit={(text) => { send(text); setDraft(""); }}'," loading={streaming}"," onCancel={() => setStreaming(false)}",' placeholder={t("chat.placeholder")}',' footer={t("dataEntry.chatComposer.hintEnter")}',"/>"].join(`
2407
2407
  `),docPath:"data-entry/chat-composer.tsx",storyPath:"data-entry/ChatComposer.stories.tsx",rules:[2,6,43,45]},{name:"ChatSuggestion",group:"data-entry",tagline:"Trigger-character autocomplete over a ChatComposer (Ant Design X Suggestion): type / at a word boundary and a Command list opens against the composer, driven from the textarea without ever taking focus off it.",props:[{name:"items",type:"ChatSuggestionItemProp[]",required:!0,description:"The rows to offer: { value, label?, description?, icon?, disabled?, children? }. One level of children is honoured \u2014 picking a parent drills into it instead of emitting."},{name:"onValueChange",type:"(value: string) => void",description:"Fires with the picked row's value. The CALLER owns what that does to the draft text \u2014 the component never rewrites the textarea behind your back."},{name:"triggerCharacter",type:"string",defaultValue:'"/"',description:'The character that opens the list when typed at a word boundary (use "@" for a mention list).'},{name:"open",type:"boolean",description:"Controlled open state of the list."},{name:"defaultOpen",type:"boolean",description:"Uncontrolled initial open state."},{name:"onOpenChange",type:"(open: boolean) => void",description:"Open-state change handler."},{name:"children",type:"(props: { onTrigger: (value?: string | false) => void; onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> }) => React.ReactNode",required:!0,description:"Render prop wrapping the composer. Call onTrigger from the composer's onValueChange and forward onKeyDown to its onKeyDown."},{name:"emptyMessage",type:"string",description:"Shown when the query matches nothing (localized default otherwise)."},{name:"listLabel",type:"string",description:"Accessible name of the listbox (localized default otherwise)."},{name:"id",type:"string",description:"DOM id of the anchor wrapping the composer."}],usage:["DO wire BOTH halves of the render prop: `onTrigger` from the composer's onValueChange and `onKeyDown` from its onKeyDown. With only one wired the list either never opens or cannot be driven.","DO decide yourself what a pick does to the draft \u2014 onValueChange hands you the value; the typed /query is still in the box, so replace it or append to it as your screen needs.","DON'T hand-roll a listbox next to a textarea. This composes the real Command (cmdk) inside a Popover, which already ships the listbox/option roles, active-row bookkeeping and scroll-into-view.","DO rely on Escape: it closes the list, returns focus to the textarea and leaves the typed text intact. It also stops propagating, so a composer inside a Dialog does not close the Dialog too.","DON'T expect it to filter server-side \u2014 filtering is a plain substring match over label/value/description. For a remote list, filter `items` yourself as the query changes."],useCases:["Slash commands over an assistant composer (/summarize, /translate, /explain).",'Mention picker in a comment composer (triggerCharacter="@").',"Prompt-template inserter grouped one level deep (a category row that drills into its templates)."],related:["ChatComposer \u2014 the control it wraps; use it alone when there is nothing to suggest.","Command / CommandPalette \u2014 a full-screen command surface opened by a shortcut, not by a character in a draft.","Select (showSearch) \u2014 the searchable single-select; a suggestion list edits free text, it does not hold a value."],example:['import { ChatComposer, ChatSuggestion } from "@godxjp/ui/data-entry";',"",'const [draft, setDraft] = useState("");',"","<ChatSuggestion"," items={[",' { value: "summarize", label: "\u8981\u7D04\u3059\u308B", description: "Summarize the thread" },',' { value: "translate", label: "\u7FFB\u8A33\u3059\u308B" },'," ]}",' onValueChange={(value) => setDraft("/" + value + " ")}',">"," {({ onTrigger, onKeyDown }) => ("," <ChatComposer"," value={draft}"," onValueChange={(next) => { setDraft(next); onTrigger(next); }}"," onKeyDown={onKeyDown}"," onSubmit={(text) => send(text)}"," />"," )}","</ChatSuggestion>"].join(`
2408
2408
  `),docPath:"data-entry/chat-composer.tsx",storyPath:"data-entry/ChatSuggestion.stories.tsx",rules:[2,3,6]},{name:"Conversations",group:"navigation",tagline:"The session rail of a chat surface (Ant Design X Conversations): past conversations, the current one marked with aria-current, a per-row overflow menu, and recency buckets \u2014 the whole rail one roving-tabindex tab stop, not one tab stop per conversation.",props:[{name:"items",type:"(ConversationsItemProp | ConversationsDividerProp)[]",description:'The rows. A conversation is { key, label?, group?, icon?, disabled? }; a rule between runs is { type: "divider", key?, dashed? }. Ant Design X `items`.'},{name:"activeKey",type:"string",description:"Controlled selection \u2014 the key of the conversation on screen. Ant Design X `activeKey`."},{name:"defaultActiveKey",type:"string",description:"Uncontrolled initial selection. Ant Design X `defaultActiveKey`."},{name:"onActiveChange",type:"(key: string, item?: ConversationsItemProp | ConversationsDividerProp) => void",description:"Fires with the picked key and the entry behind it. Ant Design X `onActiveChange`."},{name:"menu",type:"ConversationsMenuProp | ((conversation: ConversationsItemProp) => ConversationsMenuProp | undefined)",description:"The per-row overflow menu: { items: [{ key, label, icon?, danger?, disabled? }], onClick?, triggerLabel? }. Pass a function to vary it per row, or return undefined for a row that has no menu. Ant Design X `menu` (antd MenuProps there)."},{name:"groupable",type:"boolean | ConversationsGroupableProp",description:"Bucket rows by their `group` field. The object form takes label (node or (group) => node), collapsible (boolean or (group) => boolean), defaultExpandedKeys, expandedKeys and onExpand. Ant Design X `groupable`."},{name:"creation",type:"ConversationsCreationProp",description:'The "new conversation" button pinned above the rail: { label?, icon?, disabled?, onClick? }. Ant Design X `creation`.'},{name:"label",type:"string",description:"Accessible name of the rail (a plain string \u2014 it lands on aria-label). Localized default otherwise."},{name:"id",type:"string",description:"DOM id of the rail root."}],usage:["DO give every conversation a stable `key` \u2014 it is what activeKey, onActiveChange and the menu callback all address. A key that changes on re-render moves the selection.","DO reach for `menu` for rename/delete instead of adding a second Button to each row. The trigger is keyboard-reachable with the forward arrow (\u2192 in LTR, \u2190 in RTL), so a row's second action costs no extra tab stop.",'DO pass `menu.triggerLabel` when the rows are user content: the default names the row, and twelve identical "More actions" buttons are indistinguishable in a screen reader\'s element list.',"DON'T hand-roll the rail out of full-width Buttons plus aria-current. That is one tab stop PER conversation; this is one for the whole rail, with \u2191/\u2193/Home/End inside it.","DON'T expect `styles`/`classNames` from Ant Design X \u2014 they are deliberately not ported. Retune the rail through the --conversations-* tokens (rules #44/#45).",'DO use `groupable={{ collapsible: true }}` for "Today / Previous 7 days": the bucket headings join the same roving order, so collapsing a bucket is reachable without leaving the rail.'],useCases:["The assistant rail of a chat product \u2014 past sessions, the current one marked, rename and delete per row.","Recency buckets over a long history (Today / Yesterday / Previous 7 days) with the older buckets collapsed.","A rail beside ChatBubbleList and ChatComposer: the three are one surface, and Conversations is the half that used to be missing."],related:["ChatBubbleList \u2014 the feed beside this rail; Conversations picks WHICH feed is shown.","ListRow \u2014 a single-line entity row with a trailing action, for short lists inside a Card. It has no selection, no roving focus and no grouping.","Sidebar / NavList \u2014 route navigation. Use those when a row changes the URL; use Conversations when a row changes which conversation the surface is on.","DropdownMenu \u2014 what the per-row `menu` renders; compose it directly when the menu is not attached to a conversation row."],example:['import { Conversations } from "@godxjp/ui/navigation";',"",'const [active, setActive] = useState("c1");',"","<Conversations"," activeKey={active}"," onActiveChange={setActive}"," groupable={{ collapsible: true }}"," creation={{ onClick: () => startNewChat() }}"," items={[",' { key: "c1", label: "\u8ACB\u6C42\u66F8\u306E\u4E0B\u66F8\u304D", group: "today" },',' { key: "c2", label: "\u7D4C\u8CBB\u7CBE\u7B97\u306E\u898F\u5247", group: "today" },',' { key: "c3", label: "\u51FA\u5F35\u624B\u5F53\u306E\u78BA\u8A8D", group: "earlier" },'," ]}"," menu={{"," items: [",' { key: "rename", label: "\u540D\u524D\u3092\u5909\u66F4" },',' { key: "delete", label: "\u524A\u9664", danger: true },'," ],"," onClick: ({ key, conversation }) => run(key, conversation.key),"," }}","/>"].join(`
2409
2409
  `),docPath:"navigation/conversations.tsx",storyPath:"navigation/Conversations.stories.tsx",rules:[2,6,23,44,45]},{name:"Welcome",group:"data-display",tagline:"The greeting block at the head of an empty conversation (Ant Design X Welcome): glyph, greeting, one line under it, and a trailing slot ON THE TITLE ROW \u2014 which is the placement a hand-roll gets wrong.",props:[{name:"icon",type:"React.ReactNode | string",description:'Leading glyph. A STRING beginning with http(s) is rendered as a decorative <img alt=""> (Ant Design X does the same, with alt="icon"); any other string renders as text.'},{name:"title",type:"React.ReactNode",description:"The greeting. Renders as an <h4>, which is Ant Design X's hardcoded Typography.Title level={4}."},{name:"description",type:"React.ReactNode",description:"The line under the greeting."},{name:"extra",type:"React.ReactNode",description:"Trailing slot on the TITLE row \u2014 a dismiss button, a model picker. Not under the description."},{name:"variant",type:'"filled" | "borderless"',defaultValue:'"filled"',description:"filled gives the block its own tinted ground and hairline; borderless lets it sit on the page."},{name:"id",type:"string",description:"DOM id of the block."}],usage:["DO put it above the composer on an empty chat, with ChatSuggestion or a Prompts row beneath it \u2014 that is the surface it belongs to.","DO pass `extra` for the one action the greeting carries (dismiss, switch model). It lands beside the title, top-aligned, so a two-line title does not float it.","DON'T reach for it as a generic page header \u2014 that is PageContainer's title/subtitle/extra, which owns the page rhythm.","DON'T expect a heading-level prop: Ant Design X hardcodes level 4 and this ports that. Wrap it in your own heading hierarchy if the page needs a different rung.","DON'T expect `styles`/`classNames` from Ant Design X \u2014 retune through the --welcome-* tokens."],useCases:["The first screen of an assistant, before the first message.","The head of a fresh conversation started from the Conversations rail.","A feature introduction card inside a chat surface, dismissed through `extra`."],related:["EmptyState \u2014 the general 'nothing here yet' block for a list or a table. Welcome is the chat surface's greeting and carries an icon/title/description/extra shape of its own.","PageContainer \u2014 owns the PAGE header; Welcome sits inside the page body.","ChatSuggestion / ChatBubbleList \u2014 the rest of the same surface."],example:['import { Welcome } from "@godxjp/ui/data-display";','import { Button } from "@godxjp/ui/general";','import { Bot } from "lucide-react";',"","<Welcome"," icon={<Bot />}",' title="\u3053\u3093\u306B\u3061\u306F"',' description="\u8ACB\u6C42\u3001\u7D4C\u8CBB\u3001\u52E4\u6020\u306E\u3053\u3068\u306A\u3089\u304A\u624B\u4F1D\u3044\u3067\u304D\u307E\u3059\u3002"',' extra={<Button variant="ghost" size="sm">\u9589\u3058\u308B</Button>}',"/>"].join(`
@@ -4430,7 +4430,7 @@ A block with no reason is IGNORED and the finding stands. An unclosed block runs
4430
4430
  The class-shaped rules (gap-*/p-*/m-*, bg-<palette>-*, w-[\u2026], pr-*, dark:*) only read class
4431
4431
  expressions \u2014 a className/class attribute, a class-named binding (\`baseClass\`, \`statusStyles\`,
4432
4432
  \`badgeVariants\`) or a cn()/clsx()/cva() call \u2014 so prose that merely spells a utility is not a
4433
- 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:"26.2.0",godxUiCompatibility:"26.2.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
+ 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:"26.3.0",godxUiCompatibility:"26.3.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})
4434
4434
 
4435
4435
  `;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}]\`
4436
4436
  `,e+=`**When to use:** ${t.whenToUse}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@godxjp/ui-mcp",
3
- "version": "26.2.0",
4
- "godxUiCompatibility": "26.2.x",
3
+ "version": "26.3.0",
4
+ "godxUiCompatibility": "26.3.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",