@godxjp/ui-mcp 33.27.1 → 33.27.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +339 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -178,7 +178,7 @@ import { Button } from "@godxjp/ui/general";
|
|
|
178
178
|
}
|
|
179
179
|
>
|
|
180
180
|
{body}
|
|
181
|
-
</PageContainer>`,docPath:"docs/layout/page-cover.tsx",storyPath:"layout/page-cover.tsx",rules:[]},{name:"Flex",absorbed:["SpaceCompact"],group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls \u2014 and, with `attached`, a row of controls welded into ONE visual unit (antd Space.Compact).",props:[{name:"viewport",type:"boolean",description:"Measure actual top and fit remaining visual viewport; observes resize and scroll."},{name:"basis",type:'"equal"',description:"Equal flexible region with zero block and inline minimum."},{name:"overflow",type:'"hidden" | "auto" | "scroll-x" | "scroll-y"',description:"Internal scroll ownership without document growth."},{name:"rotate",type:"0 | 90 | 180 | 270",description:"Declarative orientation, never infers an action."},{name:"animated",type:"boolean",description:"Orientation transition only; reduced motion is immediate."},{name:"attached",type:"boolean",defaultValue:"false",description:'Weld the children into one control \u2014 antd Space.Compact (v32 folded `SpaceCompact` in here). Each child gets its own flex-item wrapper, inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring, name and `size`. `direction` picks the axis ("row" default, "col"); `gap`/`wrap`/`pad` and the other layout knobs do not apply (a seam has no gap). Named (aria-label/-labelledby, e.g. by a wrapping FormField) it becomes role="group".'},{name:"fullWidth",type:"boolean",defaultValue:"false",description:"`attached` only. The joined row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth). Field children grow to fill it; a Button keeps its content width (gh#1062)."},{name:"density",type:'"compact" | "default" | "comfortable"',description:"`attached` only. Scoped control density for the whole joined row (antd Space.Compact `size`). Each child still carries its OWN `size` prop."},{name:"reveal",type:'"hover"',description:"Floating child actions shown on parent hover/focus-within; always visible on touch."},{name:"bleed",type:"GapProp",description:"Negative inline inset on the token scale."},{name:"surface",type:'"muted" | "popover" | "warning" | "felt" | "etched"',description:"Lightweight surface, without Card structure. Felt is a themeable textured material with scoped high-contrast ink and focus roles; etched is a transparent outlined region."},{name:"shrink",type:"boolean",description:"false prevents shrinking."},{name:"grow",type:"boolean",description:"Grow into remaining flex space."},{name:"as",type:'"div" | "span" | "ul" | "ol" | "li"',description:'Semantic element. A list keeps its marker and indentation unless marker="none" says otherwise.'},{name:"marker",type:'"disc" | "decimal" | "none"',defaultValue:'as="ul" \u2192 "disc", as="ol" \u2192 "decimal"',description:'Marker for a LIST element; ignored by every other tag. marker="none" emits NO data-list, so the list keeps its <ul>/<ol>, its <li> semantics and its gap token, and loses the bullet AND the --space-5 indent \u2014 that is the container a short entity list needs: <Flex as="ul" marker="none" direction="col" gap="none"> with <ListRow as="li"> rows. Before it, the bullet was unconditional and .ui-flex[data-list] > li { display: list-item } outranked [data-slot="list-row"] { display: flex }, so rows fell out of flex layout (94.58px tall instead of 69.98px) and the only move left was a raw <ul>, which carries no gap token (gh#714). disc/decimal stay available so an <ol> can be bulleted, or a <ul> numbered, without a list-style-type in a className.'},{name:"direction",type:'"row" | "col" | {base?: "row" | "col"; sm?: "row" | "col"; md?: "row" | "col"; lg?: "row" | "col"; xl?: "row" | "col"}',description:"Responsive axis; omitted steps inherit."},{name:"gap",type:'"none" | "xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:'Token gap between children, shared with other layout primitives. "none" is a DELIBERATE zero for two lines that read as one block \u2014 a name over its role, a weekday over its date, a tab bar with no seam \u2014 not a way to opt out of the token scale.'},{name:"gapRaw",type:"number",description:"ESCAPE HATCH: a gap in pixels, off every step of the scale. Real designs land on 2px, 5px, 6px, 10px, 14px \u2014 rounding to the nearest named step drifts the layout, and writing the literal in a className is blocked by ui-audit. Spend the ten named steps FIRST: `gap={3}` is 12px and follows the user's --scaling, gapRaw does not. It wins over `gap` (which then emits no class, so the two cannot fight over specificity) and leaves data-gap-raw on the DOM, so every escape stays countable."},{name:"pad",type:"number | { inline?, block?, inlineStart?, inlineEnd?, blockStart?, blockEnd? }",description:'Inner padding on the token scale \u2014 one step for all four sides, or an object keyed by LOGICAL side. Exists because a missing padding prop produced 42 of 51 ui-audit errors in one real consumer (gh#408): `<Flex className="p-3">` was the only move left.'},{name:"padRaw",type:"number | { inline?, block?, inlineStart?, inlineEnd?, blockStart?, blockEnd? }",description:"Raw-pixel padding for values off the scale, same contract and same price as gapRaw: it leaves data-pad-raw on the DOM so each escape is countable. Overrides `pad` PER SIDE, not as a whole."},{name:"fill",type:"boolean",defaultValue:"false",description:'Take the space the siblings leave \u2014 the ELASTIC column of a `fixed | elastic | fixed` row (name \xB7 meter \xB7 figures). Also sets min-inline-size: 0, which is what lets a truncating child ellipse instead of pushing the row wider. Without this axis the only move was className="flex-1 min-w-0", which ui-audit blocks (gh#405 \xA72).'},{name:"width",type:"WidthProp",description:"A FIXED column: number = px, string = any CSS length. Comes with flex: none \u2014 a width a sibling can still squeeze is a suggestion, and stacked rows would start at different x offsets. Leaves data-width-raw on the DOM so the measurement stays countable, like gapRaw/padRaw."},{name:"measure",type:'"narrow" | "medium" | "wide" | "card" | "card-compact"',description:'CENTRE this box and CAP it at a page measure (--page-measure-narrow 42rem / -medium 48rem / -wide 72rem): margin-inline:auto + inline-size:100% + max-inline-size. THE public answer to "full-bleed outside, measured column inside" \u2014 a marketing <section> paints edge to edge and its inner Flex measure="wide" holds the content on the page measure. Deliberately NOT on PageContainer: that owns page padding and a header/toolbar/footer scaffold, so a full-bleed page cannot consume it, and the refusal left the centred column owned by nobody \u2014 three showcases each hand-wrote the same four declarations (gh#839). It adds NO gutter: the page inset is pad={{ inline: 6 }}, and one band with two padding owners drifts. A MAX, so nothing binds below the cap at 390px. An explicit `width` wins. Do not give a READING column "wide" \u2014 prose stays narrow/medium, which bracket Bringhurst\'s 45-75 characters. Omitted emits no attribute at all.'},{name:"align",type:'"start" | "center" | "end" | "stretch" | "baseline"',description:"Cross-axis alignment, emitted as a data attribute for the layout CSS."},{name:"justify",type:'"start" | "center" | "end" | "between" | "around" | "evenly"',description:"Main-axis distribution, emitted as a data attribute for the layout CSS."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allows children to wrap onto additional flex lines."},{name:"hideBelow",type:'"sm" | "md" | "lg" | "xl"',description:'Drop this region below a canonical breakpoint step (sm 40rem, md 48rem, lg 64rem, xl 80rem). THE public way to make a region responsive without a page-local media query \u2014 a public header hides its anchor navigation with hideBelow="md". Omitted by default, so no attribute is emitted and no rule matches. display:none also removes it from the a11y tree, so keep its destinations reachable elsewhere (a footer nav) at that width.'},{name:"hideFrom",type:'"sm" | "md" | "lg" | "xl"',description:"Inverse of hideBelow \u2014 drop the region FROM that step upwards, i.e. keep it only on the narrow side (a compact-only affordance)."},{name:"hideBelowRaw",type:"number",description:"ESCAPE HATCH: the same drop, at a width in PIXELS off every step of the scale \u2014 the gapRaw/padRaw contract on the breakpoint axis (gh#528). An off-scale breakpoint is a LOCAL EXCEPTION, not a new tier: sm/md/lg/xl are shared with collapseBelow and --master-detail-collapse-below, so two regions asked to fold at the same step really do fold together, while a raw width folds one region only. Spend the four steps first; reach here when the canonical design specifies a width the scale has not got (a nav that becomes a hamburger at 900px). It leaves data-hide-below-raw on the DOM so every escape stays countable, and prints one deduped media rule per distinct width (a media query cannot read a var()). It wins over hideBelow, which then emits nothing."},{name:"hideFromRaw",type:"number",description:"Inverse of hideBelowRaw, same contract and same price (data-hide-from-raw). THE SEAM: hideBelowRaw hides while width < N and hideFromRaw hides while width >= N \u2014 the same comparisons as the token steps, so the pair is exact complements and at exactly N the hideBelowRaw region is the visible one. Never hand-roll this with an inclusive max-width: `<= N` paired with `>= N` hides BOTH at exactly N, the 1px hole a consumer measured on a hand-rolled max-[900px]: pair."}],usage:['DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',"DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than simple row composition.','DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. For pure vertical stacking without alignment control, `direction="col"` is sufficient.',"DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.",'FULL-BLEED BAND: a marketing/landing section is a `<section>` carrying the edge-to-edge paint (background, border, halo) around ONE `<Flex direction="col" measure="wide" pad={{ inline: 6, block: 20 }}>`. The measure centres and caps the column, `pad` owns the gutter and the band rhythm (`block: 20` = 80px, `block: 24` = 96px \u2014 the two band steps). Never a page-local `max-width: 1200px` + `margin-inline: auto` class, and never an inline style saying the same thing: that four-declaration idiom is what this prop replaced (gh#839).','Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper. When the parent only accepts phrasing content \u2014 a TabsTrigger, PopoverTrigger or Button, all of which render a <button> \u2014 pass `as="span"` rather than reaching for a raw `<span className="flex \u2026">`.','SEMANTIC LIST: a list of rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children \u2014 never a raw `<ul>` (no gap token), never `<div role="list">` + `<div role="listitem">` (ARIA re-describing markup HTML already has), and never a wrapper around each row: the divider is `:not(:last-child)` among SIBLINGS, so a row alone in its own wrapper loses it silently. `ui-audit` flags all three as `no-hand-rolled-list`.',"NAMED FLEX = GROUP: a role-less div may not carry a naming attribute (axe aria-allowed-attr), so a Flex given `aria-label`/`aria-labelledby` \u2014 e.g. by FormField wrapping a composite range/\u5E74\u6708 field \u2014 automatically renders `role='group'`, folds `aria-errormessage` into `aria-describedby`, and drops the widget-only `aria-required`/`aria-invalid`. Passing an explicit `role` opts out of all of this and the caller owns the attribute set.",'ATTACHED: DO reach for it when two or more controls must read as ONE field \u2014 a number + a unit ("\u6BCE [2] [\u9031 \u25BE] \u3054\u3068"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); `Flex attached` keeps them fused on one line regardless of the grid.','ATTACHED: ONE label for the pair vs per-control: wrap the WHOLE `Flex attached` in a single `FormField label="\u2026"` when the pair reads as one field ("\u7E70\u308A\u8FD4\u3057\u9593\u9694" over a number+unit row) \u2014 a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED `Flex attached` with no explicit `role` promotes itself to `role="group"` automatically, the same contract `Flex` already honours for a range/\u5E74\u6708 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare \u2014 prefer the one-label pattern for a welded control).',"ATTACHED: DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself \u2014 `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.","ATTACHED: DO set `size` on each CHILD individually, not on `Flex attached` \u2014 the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).","ATTACHED: DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",'ATTACHED: `fullWidth` is antd `Space.Compact block` (gh#1062): in a row, a FIELD child (Input and its affix/Password/Search forms, NumberInput, Textarea, TagInput, the Date/Time pickers, and the Select/Cascader/TreeSelect/SearchSelect trigger) grows to fill the row, and every other child \u2014 a Button, an icon button \u2014 keeps its content width. So `[Input][+ Add]` is a full-width field with its submit at content width, and two fields (NumberInput + Select) share the row evenly between them. Deviation from antd, written down: antd grows only its Input family (`width: 100%`); Select/InputNumber/DatePicker keep their own width there. Here every field grows, so two fields never leave an empty tail. `direction="col"` + `fullWidth` stacks full-width children as before.','ATTACHED: DON\'T expect corner-radius welding on `direction="col"` yet \u2014 the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one).'],useCases:["Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.","Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.","Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.","Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.","Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.","Low-level layout composition inside custom components where raw flex classes would duplicate the primitive.",'ATTACHED: a recurring-issue interval row, "\u6BCE [NumberInput] [Select \u9031/\u6708/\u65E5 \u25BE] \u3054\u3068", fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.',"ATTACHED: A currency amount: [Select \u901A\u8CA8 \u25BE][NumberInput \u91D1\u984D] welded so the currency reads as part of the amount field, not a separate control beside it.","ATTACHED: A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."],related:["Flex `direction='col'` \u2014 the standard pattern for ordinary vertical block spacing; use explicit `align`, `justify`, or `wrap` props when you need more control.","Flex `direction='row'` \u2014 the standard pattern for simple horizontal groups; add `wrap` and `align='center'` for the typical row with wrapped centered items.","ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.","PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars. PageContainer `measure` bounds an APP page (header and body together, narrow | medium); Flex `measure` bounds ONE centred column inside a full-bleed band, and is the only one that reaches the marketing `wide` step."],example:`import { Flex } from "@godxjp/ui/layout";
|
|
181
|
+
</PageContainer>`,docPath:"docs/layout/page-cover.tsx",storyPath:"layout/page-cover.tsx",rules:[]},{name:"Flex",absorbed:["SpaceCompact"],group:"layout",tagline:"Token-spaced flex primitive with explicit direction, alignment, justification, and wrapping controls \u2014 and, with `attached`, a row of controls welded into ONE visual unit (antd Space.Compact).",props:[{name:"viewport",type:"boolean",description:"Measure actual top and fit remaining visual viewport; observes resize and scroll."},{name:"basis",type:'"equal"',description:"Equal flexible region with zero block and inline minimum."},{name:"overflow",type:'"hidden" | "auto" | "scroll-x" | "scroll-y"',description:"Internal scroll ownership without document growth."},{name:"rotate",type:"0 | 90 | 180 | 270",description:"Declarative orientation, never infers an action."},{name:"animated",type:"boolean",description:"Orientation transition only; reduced motion is immediate."},{name:"attached",type:"boolean",defaultValue:"false",description:'Weld the children into one control \u2014 antd Space.Compact (v32 folded `SpaceCompact` in here). Each child gets its own flex-item wrapper, inner corner radii are zeroed and the shared border collapses to one hairline; each child keeps its own focus ring, name and `size`. `direction` picks the axis ("row" default, "col"); `gap`/`wrap`/`pad` and the other layout knobs do not apply (a seam has no gap). Named (aria-label/-labelledby, e.g. by a wrapping FormField) it becomes role="group".'},{name:"fullWidth",type:"boolean",defaultValue:"false",description:"`attached` only. The joined row fills its parent's inline size (antd `block`, renamed to match Button.fullWidth). Field children grow to fill it; a Button keeps its content width (gh#1062)."},{name:"density",type:'"compact" | "default" | "comfortable"',description:"`attached` only. Scoped control density for the whole joined row (antd Space.Compact `size`). Each child still carries its OWN `size` prop."},{name:"reveal",type:'"hover"',description:"Floating child actions shown on parent hover/focus-within; always visible on touch."},{name:"bleed",type:"GapProp",description:"Negative inline inset on the token scale."},{name:"surface",type:'"muted" | "popover" | "warning" | "felt" | "etched"',description:"Lightweight surface, without Card structure. Felt is a themeable textured material with scoped high-contrast ink and focus roles; etched is a transparent outlined region."},{name:"shrink",type:"boolean",description:"false prevents shrinking."},{name:"grow",type:"boolean",description:"Grow into remaining flex space."},{name:"as",type:'"div" | "span" | "ul" | "ol" | "li"',description:'Semantic element. A list keeps its marker and indentation unless marker="none" says otherwise.'},{name:"marker",type:'"disc" | "decimal" | "none"',defaultValue:'as="ul" \u2192 "disc", as="ol" \u2192 "decimal"',description:'Marker for a LIST element; ignored by every other tag. marker="none" emits NO data-list, so the list keeps its <ul>/<ol>, its <li> semantics and its gap token, and loses the bullet AND the --space-5 indent \u2014 that is the container a short entity list needs: <Flex as="ul" marker="none" direction="col" gap="none"> with <ListRow as="li"> rows. Before it, the bullet was unconditional and .ui-flex[data-list] > li { display: list-item } outranked [data-slot="list-row"] { display: flex }, so rows fell out of flex layout (94.58px tall instead of 69.98px) and the only move left was a raw <ul>, which carries no gap token (gh#714). disc/decimal stay available so an <ol> can be bulleted, or a <ul> numbered, without a list-style-type in a className.'},{name:"direction",type:'"row" | "col" | {base?: "row" | "col"; sm?: "row" | "col"; md?: "row" | "col"; lg?: "row" | "col"; xl?: "row" | "col"}',description:"Responsive axis; omitted steps inherit."},{name:"gap",type:'"none" | "xs" | "sm" | "md" | "lg" | "xl"',defaultValue:'"md"',description:'Token gap between children, shared with other layout primitives. "none" is a DELIBERATE zero for two lines that read as one block \u2014 a name over its role, a weekday over its date, a tab bar with no seam \u2014 and for a list of ListRows (`<Flex as="ul" marker="none" direction="col" gap="none">`), whose own dividers are the seam: any gap would split an `unread` band into separate stripes. It is not a way to opt out of the token scale anywhere else.'},{name:"gapRaw",type:"number",description:"ESCAPE HATCH: a gap in pixels, off every step of the scale. Real designs land on 2px, 5px, 6px, 10px, 14px \u2014 rounding to the nearest named step drifts the layout, and writing the literal in a className is blocked by ui-audit. Spend the ten named steps FIRST: `gap={3}` is 12px and follows the user's --scaling, gapRaw does not. It wins over `gap` (which then emits no class, so the two cannot fight over specificity) and leaves data-gap-raw on the DOM, so every escape stays countable."},{name:"pad",type:"number | { inline?, block?, inlineStart?, inlineEnd?, blockStart?, blockEnd? }",description:'Inner padding on the token scale \u2014 one step for all four sides, or an object keyed by LOGICAL side. Exists because a missing padding prop produced 42 of 51 ui-audit errors in one real consumer (gh#408): `<Flex className="p-3">` was the only move left.'},{name:"padRaw",type:"number | { inline?, block?, inlineStart?, inlineEnd?, blockStart?, blockEnd? }",description:"Raw-pixel padding for values off the scale, same contract and same price as gapRaw: it leaves data-pad-raw on the DOM so each escape is countable. Overrides `pad` PER SIDE, not as a whole."},{name:"fill",type:"boolean",defaultValue:"false",description:'Take the space the siblings leave \u2014 the ELASTIC column of a `fixed | elastic | fixed` row (name \xB7 meter \xB7 figures). Also sets min-inline-size: 0, which is what lets a truncating child ellipse instead of pushing the row wider. Without this axis the only move was className="flex-1 min-w-0", which ui-audit blocks (gh#405 \xA72).'},{name:"width",type:"WidthProp",description:"A FIXED column: number = px, string = any CSS length. Comes with flex: none \u2014 a width a sibling can still squeeze is a suggestion, and stacked rows would start at different x offsets. Leaves data-width-raw on the DOM so the measurement stays countable, like gapRaw/padRaw."},{name:"measure",type:'"narrow" | "medium" | "wide" | "card" | "card-compact"',description:'CENTRE this box and CAP it at a page measure (--page-measure-narrow 42rem / -medium 48rem / -wide 72rem): margin-inline:auto + inline-size:100% + max-inline-size. THE public answer to "full-bleed outside, measured column inside" \u2014 a marketing <section> paints edge to edge and its inner Flex measure="wide" holds the content on the page measure. Deliberately NOT on PageContainer: that owns page padding and a header/toolbar/footer scaffold, so a full-bleed page cannot consume it, and the refusal left the centred column owned by nobody \u2014 three showcases each hand-wrote the same four declarations (gh#839). It adds NO gutter: the page inset is pad={{ inline: 6 }}, and one band with two padding owners drifts. A MAX, so nothing binds below the cap at 390px. An explicit `width` wins. Do not give a READING column "wide" \u2014 prose stays narrow/medium, which bracket Bringhurst\'s 45-75 characters. Omitted emits no attribute at all.'},{name:"align",type:'"start" | "center" | "end" | "stretch" | "baseline"',description:"Cross-axis alignment, emitted as a data attribute for the layout CSS."},{name:"justify",type:'"start" | "center" | "end" | "between" | "around" | "evenly"',description:"Main-axis distribution, emitted as a data attribute for the layout CSS."},{name:"wrap",type:"boolean",defaultValue:"false",description:"Allows children to wrap onto additional flex lines."},{name:"hideBelow",type:'"sm" | "md" | "lg" | "xl"',description:'Drop this region below a canonical breakpoint step (sm 40rem, md 48rem, lg 64rem, xl 80rem). THE public way to make a region responsive without a page-local media query \u2014 a public header hides its anchor navigation with hideBelow="md". Omitted by default, so no attribute is emitted and no rule matches. display:none also removes it from the a11y tree, so keep its destinations reachable elsewhere (a footer nav) at that width.'},{name:"hideFrom",type:'"sm" | "md" | "lg" | "xl"',description:"Inverse of hideBelow \u2014 drop the region FROM that step upwards, i.e. keep it only on the narrow side (a compact-only affordance)."},{name:"hideBelowRaw",type:"number",description:"ESCAPE HATCH: the same drop, at a width in PIXELS off every step of the scale \u2014 the gapRaw/padRaw contract on the breakpoint axis (gh#528). An off-scale breakpoint is a LOCAL EXCEPTION, not a new tier: sm/md/lg/xl are shared with collapseBelow and --master-detail-collapse-below, so two regions asked to fold at the same step really do fold together, while a raw width folds one region only. Spend the four steps first; reach here when the canonical design specifies a width the scale has not got (a nav that becomes a hamburger at 900px). It leaves data-hide-below-raw on the DOM so every escape stays countable, and prints one deduped media rule per distinct width (a media query cannot read a var()). It wins over hideBelow, which then emits nothing."},{name:"hideFromRaw",type:"number",description:"Inverse of hideBelowRaw, same contract and same price (data-hide-from-raw). THE SEAM: hideBelowRaw hides while width < N and hideFromRaw hides while width >= N \u2014 the same comparisons as the token steps, so the pair is exact complements and at exactly N the hideBelowRaw region is the visible one. Never hand-roll this with an inclusive max-width: `<= N` paired with `>= N` hides BOTH at exactly N, the 1px hole a consumer measured on a hand-rolled max-[900px]: pair."}],usage:['DO import from `@godxjp/ui/layout` and reach for Flex when the axis, alignment, justification, or wrap behavior is part of the component contract: `import { Flex } from "@godxjp/ui/layout"`.',"DO keep spacing on the `gap` prop instead of raw `gap-*`, `space-*`, or padding utilities. Flex uses the same token scale as other layout primitives, so spacing remains tied to the design system.",'DO use `direction="row"` with `wrap` for responsive control rows, chip clusters, and action groups that need more control than simple row composition.','DO use `direction="col"` for vertical groupings that need explicit `align` or `justify` behavior. For pure vertical stacking without alignment control, `direction="col"` is sufficient.',"DON'T override the axis with `className` after choosing a direction prop. Keep the layout intent in props so catalog guidance and data attributes stay accurate.",'FULL-BLEED BAND: a marketing/landing section is a `<section>` carrying the edge-to-edge paint (background, border, halo) around ONE `<Flex direction="col" measure="wide" pad={{ inline: 6, block: 20 }}>`. The measure centres and caps the column, `pad` owns the gutter and the band rhythm (`block: 20` = 80px, `block: 24` = 96px \u2014 the two band steps). Never a page-local `max-width: 1200px` + `margin-inline: auto` class, and never an inline style saying the same thing: that four-declaration idiom is what this prop replaced (gh#839).','Flex is a plain div with React.HTMLAttributes<HTMLDivElement>; pass `id`, `role`, `aria-*`, `data-*`, and structural className values as needed, but do not use it as a semantic form or button wrapper. When the parent only accepts phrasing content \u2014 a TabsTrigger, PopoverTrigger or Button, all of which render a <button> \u2014 pass `as="span"` rather than reaching for a raw `<span className="flex \u2026">`.','SEMANTIC LIST: a list of rows is `<Flex as="ul" marker="none" direction="col" gap="none">` with `<ListRow as="li">` children \u2014 never a raw `<ul>` (no gap token), never `<div role="list">` + `<div role="listitem">` (ARIA re-describing markup HTML already has), and never a wrapper around each row: the divider is `:not(:last-child)` among SIBLINGS, so a row alone in its own wrapper loses it silently. `ui-audit` flags all three as `no-hand-rolled-list`.',"NAMED FLEX = GROUP: a role-less div may not carry a naming attribute (axe aria-allowed-attr), so a Flex given `aria-label`/`aria-labelledby` \u2014 e.g. by FormField wrapping a composite range/\u5E74\u6708 field \u2014 automatically renders `role='group'`, folds `aria-errormessage` into `aria-describedby`, and drops the widget-only `aria-required`/`aria-invalid`. Passing an explicit `role` opts out of all of this and the caller owns the attribute set.",'ATTACHED: DO reach for it when two or more controls must read as ONE field \u2014 a number + a unit ("\u6BCE [2] [\u9031 \u25BE] \u3054\u3068"), a currency Select welded to an amount Input, a search field with an attached filter Select. `ResponsiveGrid columns={2}` puts them in separate cells (which collapses to ONE column on a narrow form card); `Flex attached` keeps them fused on one line regardless of the grid.','ATTACHED: ONE label for the pair vs per-control: wrap the WHOLE `Flex attached` in a single `FormField label="\u2026"` when the pair reads as one field ("\u7E70\u308A\u8FD4\u3057\u9593\u9694" over a number+unit row) \u2014 a role-less `<div>` cannot carry a cloned `aria-label`/`aria-labelledby` (axe `aria-allowed-attr`), so a NAMED `Flex attached` with no explicit `role` promotes itself to `role="group"` automatically, the same contract `Flex` already honours for a range/\u5E74\u6708 pair. Give each CHILD its own `aria-label` instead only when the two controls are independently meaningful outside the row (rare \u2014 prefer the one-label pattern for a welded control).',"ATTACHED: DON'T reach for `className=\"rounded-none\"` / `[&>*:not(:first-child)]` utilities to join controls yourself \u2014 `ui-audit`'s `no-utility-layout` rule blocks exactly that, and it is precisely the corner-radius seam this component owns.","ATTACHED: DO set `size` on each CHILD individually, not on `Flex attached` \u2014 the row has no size prop of its own by design (see `density`); every control already owns its own `size` axis (`xs|sm|md|lg`).","ATTACHED: DO use `fullWidth` inside a narrow form card so the joined row spans the field column, exactly like a lone Input would.",'ATTACHED: `fullWidth` is antd `Space.Compact block` (gh#1062): in a row, a FIELD child (Input and its affix/Password/Search forms, NumberInput, Textarea, TagInput, the Date/Time pickers, and the Select/Cascader/TreeSelect/SearchSelect trigger) grows to fill the row, and every other child \u2014 a Button, an icon button \u2014 keeps its content width. So `[Input][+ Add]` is a full-width field with its submit at content width, and two fields (NumberInput + Select) share the row evenly between them. Deviation from antd, written down: antd grows only its Input family (`width: 100%`); Select/InputNumber/DatePicker keep their own width there. Here every field grows, so two fields never leave an empty tail. `direction="col"` + `fullWidth` stacks full-width children as before.','ATTACHED: DON\'T expect corner-radius welding on `direction="col"` yet \u2014 the shared border still collapses, but each child keeps all four of its own corners rounded until a block-axis radius knob exists on the Input/trigger families (documented gap, not a silent one).'],useCases:["Toolbar internals where controls should sit in a row, wrap on narrow widths, and stay vertically centered.","Card headers that need title content on the left and actions on the right via `justify='between'` without hand-rolling flex utility classes.","Empty-state or loading blocks that center content on both axes using `align='center'` and `justify='center'`.","Form sub-sections where a vertical group needs stretched children or centered helper content beyond what a plain column Flex provides.","Badge, chip, or tag clusters where wrapping is required but the caller also needs explicit gap control.","Low-level layout composition inside custom components where raw flex classes would duplicate the primitive.",'ATTACHED: a recurring-issue interval row, "\u6BCE [NumberInput] [Select \u9031/\u6708/\u65E5 \u25BE] \u3054\u3068", fused on one line inside a FormField, inside a 2-column ResponsiveGrid form card that collapses to one column.',"ATTACHED: A currency amount: [Select \u901A\u8CA8 \u25BE][NumberInput \u91D1\u984D] welded so the currency reads as part of the amount field, not a separate control beside it.","ATTACHED: A filtered search bar: [Select scope][SearchInput query] as one visual field, `fullWidth` inside a page toolbar."],related:["Flex `direction='col'` \u2014 the standard pattern for ordinary vertical block spacing; use explicit `align`, `justify`, or `wrap` props when you need more control.","Flex `direction='row'` \u2014 the standard pattern for simple horizontal groups; add `wrap` and `align='center'` for the typical row with wrapped centered items.","ResponsiveGrid \u2014 use for equal-width, multi-column tile layouts. Flex arranges children on one flex axis and does not provide column-count behavior.","PageContainer \u2014 page scaffold and padding context. Flex is an inner layout primitive used inside page sections, cards, dialogs, and toolbars. PageContainer `measure` bounds an APP page (header and body together, narrow | medium); Flex `measure` bounds ONE centred column inside a full-bleed band, and is the only one that reaches the marketing `wide` step."],example:`import { Flex } from "@godxjp/ui/layout";
|
|
182
182
|
import { Button } from "@godxjp/ui/general";
|
|
183
183
|
|
|
184
184
|
<Flex direction="row" gap="sm" align="center" justify="between" wrap>
|
|
@@ -869,7 +869,7 @@ import { Flex } from "@godxjp/ui/layout";
|
|
|
869
869
|
]}
|
|
870
870
|
/>`,docPath:"data-display/feature-list.tsx",storyPath:"data-display/feature-list.tsx",rules:[]},{name:"Persona",group:"data-display",tagline:"Compact person identity: fixed avatar, independently truncated name and muted secondary line, optional inline badge.",props:[{name:"name",type:"string",required:!0,description:"Full primary label, retained in native title and accessible text; dir=auto preserves its beginning in RTL."},{name:"description",type:"string",description:"Optional full secondary label (email/handle), independently truncated and titled with dir=auto."},{name:"avatarSrc",type:"string",description:"Optional image URL; Avatar owns loading and fallback."},{name:"avatarFallback",type:"ReactNode",description:"Fallback content; defaults to the first Unicode code point of name. Avatar is decorative beside the full name."},{name:"trailing",type:"ReactNode",description:"Optional noninteractive badge beside the name, outside its truncation box."},{name:"size",type:'"sm" | "md"',defaultValue:'"md"',description:"Avatar control tier and name type scale; secondary text stays xs."},{name:"className",type:"string",description:"Root class; native span attributes and ref are forwarded."}],usage:["Use inside DataTable renderers and stacked rows. Persona owns no row padding, navigation, account action or business state.","Pass localized labels and badge content. Retune --persona-* tokens; avatar sizing uses existing --avatar-* tokens.","Both full labels stay in the accessibility tree and native titles. Keep trailing content noninteractive."],useCases:["Member lists","Assignee and owner cells"],related:["Avatar \u2014 mark only","ListRow \u2014 padded short-list row with actions","AccountChip \u2014 signed-in account control"],example:`import { Persona } from "@godxjp/ui/data-display";
|
|
871
871
|
|
|
872
|
-
<Persona name={member.name} description={member.email} size="sm" />`,docPath:"data-display/persona.tsx",storyPath:"data-display/persona.tsx",rules:[6,45]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"asChild",type:"boolean",defaultValue:"false",description:'Supply a link child to make the entire row a native link. Use aria-current=page for the current destination; never nest interactive trailing controls. Combine with `as="li"` inside a `<Flex as="ul" marker="none">` \u2014 the row stays the link and `as` supplies the list item around it.'},{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line. ONE line, deliberately: a row that wants two (an endpoint, then its events) is asking for a different surface \u2014 antd `List.Item.Meta` has the same single `description`, so this is NOT an antd parity gap. Put the second line in `trailing` (a Badge, a count) or move the row to `Descriptions` / a detail panel (gh#714 \xA73)."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:'Render element \u2014 `li` when the parent is a semantic list (`<Flex as="ul" marker="none">` \u2014 a raw `<ul>` carries no gap token and, without `marker="none"`, the bullet rule `.ui-flex[data-list] > li { display: list-item }` outranks `display: flex` on the row itself). WITH `asChild` the child owns the row element, so `as` becomes the list ITEM around it: `<li data-slot="list-row-item"><a data-slot="list-row">`. Use both for a list of links \u2014 do not wrap the row in an `<li>` (or a `role="listitem"` div) yourself, because that makes every row an only child and the row-to-row divider, keyed on `:not(:last-child)` among siblings, stops matching on every row.'},{name:"overflow",type:'"truncate" | "wrap"',defaultValue:'"truncate"',description:"How title/description resolve content longer than the row \u2014 `truncate` (one line + ellipsis) or `wrap` (multi-line; long unbroken tokens break via `overflow-wrap: anywhere`). Either way the content column may shrink below its intrinsic width, so the row never widens the page root."},{name:"density",type:'"default" | "compact"',defaultValue:'"default"',description:"Row geometry. It only lowers thresholds \u2014 the row and its trailing cluster still wrap, so a cluster that cannot fit drops to its own line rather than widening the page root."},{name:"unread",type:"boolean",description:"Read/unread state for notification rows \u2014 renders the indicator dot (with localized `sr-only` text, never colour alone) plus the tokenized `--list-row-unread-background`. OMIT the prop for rows with no read state; pass `false` for a read row so its title keeps the same optical axis as the unread ones."}],usage:["DO rely on ListRow for a cut title (gh#1298): a title or description that is actually truncated gets a `title` with its text (a `Text ellipsis` inside it, never a badge beside it) and unfolds while the row holds keyboard focus; when nothing in the row takes focus, the cut line gets its own tab stop. Do not add a hand-rolled Tooltip for that.","DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.","DO host ListRow in a DataTable column when the list needs ROW SELECTION (a mail/notification inbox with bulk actions) \u2014 DataTable is the only primitive that selects rows. Mark that column `flush: true` so the cell drops its own padding and the ListRow owns the inset (gh#1016); without it the cell padding stacks on the row's (a 32px dead gap after the checkbox, taller rows) and the unread band stops at the checkbox cell. DON'T zero the cell with a className.",'DO write a list of LINKS as `<Flex as="ul" marker="none" direction="col" gap="none">` + `<ListRow as="li" asChild><Link/></ListRow>` \u2014 one call gives the list item, the whole-row link and the divider. DON\'T wrap the row in your own `<li>` or `role="listitem"` element to get list semantics back: the divider rule reads `:not(:last-child)` among the rows themselves, so a wrapper per row makes each one an only child and EVERY divider disappears \u2014 silently, which is why `ui-audit` now flags it as `no-hand-rolled-list`.','DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic list, and build that list as `<Flex as="ul" marker="none">` \u2014 `ui-audit` flags a raw `<ul>`/`role="list"` as `no-hand-rolled-list`.','DO use `unread` for a notification list \u2014 the dot is a SHAPE with localized `sr-only` text ("Unread"/"Read"), so it never reads as colour alone, and the row surface reads `--list-row-unread-background` (default `hsl(var(--muted))` \u2014 chosen so the xs muted description line stays WCAG AA on the emphasized surface; `--accent` would drop it to 4.23:1). DON\'T substitute a `Badge` \u2014 that renders a labelled pill, not a compact status dot.','DO pass `density="compact"` for the canonical invitation / history row \u2014 an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (\u2248326px content at 390px), or a history row whose status Badge + ISO-8601 date belongs beside the title. Measured at 390px: 62px tall vs 126px at the default density (where the actions wrapped), history row 41px vs 114px. DON\'T reach for it just to "make things tighter" on a roomy page \u2014 the default density is the entity-row measure.','DON\'T add one-off `min-width`/wrapping CSS in the consumer app for a long title + two trailing Buttons. The row already shrinks and WRAPS: the content column keeps only `min(var(--list-row-body-min-width), 100%)` and the trailing actions drop onto their own line below the threshold. Retune the threshold with `--list-row-body-min-width` (default 12rem) and the action gap with `--list-row-trailing-gap`; pass `overflow="wrap"` (usually with `align="start"`) when the title must stay fully readable at 390px instead of truncating.','DO expect rows ANYWHERE inside a padded (non-flush) `CardContent` \u2014 directly, in a `Flex as="ul"`, or wrapped (`DataState` children in a div > `Flex as="ul"` > `ListRow as="li" asChild` > link, gh#1325) \u2014 to drop their own inline padding: the card already holds the inset, so the row text lines up with the CardTitle instead of sitting 16px inside it. A row keeps `--list-row-padding-x` inside `CardContent flush` (the documented home, with edge-to-edge dividers) \u2014 including a flush CardContent nested in a padded one \u2014 and inside a table cell.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",'Notifications inbox \u2014 `unread` rows carry the dot + emphasized surface, `overflow="wrap"` keeps a long JA/EN/VI title and its ISO-8601 timestamp readable, and two inline trailing Buttons (Mark as read / Open) wrap under the text at 390px.',"Pending invitations \u2014 Avatar in leading, the organization/invitation name as title, and Accept + Decline Buttons in trailing that stack at narrow widths without a horizontal page scrollbar."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
872
|
+
<Persona name={member.name} description={member.email} size="sm" />`,docPath:"data-display/persona.tsx",storyPath:"data-display/persona.tsx",rules:[6,45]},{name:"ListRow",group:"data-display",tagline:"Single-line entity row (leading \xB7 title/description \xB7 trailing action) for SHORT lists inside a Card \u2014 sessions, API tokens, linked accounts, passkeys, MFA factors, invitations.",props:[{name:"asChild",type:"boolean",defaultValue:"false",description:'Supply a link child to make the entire row a native link. Use aria-current=page for the current destination; never nest interactive trailing controls. Combine with `as="li"` inside a `<Flex as="ul" marker="none">` \u2014 the row stays the link and `as` supplies the list item around it.'},{name:"title",type:"ReactNode",required:!0,description:"Primary line \u2014 rendered in medium weight; truncates to one line."},{name:"description",type:"ReactNode",description:"Secondary line under the title (muted, xs); truncates to one line. ONE line, deliberately: a row that wants two (an endpoint, then its events) is asking for a different surface \u2014 antd `List.Item.Meta` has the same single `description`, so this is NOT an antd parity gap. Put the second line in `trailing` (a Badge, a count) or move the row to `Descriptions` / a detail panel (gh#714 \xA73)."},{name:"leading",type:"ReactNode",description:"Leading slot \u2014 a decorative icon or an Avatar. Mark a purely decorative icon `aria-hidden`."},{name:"trailing",type:"ReactNode",description:"Trailing slot \u2014 the row action(s): a Button / DropdownMenu trigger, a Badge, or a Switch."},{name:"align",type:'"center" | "start"',defaultValue:'"center"',description:"Cross-axis alignment of the columns; `start` for multi-line content."},{name:"as",type:'"div" | "li"',defaultValue:'"div"',description:'Render element \u2014 `li` when the parent is a semantic list (`<Flex as="ul" marker="none">` \u2014 a raw `<ul>` carries no gap token and, without `marker="none"`, the bullet rule `.ui-flex[data-list] > li { display: list-item }` outranks `display: flex` on the row itself). WITH `asChild` the child owns the row element, so `as` becomes the list ITEM around it: `<li data-slot="list-row-item"><a data-slot="list-row">`. Use both for a list of links \u2014 do not wrap the row in an `<li>` (or a `role="listitem"` div) yourself, because that makes every row an only child and the row-to-row divider, keyed on `:not(:last-child)` among siblings, stops matching on every row.'},{name:"overflow",type:'"truncate" | "wrap"',defaultValue:'"truncate"',description:"How title/description resolve content longer than the row \u2014 `truncate` (one line + ellipsis) or `wrap` (multi-line; long unbroken tokens break via `overflow-wrap: anywhere`). Either way the content column may shrink below its intrinsic width, so the row never widens the page root."},{name:"density",type:'"default" | "compact"',defaultValue:'"default"',description:"Row geometry. It only lowers thresholds \u2014 the row and its trailing cluster still wrap, so a cluster that cannot fit drops to its own line rather than widening the page root."},{name:"unread",type:"boolean",description:"Read/unread state for notification rows \u2014 renders the indicator dot (with localized `sr-only` text, never colour alone) plus the tokenized `--list-row-unread-background`. OMIT the prop for rows with no read state; pass `false` for a read row so its title keeps the same optical axis as the unread ones."}],usage:["DO rely on ListRow for a cut title (gh#1298): a title or description that is actually truncated gets a `title` with its text (a `Text ellipsis` inside it, never a badge beside it) and unfolds while the row holds keyboard focus; when nothing in the row takes focus, the cut line gets its own tab stop. Do not add a hand-rolled Tooltip for that.","DO use ListRow for a SHORT (\u22482\u20138 item) list of entities inside a Card where each row is one line with an action \u2014 account sessions, API keys, linked identities, passkeys. Stack rows in a `<Card><CardContent flush>` so the rows draw their own quiet dividers edge-to-edge.","DON'T reach for DataTable here \u2014 it carries sorting/selection/pagination chrome that a 3-item list doesn't need. DON'T nest a Card per row either (card-in-card). ListRow is the in-between surface.","DO host ListRow in a DataTable column when the list needs ROW SELECTION (a mail/notification inbox with bulk actions) \u2014 DataTable is the only primitive that selects rows. Mark that column `flush: true` so the cell drops its own padding and the ListRow owns the inset (gh#1016); without it the cell padding stacks on the row's (a 32px dead gap after the checkbox, taller rows) and the unread band stops at the checkbox cell. DON'T zero the cell with a className.",'DO write a list of LINKS as `<Flex as="ul" marker="none" direction="col" gap="none">` + `<ListRow as="li" asChild><Link/></ListRow>` \u2014 one call gives the list item, the whole-row link and the divider. DON\'T wrap the row in your own `<li>` or `role="listitem"` element to get list semantics back: the divider rule reads `:not(:last-child)` among the rows themselves, so a wrapper per row makes each one an only child and EVERY divider disappears \u2014 silently, which is why `ui-audit` now flags it as `no-hand-rolled-list`.','DON\'T hand-roll `<div className="flex items-center justify-between border-b py-3">` \u2014 that is exactly the repeated pattern ListRow replaces (border/radius/padding are tokenized via `--list-row-*`).','DO put the row\'s action in `trailing` (a `ghost`/`outline` Button, a DropdownMenu trigger, a Switch, or a status Badge). DO pass `as="li"` when the rows live inside a semantic list, and build that list as `<Flex as="ul" marker="none" direction="col" gap="none">` (the rows draw their own dividers) \u2014 `ui-audit` flags a raw `<ul>`/`role="list"` as `no-hand-rolled-list`.','DO use `unread` for a notification list \u2014 the dot is a SHAPE with localized `sr-only` text ("Unread"/"Read"), so it never reads as colour alone, and the row surface reads `--list-row-unread-background` (default `hsl(var(--muted))` \u2014 chosen so the xs muted description line stays WCAG AA on the emphasized surface; `--accent` would drop it to 4.23:1). DON\'T substitute a `Badge` \u2014 that renders a labelled pill, not a compact status dot.','DO pass `density="compact"` for the canonical invitation / history row \u2014 an Avatar, a title (+ description) and one or two small trailing Buttons that must read as ONE line inside a narrow card (\u2248326px content at 390px), or a history row whose status Badge + ISO-8601 date belongs beside the title. Measured at 390px: 62px tall vs 126px at the default density (where the actions wrapped), history row 41px vs 114px. DON\'T reach for it just to "make things tighter" on a roomy page \u2014 the default density is the entity-row measure.','DON\'T add one-off `min-width`/wrapping CSS in the consumer app for a long title + two trailing Buttons. The row already shrinks and WRAPS: the content column keeps only `min(var(--list-row-body-min-width), 100%)` and the trailing actions drop onto their own line below the threshold. Retune the threshold with `--list-row-body-min-width` (default 12rem) and the action gap with `--list-row-trailing-gap`; pass `overflow="wrap"` (usually with `align="start"`) when the title must stay fully readable at 390px instead of truncating.','DO expect rows ANYWHERE inside a padded (non-flush) `CardContent` \u2014 directly, in a `Flex as="ul"`, or wrapped (`DataState` children in a div > `Flex as="ul"` > `ListRow as="li" asChild` > link, gh#1325) \u2014 to drop their own inline padding: the card already holds the inset, so the row text lines up with the CardTitle instead of sitting 16px inside it. A row keeps `--list-row-padding-x` inside `CardContent flush` (the documented home, with edge-to-edge dividers) \u2014 including a flush CardContent nested in a padded one \u2014 and inside a table cell.'],useCases:["Account security page \u2014 a list of active sessions (device + last-seen as title/description, a destructive 'Revoke' Button in trailing).","Developer settings \u2014 API tokens or passkeys, each row showing the name + created date and a DropdownMenu of actions.","Linked accounts / SSO \u2014 an IdP icon in leading, the provider name + connected email, and a Switch or 'Disconnect' Button trailing.",'Notifications inbox \u2014 `unread` rows carry the dot + emphasized surface, `overflow="wrap"` keeps a long JA/EN/VI title and its ISO-8601 timestamp readable, and two inline trailing Buttons (Mark as read / Open) wrap under the text at 390px.',"Pending invitations \u2014 Avatar in leading, the organization/invitation name as title, and Accept + Decline Buttons in trailing that stack at narrow widths without a horizontal page scrollbar."],related:["DataTable \u2014 use instead when the list is long or needs sorting/selection/pagination; ListRow is for short, chrome-light lists.","Card \u2014 ListRow is designed to live inside `<CardContent flush>`; the Card supplies the outer surface and the closing border.","Descriptions \u2014 for a key/value metadata grid on a detail page (no per-row action); ListRow is for actionable entity rows."],example:`import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
873
873
|
import { Button } from "@godxjp/ui/general";
|
|
874
874
|
import { Smartphone } from "lucide-react";
|
|
875
875
|
import { Flex } from "@godxjp/ui/layout";
|
|
@@ -4673,7 +4673,237 @@ export function MfaChallengePage({ code, onCodeChange, error, pending, onVerify
|
|
|
4673
4673
|
|
|
4674
4674
|
// ANTI-PATTERNS: a page-local --auth-shell-card-max-width override or a .recovery-panel{width:432px}
|
|
4675
4675
|
// class \xB7 AuthIdentity above the panel \xB7 TwoFactorSetup as a challenge \xB7 six Inputs for the OTP row \xB7
|
|
4676
|
-
// re-scoping --control-height to widen the slots (use --otp-slot-size) \xB7 two primary actions.`},{name:"
|
|
4676
|
+
// re-scoping --control-height to widen the slots (use --otp-slot-size) \xB7 two primary actions.`},{name:"auth-brand-surface",aliases:["brand-surface","expressive-login","login-art","sign-in-hero","branded-sign-in"],tagline:"EXPRESSIVE sign-in surface (opt-in, owner-chosen) on the restrained kit: a picture of the product itself (SVG from fixed data) beside kit-only form controls, kit tokens re-set per theme with --primary/--ring untouched, ThemeScope, GSAP entrance (form first) + pausable ambient loop with a reduced-motion branch, a designed 390px band. Governed by the shipped skill godxjp-ui-brand-surface; CSS in its references/exemplar-css.md. The restrained default stays auth-recovery-panels.",tags:["auth","sign-in","login","brand","onboarding","motion","gsap","art-direction","layout"],code:`// An EXPRESSIVE sign-in surface on the restrained kit. Governing skill: godxjp-ui-brand-surface
|
|
4677
|
+
// (skills/godxjp-ui-brand-surface/SKILL.md, also \`get_consumer_skill\`). Its CSS is ONE file scoped
|
|
4678
|
+
// to .brand-surface: skills/godxjp-ui-brand-surface/references/exemplar-css.md.
|
|
4679
|
+
//
|
|
4680
|
+
// Expressive outside, kit inside: every control is a kit component; the picture is a stage behind
|
|
4681
|
+
// and beside them. No hex, no utility class, no hand-rolled surface in this file, so
|
|
4682
|
+
// \`npx @godxjp/ui audit\` stays at zero; the look lives in the CSS file.
|
|
4683
|
+
//
|
|
4684
|
+
// The picture shows THE PRODUCT ITSELF (here: the units it manages, the routes between them and
|
|
4685
|
+
// work moving along the routes), drawn as SVG from fixed numbers: the same picture on every load,
|
|
4686
|
+
// no image, no WebGL, no network. Replace NODES and LINKS with your product's own objects.
|
|
4687
|
+
//
|
|
4688
|
+
// Consumer dependencies, pinned in your package.json: gsap, @gsap/react, lucide-react.
|
|
4689
|
+
// Wrap it: every shared auth page (sign-in, second factor, reset, invitation) passes its form as
|
|
4690
|
+
// children, so they change together.
|
|
4691
|
+
import { useGSAP } from "@gsap/react";
|
|
4692
|
+
import { gsap } from "gsap";
|
|
4693
|
+
import { Pause, Play } from "lucide-react";
|
|
4694
|
+
import { type ReactNode, useRef, useState } from "react";
|
|
4695
|
+
import { ThemeScope } from "@godxjp/ui/app";
|
|
4696
|
+
import { Alert, AlertDescription } from "@godxjp/ui/feedback";
|
|
4697
|
+
import { Button, Heading, Text } from "@godxjp/ui/general";
|
|
4698
|
+
import { Flex } from "@godxjp/ui/layout";
|
|
4699
|
+
import { AppSettingPicker } from "@godxjp/ui/navigation";
|
|
4700
|
+
|
|
4701
|
+
gsap.registerPlugin(useGSAP);
|
|
4702
|
+
|
|
4703
|
+
type Point = { x: number; y: number };
|
|
4704
|
+
type MapNode = Point & { id: string; kind: "unit" | "actor" };
|
|
4705
|
+
|
|
4706
|
+
/** A metro-map route: straight along the longer axis, then 45 degrees into the target. */
|
|
4707
|
+
function route(a: Point, b: Point): { d: string; length: number } {
|
|
4708
|
+
const dx = b.x - a.x;
|
|
4709
|
+
const dy = b.y - a.y;
|
|
4710
|
+
const ax = Math.abs(dx);
|
|
4711
|
+
const ay = Math.abs(dy);
|
|
4712
|
+
const bend =
|
|
4713
|
+
ax >= ay ? { x: b.x - Math.sign(dx) * ay, y: a.y } : { x: a.x, y: b.y - Math.sign(dy) * ax };
|
|
4714
|
+
const length = Math.hypot(bend.x - a.x, bend.y - a.y) + Math.hypot(b.x - bend.x, b.y - bend.y);
|
|
4715
|
+
return { d: \`M\${a.x} \${a.y}L\${bend.x} \${bend.y}L\${b.x} \${b.y}\`, length: Math.round(length) };
|
|
4716
|
+
}
|
|
4717
|
+
|
|
4718
|
+
// PLACEHOLDER topology: replace with what your product manages. Labels are decoration
|
|
4719
|
+
// (the SVG is aria-hidden), so they carry nothing the page needs.
|
|
4720
|
+
const NODES: MapNode[] = [
|
|
4721
|
+
{ id: "gateway", x: 120, y: 180, kind: "unit" },
|
|
4722
|
+
{ id: "secrets", x: 300, y: 120, kind: "unit" },
|
|
4723
|
+
{ id: "build-01", x: 480, y: 240, kind: "unit" },
|
|
4724
|
+
{ id: "build-02", x: 620, y: 120, kind: "unit" },
|
|
4725
|
+
{ id: "worker\xB7a", x: 180, y: 360, kind: "actor" },
|
|
4726
|
+
{ id: "node-03", x: 420, y: 420, kind: "unit" },
|
|
4727
|
+
{ id: "node-04", x: 620, y: 420, kind: "unit" },
|
|
4728
|
+
{ id: "desk-17", x: 100, y: 560, kind: "unit" },
|
|
4729
|
+
{ id: "worker\xB7b", x: 300, y: 620, kind: "actor" },
|
|
4730
|
+
{ id: "db-main", x: 540, y: 620, kind: "unit" },
|
|
4731
|
+
{ id: "edge-02", x: 640, y: 780, kind: "unit" },
|
|
4732
|
+
{ id: "files", x: 200, y: 800, kind: "unit" },
|
|
4733
|
+
{ id: "worker\xB7c", x: 420, y: 800, kind: "actor" },
|
|
4734
|
+
];
|
|
4735
|
+
const LINKS: [string, string][] = [
|
|
4736
|
+
["gateway", "secrets"], ["secrets", "build-02"], ["secrets", "build-01"], ["build-01", "build-02"],
|
|
4737
|
+
["gateway", "worker\xB7a"], ["worker\xB7a", "node-03"], ["build-01", "node-03"], ["node-03", "node-04"],
|
|
4738
|
+
["build-02", "node-04"], ["worker\xB7a", "desk-17"], ["desk-17", "worker\xB7b"], ["node-03", "worker\xB7b"],
|
|
4739
|
+
["worker\xB7b", "db-main"], ["node-04", "db-main"], ["db-main", "edge-02"], ["worker\xB7b", "files"],
|
|
4740
|
+
["files", "worker\xB7c"], ["worker\xB7c", "db-main"], ["worker\xB7c", "edge-02"],
|
|
4741
|
+
];
|
|
4742
|
+
const at = (id: string) => NODES.find((n) => n.id === id) as MapNode;
|
|
4743
|
+
const ROUTES = LINKS.map(([a, b]) => route(at(a), at(b)));
|
|
4744
|
+
|
|
4745
|
+
function ProductMap() {
|
|
4746
|
+
return (
|
|
4747
|
+
<svg className="brand-surface-art" viewBox="0 0 720 900" preserveAspectRatio="xMidYMid slice" aria-hidden="true" focusable="false">
|
|
4748
|
+
<defs>
|
|
4749
|
+
<pattern id="brand-surface-grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
|
4750
|
+
<circle cx="1" cy="1" r="1" className="brand-surface-dot" />
|
|
4751
|
+
</pattern>
|
|
4752
|
+
</defs>
|
|
4753
|
+
<rect className="brand-surface-grid" width="720" height="900" fill="url(#brand-surface-grid)" />
|
|
4754
|
+
<g>
|
|
4755
|
+
{ROUTES.map((r, i) => (
|
|
4756
|
+
<path key={i} className="brand-surface-route" d={r.d} strokeDasharray={r.length} strokeDashoffset={0} data-length={r.length} />
|
|
4757
|
+
))}
|
|
4758
|
+
</g>
|
|
4759
|
+
<g className="brand-surface-packets">
|
|
4760
|
+
{ROUTES.map((r, i) => (
|
|
4761
|
+
<path key={i} className="brand-surface-packet" d={r.d} strokeDasharray={\`18 \${r.length + 18}\`} strokeDashoffset={18} data-length={r.length} />
|
|
4762
|
+
))}
|
|
4763
|
+
</g>
|
|
4764
|
+
<g>
|
|
4765
|
+
{NODES.map((n) =>
|
|
4766
|
+
n.kind === "unit" ? (
|
|
4767
|
+
<g key={n.id} className="brand-surface-node" transform={\`translate(\${n.x} \${n.y})\`}>
|
|
4768
|
+
<rect x="-17" y="-11" width="34" height="22" rx="4" className="brand-surface-box" />
|
|
4769
|
+
<circle cx="-8" cy="0" r="2.2" className="brand-surface-led" />
|
|
4770
|
+
<text x="24" y="4" className="brand-surface-label">{n.id}</text>
|
|
4771
|
+
</g>
|
|
4772
|
+
) : (
|
|
4773
|
+
<g key={n.id} className="brand-surface-node brand-surface-actor" transform={\`translate(\${n.x} \${n.y})\`}>
|
|
4774
|
+
<circle r="11" className="brand-surface-ring" />
|
|
4775
|
+
<circle r="4" className="brand-surface-core" />
|
|
4776
|
+
<text x="20" y="4" className="brand-surface-label">{n.id}</text>
|
|
4777
|
+
</g>
|
|
4778
|
+
),
|
|
4779
|
+
)}
|
|
4780
|
+
</g>
|
|
4781
|
+
</svg>
|
|
4782
|
+
);
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
const q = (root: HTMLElement, selector: string) => Array.from(root.querySelectorAll<Element>(selector));
|
|
4786
|
+
|
|
4787
|
+
/** The form arrives FIRST and fast (the person came to sign in); the picture keeps arriving behind it. */
|
|
4788
|
+
function entrance(root: HTMLElement): gsap.core.Timeline {
|
|
4789
|
+
return gsap
|
|
4790
|
+
.timeline({ defaults: { ease: "expo.out" } })
|
|
4791
|
+
.from(q(root, ".brand-surface-panel-in > *"), { autoAlpha: 0, y: 14, duration: 0.6, stagger: 0.06 }, 0.1)
|
|
4792
|
+
.from(q(root, ".brand-surface-brand > *"), { autoAlpha: 0, y: 18, duration: 0.9, stagger: 0.1 }, 0.2)
|
|
4793
|
+
.from(q(root, ".brand-surface-grid"), { autoAlpha: 0, duration: 1.2, ease: "power1.out" }, 0)
|
|
4794
|
+
.from(q(root, ".brand-surface-route"), {
|
|
4795
|
+
strokeDashoffset: (_: number, el: Element) => Number(el.getAttribute("data-length")),
|
|
4796
|
+
duration: 1.1,
|
|
4797
|
+
stagger: 0.035,
|
|
4798
|
+
ease: "power3.inOut",
|
|
4799
|
+
}, 0.1)
|
|
4800
|
+
.from(q(root, ".brand-surface-node"), { autoAlpha: 0, scale: 0.5, transformOrigin: "50% 50%", duration: 0.7, stagger: { each: 0.04, from: "random" } }, 0.35)
|
|
4801
|
+
.from(q(root, ".brand-surface-label"), { autoAlpha: 0, duration: 0.6, stagger: 0.02, ease: "power1.out" }, 0.9);
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
/** What keeps moving once the screen has arrived: work running along the routes. Pausable. */
|
|
4805
|
+
function ambient(root: HTMLElement): gsap.core.Timeline {
|
|
4806
|
+
const tl = gsap.timeline({ repeat: -1, paused: true });
|
|
4807
|
+
q(root, ".brand-surface-packet").forEach((packet, i) => {
|
|
4808
|
+
const length = Number(packet.getAttribute("data-length"));
|
|
4809
|
+
tl.fromTo(packet, { strokeDashoffset: 18 }, { strokeDashoffset: -length, duration: length / 170, ease: "none" }, ((i * 7) % 19) * 0.55);
|
|
4810
|
+
});
|
|
4811
|
+
tl.to(q(root, ".brand-surface-actor .brand-surface-core"), { scale: 1.6, transformOrigin: "50% 50%", duration: 0.5, yoyo: true, repeat: 1, stagger: 1.4, ease: "sine.inOut" }, 0.4);
|
|
4812
|
+
return tl;
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
export interface BrandSurfaceProps {
|
|
4816
|
+
/** The product's identity: its own \`<Logo \u2026 wordmark={\u2026} />\`, or the tenant's uploaded logo and name. Never redrawn here. */
|
|
4817
|
+
brand: ReactNode;
|
|
4818
|
+
/** ONE plain value line, already translated. */
|
|
4819
|
+
valueLine: ReactNode;
|
|
4820
|
+
/** Optional fact line (what the product connects), set in mono. */
|
|
4821
|
+
facts?: ReactNode;
|
|
4822
|
+
/** The page's one h1: "Sign in", "Reset password", \u2026 */
|
|
4823
|
+
title: string;
|
|
4824
|
+
error?: string;
|
|
4825
|
+
pauseLabel: string;
|
|
4826
|
+
playLabel: string;
|
|
4827
|
+
/** false when the page speaks a language the server chose. */
|
|
4828
|
+
localePicker?: boolean;
|
|
4829
|
+
/** The form: Form + FormField + Input / PasswordInput + Button, exactly as on a plain auth page. */
|
|
4830
|
+
children: ReactNode;
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
export function BrandSurface({ brand, valueLine, facts, title, error, pauseLabel, playLabel, localePicker = true, children }: BrandSurfaceProps) {
|
|
4834
|
+
const root = useRef<HTMLElement>(null);
|
|
4835
|
+
const loop = useRef<gsap.core.Timeline | null>(null);
|
|
4836
|
+
const [still, setStill] = useState(false);
|
|
4837
|
+
const stillRef = useRef(false);
|
|
4838
|
+
|
|
4839
|
+
useGSAP(
|
|
4840
|
+
() => {
|
|
4841
|
+
const element = root.current;
|
|
4842
|
+
if (!element) return;
|
|
4843
|
+
const mm = gsap.matchMedia();
|
|
4844
|
+
mm.add({ full: "(prefers-reduced-motion: no-preference)", reduced: "(prefers-reduced-motion: reduce)" }, (context) => {
|
|
4845
|
+
if (context.conditions?.reduced) {
|
|
4846
|
+
// At rest: the picture is drawn, the form fades in, nothing loops.
|
|
4847
|
+
gsap.from(q(element, ".brand-surface-panel-in"), { autoAlpha: 0, duration: 0.3, ease: "power1.out" });
|
|
4848
|
+
loop.current = null;
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
loop.current = ambient(element);
|
|
4852
|
+
entrance(element).eventCallback("onComplete", () => {
|
|
4853
|
+
if (!stillRef.current) loop.current?.play();
|
|
4854
|
+
});
|
|
4855
|
+
});
|
|
4856
|
+
return () => mm.revert();
|
|
4857
|
+
},
|
|
4858
|
+
{ scope: root },
|
|
4859
|
+
);
|
|
4860
|
+
|
|
4861
|
+
function toggleMotion() {
|
|
4862
|
+
const next = !still;
|
|
4863
|
+
stillRef.current = next;
|
|
4864
|
+
setStill(next);
|
|
4865
|
+
if (next) loop.current?.pause();
|
|
4866
|
+
else loop.current?.play();
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
return (
|
|
4870
|
+
// ThemeScope carries the surface's re-set tokens into the overlays it opens (the locale menu).
|
|
4871
|
+
<ThemeScope className="brand-surface">
|
|
4872
|
+
<main ref={root} className="brand-surface-main">
|
|
4873
|
+
<div className="brand-surface-stage">
|
|
4874
|
+
<ProductMap />
|
|
4875
|
+
</div>
|
|
4876
|
+
<header className="brand-surface-brand">
|
|
4877
|
+
{brand}
|
|
4878
|
+
<Text as="p" className="brand-surface-value" break="phrase">{valueLine}</Text>
|
|
4879
|
+
{facts ? <Text as="p" mono className="brand-surface-facts">{facts}</Text> : null}
|
|
4880
|
+
</header>
|
|
4881
|
+
<section className="brand-surface-panel" aria-labelledby="brand-surface-title">
|
|
4882
|
+
<div className="brand-surface-panel-in">
|
|
4883
|
+
<Heading level={1} id="brand-surface-title" className="brand-surface-title">{title}</Heading>
|
|
4884
|
+
{error ? <Alert tone="destructive"><AlertDescription>{error}</AlertDescription></Alert> : null}
|
|
4885
|
+
<div>{children}</div>
|
|
4886
|
+
<Flex align="center" justify="between" gap="sm">
|
|
4887
|
+
{localePicker ? <AppSettingPicker kind="locale" appearance="labeled" compact /> : <span />}
|
|
4888
|
+
<AppSettingPicker kind="theme" menu={false} appearance="icon" />
|
|
4889
|
+
</Flex>
|
|
4890
|
+
</div>
|
|
4891
|
+
</section>
|
|
4892
|
+
{/* Beside the picture it stops, last in the tab order (WCAG 2.2.2). Hidden by the CSS under reduced motion. */}
|
|
4893
|
+
<Button type="button" variant="ghost" size="icon" className="brand-surface-motion" onClick={toggleMotion} aria-label={still ? playLabel : pauseLabel}>
|
|
4894
|
+
{still ? <Play aria-hidden /> : <Pause aria-hidden />}
|
|
4895
|
+
</Button>
|
|
4896
|
+
</main>
|
|
4897
|
+
</ThemeScope>
|
|
4898
|
+
);
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
// Use:
|
|
4902
|
+
// <BrandSurface brand={<Logo glyph={<YourMark />} size="lg" wordmark="Acme Ops" label="Acme Ops" />}
|
|
4903
|
+
// valueLine={t("auth.valueLine")} title={t("auth.signIn")} pauseLabel={t("motion.pause")} playLabel={t("motion.play")}>
|
|
4904
|
+
// <SignInForm />
|
|
4905
|
+
// </BrandSurface>
|
|
4906
|
+
`},{name:"account-recovery-settings",aliases:["recovery-email","backup-codes","security-settings","recovery-settings"],tagline:"Signed-in recovery: compact method status/action rows (email \xB7 phone \xB7 backup codes) + a separate password-change destination \u2014 capability-aware, risk-appropriate, NO stacked Info Alerts.",tags:["account","recovery","security","backup-codes","password","settings"],code:`// Research basis: Google Account recovery (email/phone + backup codes), Microsoft Security info,
|
|
4677
4907
|
// GitHub recovery codes. Signed-IN recovery SETTINGS (manage your methods) is a DIFFERENT surface
|
|
4678
4908
|
// from the signed-OUT forgot-password JOURNEY \u2014 do not mix them on one page.
|
|
4679
4909
|
import { Card, CardContent, CardHeader, CardTitle, ListRow, Badge } from "@godxjp/ui/data-display";
|
|
@@ -5123,7 +5353,7 @@ const THEME = \`
|
|
|
5123
5353
|
// Check the hero's computed font-size at 390, and remember ResponsiveGrid steps
|
|
5124
5354
|
// are CONTAINER widths: inside a 1280 shell with 32px gutters, a 768 tablet hands
|
|
5125
5355
|
// the grid 704px, so \`md\` (48rem) never fires there \u2014 key that column to \`sm\`.
|
|
5126
|
-
`}];function B(e){let t=e.trim().toLowerCase();return S.find(a=>a.name===t||a.aliases?.includes(t))}function
|
|
5356
|
+
`}];function B(e){let t=e.trim().toLowerCase();return S.find(a=>a.name===t||a.aliases?.includes(t))}function ne(e){let t=e.trim().toLowerCase();return t===""?S:S.filter(a=>a.name.includes(t)||a.aliases?.some(i=>i.includes(t))||a.tagline.toLowerCase().includes(t)||a.tags.some(i=>i.includes(t)))}function _(e){return e.audience!=="core"}var A=[{id:"contract",audience:"consumer",name:"H\u1EE3p \u0111\u1ED3ng c\u1EE9ng \u2014 lu\u1EADt + v\xED d\u1EE5 ch\u1EA1y \u0111\u01B0\u1EE3c",whenToUse:"\u0110\u1ECCC TR\u01AF\u1EDAC TI\xCAN khi vi\u1EBFt b\u1EA5t k\u1EF3 giao di\u1EC7n n\xE0o b\u1EB1ng @godxjp/ui. Lu\u1EADt c\u1EE9ng, m\u1ED7i lu\u1EADt k\xE8m c\u1ED5ng CI s\u1EBD \u0111\u1ECF n\u1EBFu vi ph\u1EA1m, m\xE3 SAI v\xE0 m\xE3 \u0110\xDANG. D\xF9ng cho: d\u1EF1ng trang, d\u1EF1ng form, kho\u1EA3ng c\xE1ch, b\u1ED1 c\u1EE5c, m\xE0u, ch\u1EEF, khi thang token kh\xF4ng c\xF3 gi\xE1 tr\u1ECB c\u1EA7n.",source:"docs/CONSUMER-RULES.md + ui-audit.mjs + component-api-manifest.json",sections:[{id:"loop",title:"V\xF2ng l\xE0m vi\u1EC7c",tagline:"Ba b\u01B0\u1EDBc. B\u1ECF b\u01B0\u1EDBc 2 l\xE0 ngu\u1ED3n g\u1ED1c c\u1EE7a m\u1ECDi l\u1EC7ch design.",body:`1. H\u1ECFi API TR\u01AF\u1EDAC khi vi\u1EBFt \u2014 \u0111\u1EEBng \u0111o\xE1n t\xEAn prop:
|
|
5127
5357
|
get_component({ name: "Flex" }) \u2192 prop + gi\xE1 tr\u1ECB h\u1EE3p l\u1EC7
|
|
5128
5358
|
search_components({ query: "..." }) \u2192 t\xECm component \u0111\xFAng vi\u1EC7c
|
|
5129
5359
|
|
|
@@ -5828,7 +6058,7 @@ for real): StatCard 30KB \xB7 Input 52 \xB7 Button 56 \xB7 DataTable 79 \xB7 Sel
|
|
|
5828
6058
|
DataTable at 50/page does not need it. The dist is bundler-oriented ESM (extensionless + JSON
|
|
5829
6059
|
imports): fine for vite/webpack consumers; when testing against a local checkout run tests on an
|
|
5830
6060
|
npm-pack TARBALL install, not a file: symlink (the symlink's nested node_modules creates a
|
|
5831
|
-
dual-React artifact under vitest).`}]},{id:"design-complex-admin",audience:"consumer",name:"Design a complex admin console \u2014 wireframe-first, layered IA",whenToUse:"You are building or redesigning a COMPLEX multi-layer admin/console/back-office UI (platform admin, workspace admin, multi-tenant management, control panel) \u2014 the kind that spans several administrative tiers (Platform \u2192 Org/Tenant \u2192 Brand \u2192 Org-unit \u2192 Shop \u2192 Staff), not one flat resource list. Read this BEFORE writing JSX: it forces the right order \u2014 lock the entity model \u2192 study exemplars \u2192 design a LAYERED information architecture \u2192 wireframe (as an Artifact) \u2192 validate \u2192 only THEN code. Stops the flat-console anti-pattern (every resource in one list, create-form + list + detail crammed on one screen, no dashboards). For a single ordinary screen use compose-a-screen; for a design handoff bundle use design-to-page.",source:"@godxjp/ui
|
|
6061
|
+
dual-React artifact under vitest).`}]},{id:"design-complex-admin",audience:"consumer",name:"Design a complex admin console \u2014 wireframe-first, layered IA",whenToUse:"You are building or redesigning a COMPLEX multi-layer admin/console/back-office UI (platform admin, workspace admin, multi-tenant management, control panel) \u2014 the kind that spans several administrative tiers (Platform \u2192 Org/Tenant \u2192 Brand \u2192 Org-unit \u2192 Shop \u2192 Staff), not one flat resource list. Read this BEFORE writing JSX: it forces the right order \u2014 lock the entity model \u2192 study exemplars \u2192 design a LAYERED information architecture \u2192 wireframe (as an Artifact) \u2192 validate \u2192 only THEN code. Stops the flat-console anti-pattern (every resource in one list, create-form + list + detail crammed on one screen, no dashboards). For a single ordinary screen use compose-a-screen; for a design handoff bundle use design-to-page.",source:"@godxjp/ui skills/godxjp-ui-design/references/complex-admin.md \u2014 the multi-tenant admin-console redesign process (entity-tree concept \u2192 layered wireframe \u2192 code)",sections:[{id:"entity-model-first",title:"Step 0 \u2014 Lock the entity model before you draw a pixel",tagline:"Enumerate entities, cardinality, boundaries, and what does NOT exist; get it confirmed first.",body:`Do NOT open an editor at a flat "here are the resources" list. First produce a
|
|
5832
6062
|
CONCEPT artifact (a tree of entities + a glossary + the rules) and get it confirmed:
|
|
5833
6063
|
- every entity + its cardinality (1-N / N-N) + its boundary (tenant / RLS / subscription scope);
|
|
5834
6064
|
- the identity split matters \u2014 Principal/User (can log in, carries authz) \u2260 Employee/Staff
|
|
@@ -5874,7 +6104,7 @@ Only after sign-off do you move to code. A wireframe skipped = a redesign reliti
|
|
|
5874
6104
|
6. Hand-rolled UI instead of @godxjp/ui primitives (raw nav buttons, styled-div "Card", raw table).
|
|
5875
6105
|
Every run of this skill produces, in order: (1) a stakeholder-confirmed concept/entity artifact \u2192
|
|
5876
6106
|
(2) a layered IA doc \u2192 (3) an approved wireframe Artifact \u2192 (4) a per-route implementation checklist.
|
|
5877
|
-
Do not advance a step until the prior artifact is signed off.`}]}];function P(e){return A.find(t=>t.id===e)}function se(e,t){return P(e)?.sections.find(a=>a.id===t)}var Ge=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");function Ve(e,t){return/[^\p{ASCII}]/u.test(t)?e.includes(t):new RegExp(`(^|[^a-z0-9])${Ge(t)}(e?s)?([^a-z0-9]|$)`).test(e)}var
|
|
6107
|
+
Do not advance a step until the prior artifact is signed off.`}]}];function P(e){return A.find(t=>t.id===e)}function se(e,t){return P(e)?.sections.find(a=>a.id===t)}var Ge=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");function Ve(e,t){return/[^\p{ASCII}]/u.test(t)?e.includes(t):new RegExp(`(^|[^a-z0-9])${Ge(t)}(e?s)?([^a-z0-9]|$)`).test(e)}var oe=["landing","landing page","marketing","lp","hero","long scroll","campaign site"],re=["dashboard","table","list","admin","settings","setting","document","workspace","form","detail page","approval","console","back office","back-office","screen","page","view","app"];function le(e,t){let a=e.toLowerCase(),i=[],n=c=>c.some(d=>Ve(a,d)),o=n(oe),r=(c,d,T,D,y)=>{n(c)&&(i.some(g=>g.skill===d&&g.section===T)||i.push({skill:d,section:T,why:D,alsoSee:y}))},s=(...c)=>{o&&r(...c)},h=()=>{o||r(re,"compose-a-screen","pick-primitives","An app screen: start from DESIGN.md (get_design) and the matching get_exemplar, compose real primitives, then screenshot-compare at 390/768/1280.",["compose-a-screen/assemble-screen","design-to-page/dna","taste/one-intent-per-screen"])},f=n(["spacing","gap","kho\u1EA3ng c\xE1ch","padding","margin","color","colour","m\xE0u","token","typography","prop","audit","l\u1ED7i","error"]);f||h();let u=i.some(c=>c.skill==="compose-a-screen");r([...u?re:[],"spacing","gap","kho\u1EA3ng c\xE1ch","padding","margin","layout","b\u1ED1 c\u1EE5c","page","trang","grid","l\u01B0\u1EDBi","form","input","button","control","color","colour","m\xE0u","token","text","ch\u1EEF","typography","component","prop","audit","l\u1ED7i","error","how do i","l\xE0m sao","b\u1EAFt \u0111\u1EA7u","start"],"contract","<see whenToUse>","Lu\u1EADt c\u1EE9ng c\u1EE7a @godxjp/ui, m\u1ED7i lu\u1EADt k\xE8m c\u1ED5ng CI v\xE0 m\xE3 SAI/\u0110\xDANG. \u0110\u1ECDc tr\u01B0\u1EDBc m\u1ECDi skill v\u1EC1 gu.",["contract/loop","contract/spacing","contract/escape"]),f&&h(),r(["filter","filter bar","filter strip","filtering","faceted search","faceted filter","facet","filter layout","saved views","saved view","docked filter","filter panel","filter sidebar","\u7D5E\u308A\u8FBC\u307F","\u691C\u7D22\u6761\u4EF6","\u691C\u7D22\u30D5\u30A9\u30FC\u30E0","\u7D5E\u308A\u8FBC\u307F\u6761\u4EF6","advanced search","query filter","kintone filter","salesforce list view filter","list view filter","condition builder","filter logic","\u30D5\u30A3\u30EB\u30BF\u30FC","b\u1ED9 l\u1ECDc"],"crud-list","filter-bar","Cardinal rule #51: FilterBar wraps, never scrolls sideways, filters packed at the start; 2\u20134 filters inline, the rest behind ONE 'More filters' Sheet (get_pattern list-filters). Other layouts \u2014 saved views, Filter menu + chips, faceted sidebar, docked panel, compact in-card, date range, phone \u2014 get_pattern filter-bar-layouts. Submit-driven: \u691C\u7D22\u6761\u4EF6\u30D5\u30A9\u30FC\u30E0 / advanced search \u2192 SearchForm (get_pattern search-form); kintone / Salesforce-style field \xD7 operator rows \u2192 get_pattern condition-builder.",["crud-list/search-form"]),s(["premium","awwwards","agency","linear","apple","high-end","luxury"],"soft","vibe-archetypes","Marketing-only premium tier \u2014 pick a Vibe + Layout archetype + apply Double-Bezel.",["soft/double-bezel","soft/magnetic-hover"]),s(oe,"imagegen-web","hero-composition-bias","Landing pages benefit from hero composition variety + per-section image generation.",["gpt-tasteskill/principles","soft/layout-archetypes"]),s(["mobile app","ios","android","phone screen","onboarding flow"],"imagegen-mobile","principles","Mobile marketing screens \u2014 generate screens first, avoid phone-shaped-website.",["taste/mobile-first"]),s(["editorial","notion","magazine","blog"],"minimalist","palette","Marketing-only editorial look: warm monochrome + spot pastels + serif headings.",["minimalist/typography"]),s(["brutalist","raw","terminal","swiss"],"brutalist","principles","Marketing-only brutalist look (rigid grids, utilitarian colour)."),r(["brand","identity","logo","guidelines"],"brandkit","principles","Brand identity work \u2014 boards before screens."),r(["refactor","redesign","upgrade existing","audit"],"redesign","fix-priority","Existing project = run audit first, fix in priority order (font \u2192 palette \u2192 states \u2192 ...).",["redesign/audit-checklist"]),r(["form","validation","submit","sign up","registration"],"taste","form-discipline","Form must have explicit label + help + error wired via FormField (rule 34)."),r(["loading","saving","skeleton","spinner"],"taste","loading-states","Skeleton for INIT fetch, Spinner for active work. Never mix."),r(["mobile first","responsive","breakpoint"],"taste","mobile-first","Default styles target xs. Touch targets \u2265 44px. Responsive props, not window.innerWidth."),r(["complete code","full implementation","no placeholder"],"output","banned","Banned: // ..., // TODO, 'for brevity'. Ship complete runnable code."),s(["gsap","scrolltrigger","scroll choreography","pinning","scroll-triggered"],"gpt-tasteskill","principles","GSAP ScrollTrigger \u2014 pinning, stacking, scrubbing."),r(["from image","image to code","design first"],"image-to-code","workflow","Generate design image first \u2192 analyze \u2192 implement."),r(["handoff","design bundle","claude design","prototype","build the page","implement the design","build this screen","mockup"],"design-to-page","map-to-primitives","Map every block to a real @godxjp/ui primitive (MCP-first), consume existing tokens, apply the reference-design DNA, tables central, gaps \u2192 extend-or-ask, verify.",["design-to-page/read-intent","design-to-page/dna","design-to-page/tables-central"]),r(["compose a screen","new screen","new page","create a page","create a screen","build a view","build a screen","from scratch","screen from a brief"],"compose-a-screen","pick-primitives","Build a new app screen from real @godxjp/ui primitives (MCP-first): one-intent-per-screen, real chrome, every state + a11y + i18n, verify.",["compose-a-screen/assemble-screen","compose-a-screen/state-and-a11y","taste/one-intent-per-screen"]),r(["bug in godx","godx-ui bug","report a bug","file an issue","gh issue","can't follow the rule","library is broken","primitive is broken"],"compose-a-screen","report-bug","If @godxjp/ui itself is at fault, don't fake a workaround \u2014 file a detailed gh issue (use draft_bug_report).",["design-to-page/report-bug"]),r(["slow","performance","perf","violation","long task","lag","janky","re-render","rerender","bundle size","code splitting","tree-shaking","tree shaking","ch\u1EADm","l\u1ED7i hi\u1EC7u n\u0103ng","\u91CD\u3044"],"app-performance","measure-first","Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",["app-performance/filter-pane-memo","app-performance/heavy-panels","app-performance/bundle-budget"]),r(["admin console","admin panel","control panel","back-office","back office","management console","management ui","multi-tenant","multi tenant","platform admin","workspace admin","console qu\u1EA3n tr\u1ECB","layered ia","information architecture"],"design-complex-admin","entity-model-first","A complex multi-tier admin console: lock the entity model \u2192 study exemplars \u2192 design a LAYERED IA \u2192 wireframe (Artifact) \u2192 validate \u2192 build route-by-route. Kills the flat-console anti-pattern.",["design-complex-admin/layered-ia","design-complex-admin/wireframe-first","compose-a-screen/pick-primitives"]);let l=t?.consumerOnly?i.filter(c=>{let d=P(c.skill);return!d||d.audience!=="core"}):i;return l.length===0?t?.consumerOnly?[{skill:"compose-a-screen",section:"pick-primitives",why:`No keyword match for "${e}". Default consumer path: compose the screen from real primitives via the MCP.`,alsoSee:["design-to-page/map-to-primitives","taste/one-intent-per-screen"]}]:[{skill:"taste",section:"<see whenToUse>",why:`No keyword match for "${e}". Default to the "taste" baseline \u2014 see whenToUse for sections.`}]:l}var L=[{category:"visual",name:"Purple-blue gradient hero",body:`The default LLM color palette \u2014 purple \u2192 blue \u2192 cyan radial /
|
|
5878
6108
|
linear gradient as hero background. Looks like every AI-generated
|
|
5879
6109
|
SaaS landing page from 2023.`,fix:`Use the framework's accent palette (\`data-accent="blue"\` /
|
|
5880
6110
|
"violet" / "cyan" / "green" / "orange" / "rose"). Solid surface
|
|
@@ -6011,7 +6241,7 @@ A block with no reason is IGNORED and the finding stands. An unclosed block runs
|
|
|
6011
6241
|
The class-shaped rules (gap-*/p-*/m-*, bg-<palette>-*, w-[\u2026], pr-*, dark:*) only read class
|
|
6012
6242
|
expressions \u2014 a className/class attribute, a class-named binding (\`baseClass\`, \`statusStyles\`,
|
|
6013
6243
|
\`badgeVariants\`) or a cn()/clsx()/cva() call \u2014 so prose that merely spells a utility is not a
|
|
6014
|
-
finding and needs no suppression.`,ue=[{id:"no-direct-sonner-import",severity:"error",category:"api",standard:null,fix:'Use `import { toast } from "@godxjp/ui"` (or @godxjp/ui/feedback). A consumer-installed sonner can have a separate toast store from the kit Toaster and silently hide notifications. Sonner remains a dependency; no peer migration is required.'},{id:"dropdown-menu-non-menu-children",severity:"error",category:"composition",standard:null,fix:"DropdownMenuContent accepts menu collection nodes only. Use DropdownMenuItem for actions and Popover + ListRow for arbitrary headers, notification lists and links. Custom components returning menu items remain valid."},{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:"nested-card",severity:"warn",category:"composition",standard:null,fix:'A Card (or StatCard) inside a Card draws two borders for one boundary. Group inside the outer Card with <Heading> + <Flex direction="col"> or <Separator />.'},{id:"card-per-section",severity:"warn",category:"composition",standard:null,fix:'5+ Cards in one file is a Card per section. Mark a section with <Heading level={2}> and section spacing (PageContainer children / Flex gap="lg"); keep Card for a real boundary (a table surface, a selectable item).'},{id:"kpi-row-off-dashboard",severity:"warn",category:"composition",standard:null,fix:"3+ StatCards outside a file whose path names a dashboard. A row count is not a KPI: drop the row and show the total in <Pagination showTotal>. StatCard rows are for a dashboard with comparison or trend (delta)."},{id:"form-on-list-page",severity:"warn",category:"composition",standard:null,fix:'A submitting Form with 3+ FormFields and a DataTable/Table/List/ListRow on the same page is a create form inlined beside its list (design-complex-admin anti-pattern #3). Give create its own route, or a focused Dialog/Sheet, and link to it from the list page\'s PageContainer `extra`. Not findings: a Form inside a Dialog/Sheet/Drawer, a `role="search"` Form or a SearchForm (a \u691C\u7D22\u6761\u4EF6\u30D5\u30A9\u30FC\u30E0 searches the list, it does not create into it), a Form that never submits (no onSubmit, or one that only calls preventDefault: a filter bar), a 1-2 field form, an editor whose Table sits inside its Form, the page\'s own editor (a Form whose `id` a header `<Button type="submit" form={FORM_ID}>` submits, with a read-only list beside it), and a list page and a create page that one file renders in two PageContainers.'},{id:"hand-rolled-filter-bar",severity:"warn",category:"composition",standard:"@godxjp/ui cardinal rule #51",fix:'A list page (DataTable/Table/List/ListRow in the same PageContainer) whose 2+ filter controls (a FormField holding Select/SearchInput/Switch/Checkbox/DatePicker\u2026, or a self-labelled bare one) sit in your own Flex/div/Card/ResponsiveGrid instead of a <FilterBar>. Move them into <FilterBar> (`search` / `filters` / FilterBarGroup children / `actions`): it packs filters at the START with one gap, wraps, and owns clear + chips. justify="between"|"around"|"evenly" across filters is named in the snippet \u2014 the only far-end element of a filter bar is its `actions` slot. Past 2\u20134 filters use pattern list-filters; for other layouts (saved views, faceted sidebar, docked panel\u2026) see pattern filter-bar-layouts. Not findings: FilterBar/Toolbar/SearchForm children, a condition builder inside a submitting `role="search"` Form, a Sheet/Dialog/Drawer/Popover body, a Form that submits (a search form), a column of fields (an editor panel), cell editors inside a table, a single search box.'},{id:"empty-state-icon-in-card",severity:"warn",category:"composition",standard:null,fix:'In an admin surface an empty state is one sentence and one action. Drop `icon` from an EmptyState inside a Card, or use variant="compact".'},{id:"badge-wall",severity:"warn",category:"composition",standard:null,fix:"5+ Badge/Tag in one file. Badge is for status only; render categories, tiers and scopes as plain text, one status column per table."},{id:"alert-per-page",severity:"warn",category:"composition",standard:null,fix:"4+ Alerts in one file. Keep one Alert for a state that blocks or changes the task; field errors go in FormField, results in a toast, standing info in plain text."},{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:"formfield-needs-form",severity:"error",category:"composition",standard:"@godxjp/ui Form (gh#998)",fix:'Wrap FormFields in a <Form> (layout="horizontal" labelWidth controlWidth is the desktop default; layout="vertical" is valid for long labels or a narrow container); a row of fields is <Flex attached> or <Form columns>, never a hand-rolled <Flex>. A field component whose whole output is one FormField is exempt, and so is a field-group component that every caller renders inside a <Form> (checked across the project); when only some callers do, wrap the bare call sites the message names, not the fields. A call site inside a same-file render helper (`const legFields = (\u2026) => \u2026`) counts as under the <Form> its every call sits in (gh#1260).'},{id:"mixed-button-size",severity:"error",category:"composition",standard:null,fix:'Sibling <Button>s under one parent (through fragments, {cond && \u2026}, ternaries, Tooltip wrappers) share ONE size; a missing size is default; icon-sm pairs with sm, icon-xs with xs, icon with default. Never `size="sm"` beside a default Button in one action row.'},{id:"dialog-form-too-big",severity:"error",category:"composition",standard:"@godxjp/ui form placement (gh#998)",fix:"Three or more FormFields in a Dialog body is a page: give the form its own route. Dialogs hold a confirmation or one or two fields; a side Sheet (drawer) may hold a filter or edit form."},{id:"filter-strip-no-scroll",severity:"error",category:"composition",standard:"WCAG 2.2 SC 1.4.10 (Reflow) \xB7 @godxjp/ui cardinal rule #51",fix:'A box that scrolls on the inline axis (`overflow-x-auto`/`overflow-x-scroll`, or `<ScrollArea orientation="horizontal"|"both">`) around FilterBar/Toolbar/FormField/Select/SearchInput/Input/a picker is a sideways-scrolling filter strip. Drop the scroller and let <FilterBar> wrap; keep the 2\u20134 most-used filters inline and put the rest behind ONE \'More filters\' Button that opens a Sheet (get_rule 51, get_pattern list-filters). A scroller whose body holds a Table/DataTable or Cards (a kanban board) is two-dimensional content and is not flagged; a vertical pane (`overflow-y-auto`, a Sheet body) is not flagged.'},{id:"select-width-hint",severity:"warn",category:"composition",standard:"GOV.UK Design System \xB7 text input width",fix:"Size a Select for its content \u2014 `controlWidth` on the FormField, or once on the <Form>."},{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:"filter-bar-no-overflow",severity:"error",category:"api",standard:null,fix:"FilterBar/Toolbar has no `overflow` prop (removed in 33.0.0): the bar wraps and never scrolls sideways. Delete it; put rarely used filters behind one 'More filters' Button that opens a Sheet."},{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 \xB7 Accessible Name Computation 1.2",fix:"Name <Button size='icon'> with aria-label={t('\u2026')} OR from content \u2014 a <VisuallyHidden>/sr-only child beside the aria-hidden glyph. Text inside an aria-hidden subtree names nothing."},{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-hand-rolled-scrollport",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.1.1 (Keyboard) \xB7 WAI-ARIA 1.2 (group) \xB7 Deque axe-core scrollable-region-focusable",fix:'Replace className="overflow-auto / overflow-y-auto / overflow-x-auto / overflow-scroll" on your own element with <ScrollArea label={t("\u2026")} orientation>, which is the tab stop, the role and the localized name \u2014 and withholds all three while there is nothing to scroll. A browser audit only fails a scrollport whose content has NO focusable child, so the same markup is clean or broken depending on the data; this reads the markup instead (gh#825). overflow-hidden is a clipping box, not a scrollport, and is not flagged. A strip of FILTERS or form controls is not a scrollport at all: it wraps (`filter-strip-no-scroll`, cardinal rule #51) \u2014 do not move it into a horizontal ScrollArea.'},{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."},{id:"lucide-icon-needs-size",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.4.4 \xB7 @godxjp/ui icon scale (--icon-size-*)",fix:'A lucide glyph outside a sizing context draws at its intrinsic 24px. Render it as <Icon as={Lock} size="sm" tone="muted" /> \u2014 the primitive puts it on the --icon-size-* scale and is aria-hidden unless you pass a label.'},{id:"no-hand-rolled-list",severity:"warn",category:"composition",standard:"WAI-ARIA 1.2 (list / listitem) \xB7 HTML Living Standard (ul/ol/li) \xB7 WCAG 2.2 SC 1.3.1",fix:'Build the list as <Flex as="ul" marker="none" direction="col" gap="none"> with <ListRow as="li"> rows \u2014 not a raw <ul>/<ol> (no gap token), not <div role="list">/<div role="listitem">, and never a wrapper around each row: the divider is :not(:last-child) among SIBLINGS, so a row alone in its own wrapper loses it silently (a consumer lost every divider in a settings menu and a dashboard this way). marker="none" keeps the element, the <li> semantics and the gap, and drops the bullet and the --space-5 indent (gh#714). A deliberate exception \u2014 a drag-and-drop Kanban column, an evidence list inside a TableCell \u2014 takes an ui-audit-disable-line that says so.'},{id:"no-hand-rolled-break-anywhere",severity:"warn",category:"composition",standard:"CSS Text 3 \xA75.5 (overflow-wrap) \xB7 WCAG 2.2 SC 1.4.10 (Reflow)",fix:`Replace className="[overflow-wrap:anywhere] break-words whitespace-normal" (or wrap-anywhere) with <Text break="anywhere">, which emits overflow-wrap: anywhere AND releases a table cell's inherited nowrap, so an email, code or id shrinks its column to the viewport. Not whitespace="pre-wrap": its break-word does not lower min-content, so a table cell stays wide (gh#927).`}];function be(e){return e?ue.filter(t=>t.category===e):ue}var ve="node node_modules/@godxjp/ui/scripts/visual-audit.mjs [--widths 390x844,1280x900] [--storage-state state.json] [--text-spacing] [--text-zoom 200] [--screenshots DIR] [--playwright-module PATH] [--executable-path CHROME] <baseUrl> [route \u2026] (--playwright-module points at an existing playwright or playwright-core install (e.g. the npx cache) \u2014 resolution order is that path, then the playwright peer, then playwright-core, and the bootstrap error lists everything tried; --executable-path is the Chrome passed to chromium.launch, over PLAYWRIGHT_CHROMIUM_EXECUTABLE; --storage-state takes a Playwright storageState JSON so signed-in routes are audited signed in; --text-spacing / --text-zoom 200 re-render each width with the WCAG 1.4.12 / 1.4.4 override and add evidence[] (geometry delta vs the unmodified render, C01 under the override, screenshot paths with --screenshots) \u2014 a zero delta is an infra error 'override not applied', never a pass, and visible text that did not take the override is listed in notTaken[] (selector + text) and fails the cell 'override partially applied' (hidden, aria-hidden, sr-only and svg text exempt); every route at 320\xD7900, 390\xD7844, 768\xD71024, 1024\xD7900, 1280\xD7900, 844\xD7390 by default; findings carry viewport + element + threshold origin; JSON adds unmapped[] (reviewer required, never a pass), matrix[] (per-width rule status + reviewer checklist) and acceptance \u2014 at best 'probes passed; independent acceptance pending', never done. 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)",fe=[{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 \u2014 86 KB instead of 367 KB gzip, since the bundled @font-face declarations are most of the CSS, gh#971); 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:"mixed-button-height",severity:"error",category:"layout",standard:"@godxjp/ui Button size (one size per row) \xB7 Nielsen consistency heuristic",fix:"Buttons in one flex row must render at one height (within 0.5px): one `size` per row; icon-sm pairs with sm, icon-xs with xs, icon with default. Runtime twin of the static mixed-button-size rule."},{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. The accent @godxjp/ui itself ships is exempt (gh#823) \u2014 this finding is always a colour someone chose."},{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."},{id:"c01-content-reachable",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 / 1.4.4 / 1.4.12; 1.5px tolerance repo-chosen threshold",kind:"probe",fix:"Nothing may be cut off: no horizontal scroll on the document or the app's main region, and no text/icon partly hidden by an overflow:hidden/clip ancestor. Let the container shrink its content (min-width:0 + wrap), or truncate with a recoverable full value (title / aria-label). Kit Breadcrumb ellipsis requires the complete label in both title and aria-label on its clipping element; a tooltip alone or a partial label is insufficient."},{id:"c02-search-usable",severity:"warn",category:"layout",standard:"search text area \u2265 8ch after padding/icon, hint fits within 1.5px (repo-chosen threshold); WCAG 1.4.10 principle",kind:"probe",fix:"A search field narrower than 8ch is not a search field. Below the width where it fits, show a named search trigger (data-audit-role=search-trigger) that opens a full-width field; keep the scope hint visible somewhere when the placeholder no longer fits."},{id:"c03-single-brand",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist only",kind:"checklist",fix:"Reviewer counts brand presentations in the closed and open shell: one logo+wordmark, distinct from the service name and breadcrumb."},{id:"c04-compact-account-name",severity:"warn",category:"a11y",standard:"compact (<600px) bar shows avatar-only or measured bounded shared-device identity (repo-chosen threshold); trigger name per WCAG 2.2 SC 4.1.2",kind:"probe",fix:"Shared-device exception: Topbar identity with TopbarItem truncate keeps a bounded, ellipsised name and full title/accessible name. Otherwise below 600px the bar shows the avatar only: hide the account name (it belongs in the opened account menu) and make the avatar the trigger \u2014 accessible name + aria-haspopup, opening identity and account controls."},{id:"c05-compact-editor-inset",severity:"warn",category:"layout",standard:"first unindented line \u2264 24px from the editor inner edge below 600px (repo-chosen threshold); WCAG 1.4.10 principle",kind:"probe",fix:"Reclaim the desktop drag-handle gutter on phones: the editor's base inset (padding included) is at most 24px below 600px; keep list indentation, and give tap/keyboard alternatives to the hover handle."},{id:"c06-compact-actions",severity:"warn",category:"layout",standard:"\u22642 direct actions + \u22641 overflow per task-action group below 600px (repo-chosen threshold); targets \u226524\xD724 per WCAG 2.2 SC 2.5.8",kind:"probe",fix:"On phones a task-action group shows at most two direct actions plus one overflow menu that really holds the rest; every target at least 24\xD724 CSS px (or a documented SC 2.5.8 exception)."},{id:"c07-width-control",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist (no library hook maps a width control)",kind:"checklist",fix:"A wide-page control must move content to an approved destination width (\xB11px), or be hidden on phones / labelled as a desktop-only preference that persists."},{id:"c08-sidebar-field-edges",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist (equivalent groups are a semantic call)",kind:"checklist",fix:"Equivalent sidebar fields share left and right edges within 2px; the reviewer inventories the fields before reading any mapping."},{id:"c09-progress-role",severity:"warn",category:"color",standard:"repo-chosen policy \u2014 human checklist (tone \u2194 meaning is a semantic call)",kind:"checklist",fix:"Ordinary branded progress uses the brand role; a status tone (success/warning/destructive) only where the bar reports that status. Thickness matches the approved variant \xB11px."},{id:"c10-rail-toggle-attached",severity:"warn",category:"layout",standard:"toggle centre within 8px of the declared rail edge at \u22651024px (repo-chosen threshold)",kind:"probe",fix:"An edge-mounted rail toggle sits on the rail edge it controls in both desktop states. Declare the pattern with data-audit-role=rail-toggle (+ data-audit-edge=start|end). A toggle inside `Sidebar action` is the supported placement and reports not-applicable-sidebar-action."},{id:"c11-search-keyline",severity:"warn",category:"layout",standard:"desktop search start within 2px of the content column start (repo-chosen threshold)",kind:"probe",fix:"A centred top-bar search starts on the content column's keyline, not the window's centre. Applies when the search sits in Topbar `center` (or carries data-audit-keyline)."},{id:"c12-tracked-uppercase-label",severity:"warn",category:"layout",standard:"section label uppercase AND \u226412px AND tracking \u22650.04em fails (repo-chosen threshold); no WCAG font floor implied",kind:"probe",fix:"Section labels read as sentence-case text at the body scale, not tiny tracked capitals. Literal domain codes are exempt when marked as such (<code>, <kbd>, data-audit-literal)."}];function ye(e){return e?fe.filter(t=>t.category===e):fe}var H='---\nversion: alpha\nname: godxjp-ui\ndescription: "Restrained Japanese-enterprise admin UI. Warm off-white canvas, near-black ink, one neutral accent, hairline borders, 14px/1.7 type, dense tables. Hierarchy comes from spacing steps and type, not from boxes, colour or shadow."\n\ncolors:\n canvas: "#fdfdfc" # --background (= --card, --popover)\n ink: "#24231e" # --foreground\n ink-muted: "#68665e" # --muted-foreground\n ink-tertiary: "#928f87" # --text-tertiary (captions, meta)\n primary: "#18181b" # --primary. Neutral placeholder: set your brand here\n on-primary: "#fdfdfc" # --primary-foreground\n ring: "{colors.primary}" # --ring follows --primary; no separate focus hue\n muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)\n accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)\n hairline: "#eeedec" # --border (dividers, card edges, table rules)\n control-edge: "#8b8279" # --input (the edge of an input; 3:1 against canvas)\n success: "#68be8d" # --success \u82E5\u7AF9\n warning: "#f8b500" # --warning \u5C71\u5439\n info: "#4c6cb3" # --info \u7FA4\u9752\n attention: "#eb6101" # --attention \u6731 (non-destructive urgency: late, overdue)\n destructive: "#b7282e" # --destructive \u831C (delete, failed, irreversible)\n text-success: "#09673b" # --text-success (status as text, AA on canvas)\n text-warning: "#8f5600" # --text-warning\n text-info: "#1d48a5" # --text-info\n text-error: "#aa181f" # --text-error\n\ntypography:\n family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"\n base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)\n page-title: { size: "28px (--page-title-font-size); 21px compact", weight: 500, lineHeight: 1.25 }\n section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }\n card-title: { size: "~15.7px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }\n body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body\n caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }\n weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600\n numerals: tabular # Text tabular / Badge tabular on every number column\n\nrounded:\n base: 6px # --radius (rides --scaling)\n steps: "--radius-sm/md/lg/xl = radius / \u03C6\xB2, / \u03C6, \xD71, \xD7 \u03C6"\n card: "~9.7px (--card-radius = radius \xD7 \u03C6)"\n pill: 9999px # --radius-pill: switch, radio, pill badge only\n\nspacing:\n scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"\n stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap\n inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap\n page: { x: 32px, y: 32px } # --space-page-x / -y\n section: 16px # --space-section\n\ndensity:\n axis: "AppProvider density / PageContainer density / data-density"\n compact: "--scaling 0.92 (data-heavy tables)"\n default: "--scaling 1 (control and row height 36px, --band-height-lg)"\n comfortable: "--scaling 1.08 (login, public, touch)"\n touch-floor: 44px # --band-height-xl\n\ncomponents:\n button-primary:\n {\n background: "{colors.primary}",\n text: "{colors.on-primary}",\n height: "--control-height",\n radius: "{rounded.base}",\n }\n button-secondary: { variant: outline, edge: "{colors.control-edge}" }\n input:\n {\n background: "{colors.canvas}",\n edge: "{colors.control-edge}",\n focus: "edge recolours to {colors.ring}",\n }\n card:\n {\n background: "{colors.canvas}",\n border: "1px {colors.hairline}",\n shadow: "none (--card-shadow)",\n radius: "{rounded.card}",\n padding: "--card-space-inset",\n }\n table:\n { rule: "1px {colors.hairline}", rowHeight: "--table-row-height", header: "{colors.ink-muted}" }\n badge: { variant: outline, tone: "status role only" }\n---\n\n## Overview\n\nA calm, dense tool for people who use it all day. The look is restrained Japanese enterprise: \u6E0B\u307F (low chroma), \u9593 (even breathing room), \u7C21\u7D20 (nothing that does not carry information). Distinctiveness comes from exact spacing, alignment and type, never from decoration.\n\nThe default `--primary` is a neutral ink (`240 6% 10%`). It is a placeholder, not a brand. Set the brand once with `--primary` in your theme, `tenantTheme(\'#RRGGBB\')` from `@godxjp/ui/app`, a `data-brand` preset (`brand`, `crm`, `logistics`, `partner`, `slate`) or `@godxjp/ui/themes/godx.css`. `--ring` follows `--primary`.\n\n## Colors\n\nColour is scarce. Most of the screen is canvas, ink and hairline.\n\n- **Accent (`--primary`)**: the one primary action per view, focus ring, links, the selected nav item. Nothing else: no tinted headers, avatars, icons or section backgrounds.\n- **Status (\u82E5\u7AF9 success, \u5C71\u5439 warning, \u7FA4\u9752 info, \u6731 attention, \u831C destructive)**: only to state a status. Use `Badge status=\u2026` (maps lifecycle keys to a fixed tone and icon) or `tone`. For status as text use the `--text-*` roles. Status colours are never re-themed per tenant.\n- \u6731 attention for urgency that is not destructive (late, overdue); \u831C only for loss (delete, failed, irreversible).\n- `--accent` is the hover/selected surface, not a brand colour. `--muted` is for quiet fills.\n- No gradients, glows or tinted page sections on app surfaces.\n\n## Typography\n\n- One family. Hierarchy from size steps, weight 400/500/700 and `ink` vs `ink-muted`. Page title ~20px; anything larger belongs on a marketing page. One exception: a long-form document title (Reader) is ~22px, two steps above its ~17.6px section headings, because one step reads flat there.\n- Body 14px / 1.7. Captions and meta 12.5px muted. `Text tabular` on every number, time and money column.\n- Japanese prose: `Text break="phrase"` so lines break between phrases. `Prose`/`Markdown` and `LegalDocumentShell` bodies do this themselves under `lang="ja"`.\n- Copy is quiet and factual: no praise, no alarm, no emoji, no em dash in UI strings. A success message states the fact (\u300C\u627F\u8A8D\u3057\u307E\u3057\u305F\u300D).\n\n## Layout & Density\n\n- Hierarchy is spacing first: related items `gap="xs"`/`"sm"`, groups `"md"`, sections `"lg"`/`"xl"`. If every gap is the same, nothing is grouped.\n- A section is a `Heading` plus spacing. Add a `Separator` when two sections must not read as one. Use a surface only when it is a real boundary (see Components).\n- Pick one density per page up front: `compact` for review grids, `default` everywhere else, `comfortable` for login and touch.\n- Content measure: forms and settings in `PageContainer measure="narrow"` or `"medium"`; tables use the full width.\n- Spacing comes from component props (`gap`, `pad`) and tokens, never `p-4`/`mt-6` literals.\n\n## Elevation\n\n- Flat. A card is canvas with a 1px `--border` hairline and no resting shadow; elevation is reserved for floating surfaces.\n- `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.\n- No hover lift on data cards. Depth never signals importance.\n\n## Components\n\n- **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`. Omit `title` only when the content draws the page\'s one `<h1>` (a document reader); PageContainer then renders no heading.\n- **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.\n- **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header. It wraps and never scrolls sideways; the 2\u20134 most-used filters stay inline and the rest go behind one "More filters" Button that opens a `Sheet` (cardinal rule #51, pattern `list-filters`).\n- **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.\n- **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).\n- **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.\n- **StatCard** only on a dashboard, always with `delta` that compares (vs last week, vs target). **EmptyState**: one sentence and at most one action.\n- **Alert** only for something the user must act on now. **Badge** only for status; one per row at most.\n\n## Admin Screen Anatomy\n\n### List\n\nHeader: title, then `extra` with the primary create action last. Below it a `Toolbar` with search and filters. Then one Card holding the `DataTable` (`CardContent flush`) and `Pagination`. No KPI row, no subtitle that restates the title, no filter Card separate from the table.\n\n### Detail\n\nHeader: record name as title, status `Badge` beside it, actions in `extra` (one primary). Body: `Descriptions` for metadata, then sections (Heading + content) for related records and history (`Timeline`). Use `Tabs` only when sections are independent and long. No Card per section; no icon beside every heading.\n\n### Form\n\n`Form` in a narrow measure; fields grouped by Heading sections, not Cards. Labels via `FormField`; helper text only when it adds information. One primary submit and a secondary cancel at the end, or in a sticky footer for long forms. Errors inline on the field plus `FormErrors` summary on submit.\n\n### Editor\n\nEdit (or create) one document or record. Header: record name as title, status `Badge` beside it, a subtitle only for a fact such as when it was last saved, and ONE primary Save in `extra` (`Button type="submit" form=<form id>`), Cancel `outline` before it. Body: one `Form` in a `SplitPane`: the main column holds the title field and a large body editor (`BlockEditor`, or `Textarea autoSize`); the aside holds metadata fields and read-only facts in `Descriptions`. The change note goes either directly under the body (where the writer finishes) or last in the aside \u2014 pick one per app and keep it. Name each aside group with a `Heading level={2}` one step above its field labels. The main\u2194aside gap is `--split-pane-gap` (default 16px); set it once in the theme. A part that is its own controlled component with its own `Form` (forms cannot nest) stays a separate form; the header Save then submits by handler (`onClick`) instead of `form=<id>`, still one Save. No Card per field group, no second Save at the bottom.\n\n### Reader\n\nA long-form document to read. The document owns the page\'s one `<h1>`: `PageContainer` gets `breadcrumb` and `extra` (Edit) and no `title`. `LegalDocumentShell` draws the reading column: title two steps above the section headings, one metadata line (version, date with `dateKind="updated"` for a revision), the contents rail, and prose at a reading measure (Japanese breaks between phrases under `lang="ja"`; stacked, a contents entry hugs its label). No Card around the article, no subtitle, no icon per heading.\n\n### Settings\n\nEach section is a Heading (plus one muted line if it adds a fact) over its content: `Descriptions` for read-only values, `ListRow` rows in one `Card` with `CardContent flush` for controls (`trailing` holds the control). Separate sections with spacing or a `Separator`. Changes that apply instantly need no Save button; a page with a Save button applies nothing until saved. Destructive actions last, in one `Card accent="destructive"`, each behind `AlertDialog`.\n\n### Dashboard\n\nKPI tiles belong on pages whose job is monitoring \u2014 dashboards, report heads (a short count strip above a list or report is fine too). Keep the set small; around four `StatCard`s is a good default, not a limit. Give a tile a comparison `delta` when a meaningful baseline exists (previous period, target, plan), with `inverse` when up is bad; a count of work to do (pending, overdue, unread) instead links to the list already filtered to those items \u2014 never invent a comparison. Every tile states its label, unit, period and scope. Below, panels in `ResponsiveGrid`, each a Card with a title and one "view all" link: a work queue, a short table, a chart.\n\n### Approval queue\n\nA list whose rows are decisions. Show requester, target, reason, submitted time; approve and reject reachable from the row (kebab or row buttons); with `DataTable`, also in `BulkActions`. Escalated or overdue rows get one status Badge, not a coloured row.\n\n## Do\'s and Don\'ts\n\n### Do\n\n- Lead with the data. The first thing below the header is the table, form or record.\n- Use one `Button` default variant per view; the rest `outline` or `ghost`.\n- Group by spacing, then by heading, then by Separator, and only then by Card.\n- Write the subtitle only when it adds a fact the title does not (date, scope, count).\n- Right-align numbers, `tabular` digits, ISO/`Intl` dates.\n\n### Don\'t\n\n- A subtitle that restates or translates the title.\n- A StatCard row above a list or form.\n- A Card around every section, each with Header + Title + Description.\n- An EmptyState with a large icon inside a Card; an icon in every card header.\n- Alert banners as page decoration; several Alerts stacked.\n- Badge walls: a badge per cell, rainbow tones, badges for non-status labels.\n- Uniform `gap` everywhere; arbitrary px spacing.\n- Coloured avatars, tinted section backgrounds, gradients, hover lift.\n\n## Responsive\n\n- Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.\n- `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.\n- `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.\n- Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.\n- Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.\n- Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.\n- Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.\n- Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu\'s `DropdownMenuLabel`, so a phone shows the avatar only.\n- A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.\n\n## Iteration Guide\n\n1. Pick the archetype, call `get_exemplar` and build from that screen, not from memory.\n2. `npx @godxjp/ui audit <files>` \u2014 zero errors.\n3. `npx @godxjp/ui visual-audit <url> <routes>` \u2014 runs every route at 320, 390, 768, 1024, 1280 and 844\xD7390 (landscape phone) and measures what a screenshot glance misses: clipped or squeezed controls (WCAG 1.4.10 has two halves \u2014 no two-way scrolling AND no lost information; page overflow alone checks only the first), compact search, avatar-only identity, editor inset, action count and target size, rail and search keylines, tiny tracked capitals. Zero machine failures at every width; an "unmapped" result is not a pass. Signed-in routes: `--storage-state state.json` (Playwright storageState). WCAG 1.4.12 / 1.4.4 cells: `--text-spacing` / `--text-zoom 200` (add `--screenshots DIR`) \u2014 each reports its geometry delta against the base render, and a zero delta is "override not applied", not a pass. Visible text that did not take the override is listed in `notTaken` (selector + text) and fails the cell as "override partially applied".\n4. Inspect every screenshot at every width, region by region \u2014 top bar, page header, body, aside or rail, menus and drawers \u2014 against the checklist the report prints. A region you were told not to change is still inspected; a defect there becomes an issue, not a silent pass.\n5. No top-N and no round cap. Stop only when every width has zero machine failures and every checklist row is pass or a written N/A.\n6. The builder does not accept its own screen. Report "probes passed; independent acceptance pending" with the report JSON, the screenshots per width and the checklist; acceptance comes from someone who did not build it (a person, or a separate agent given only the build and the artifacts). Never call a screen done on self-review.\n\n## Known Gaps\n\n- The default primary is neutral by design; a product without a brand colour looks monochrome until one is set.\n- `ui-audit` checks primitives and tokens, not structure: a page can pass it and still show every Don\'t above.\n- No edge-mounted rail toggle (a control straddling the sidebar border); `Sidebar action` is the supported place. Under `topbarSpan="full"` the bar shares no keyline with the content column.\n- `card-per-section` counts Cards per file, so a screen split across components can pass with a Card around every section.\n- Exemplar previews deploy on release tags, so they can lag `main`. On GitHub Pages a showcase URL answers HTTP 404 and renders through `404.html`, so open it in a browser; a plain fetch sees the 404.\n- Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.\n- From 640px up, `BlockEditor` text does not share the keyline of the `Input` above it: measured at 768/1024/1280, input text 13px inside its frame, editor text 57px inside (\u039444px), because the \u22EE\u22EE/+ handles (two 24px targets) sit in a start gutter inside the frame. Aligning the text would put the handles outside the field border, where a content column 24px from the viewport edge has no room. Below 640px the gutter collapses (C05). `--block-editor-gutter-inline-size` sets the gutter width; it cannot close the gap, because the handles live inside it. Accepted, not a defect to fix in the app.\n- On narrow screens the `PageContainer` title steps down (\u224817.6px) while body h2 stays \u224815.7px \u2014 one step apart. A document page not built on `LegalDocumentShell` keeps only that step on phones; there is no intermediate page-title size yet.\n\nModern refresh: field rows use 20px; form blocks 24px. Keyboard focus uses a 2px ring and 2px offset (navigation keeps its existing inset). Overlay scrim uses 40% black. Compact page gutters remain 16px. See docs/TOKENS.md for complete restoration.\n';var we=e=>e.toLowerCase().replace(/&/g," ").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");function W(e=H){let t=[],a=e,i=/^---\n[\s\S]*?\n---\n/.exec(e);i&&(t.push({id:"tokens",title:"Tokens (YAML front matter)",body:i[0].trim()}),a=e.slice(i[0].length));for(let o of a.split(/^(?=## )/m)){let n=/^## (.+)$/m.exec(o);!n||!o.startsWith("## ")||t.push({id:we(n[1]),title:n[1].trim(),body:o.trim()})}return t}function ke(e){let t=we(e);if(!t)return;let a=W();return a.find(i=>i.id===t)??a.find(i=>i.id.startsWith(t))??a.find(i=>i.id.split("-").includes(t))}function xe(e){let t=W().find(a=>a.id==="admin-screen-anatomy");if(t)for(let a of t.body.split(/^(?=### )/m)){let i=/^### (.+)$/m.exec(a);if(i&&i[1].trim().toLowerCase()===e.trim().toLowerCase())return a.trim()}}var Ke="https://godx-jp.github.io/godxjp-ui/",O=[{archetype:"list",aliases:["index","table","crud","collection","\u4E00\u89A7"],showcase:"table-crud-list",anatomy:["List"],focus:"Header actions in `extra`, Toolbar filters above the table (the bar wraps, never scrolls sideways; many filters \u2192 pattern `list-filters`, rule #51), one Card with DataTable + Pagination."},{archetype:"detail",aliases:["record","show","master-detail","\u8A73\u7D30"],showcase:"table-master-detail",anatomy:["Detail"],focus:"Record header with status Badge and actions, Descriptions metadata, history as a headed section."},{archetype:"editor",aliases:["form","edit","create","document-editor","record-editor","\u7DE8\u96C6"],showcase:"page-editor",anatomy:["Editor","Form"],focus:"One primary Save in `extra` submitting the Form by id; title field + large body editor in the main column, metadata + change note in a SplitPane aside. The body uses @godxjp/block-editor (a second package); `Textarea autoSize` fits the same FormField."},{archetype:"reader",aliases:["document","article","wiki","view","read","\u95B2\u89A7"],showcase:"page-reader",anatomy:["Reader"],focus:"PageContainer with breadcrumb + `extra` and NO `title`; LegalDocumentShell (@godxjp/ui/lab) owns the h1, metadata line, contents rail and prose at reading width."},{archetype:"settings",aliases:["preferences","account","\u8A2D\u5B9A"],showcase:"settings-account-sections",anatomy:["Settings"],focus:"Heading + ListRow sections without Cards; one destructive Card at the end."},{archetype:"dashboard",aliases:["overview","home","kpi","\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9"],showcase:"case1-warehouse-dashboard",anatomy:["Dashboard"],focus:"Four StatCards with comparison deltas, then titled panels in a ResponsiveGrid."},{archetype:"approval-queue",aliases:["approval","queue","inbox","review","\u627F\u8A8D"],showcase:"table-approval-queue",anatomy:["Approval queue"],focus:'Decision rows in `Table preset="action-collection"`, row actions in one kebab.'}],Se=e=>e.trim().toLowerCase().replace(/[\s_]+/g,"-");function Te(e){let t=Se(e);if(t)return O.find(a=>a.archetype===t||a.showcase===t||a.aliases.some(i=>Se(i)===t))}function Ce(e){return`${Ke}showcase/${e}`}var Ee={"case1-warehouse-dashboard":`/**
|
|
6244
|
+
finding and needs no suppression.`,ue=[{id:"no-direct-sonner-import",severity:"error",category:"api",standard:null,fix:'Use `import { toast } from "@godxjp/ui"` (or @godxjp/ui/feedback). A consumer-installed sonner can have a separate toast store from the kit Toaster and silently hide notifications. Sonner remains a dependency; no peer migration is required.'},{id:"dropdown-menu-non-menu-children",severity:"error",category:"composition",standard:null,fix:"DropdownMenuContent accepts menu collection nodes only. Use DropdownMenuItem for actions and Popover + ListRow for arbitrary headers, notification lists and links. Custom components returning menu items remain valid."},{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:"nested-card",severity:"warn",category:"composition",standard:null,fix:'A Card (or StatCard) inside a Card draws two borders for one boundary. Group inside the outer Card with <Heading> + <Flex direction="col"> or <Separator />.'},{id:"card-per-section",severity:"warn",category:"composition",standard:null,fix:'5+ Cards in one file is a Card per section. Mark a section with <Heading level={2}> and section spacing (PageContainer children / Flex gap="lg"); keep Card for a real boundary (a table surface, a selectable item).'},{id:"kpi-row-off-dashboard",severity:"warn",category:"composition",standard:null,fix:"3+ StatCards outside a file whose path names a dashboard. A row count is not a KPI: drop the row and show the total in <Pagination showTotal>. StatCard rows are for a dashboard with comparison or trend (delta)."},{id:"form-on-list-page",severity:"warn",category:"composition",standard:null,fix:'A submitting Form with 3+ FormFields and a DataTable/Table/List/ListRow on the same page is a create form inlined beside its list (design-complex-admin anti-pattern #3). Give create its own route, or a focused Dialog/Sheet, and link to it from the list page\'s PageContainer `extra`. Not findings: a Form inside a Dialog/Sheet/Drawer, a `role="search"` Form or a SearchForm (a \u691C\u7D22\u6761\u4EF6\u30D5\u30A9\u30FC\u30E0 searches the list, it does not create into it), a Form that never submits (no onSubmit, or one that only calls preventDefault: a filter bar), a 1-2 field form, an editor whose Table sits inside its Form, the page\'s own editor (a Form whose `id` a header `<Button type="submit" form={FORM_ID}>` submits, with a read-only list beside it), and a list page and a create page that one file renders in two PageContainers.'},{id:"hand-rolled-filter-bar",severity:"warn",category:"composition",standard:"@godxjp/ui cardinal rule #51",fix:'A list page (DataTable/Table/List/ListRow in the same PageContainer) whose 2+ filter controls (a FormField holding Select/SearchInput/Switch/Checkbox/DatePicker\u2026, or a self-labelled bare one) sit in your own Flex/div/Card/ResponsiveGrid instead of a <FilterBar>. Move them into <FilterBar> (`search` / `filters` / FilterBarGroup children / `actions`): it packs filters at the START with one gap, wraps, and owns clear + chips. justify="between"|"around"|"evenly" across filters is named in the snippet \u2014 the only far-end element of a filter bar is its `actions` slot. Past 2\u20134 filters use pattern list-filters; for other layouts (saved views, faceted sidebar, docked panel\u2026) see pattern filter-bar-layouts. Not findings: FilterBar/Toolbar/SearchForm children, a condition builder inside a submitting `role="search"` Form, a Sheet/Dialog/Drawer/Popover body, a Form that submits (a search form), a column of fields (an editor panel), cell editors inside a table, a single search box.'},{id:"empty-state-icon-in-card",severity:"warn",category:"composition",standard:null,fix:'In an admin surface an empty state is one sentence and one action. Drop `icon` from an EmptyState inside a Card, or use variant="compact".'},{id:"badge-wall",severity:"warn",category:"composition",standard:null,fix:"5+ Badge/Tag in one file. Badge is for status only; render categories, tiers and scopes as plain text, one status column per table."},{id:"alert-per-page",severity:"warn",category:"composition",standard:null,fix:"4+ Alerts in one file. Keep one Alert for a state that blocks or changes the task; field errors go in FormField, results in a toast, standing info in plain text."},{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:"formfield-needs-form",severity:"error",category:"composition",standard:"@godxjp/ui Form (gh#998)",fix:'Wrap FormFields in a <Form> (layout="horizontal" labelWidth controlWidth is the desktop default; layout="vertical" is valid for long labels or a narrow container); a row of fields is <Flex attached> or <Form columns>, never a hand-rolled <Flex>. A field component whose whole output is one FormField is exempt, and so is a field-group component that every caller renders inside a <Form> (checked across the project); when only some callers do, wrap the bare call sites the message names, not the fields. A call site inside a same-file render helper (`const legFields = (\u2026) => \u2026`) counts as under the <Form> its every call sits in (gh#1260).'},{id:"mixed-button-size",severity:"error",category:"composition",standard:null,fix:'Sibling <Button>s under one parent (through fragments, {cond && \u2026}, ternaries, Tooltip wrappers) share ONE size; a missing size is default; icon-sm pairs with sm, icon-xs with xs, icon with default. Never `size="sm"` beside a default Button in one action row.'},{id:"dialog-form-too-big",severity:"error",category:"composition",standard:"@godxjp/ui form placement (gh#998)",fix:"Three or more FormFields in a Dialog body is a page: give the form its own route. Dialogs hold a confirmation or one or two fields; a side Sheet (drawer) may hold a filter or edit form."},{id:"filter-strip-no-scroll",severity:"error",category:"composition",standard:"WCAG 2.2 SC 1.4.10 (Reflow) \xB7 @godxjp/ui cardinal rule #51",fix:'A box that scrolls on the inline axis (`overflow-x-auto`/`overflow-x-scroll`, or `<ScrollArea orientation="horizontal"|"both">`) around FilterBar/Toolbar/FormField/Select/SearchInput/Input/a picker is a sideways-scrolling filter strip. Drop the scroller and let <FilterBar> wrap; keep the 2\u20134 most-used filters inline and put the rest behind ONE \'More filters\' Button that opens a Sheet (get_rule 51, get_pattern list-filters). A scroller whose body holds a Table/DataTable or Cards (a kanban board) is two-dimensional content and is not flagged; a vertical pane (`overflow-y-auto`, a Sheet body) is not flagged.'},{id:"select-width-hint",severity:"warn",category:"composition",standard:"GOV.UK Design System \xB7 text input width",fix:"Size a Select for its content \u2014 `controlWidth` on the FormField, or once on the <Form>."},{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:"filter-bar-no-overflow",severity:"error",category:"api",standard:null,fix:"FilterBar/Toolbar has no `overflow` prop (removed in 33.0.0): the bar wraps and never scrolls sideways. Delete it; put rarely used filters behind one 'More filters' Button that opens a Sheet."},{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 \xB7 Accessible Name Computation 1.2",fix:"Name <Button size='icon'> with aria-label={t('\u2026')} OR from content \u2014 a <VisuallyHidden>/sr-only child beside the aria-hidden glyph. Text inside an aria-hidden subtree names nothing."},{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-hand-rolled-scrollport",severity:"warn",category:"a11y",standard:"WCAG 2.2 SC 2.1.1 (Keyboard) \xB7 WAI-ARIA 1.2 (group) \xB7 Deque axe-core scrollable-region-focusable",fix:'Replace className="overflow-auto / overflow-y-auto / overflow-x-auto / overflow-scroll" on your own element with <ScrollArea label={t("\u2026")} orientation>, which is the tab stop, the role and the localized name \u2014 and withholds all three while there is nothing to scroll. A browser audit only fails a scrollport whose content has NO focusable child, so the same markup is clean or broken depending on the data; this reads the markup instead (gh#825). overflow-hidden is a clipping box, not a scrollport, and is not flagged. A strip of FILTERS or form controls is not a scrollport at all: it wraps (`filter-strip-no-scroll`, cardinal rule #51) \u2014 do not move it into a horizontal ScrollArea.'},{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."},{id:"lucide-icon-needs-size",severity:"warn",category:"composition",standard:"WCAG 2.2 SC 1.4.4 \xB7 @godxjp/ui icon scale (--icon-size-*)",fix:'A lucide glyph outside a sizing context draws at its intrinsic 24px. Render it as <Icon as={Lock} size="sm" tone="muted" /> \u2014 the primitive puts it on the --icon-size-* scale and is aria-hidden unless you pass a label.'},{id:"no-hand-rolled-list",severity:"warn",category:"composition",standard:"WAI-ARIA 1.2 (list / listitem) \xB7 HTML Living Standard (ul/ol/li) \xB7 WCAG 2.2 SC 1.3.1",fix:'Build the list as <Flex as="ul" marker="none" direction="col" gap="none"> with <ListRow as="li"> rows \u2014 not a raw <ul>/<ol> (no gap token), not <div role="list">/<div role="listitem">, and never a wrapper around each row: the divider is :not(:last-child) among SIBLINGS, so a row alone in its own wrapper loses it silently (a consumer lost every divider in a settings menu and a dashboard this way). marker="none" keeps the element, the <li> semantics and the gap, and drops the bullet and the --space-5 indent (gh#714). A deliberate exception \u2014 a drag-and-drop Kanban column, an evidence list inside a TableCell \u2014 takes an ui-audit-disable-line that says so.'},{id:"no-hand-rolled-break-anywhere",severity:"warn",category:"composition",standard:"CSS Text 3 \xA75.5 (overflow-wrap) \xB7 WCAG 2.2 SC 1.4.10 (Reflow)",fix:`Replace className="[overflow-wrap:anywhere] break-words whitespace-normal" (or wrap-anywhere) with <Text break="anywhere">, which emits overflow-wrap: anywhere AND releases a table cell's inherited nowrap, so an email, code or id shrinks its column to the viewport. Not whitespace="pre-wrap": its break-word does not lower min-content, so a table cell stays wide (gh#927).`}];function be(e){return e?ue.filter(t=>t.category===e):ue}var ve="node node_modules/@godxjp/ui/scripts/visual-audit.mjs [--widths 390x844,1280x900] [--storage-state state.json] [--text-spacing] [--text-zoom 200] [--screenshots DIR] [--playwright-module PATH] [--executable-path CHROME] <baseUrl> [route \u2026] (--playwright-module points at an existing playwright or playwright-core install (e.g. the npx cache) \u2014 resolution order is that path, then the playwright peer, then playwright-core, and the bootstrap error lists everything tried; --executable-path is the Chrome passed to chromium.launch, over PLAYWRIGHT_CHROMIUM_EXECUTABLE; --storage-state takes a Playwright storageState JSON so signed-in routes are audited signed in; --text-spacing / --text-zoom 200 re-render each width with the WCAG 1.4.12 / 1.4.4 override and add evidence[] (geometry delta vs the unmodified render, C01 under the override, screenshot paths with --screenshots) \u2014 a zero delta is an infra error 'override not applied', never a pass, and visible text that did not take the override is listed in notTaken[] (selector + text) and fails the cell 'override partially applied' (hidden, aria-hidden, sr-only and svg text exempt); every route at 320\xD7900, 390\xD7844, 768\xD71024, 1024\xD7900, 1280\xD7900, 844\xD7390 by default; findings carry viewport + element + threshold origin; JSON adds unmapped[] (reviewer required, never a pass), matrix[] (per-width rule status + reviewer checklist) and acceptance \u2014 at best 'probes passed; independent acceptance pending', never done. 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)",fe=[{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 \u2014 86 KB instead of 367 KB gzip, since the bundled @font-face declarations are most of the CSS, gh#971); 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:"mixed-button-height",severity:"error",category:"layout",standard:"@godxjp/ui Button size (one size per row) \xB7 Nielsen consistency heuristic",fix:"Buttons in one flex row must render at one height (within 0.5px): one `size` per row; icon-sm pairs with sm, icon-xs with xs, icon with default. Runtime twin of the static mixed-button-size rule."},{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. The accent @godxjp/ui itself ships is exempt (gh#823) \u2014 this finding is always a colour someone chose."},{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."},{id:"c01-content-reachable",severity:"warn",category:"layout",standard:"WCAG 2.2 SC 1.4.10 / 1.4.4 / 1.4.12; 1.5px tolerance repo-chosen threshold",kind:"probe",fix:"Nothing may be cut off: no horizontal scroll on the document or the app's main region, and no text/icon partly hidden by an overflow:hidden/clip ancestor. Let the container shrink its content (min-width:0 + wrap), or truncate with a recoverable full value (title / aria-label). Kit Breadcrumb ellipsis requires the complete label in both title and aria-label on its clipping element; a tooltip alone or a partial label is insufficient."},{id:"c02-search-usable",severity:"warn",category:"layout",standard:"search text area \u2265 8ch after padding/icon, hint fits within 1.5px (repo-chosen threshold); WCAG 1.4.10 principle",kind:"probe",fix:"A search field narrower than 8ch is not a search field. Below the width where it fits, show a named search trigger (data-audit-role=search-trigger) that opens a full-width field; keep the scope hint visible somewhere when the placeholder no longer fits."},{id:"c03-single-brand",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist only",kind:"checklist",fix:"Reviewer counts brand presentations in the closed and open shell: one logo+wordmark, distinct from the service name and breadcrumb."},{id:"c04-compact-account-name",severity:"warn",category:"a11y",standard:"compact (<600px) bar shows avatar-only or measured bounded shared-device identity (repo-chosen threshold); trigger name per WCAG 2.2 SC 4.1.2",kind:"probe",fix:"Shared-device exception: Topbar identity with TopbarItem truncate keeps a bounded, ellipsised name and full title/accessible name. Otherwise below 600px the bar shows the avatar only: hide the account name (it belongs in the opened account menu) and make the avatar the trigger \u2014 accessible name + aria-haspopup, opening identity and account controls."},{id:"c05-compact-editor-inset",severity:"warn",category:"layout",standard:"first unindented line \u2264 24px from the editor inner edge below 600px (repo-chosen threshold); WCAG 1.4.10 principle",kind:"probe",fix:"Reclaim the desktop drag-handle gutter on phones: the editor's base inset (padding included) is at most 24px below 600px; keep list indentation, and give tap/keyboard alternatives to the hover handle."},{id:"c06-compact-actions",severity:"warn",category:"layout",standard:"\u22642 direct actions + \u22641 overflow per task-action group below 600px (repo-chosen threshold); targets \u226524\xD724 per WCAG 2.2 SC 2.5.8",kind:"probe",fix:"On phones a task-action group shows at most two direct actions plus one overflow menu that really holds the rest; every target at least 24\xD724 CSS px (or a documented SC 2.5.8 exception)."},{id:"c07-width-control",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist (no library hook maps a width control)",kind:"checklist",fix:"A wide-page control must move content to an approved destination width (\xB11px), or be hidden on phones / labelled as a desktop-only preference that persists."},{id:"c08-sidebar-field-edges",severity:"warn",category:"layout",standard:"repo-chosen policy \u2014 human checklist (equivalent groups are a semantic call)",kind:"checklist",fix:"Equivalent sidebar fields share left and right edges within 2px; the reviewer inventories the fields before reading any mapping."},{id:"c09-progress-role",severity:"warn",category:"color",standard:"repo-chosen policy \u2014 human checklist (tone \u2194 meaning is a semantic call)",kind:"checklist",fix:"Ordinary branded progress uses the brand role; a status tone (success/warning/destructive) only where the bar reports that status. Thickness matches the approved variant \xB11px."},{id:"c10-rail-toggle-attached",severity:"warn",category:"layout",standard:"toggle centre within 8px of the declared rail edge at \u22651024px (repo-chosen threshold)",kind:"probe",fix:"An edge-mounted rail toggle sits on the rail edge it controls in both desktop states. Declare the pattern with data-audit-role=rail-toggle (+ data-audit-edge=start|end). A toggle inside `Sidebar action` is the supported placement and reports not-applicable-sidebar-action."},{id:"c11-search-keyline",severity:"warn",category:"layout",standard:"desktop search start within 2px of the content column start (repo-chosen threshold)",kind:"probe",fix:"A centred top-bar search starts on the content column's keyline, not the window's centre. Applies when the search sits in Topbar `center` (or carries data-audit-keyline)."},{id:"c12-tracked-uppercase-label",severity:"warn",category:"layout",standard:"section label uppercase AND \u226412px AND tracking \u22650.04em fails (repo-chosen threshold); no WCAG font floor implied",kind:"probe",fix:"Section labels read as sentence-case text at the body scale, not tiny tracked capitals. Literal domain codes are exempt when marked as such (<code>, <kbd>, data-audit-literal)."}];function ye(e){return e?fe.filter(t=>t.category===e):fe}var H='---\nversion: alpha\nname: godxjp-ui\ndescription: "Restrained Japanese-enterprise admin UI. Warm off-white canvas, near-black ink, one neutral accent, hairline borders, 14px/1.7 type, dense tables. Hierarchy comes from spacing steps and type, not from boxes, colour or shadow."\n\ncolors:\n canvas: "#fdfdfc" # --background (= --card, --popover)\n ink: "#24231e" # --foreground\n ink-muted: "#68665e" # --muted-foreground\n ink-tertiary: "#928f87" # --text-tertiary (captions, meta)\n primary: "#18181b" # --primary. Neutral placeholder: set your brand here\n on-primary: "#fdfdfc" # --primary-foreground\n ring: "{colors.primary}" # --ring follows --primary; no separate focus hue\n muted: "#f4f3f0" # --muted / --secondary (quiet fills, zebra, hover)\n accent: "#ebe9e5" # --accent (hover/selected background, NOT a brand colour)\n hairline: "#eeedec" # --border (dividers, card edges, table rules)\n control-edge: "#8b8279" # --input (the edge of an input; 3:1 against canvas)\n success: "#68be8d" # --success \u82E5\u7AF9\n warning: "#f8b500" # --warning \u5C71\u5439\n info: "#4c6cb3" # --info \u7FA4\u9752\n attention: "#eb6101" # --attention \u6731 (non-destructive urgency: late, overdue)\n destructive: "#b7282e" # --destructive \u831C (delete, failed, irreversible)\n text-success: "#09673b" # --text-success (status as text, AA on canvas)\n text-warning: "#8f5600" # --text-warning\n text-info: "#1d48a5" # --text-info\n text-error: "#aa181f" # --text-error\n\ntypography:\n family: "system stack (--font-sans-base); Noto Sans JP via opt-in @godxjp/ui/styles/fonts"\n base: 14px # --font-size-base; every step derives from it (ratio --font-size-ratio 1.1227)\n page-title: { size: "28px (--page-title-font-size); 21px compact", weight: 500, lineHeight: 1.25 }\n section-title: { size: "~17.6px (--heading-h2)", weight: 500, lineHeight: 1.25 }\n card-title: { size: "~15.7px (--card-title-font-size)", weight: 500, lineHeight: 1.25 }\n body: { size: 14px, weight: 400, lineHeight: 1.7 } # --line-height-body\n caption: { size: "~12.5px (--font-size-xs)", weight: 400, lineHeight: 1.5 }\n weights: [400, 500, 700] # --font-weight-semibold is 500; there is no 600\n numerals: tabular # Text tabular / Badge tabular on every number column\n\nrounded:\n base: 6px # --radius (rides --scaling)\n steps: "--radius-sm/md/lg/xl = radius / \u03C6\xB2, / \u03C6, \xD71, \xD7 \u03C6"\n card: "~9.7px (--card-radius = radius \xD7 \u03C6)"\n pill: 9999px # --radius-pill: switch, radio, pill badge only\n\nspacing:\n scale: "--space-1..24 = 4px steps (4, 8, 12, 16, 20, 24, 32, 40, 48, 80, 96)"\n stack: { xs: 4px, sm: 8px, md: 16px, lg: 24px, xl: 40px } # Flex direction="col" gap\n inline: { xs: 4px, sm: 8px, md: 12px, lg: 16px, xl: 24px } # Flex direction="row" gap\n page: { x: 32px, y: 32px } # --space-page-x / -y\n section: 16px # --space-section\n\ndensity:\n axis: "AppProvider density / PageContainer density / data-density"\n compact: "--scaling 0.92 (data-heavy tables)"\n default: "--scaling 1 (control and row height 36px, --band-height-lg)"\n comfortable: "--scaling 1.08 (login, public, touch)"\n touch-floor: 44px # --band-height-xl\n\ncomponents:\n button-primary:\n {\n background: "{colors.primary}",\n text: "{colors.on-primary}",\n height: "--control-height",\n radius: "{rounded.base}",\n }\n button-secondary: { variant: outline, edge: "{colors.control-edge}" }\n input:\n {\n background: "{colors.canvas}",\n edge: "{colors.control-edge}",\n focus: "edge recolours to {colors.ring}",\n }\n card:\n {\n background: "{colors.canvas}",\n border: "1px {colors.hairline}",\n shadow: "none (--card-shadow)",\n radius: "{rounded.card}",\n padding: "--card-space-inset",\n }\n table:\n { rule: "1px {colors.hairline}", rowHeight: "--table-row-height", header: "{colors.ink-muted}" }\n badge: { variant: outline, tone: "status role only" }\n---\n\n## Overview\n\nA calm, dense tool for people who use it all day. The look is restrained Japanese enterprise: \u6E0B\u307F (low chroma), \u9593 (even breathing room), \u7C21\u7D20 (nothing that does not carry information). Distinctiveness comes from exact spacing, alignment and type, never from decoration.\n\nThe default `--primary` is a neutral ink (`240 6% 10%`). It is a placeholder, not a brand. Set the brand once with `--primary` in your theme, `tenantTheme(\'#RRGGBB\')` from `@godxjp/ui/app`, a `data-brand` preset (`brand`, `crm`, `logistics`, `partner`, `slate`) or `@godxjp/ui/themes/godx.css`. `--ring` follows `--primary`.\n\n## Colors\n\nColour is scarce. Most of the screen is canvas, ink and hairline.\n\n- **Accent (`--primary`)**: the one primary action per view, focus ring, links, the selected nav item. Nothing else: no tinted headers, avatars, icons or section backgrounds.\n- **Status (\u82E5\u7AF9 success, \u5C71\u5439 warning, \u7FA4\u9752 info, \u6731 attention, \u831C destructive)**: only to state a status. Use `Badge status=\u2026` (maps lifecycle keys to a fixed tone and icon) or `tone`. For status as text use the `--text-*` roles. Status colours are never re-themed per tenant.\n- \u6731 attention for urgency that is not destructive (late, overdue); \u831C only for loss (delete, failed, irreversible).\n- `--accent` is the hover/selected surface, not a brand colour. `--muted` is for quiet fills.\n- No gradients, glows or tinted page sections on app surfaces.\n\n## Typography\n\n- One family. Hierarchy from size steps, weight 400/500/700 and `ink` vs `ink-muted`. Page title ~20px; anything larger belongs on a marketing page. One exception: a long-form document title (Reader) is ~22px, two steps above its ~17.6px section headings, because one step reads flat there.\n- Body 14px / 1.7. Captions and meta 12.5px muted. `Text tabular` on every number, time and money column.\n- Japanese prose: `Text break="phrase"` so lines break between phrases. `Prose`/`Markdown` and `LegalDocumentShell` bodies do this themselves under `lang="ja"`.\n- Copy is quiet and factual: no praise, no alarm, no emoji, no em dash in UI strings. A success message states the fact (\u300C\u627F\u8A8D\u3057\u307E\u3057\u305F\u300D).\n\n## Layout & Density\n\n- Hierarchy is spacing first: related items `gap="xs"`/`"sm"`, groups `"md"`, sections `"lg"`/`"xl"`. If every gap is the same, nothing is grouped.\n- A section is a `Heading` plus spacing. Add a `Separator` when two sections must not read as one. Use a surface only when it is a real boundary (see Components).\n- Pick one density per page up front: `compact` for review grids, `default` everywhere else, `comfortable` for login and touch.\n- Content measure: forms and settings in `PageContainer measure="narrow"` or `"medium"`; tables use the full width.\n- Spacing comes from component props (`gap`, `pad`) and tokens, never `p-4`/`mt-6` literals.\n\n## Elevation\n\n- Flat. A card is canvas with a 1px `--border` hairline and no resting shadow; elevation is reserved for floating surfaces.\n- `--shadow-md`/`--shadow-lg` belong to things that float: popover, dropdown, dialog, sheet, toast.\n- No hover lift on data cards. Depth never signals importance.\n\n## Components\n\n- **PageContainer** is the page shell: `title`, optional `subtitle`, `breadcrumb`, `extra` (page actions, primary last), `toolbar`, `footer`. Omit `title` only when the content draws the page\'s one `<h1>` (a document reader); PageContainer then renders no heading.\n- **Card** only for a real boundary: a table with its own toolbar and pagination, a list of setting rows, a dashboard panel, a danger zone (`accent="destructive"`), a selectable item. Not around every section, never nested. `CardContent flush` for tables and row lists.\n- **Toolbar** (+ `ToolbarGroup`) is the filter strip: search, labelled filters, clear-all, result count. It sits above the table, never in the page header. It wraps and never scrolls sideways; the 2\u20134 most-used filters stay inline and the rest go behind one "More filters" Button that opens a `Sheet` (cardinal rule #51, pattern `list-filters`).\n- **DataTable** for lists: sort, selection with `DataTable.BulkActions`, density, empty/loading/error states. Row actions in one kebab `DropdownMenu`.\n- **Descriptions** for record metadata. **ListRow** for setting rows (`trailing` holds the control).\n- **Form + FormField** for every input; actions at the end of the form or in a sticky `PageContainer footer`. Create/edit is a page, not a dialog.\n- **StatCard** only on a dashboard, always with `delta` that compares (vs last week, vs target). **EmptyState**: one sentence and at most one action.\n- **Alert** only for something the user must act on now. **Badge** only for status; one per row at most.\n\n## Admin Screen Anatomy\n\n### List\n\nHeader: title, then `extra` with the primary create action last. Below it a `Toolbar` with search and filters. Then one Card holding the `DataTable` (`CardContent flush`) and `Pagination`. No KPI row, no subtitle that restates the title, no filter Card separate from the table.\n\n### Detail\n\nHeader: record name as title, status `Badge` beside it, actions in `extra` (one primary). Body: `Descriptions` for metadata, then sections (Heading + content) for related records and history (`Timeline`). Use `Tabs` only when sections are independent and long. No Card per section; no icon beside every heading.\n\n### Form\n\n`Form` in a narrow measure; fields grouped by Heading sections, not Cards. Labels via `FormField`; helper text only when it adds information. One primary submit and a secondary cancel at the end, or in a sticky footer for long forms. Errors inline on the field plus `FormErrors` summary on submit.\n\n### Editor\n\nEdit (or create) one document or record. Header: record name as title, status `Badge` beside it, a subtitle only for a fact such as when it was last saved, and ONE primary Save in `extra` (`Button type="submit" form=<form id>`), Cancel `outline` before it. Body: one `Form` in a `SplitPane`: the main column holds the title field and a large body editor (`BlockEditor`, or `Textarea autoSize`); the aside holds metadata fields and read-only facts in `Descriptions`. The change note goes either directly under the body (where the writer finishes) or last in the aside \u2014 pick one per app and keep it. Name each aside group with a `Heading level={2}` one step above its field labels. The main\u2194aside gap is `--split-pane-gap` (default 16px); set it once in the theme. A part that is its own controlled component with its own `Form` (forms cannot nest) stays a separate form; the header Save then submits by handler (`onClick`) instead of `form=<id>`, still one Save. No Card per field group, no second Save at the bottom.\n\n### Reader\n\nA long-form document to read. The document owns the page\'s one `<h1>`: `PageContainer` gets `breadcrumb` and `extra` (Edit) and no `title`. `LegalDocumentShell` draws the reading column: title two steps above the section headings, one metadata line (version, date with `dateKind="updated"` for a revision), the contents rail, and prose at a reading measure (Japanese breaks between phrases under `lang="ja"`; stacked, a contents entry hugs its label). No Card around the article, no subtitle, no icon per heading.\n\n### Settings\n\nEach section is a Heading (plus one muted line if it adds a fact) over its content: `Descriptions` for read-only values, `ListRow` rows in one `Card` with `CardContent flush` for controls (`trailing` holds the control). Separate sections with spacing or a `Separator`. Changes that apply instantly need no Save button; a page with a Save button applies nothing until saved. Destructive actions last, in one `Card accent="destructive"`, each behind `AlertDialog`.\n\n### Dashboard\n\nKPI tiles belong on pages whose job is monitoring \u2014 dashboards, report heads (a short count strip above a list or report is fine too). Keep the set small; around four `StatCard`s is a good default, not a limit. Give a tile a comparison `delta` when a meaningful baseline exists (previous period, target, plan), with `inverse` when up is bad; a count of work to do (pending, overdue, unread) instead links to the list already filtered to those items \u2014 never invent a comparison. Every tile states its label, unit, period and scope. Below, panels in `ResponsiveGrid`, each a Card with a title and one "view all" link: a work queue, a short table, a chart.\n\n### Approval queue\n\nA list whose rows are decisions. Show requester, target, reason, submitted time; approve and reject reachable from the row (kebab or row buttons); with `DataTable`, also in `BulkActions`. Escalated or overdue rows get one status Badge, not a coloured row.\n\n## Do\'s and Don\'ts\n\n### Do\n\n- Lead with the data. The first thing below the header is the table, form or record.\n- Use one `Button` default variant per view; the rest `outline` or `ghost`.\n- Group by spacing, then by heading, then by Separator, and only then by Card.\n- Write the subtitle only when it adds a fact the title does not (date, scope, count).\n- Right-align numbers, `tabular` digits, ISO/`Intl` dates.\n\n### Don\'t\n\n- A subtitle that restates or translates the title.\n- A StatCard row above a list or form.\n- A Card around every section, each with Header + Title + Description.\n- An EmptyState with a large icon inside a Card; an icon in every card header.\n- Alert banners as page decoration; several Alerts stacked.\n- Badge walls: a badge per cell, rainbow tones, badges for non-status labels.\n- Uniform `gap` everywhere; arbitrary px spacing.\n- Coloured avatars, tinted section backgrounds, gradients, hover lift.\n\n## Responsive\n\n- Check 390, 768 and 1280 wide. Nothing scrolls horizontally except a wide table inside its own container.\n- `DataTable` hides `hiddenOnMobile` columns and can stack rows (`collapseBelow`); `Table preset="action-collection"` keeps the actions column visible at 390.\n- `ResponsiveGrid` columns follow its container. `AppShell` turns the sidebar into a drawer on narrow screens.\n- Header `extra` wraps under the title; a toolbar wraps its filters at every width, phone included: no sideways-scrolling strip of filters or filter chips (rule #51). Touch targets 44px at `comfortable`.\n- Below 640px: at most 2 direct actions in `extra` plus one overflow `DropdownMenu` for the rest; it wraps but never prioritises for you.\n- Bar search is `TopbarSearch`, never a bare `SearchInput`: it folds to a named search cell below 640px. To share the content keyline, keep the default `topbarSpan="content"` and put it first in `Topbar start`.\n- Shared-device exception (#1370): `Topbar identity={<TopbarItem truncate icon={<Avatar/>}>{name}</TopbarItem>}` keeps the name visible within a 40% share. Only the label ellipsises; the full string is the title and accessible text. Primary control floors remain intact; only the other clusters scroll or fold into `overflow="menu"`. Never use label/icon hiding props on this identity. C04 checks the bounds and recovery text, not just the opt-in.\n- Account cell: `TopbarItem icon={<Avatar/>} labelHideBelow="sm"` with the name repeated in the menu\'s `DropdownMenuLabel`, so a phone shows the avatar only.\n- A sidebar collapse toggle goes in `Sidebar action` (it receives `collapsed`), not in the bar beside the brand. The phone drawer has its own trigger.\n\n## Iteration Guide\n\n1. Pick the archetype, call `get_exemplar` and build from that screen, not from memory.\n2. `npx @godxjp/ui audit <files>` \u2014 zero errors.\n3. `npx @godxjp/ui visual-audit <url> <routes>` \u2014 runs every route at 320, 390, 768, 1024, 1280 and 844\xD7390 (landscape phone) and measures what a screenshot glance misses: clipped or squeezed controls (WCAG 1.4.10 has two halves \u2014 no two-way scrolling AND no lost information; page overflow alone checks only the first), compact search, avatar-only identity, editor inset, action count and target size, rail and search keylines, tiny tracked capitals. Zero machine failures at every width; an "unmapped" result is not a pass. Signed-in routes: `--storage-state state.json` (Playwright storageState). WCAG 1.4.12 / 1.4.4 cells: `--text-spacing` / `--text-zoom 200` (add `--screenshots DIR`) \u2014 each reports its geometry delta against the base render, and a zero delta is "override not applied", not a pass. Visible text that did not take the override is listed in `notTaken` (selector + text) and fails the cell as "override partially applied".\n4. Inspect every screenshot at every width, region by region \u2014 top bar, page header, body, aside or rail, menus and drawers \u2014 against the checklist the report prints. A region you were told not to change is still inspected; a defect there becomes an issue, not a silent pass.\n5. No top-N and no round cap. Stop only when every width has zero machine failures and every checklist row is pass or a written N/A.\n6. The builder does not accept its own screen. Report "probes passed; independent acceptance pending" with the report JSON, the screenshots per width and the checklist; acceptance comes from someone who did not build it (a person, or a separate agent given only the build and the artifacts). Never call a screen done on self-review.\n\n## Known Gaps\n\n- The default primary is neutral by design; a product without a brand colour looks monochrome until one is set.\n- The kit ships no expressive surface. A sign-in, onboarding or first empty state that should carry the brand is built on top of the kit, never inside it: the shipped skill `godxjp-ui-brand-surface` governs it (kit components on the form side, kit tokens re-set on the surface, `--primary` untouched; pattern `auth-brand-surface`). Every other screen stays as this document describes.\n- `ui-audit` checks primitives and tokens, not structure: a page can pass it and still show every Don\'t above.\n- No edge-mounted rail toggle (a control straddling the sidebar border); `Sidebar action` is the supported place. Under `topbarSpan="full"` the bar shares no keyline with the content column.\n- `card-per-section` counts Cards per file, so a screen split across components can pass with a Card around every section.\n- Exemplar previews deploy on release tags, so they can lag `main`. On GitHub Pages a showcase URL answers HTTP 404 and renders through `404.html`, so open it in a browser; a plain fetch sees the 404.\n- Dark theme (`data-theme="dark"`) is supported; the exemplars are reviewed in light.\n- From 640px up, `BlockEditor` text does not share the keyline of the `Input` above it: measured at 768/1024/1280, input text 13px inside its frame, editor text 57px inside (\u039444px), because the \u22EE\u22EE/+ handles (two 24px targets) sit in a start gutter inside the frame. Aligning the text would put the handles outside the field border, where a content column 24px from the viewport edge has no room. Below 640px the gutter collapses (C05). `--block-editor-gutter-inline-size` sets the gutter width; it cannot close the gap, because the handles live inside it. Accepted, not a defect to fix in the app.\n- On narrow screens the `PageContainer` title steps down (\u224817.6px) while body h2 stays \u224815.7px \u2014 one step apart. A document page not built on `LegalDocumentShell` keeps only that step on phones; there is no intermediate page-title size yet.\n\nModern refresh: field rows use 20px; form blocks 24px. Keyboard focus uses a 2px ring and 2px offset (navigation keeps its existing inset). Overlay scrim uses 40% black. Compact page gutters remain 16px. See docs/TOKENS.md for complete restoration.\n';var we=e=>e.toLowerCase().replace(/&/g," ").replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"");function W(e=H){let t=[],a=e,i=/^---\n[\s\S]*?\n---\n/.exec(e);i&&(t.push({id:"tokens",title:"Tokens (YAML front matter)",body:i[0].trim()}),a=e.slice(i[0].length));for(let n of a.split(/^(?=## )/m)){let o=/^## (.+)$/m.exec(n);!o||!n.startsWith("## ")||t.push({id:we(o[1]),title:o[1].trim(),body:n.trim()})}return t}function ke(e){let t=we(e);if(!t)return;let a=W();return a.find(i=>i.id===t)??a.find(i=>i.id.startsWith(t))??a.find(i=>i.id.split("-").includes(t))}function xe(e){let t=W().find(a=>a.id==="admin-screen-anatomy");if(t)for(let a of t.body.split(/^(?=### )/m)){let i=/^### (.+)$/m.exec(a);if(i&&i[1].trim().toLowerCase()===e.trim().toLowerCase())return a.trim()}}var Ke="https://godx-jp.github.io/godxjp-ui/",O=[{archetype:"list",aliases:["index","table","crud","collection","\u4E00\u89A7"],showcase:"table-crud-list",anatomy:["List"],focus:"Header actions in `extra`, Toolbar filters above the table (the bar wraps, never scrolls sideways; many filters \u2192 pattern `list-filters`, rule #51), one Card with DataTable + Pagination."},{archetype:"detail",aliases:["record","show","master-detail","\u8A73\u7D30"],showcase:"table-master-detail",anatomy:["Detail"],focus:"Record header with status Badge and actions, Descriptions metadata, history as a headed section."},{archetype:"editor",aliases:["form","edit","create","document-editor","record-editor","\u7DE8\u96C6"],showcase:"page-editor",anatomy:["Editor","Form"],focus:"One primary Save in `extra` submitting the Form by id; title field + large body editor in the main column, metadata + change note in a SplitPane aside. The body uses @godxjp/block-editor (a second package); `Textarea autoSize` fits the same FormField."},{archetype:"reader",aliases:["document","article","wiki","view","read","\u95B2\u89A7"],showcase:"page-reader",anatomy:["Reader"],focus:"PageContainer with breadcrumb + `extra` and NO `title`; LegalDocumentShell (@godxjp/ui/lab) owns the h1, metadata line, contents rail and prose at reading width."},{archetype:"settings",aliases:["preferences","account","\u8A2D\u5B9A"],showcase:"settings-account-sections",anatomy:["Settings"],focus:"Heading + ListRow sections without Cards; one destructive Card at the end."},{archetype:"dashboard",aliases:["overview","home","kpi","\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9"],showcase:"case1-warehouse-dashboard",anatomy:["Dashboard"],focus:"Four StatCards with comparison deltas, then titled panels in a ResponsiveGrid."},{archetype:"approval-queue",aliases:["approval","queue","inbox","review","\u627F\u8A8D"],showcase:"table-approval-queue",anatomy:["Approval queue"],focus:'Decision rows in `Table preset="action-collection"`, row actions in one kebab.'}],Se=e=>e.trim().toLowerCase().replace(/[\s_]+/g,"-");function Te(e){let t=Se(e);if(t)return O.find(a=>a.archetype===t||a.showcase===t||a.aliases.some(i=>Se(i)===t))}function Ce(e){return`${Ke}showcase/${e}`}var Ee={"case1-warehouse-dashboard":`/**
|
|
6015
6245
|
* Showcase \xB7 case1 \u2014 Warehouse Dashboard (T\u1ED5ng quan kho)
|
|
6016
6246
|
*
|
|
6017
6247
|
* A full standalone admin/\u52E4\u6020 dashboard page, served at \`/showcase/case1-warehouse-dashboard\`.
|
|
@@ -6235,7 +6465,7 @@ export default function WarehouseDashboardShowcase() {
|
|
|
6235
6465
|
</CardAction>
|
|
6236
6466
|
</CardHeader>
|
|
6237
6467
|
<CardContent flush>
|
|
6238
|
-
<ul
|
|
6468
|
+
<Flex as="ul" marker="none" direction="col" gap="none">
|
|
6239
6469
|
{QUEUE.map((q) => {
|
|
6240
6470
|
return (
|
|
6241
6471
|
<ListRow
|
|
@@ -6251,7 +6481,7 @@ export default function WarehouseDashboardShowcase() {
|
|
|
6251
6481
|
/>
|
|
6252
6482
|
);
|
|
6253
6483
|
})}
|
|
6254
|
-
</
|
|
6484
|
+
</Flex>
|
|
6255
6485
|
</CardContent>
|
|
6256
6486
|
</Card>
|
|
6257
6487
|
|
|
@@ -8199,17 +8429,17 @@ export default function Demo() {
|
|
|
8199
8429
|
</PageContainer>
|
|
8200
8430
|
);
|
|
8201
8431
|
}
|
|
8202
|
-
`};var p={name:"@godxjp/ui-mcp",version:"33.27.1",godxUiCompatibility:"33.27.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 N=[{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. Core only by default \u2014 pass tier="lab" for the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'},group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_utilities",description:'List every NON-component public export of @godxjp/ui \u2014 hooks, helper functions and constants (cn, formatDate, formatCurrency, toast, useDebouncedValue, buttonVariants, CHART_COLORS, SHOW_PARENT \u2026). Reach for this before hand-writing a className merger, a date/money formatter, a debounce hook or a chart palette: two products in this org each re-implemented `cn` because it could not be found. Optionally filter by kind. Then `get_component name="<name>"` for its signature, usage and example.',inputSchema:{type:"object",properties:{kind:{type:"string",enum:["hook","function","value"],description:"hook = only legal inside a component body; function = callable anywhere; value = a constant to read."}}}},{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. Core only by default; pass tier="lab" or "all" to include the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{query:{type:"string"},tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'}},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:"get_design",description:"DESIGN.md \u2014 the visual spec for screens built with @godxjp/ui: real tokens (YAML front matter), colour scarcity rules, typography, layout & density, elevation, components, ADMIN SCREEN ANATOMY (list / detail / form / settings / dashboard / approval queue), do's and don'ts, responsive, iteration guide, known gaps. Read before composing a screen. Pass `section` for one section; omit it for the whole document (~12 KB).",inputSchema:{type:"object",properties:{section:{type:"string",description:"Section id or heading word, e.g. 'tokens', 'colors', 'admin-screen-anatomy', 'dos-and-donts', 'iteration-guide'. Unknown ids list the valid ones."}}}},{name:"get_exemplar",description:"A reference screen to match for an admin archetype: its anatomy notes from DESIGN.md, the full source of the showcase built from real @godxjp/ui primitives, and its live preview URL for screenshot comparison. Archetypes: "+O.map(e=>e.archetype).join(", ")+". Omit `archetype` to list them.",inputSchema:{type:"object",properties:{archetype:{type:"string",description:O.map(e=>e.archetype).join(" | ")+" (aliases: "+O.flatMap(e=>e.aliases??[]).join(", ")+")."}}}},{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"]}}],Ze=new Set(N.map(e=>e.name));function et(e=X()){let t=p.godxUiCompatibility??p.version,a=`@godxjp/ui-mcp ${p.version} (catalog for @godxjp/ui ${t})`;if(!e)return a;let i=e.source==="node_modules"?"read from node_modules at answer time":"from GODX_UI_VERSION at launch \u2014 node_modules/@godxjp/ui not resolved";return`${a} \u2014 installed @godxjp/ui ${e.version} (${i})`}function tt(e=X()){let t=e?j(e.version):null,a=j(p.version);return!e||!t||!a?null:dt(t,a)>0?`\u26A0\uFE0F SERVER OLDER THAN INSTALLED PACKAGE: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog is BEHIND the package and may be missing props and components that exist. Do not conclude from this answer that a prop is unavailable. Restart the session so the server relaunches on the installed version (run \`npx @godxjp/ui sync-rules\` first if the project's .mcp.json still pins an older @godxjp/ui-mcp).`:t.major===a.major?null:`\u26A0\uFE0F MAJOR MISMATCH: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog may describe components that do not exist in the installed package. Pin the MCP to @godxjp/ui-mcp@${e.version} (\`npx @godxjp/ui sync-rules\` updates the project's .mcp.json; a registration outside the project needs \`claude mcp remove <key>\`), then restart the agent.`}async function U(e,t){let a=await at(e,t);if(!Ze.has(e))return a;let i=X(),o=tt(i);return`${et(i)}
|
|
8203
|
-
${
|
|
8432
|
+
`};var p={name:"@godxjp/ui-mcp",version:"33.27.3",godxUiCompatibility:"33.27.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 N=[{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. Core only by default \u2014 pass tier="lab" for the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'},group:{type:"string",enum:["general","layout","data-display","data-entry","feedback","navigation","composites","shell","providers"]}}}},{name:"list_utilities",description:'List every NON-component public export of @godxjp/ui \u2014 hooks, helper functions and constants (cn, formatDate, formatCurrency, toast, useDebouncedValue, buttonVariants, CHART_COLORS, SHOW_PARENT \u2026). Reach for this before hand-writing a className merger, a date/money formatter, a debounce hook or a chart palette: two products in this org each re-implemented `cn` because it could not be found. Optionally filter by kind. Then `get_component name="<name>"` for its signature, usage and example.',inputSchema:{type:"object",properties:{kind:{type:"string",enum:["hook","function","value"],description:"hook = only legal inside a component body; function = callable anywhere; value = a constant to read."}}}},{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. Core only by default; pass tier="lab" or "all" to include the opt-in @godxjp/ui/lab components.',inputSchema:{type:"object",properties:{query:{type:"string"},tier:{type:"string",enum:["core","lab","all"],description:'Default "core". "lab" lists the opt-in `@godxjp/ui/lab` components (niche, kept and tested, not in the default context); "all" lists both.'}},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:"get_design",description:"DESIGN.md \u2014 the visual spec for screens built with @godxjp/ui: real tokens (YAML front matter), colour scarcity rules, typography, layout & density, elevation, components, ADMIN SCREEN ANATOMY (list / detail / form / settings / dashboard / approval queue), do's and don'ts, responsive, iteration guide, known gaps. Read before composing a screen. Pass `section` for one section; omit it for the whole document (~12 KB).",inputSchema:{type:"object",properties:{section:{type:"string",description:"Section id or heading word, e.g. 'tokens', 'colors', 'admin-screen-anatomy', 'dos-and-donts', 'iteration-guide'. Unknown ids list the valid ones."}}}},{name:"get_exemplar",description:"A reference screen to match for an admin archetype: its anatomy notes from DESIGN.md, the full source of the showcase built from real @godxjp/ui primitives, and its live preview URL for screenshot comparison. Archetypes: "+O.map(e=>e.archetype).join(", ")+". Omit `archetype` to list them.",inputSchema:{type:"object",properties:{archetype:{type:"string",description:O.map(e=>e.archetype).join(" | ")+" (aliases: "+O.flatMap(e=>e.aliases??[]).join(", ")+")."}}}},{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"]}}],Ze=new Set(N.map(e=>e.name));function et(e=X()){let t=p.godxUiCompatibility??p.version,a=`@godxjp/ui-mcp ${p.version} (catalog for @godxjp/ui ${t})`;if(!e)return a;let i=e.source==="node_modules"?"read from node_modules at answer time":"from GODX_UI_VERSION at launch \u2014 node_modules/@godxjp/ui not resolved";return`${a} \u2014 installed @godxjp/ui ${e.version} (${i})`}function tt(e=X()){let t=e?j(e.version):null,a=j(p.version);return!e||!t||!a?null:dt(t,a)>0?`\u26A0\uFE0F SERVER OLDER THAN INSTALLED PACKAGE: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog is BEHIND the package and may be missing props and components that exist. Do not conclude from this answer that a prop is unavailable. Restart the session so the server relaunches on the installed version (run \`npx @godxjp/ui sync-rules\` first if the project's .mcp.json still pins an older @godxjp/ui-mcp).`:t.major===a.major?null:`\u26A0\uFE0F MAJOR MISMATCH: this server is @godxjp/ui-mcp ${p.version}, the project has @godxjp/ui ${e.version} installed \u2014 this catalog may describe components that do not exist in the installed package. Pin the MCP to @godxjp/ui-mcp@${e.version} (\`npx @godxjp/ui sync-rules\` updates the project's .mcp.json; a registration outside the project needs \`claude mcp remove <key>\`), then restart the agent.`}async function U(e,t){let a=await at(e,t);if(!Ze.has(e))return a;let i=X(),n=tt(i);return`${et(i)}
|
|
8433
|
+
${n?`${n}
|
|
8204
8434
|
`:""}
|
|
8205
|
-
${a}`}async function at(e,t){switch(e){case"list_skills":return
|
|
8435
|
+
${a}`}async function at(e,t){switch(e){case"list_skills":return ot();case"list_primitives":return Pe(t.group,t.tier);case"list_utilities":return St(t.kind);case"list_patterns":return ut();case"list_anti_ai_tells":return bt(t.category);case"list_redesign_checks":return vt(t.category);case"list_audit_rules":return gt(t.category);case"list_visual_checks":return mt(t.category);case"get_anti_ai_tell":return ft(String(t.name??""));case"get_redesign_check":return yt(String(t.symptom??""));case"get_skill_section":return Le(String(t.skill??""),String(t.section??""));case"get_component":return Tt(String(t.name??""),t.verbose===!0);case"get_pattern":return At(String(t.name??""));case"get_rule":return Ot(typeof t.number=="number"?t.number:void 0);case"get_vocab":return Nt(t.name==null?void 0:String(t.name));case"get_tokens":return Dt(t.category);case"list_consumer_skills":return rt();case"get_consumer_skill":return st(String(t.skill??""),String(t.section??""));case"route_consumer_task":return Ne(String(t.task??""),{consumerOnly:!0});case"draft_bug_report":return lt(t);case"check_compatibility":return Ie(t.version==null?void 0:String(t.version));case"route_task":return Ne(String(t.task??""));case"suggest_primitive":return Rt(String(t.use_case??""));case"search_components":return zt(String(t.query??""),t.tier);case"get_frame_coverage":return Et(t.name===void 0?void 0:String(t.name));case"get_design":return it(t.section==null?void 0:String(t.section));case"get_exemplar":return nt(t.archetype==null?"":String(t.archetype));case"lint_jsx":return It(String(t.jsx??""));default:return`Unknown tool: ${e}`}}function it(e){if(e===void 0||e.trim()==="")return H;let t=ke(e);if(t)return t.body;let a=W().map(i=>`- \`${i.id}\` \u2014 ${i.title}`).join(`
|
|
8206
8436
|
`);return`Section "${e.slice(0,80)}" not found in DESIGN.md. Sections:
|
|
8207
|
-
${a}`}function
|
|
8208
|
-
`);return`${
|
|
8209
|
-
${
|
|
8437
|
+
${a}`}function nt(e){let t=Te(e);if(!t){let n=e.trim()?`No exemplar for "${e.slice(0,80)}".`:"Pass `archetype` to get one exemplar.",o=O.map(r=>`- \`${r.archetype}\` (${r.aliases.join(", ")}) \u2192 ${r.showcase}: ${r.focus}`).join(`
|
|
8438
|
+
`);return`${n} Archetypes:
|
|
8439
|
+
${o}`}let a=Ce(t.showcase),i=t.anatomy.map(n=>xe(n)).filter(Boolean).join(`
|
|
8210
8440
|
|
|
8211
8441
|
`);return[`# Exemplar \xB7 ${t.archetype} \u2192 \`docs/showcase/${t.showcase}.tsx\``,"",`**Copy:** ${t.focus}`,"",`**Live preview:** ${a}`,"Open it in a browser and screenshot at 390 / 768 / 1280 next to yours. GitHub Pages serves it through 404.html (HTTP 404, renders client-side), so a plain fetch sees a 404; it deploys on release tags and can lag main.","","## Anatomy (DESIGN.md)","",i,"","## Source","","```tsx",Ee[t.showcase]??"(source missing \u2014 run `pnpm gen:agent-catalog`)","```"].join(`
|
|
8212
|
-
`)}function
|
|
8442
|
+
`)}function ot(){let e=`# Available skills (${A.length})
|
|
8213
8443
|
|
|
8214
8444
|
`;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 A)e+=`## ${t.id} \u2014 ${t.name} \`[${t.audience}]\`
|
|
8215
8445
|
`,e+=`**When to use:** ${t.whenToUse}
|
|
@@ -8224,7 +8454,7 @@ _Source: ${A.map(t=>t.source).filter((t,a,i)=>i.indexOf(t)===a).slice(0,3).join(
|
|
|
8224
8454
|
|
|
8225
8455
|
`,t+=`**Sections:** ${a.sections.map(i=>`\`${i.id}\``).join(", ")}
|
|
8226
8456
|
|
|
8227
|
-
`;return t}function st(e,t){let a=P(e);return a&&!_(a)?`Skill "${e}" is CORE-only (building @godxjp/ui itself) and isn't served to app-devs. Use \`list_consumer_skills\` for consumer-facing guidance${e==="component-discipline"?" \u2014 the standards you need when composing/extending are folded into `compose-a-screen/state-and-a11y` and `design-to-page/verify`.":"."}`:Le(e,t)}function lt(e){let t=g=>{let x=e[g];return typeof x=="string"?x.trim():""},a=t("summary");if(!a)return"Pass at least `summary` (one-line title). For a useful report also pass `repro`, `expected`, `actual`, and ideally `component` / `rule` / `version` / `env`. A vague report a maintainer can't reproduce is not enough.";let i=t("repro"),
|
|
8457
|
+
`;return t}function st(e,t){let a=P(e);return a&&!_(a)?`Skill "${e}" is CORE-only (building @godxjp/ui itself) and isn't served to app-devs. Use \`list_consumer_skills\` for consumer-facing guidance${e==="component-discipline"?" \u2014 the standards you need when composing/extending are folded into `compose-a-screen/state-and-a11y` and `design-to-page/verify`.":"."}`:Le(e,t)}function lt(e){let t=g=>{let x=e[g];return typeof x=="string"?x.trim():""},a=t("summary");if(!a)return"Pass at least `summary` (one-line title). For a useful report also pass `repro`, `expected`, `actual`, and ideally `component` / `rule` / `version` / `env`. A vague report a maintainer can't reproduce is not enough.";let i=t("repro"),n=t("expected"),o=t("actual"),r=t("component"),s=typeof e.rule=="number"?e.rule:void 0,h=t("version"),f=t("env"),u=[];i||u.push("repro"),n||u.push("expected"),o||u.push("actual");let l=(g,x)=>x||`_(TODO: ${g} \u2014 required for a reproducible report)_`,c=`[bug] ${a}`,d=`## Summary
|
|
8228
8458
|
|
|
8229
8459
|
${a}
|
|
8230
8460
|
|
|
@@ -8240,11 +8470,11 @@ ${l("repro",i)}
|
|
|
8240
8470
|
|
|
8241
8471
|
`,d+=`## Expected
|
|
8242
8472
|
|
|
8243
|
-
${l("expected",
|
|
8473
|
+
${l("expected",n)}
|
|
8244
8474
|
|
|
8245
8475
|
`,d+=`## Actual
|
|
8246
8476
|
|
|
8247
|
-
${l("actual",
|
|
8477
|
+
${l("actual",o)}
|
|
8248
8478
|
|
|
8249
8479
|
`,d+=`## Environment
|
|
8250
8480
|
|
|
@@ -8269,32 +8499,32 @@ ${d}
|
|
|
8269
8499
|
${D}
|
|
8270
8500
|
\`\`\`
|
|
8271
8501
|
|
|
8272
|
-
`,y+="_Reminder: don't hand-roll a fake workaround to hide the bug \u2014 file this, then mark any minimal local workaround with `// TODO(godxui#<n>)`._\n",y}function j(e){let t=/^(\d+)\.(\d+)\.(\d+)/.exec(e.trim());return t?{major:t[1],minor:t[2],patch:t[3]}:null}function dt(e,t){for(let a of["major","minor","patch"]){let i=Number(e[a])-Number(t[a]);if(i!==0)return i<0?-1:1}return 0}function Re(){let e=p.godxUiCompatibility;return e?/^(\d+)\.(\d+)\.x$/.exec(e):null}function Y(e){let t=j(e),a=Re(),i=p.godxUiCompatibility;return!t||i&&!a?!1:a?t.major===a[1]&&t.minor===a[2]:e.trim()===p.version}function $(){return process.env.GODX_UI_VERSION?.trim()||void 0}var ct=De("node_modules","@godxjp","ui","package.json"),Ae=null;function Oe(e){try{return Xe(e).mtimeMs}catch{return null}}function ht(e){let t=Je(e);for(;;){let a=De(t,ct);if(Ye(a))return a;let i=Qe(t);if(i===t)return null;t=i}}function pt(){let e=process.cwd(),t=Ae;if(t?.cwd===e&&t.path!==null&&Oe(t.path)===t.mtimeMs)return t.version;let a=ht(e),i=null;if(a)try{let
|
|
8502
|
+
`,y+="_Reminder: don't hand-roll a fake workaround to hide the bug \u2014 file this, then mark any minimal local workaround with `// TODO(godxui#<n>)`._\n",y}function j(e){let t=/^(\d+)\.(\d+)\.(\d+)/.exec(e.trim());return t?{major:t[1],minor:t[2],patch:t[3]}:null}function dt(e,t){for(let a of["major","minor","patch"]){let i=Number(e[a])-Number(t[a]);if(i!==0)return i<0?-1:1}return 0}function Re(){let e=p.godxUiCompatibility;return e?/^(\d+)\.(\d+)\.x$/.exec(e):null}function Y(e){let t=j(e),a=Re(),i=p.godxUiCompatibility;return!t||i&&!a?!1:a?t.major===a[1]&&t.minor===a[2]:e.trim()===p.version}function $(){return process.env.GODX_UI_VERSION?.trim()||void 0}var ct=De("node_modules","@godxjp","ui","package.json"),Ae=null;function Oe(e){try{return Xe(e).mtimeMs}catch{return null}}function ht(e){let t=Je(e);for(;;){let a=De(t,ct);if(Ye(a))return a;let i=Qe(t);if(i===t)return null;t=i}}function pt(){let e=process.cwd(),t=Ae;if(t?.cwd===e&&t.path!==null&&Oe(t.path)===t.mtimeMs)return t.version;let a=ht(e),i=null;if(a)try{let n=JSON.parse($e(a,"utf8"));typeof n.version=="string"&&n.version.trim()&&(i=n.version.trim())}catch{i=null}return Ae={cwd:e,path:a,mtimeMs:a?Oe(a):null,version:i},i}function X(){let e=pt();if(e)return{version:e,source:"node_modules"};let t=$();return t?{version:t,source:"GODX_UI_VERSION"}:null}function ze(e,t){return Ie(e)+`
|
|
8273
8503
|
|
|
8274
8504
|
\u26D4 **Catalog withheld for \`${t}\`** \u2014 props, examples, and pattern code are not returned when the installed @godxjp/ui version does not match this MCP catalog. Align versions (see above), restart the MCP, then retry.
|
|
8275
8505
|
`}function Ie(e){let t=p.version,a=p.godxUiCompatibility,i=`# @godxjp/ui compatibility
|
|
8276
8506
|
|
|
8277
8507
|
- MCP / catalog version (serverInfo.version): **${t}**
|
|
8278
8508
|
- Compatible @godxjp/ui range: **${a??"(unset)"}**
|
|
8279
|
-
`,
|
|
8280
|
-
\u26A0\uFE0F Could not compare \u2014 installed="${
|
|
8281
|
-
`:Y(
|
|
8282
|
-
\u2705 In lockstep \u2014 installed @godxjp/ui@${
|
|
8509
|
+
`,n=e?.trim();if(!n)return i+'\nProvide the installed version \u2014 `check_compatibility version="16.10.0"` \u2014 to get a match/mismatch verdict. Read it from the target project\'s `node_modules/@godxjp/ui/package.json` or `npm ls @godxjp/ui`.\n';let o=f=>j(f),r=Re(),s=n?o(n):null;return!s||a&&!r?i+`
|
|
8510
|
+
\u26A0\uFE0F Could not compare \u2014 installed="${n}" is not a plain x.y.z version, or the catalog range is malformed. Verify with \`npm ls @godxjp/ui\`.
|
|
8511
|
+
`:Y(n)?i+`
|
|
8512
|
+
\u2705 In lockstep \u2014 installed @godxjp/ui@${n} is described by this catalog. Prop / token / pattern guidance is safe to trust.
|
|
8283
8513
|
`:i+`
|
|
8284
|
-
\u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${
|
|
8514
|
+
\u{1F534} MISMATCH \u2014 the target project runs @godxjp/ui@${n}, but this catalog describes ${a??t}. Props, defaults, tokens, and patterns it returns may not match the installed build. Align them before trusting output:
|
|
8285
8515
|
- upgrade the app: \`npm i @godxjp/ui@${t}\`, or
|
|
8286
8516
|
- point your agent at the matching MCP release: \`@godxjp/ui-mcp@${s.major}.${s.minor}\` (same minor as the app).
|
|
8287
|
-
`}function Pe(e,t="core"){let a=R(e?G(e):w,t);if(a.length===0)return`No components${e?` in group "${e}"`:""}.`;let i=a.reduce((
|
|
8517
|
+
`}function Pe(e,t="core"){let a=R(e?G(e):w,t);if(a.length===0)return`No components${e?` in group "${e}"`:""}.`;let i=a.reduce((o,r)=>((o[r.group]??=[]).push(r),o),{}),n=`# @godxjp/ui primitives${e?` \u2014 ${e}`:""}${t==="core"?"":` (${t})`}
|
|
8288
8518
|
|
|
8289
8519
|
${a.length} components.
|
|
8290
8520
|
|
|
8291
|
-
`;if(t==="core"){let
|
|
8521
|
+
`;if(t==="core"){let o=R(e?G(e):w,"lab").length;o&&(n+=`_${o} opt-in \`@godxjp/ui/lab\` component${o===1?"":"s"} not listed \u2014 pass tier="lab" to see them._
|
|
8292
8522
|
|
|
8293
|
-
`)}for(let[
|
|
8523
|
+
`)}for(let[o,r]of Object.entries(i)){n+=`## ${o}
|
|
8294
8524
|
|
|
8295
|
-
`;for(let s of r)
|
|
8296
|
-
`;
|
|
8297
|
-
`}return
|
|
8525
|
+
`;for(let s of r)n+=`- **${s.name}** \u2014 ${s.tagline}
|
|
8526
|
+
`;n+=`
|
|
8527
|
+
`}return n}function ut(){let e=`# Canonical patterns (${S.length})
|
|
8298
8528
|
|
|
8299
8529
|
`;for(let t of S)e+=`- **${t.name}** \u2014 ${t.tagline}
|
|
8300
8530
|
_tags: ${t.tags.join(", ")}_
|
|
@@ -8314,8 +8544,8 @@ ${ve}
|
|
|
8314
8544
|
${me}
|
|
8315
8545
|
\`\`\`
|
|
8316
8546
|
|
|
8317
|
-
`;let i=t.reduce((o
|
|
8318
|
-
`;for(let r of
|
|
8547
|
+
`;let i=t.reduce((n,o)=>((n[o.category]??=[]).push(o),n),{});for(let[n,o]of Object.entries(i)){a+=`## ${n}
|
|
8548
|
+
`;for(let r of o)a+=`- **${r.id}** (${r.severity})${r.standard?` \u2014 _${r.standard}_`:""}
|
|
8319
8549
|
${r.fix}
|
|
8320
8550
|
`;a+=`
|
|
8321
8551
|
`}return a+=`## suppressing a DELIBERATE exception
|
|
@@ -8325,8 +8555,8 @@ ${ge}
|
|
|
8325
8555
|
\`\`\`
|
|
8326
8556
|
`,a}function bt(e){let t=e?de(e):L,a=`# AI tells to AVOID${e?` \u2014 ${e}`:""} (${t.length})
|
|
8327
8557
|
|
|
8328
|
-
`;a+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let i=t.reduce((o
|
|
8329
|
-
`;for(let r of
|
|
8558
|
+
`;a+='_Compact list. Use `get_anti_ai_tell name="<name>"` for the full body + fix._\n\n';let i=t.reduce((n,o)=>((n[o.category]??=[]).push(o),n),{});for(let[n,o]of Object.entries(i)){a+=`## ${n}
|
|
8559
|
+
`;for(let r of o)a+=`- ${r.name}
|
|
8330
8560
|
`;a+=`
|
|
8331
8561
|
`}return a}function ft(e){let t=L.find(a=>a.name.toLowerCase()===e.trim().toLowerCase());if(!t){let a=`Anti-AI-tell "${e}" not found. Use \`list_anti_ai_tells\` to discover. Closest:
|
|
8332
8562
|
|
|
@@ -8345,35 +8575,35 @@ ${t.fix}
|
|
|
8345
8575
|
`}function vt(e){let t=e?pe(e):q,a=`# Redesign audit${e?` \u2014 ${e}`:""} (${t.length} checks)
|
|
8346
8576
|
|
|
8347
8577
|
`;e||(a+=`## Fix priority
|
|
8348
|
-
${ce.map(
|
|
8578
|
+
${ce.map(n=>n).join(`
|
|
8349
8579
|
`)}
|
|
8350
8580
|
|
|
8351
8581
|
`,a+=`## Rules
|
|
8352
|
-
${he.map(
|
|
8582
|
+
${he.map(n=>`- ${n}`).join(`
|
|
8353
8583
|
`)}
|
|
8354
8584
|
|
|
8355
|
-
`),a+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let i=t.reduce((o
|
|
8356
|
-
`;for(let r of
|
|
8585
|
+
`),a+='_Compact list of symptoms. Use `get_redesign_check symptom="<text snippet>"` for the full fix + UI note._\n\n';let i=t.reduce((n,o)=>((n[o.category]??=[]).push(o),n),{});for(let[n,o]of Object.entries(i)){a+=`## ${n}
|
|
8586
|
+
`;for(let r of o)a+=`- ${r.symptom}
|
|
8357
8587
|
`;a+=`
|
|
8358
|
-
`}return a}function yt(e){let t=e.trim().toLowerCase();if(!t)return"Pass `symptom` \u2014 a fragment matching the audit check symptom (e.g. 'Inter everywhere' / '100vh' / 'Acme').";let a=q.filter(
|
|
8588
|
+
`}return a}function yt(e){let t=e.trim().toLowerCase();if(!t)return"Pass `symptom` \u2014 a fragment matching the audit check symptom (e.g. 'Inter everywhere' / '100vh' / 'Acme').";let a=q.filter(n=>n.symptom.toLowerCase().includes(t)||n.fix.toLowerCase().includes(t));if(!a.length)return`No redesign check matches "${e}". Use \`list_redesign_checks\` to see all.`;let i=`# Redesign checks matching "${e}" (${a.length})
|
|
8359
8589
|
|
|
8360
|
-
`;for(let
|
|
8590
|
+
`;for(let n of a)i+=`## ${n.category}
|
|
8361
8591
|
|
|
8362
|
-
**Symptom:** ${
|
|
8592
|
+
**Symptom:** ${n.symptom}
|
|
8363
8593
|
|
|
8364
|
-
**Fix:** ${
|
|
8365
|
-
${
|
|
8366
|
-
_UI note:_ ${
|
|
8594
|
+
**Fix:** ${n.fix}
|
|
8595
|
+
${n.uiNote?`
|
|
8596
|
+
_UI note:_ ${n.uiNote}
|
|
8367
8597
|
`:""}
|
|
8368
|
-
`;return i}function Le(e,t){let a=P(e);if(!a)return`Skill "${e}" not found. Use \`list_skills\` for available ids.`;if(!t){let
|
|
8598
|
+
`;return i}function Le(e,t){let a=P(e);if(!a)return`Skill "${e}" not found. Use \`list_skills\` for available ids.`;if(!t){let n=`# ${a.name}
|
|
8369
8599
|
|
|
8370
8600
|
${a.whenToUse}
|
|
8371
8601
|
|
|
8372
8602
|
## Sections
|
|
8373
|
-
`;for(let
|
|
8374
|
-
`;return
|
|
8375
|
-
`;for(let
|
|
8376
|
-
`;return
|
|
8603
|
+
`;for(let o of a.sections)n+=`- \`${o.id}\` \u2014 ${o.tagline}
|
|
8604
|
+
`;return n}let i=se(e,t);if(!i){let n=`Section "${t}" not in skill "${e}". Available:
|
|
8605
|
+
`;for(let o of a.sections)n+=`- \`${o.id}\` \u2014 ${o.tagline}
|
|
8606
|
+
`;return n}return`# ${a.name} \u2192 ${i.title}
|
|
8377
8607
|
|
|
8378
8608
|
${i.tagline}
|
|
8379
8609
|
|
|
@@ -8415,85 +8645,85 @@ ${e.example}
|
|
|
8415
8645
|
`,a}function St(e){let t=e?te(e):E;if(!t.length)return`No utilities of kind "${e}".`;let a={hook:"Hooks \u2014 only inside a component body",function:"Functions \u2014 callable anywhere",value:"Constants"},i=`# @godxjp/ui utilities (${t.length})
|
|
8416
8646
|
|
|
8417
8647
|
Not components: these have no props table. Call \`get_component name="<name>"\` for a signature, usage and example.
|
|
8418
|
-
`;for(let
|
|
8419
|
-
## ${a[
|
|
8648
|
+
`;for(let n of["function","hook","value"]){let o=t.filter(r=>r.kind===n);if(o.length){i+=`
|
|
8649
|
+
## ${a[n]}
|
|
8420
8650
|
|
|
8421
8651
|
| Name | Import from | What it does |
|
|
8422
8652
|
|---|---|---|
|
|
8423
|
-
`;for(let r of
|
|
8653
|
+
`;for(let r of o){let s=r.subpaths.map(h=>`\`@godxjp/ui${h==="."?"":h.slice(1)}\``).join(" \xB7 ");i+=`| \`${r.name}\` | ${s} | ${r.tagline.split(" \u2014 ")[0].split(". ")[0]} |
|
|
8424
8654
|
`}}}return i}var Fe='**FORM RULES** \u2014 enforced by ui-audit (`formfield-needs-form`, `dialog-form-too-big`, `select-width-hint`):\n- Wrap fields in a `<Form>` \u2014 never a group of FormFields without one, never a hand-rolled `<Flex>` row of fields. `layout="horizontal" labelWidth controlWidth` is the desktop default; `layout="vertical"` is valid for long labels, a narrow container or one short question per row.\n- Size each control for its content (GOV.UK text-input width): `controlWidth` per field or once on the Form \u2014 a port ~7rem, a short enum ~10rem.\n- Fields that belong on one row: `<Flex attached>` or `<Form columns>`.\n- GoDX house rule: three or more fields is a page (own route), not a Dialog \u2014 a product decision for task focus and recovery, not a WCAG requirement.\n- A case these do not cover: open an issue on godx-jp/godxjp-ui, use the nearest valid composition with `// TODO(godxjp-ui#<n>)` \u2014 never hand-roll.\n';function Tt(e,t=!1){let a=$();if(a&&!Y(a))return ze(a,"get_component");let i=F(e);if(!i){let s=J(e);if(s)return`\`${e}\` ships, and it is documented as part of **${s.name}** \u2014 it has no separate entry because it is a compound sub-part, alias or shim of that component.
|
|
8425
8655
|
|
|
8426
|
-
Call \`get_component name="${s.name}"\` for its props, usage and example. Do NOT hand-roll \`${e}\`.`;let h=ee(e);return h?xt(h):`Component "${e}" not found \u2014 and it is not a hook or utility either. Use \`list_primitives\` to discover components, or \`list_utilities\` for hooks and helpers.`}let
|
|
8656
|
+
Call \`get_component name="${s.name}"\` for its props, usage and example. Do NOT hand-roll \`${e}\`.`;let h=ee(e);return h?xt(h):`Component "${e}" not found \u2014 and it is not a hook or utility either. Use \`list_primitives\` to discover components, or \`list_utilities\` for hooks and helpers.`}let n=`# ${i.name}
|
|
8427
8657
|
|
|
8428
|
-
**Group:** ${i.group}`,
|
|
8658
|
+
**Group:** ${i.group}`,o=i.importPath??`@godxjp/ui/${i.group==="providers"?"app":i.group}`;n+=` \xB7 **Import:** \`import { ${i.name} } from "${o}"\`
|
|
8429
8659
|
|
|
8430
|
-
`,i.tier==="lab"&&(
|
|
8660
|
+
`,i.tier==="lab"&&(n+="> \u{1F9EA} **LAB tier** \u2014 ships from the opt-in `@godxjp/ui/lab` subpath: kept, tested and semver'd like core, but niche, so it is not in the default listings. Promoted to core once two repos import it.\n\n"),i.deprecated&&(n+=`> \u26A0\uFE0F **DEPRECATED.** Kept catalogued so you're steered to the replacement \u2014 see the tagline / Related below. Do not use in new code.
|
|
8431
8661
|
|
|
8432
|
-
`),
|
|
8662
|
+
`),n+=`${i.tagline}
|
|
8433
8663
|
|
|
8434
8664
|
## Props
|
|
8435
8665
|
|
|
8436
|
-
`,
|
|
8666
|
+
`,n+=`| Name | Type | Required | Default | Description |
|
|
8437
8667
|
|---|---|---|---|---|
|
|
8438
|
-
`;for(let s of i.props)
|
|
8439
|
-
`;let r=kt(i.name);if(r.length)if(
|
|
8668
|
+
`;for(let s of i.props)n+=`| \`${s.name}\` | \`${s.type}\` | ${s.required?"\u2713":""} | ${s.defaultValue?`\`${s.defaultValue}\``:""} | ${s.description} |
|
|
8669
|
+
`;let r=kt(i.name);if(r.length)if(n+=`
|
|
8440
8670
|
## Design tokens (theme knobs)
|
|
8441
8671
|
|
|
8442
8672
|
Override these in a service \`theme.css\` to re-tune ONLY this component (never hard-code or fork CSS \u2014 rules #44/#45/#46):
|
|
8443
8673
|
|
|
8444
|
-
`,t){
|
|
8674
|
+
`,t){n+=`| Token | Default | What it controls |
|
|
8445
8675
|
|---|---|---|
|
|
8446
|
-
`;for(let s of r)
|
|
8447
|
-
`}else{
|
|
8676
|
+
`;for(let s of r)n+=`| \`${s.name}\` | \`${s.value}\` | ${s.description} |
|
|
8677
|
+
`}else{n+=`| Token | Default |
|
|
8448
8678
|
|---|---|
|
|
8449
|
-
`;for(let s of r)
|
|
8450
|
-
`;
|
|
8679
|
+
`;for(let s of r)n+=`| \`${s.name}\` | \`${s.value}\` |
|
|
8680
|
+
`;n+=`
|
|
8451
8681
|
_${r.length} knob${r.length>1?"s":""}. G\u1ECDi \`get_component name="${i.name}" verbose=true\` \u0111\u1EC3 xem m\u1ED7i token \u0111i\u1EC1u khi\u1EC3n g\xEC._
|
|
8452
|
-
`}if(i.usage&&i.usage.length){
|
|
8682
|
+
`}if(i.usage&&i.usage.length){n+=`
|
|
8453
8683
|
## How to use it
|
|
8454
8684
|
|
|
8455
|
-
`;for(let s of i.usage)
|
|
8456
|
-
`}if(i.useCases&&i.useCases.length){
|
|
8685
|
+
`;for(let s of i.usage)n+=`- ${s}
|
|
8686
|
+
`}if(i.useCases&&i.useCases.length){n+=`
|
|
8457
8687
|
## When to reach for it (use cases)
|
|
8458
8688
|
|
|
8459
|
-
`;for(let s of i.useCases)
|
|
8460
|
-
`}if(
|
|
8689
|
+
`;for(let s of i.useCases)n+=`- ${s}
|
|
8690
|
+
`}if(n+=`
|
|
8461
8691
|
## Example
|
|
8462
8692
|
|
|
8463
8693
|
\`\`\`tsx
|
|
8464
8694
|
${i.example}
|
|
8465
8695
|
\`\`\`
|
|
8466
8696
|
|
|
8467
|
-
`,
|
|
8697
|
+
`,n+=Me(K(i.name),i.name),i.subParts&&i.subParts.length&&(n+=`## Also exported here \u2014 documented by this entry
|
|
8468
8698
|
|
|
8469
|
-
`,
|
|
8699
|
+
`,n+=`These ship from the same import and have no separate entry. They are real exports: use them, don't rebuild them.
|
|
8470
8700
|
|
|
8471
|
-
`,
|
|
8701
|
+
`,n+=i.subParts.map(s=>`\`${s}\``).join(" \xB7 ")+`
|
|
8472
8702
|
|
|
8473
|
-
`),i.absorbed&&i.absorbed.length&&(
|
|
8703
|
+
`),i.absorbed&&i.absorbed.length&&(n+=`## These do NOT exist \u2014 this is what you want instead
|
|
8474
8704
|
|
|
8475
|
-
`,
|
|
8705
|
+
`,n+=`Other libraries ship components by these names. This one does not, and never will: each was folded into \`${i.name}\`. If you were about to build one, stop and read the props above.
|
|
8476
8706
|
|
|
8477
|
-
`,
|
|
8707
|
+
`,n+=i.absorbed.map(s=>`\`${s}\``).join(" \xB7 ")+`
|
|
8478
8708
|
|
|
8479
|
-
`),i.related&&i.related.length){
|
|
8709
|
+
`),i.related&&i.related.length){n+=`## Related \u2014 don't confuse / don't reinvent
|
|
8480
8710
|
|
|
8481
|
-
`;for(let s of i.related)
|
|
8482
|
-
`;
|
|
8483
|
-
`}if(i.docPath&&(
|
|
8711
|
+
`;for(let s of i.related)n+=`- ${s}
|
|
8712
|
+
`;n+=`
|
|
8713
|
+
`}if(i.docPath&&(n+=`**Reference doc:** \`docs/reference/${i.docPath}\`
|
|
8484
8714
|
|
|
8485
|
-
`),i.storyPath&&(
|
|
8715
|
+
`),i.storyPath&&(n+=`**Docs page:** \`docs/${i.storyPath}\`
|
|
8486
8716
|
|
|
8487
|
-
`),i.rules.length){
|
|
8488
|
-
`;for(let s of i.rules){let h=I(s);
|
|
8717
|
+
`),i.rules.length){n+=`**Cardinal rules:**
|
|
8718
|
+
`;for(let s of i.rules){let h=I(s);n+=h?`- #${s} \u2014 ${h.title}
|
|
8489
8719
|
`:`- #${s}
|
|
8490
|
-
`}}return i.group==="data-entry"&&(
|
|
8491
|
-
${Fe}`),
|
|
8720
|
+
`}}return i.group==="data-entry"&&(n+=`
|
|
8721
|
+
${Fe}`),n}var Ct=new Map(b.dimensions.map(e=>[e.id,e.title]));function Me(e,t){if(!e)return`
|
|
8492
8722
|
## Verified contract coverage (issue #163)
|
|
8493
8723
|
|
|
8494
8724
|
**UNTESTED.** \`${t}\` has no entry in the frame-coverage ledger, so NOTHING about its props, states, responsive behaviour, RTL, keyboard or async lifecycle is proven by an executed case. Treat the example above as an illustration, never as evidence of support.
|
|
8495
8725
|
|
|
8496
|
-
`;let a=
|
|
8726
|
+
`;let a=o=>o.map(r=>Ct.get(r)??r).join(", "),i=`
|
|
8497
8727
|
## Verified contract coverage (issue #163)
|
|
8498
8728
|
|
|
8499
8729
|
`;i+=`> ${b.warning}
|
|
@@ -8505,7 +8735,7 @@ ${Fe}`),o}var Ct=new Map(b.dimensions.map(e=>[e.id,e.title]));function Me(e,t){i
|
|
|
8505
8735
|
`,i+=e.untested.length?`- \u26A0\uFE0F **UNTESTED \u2014 not a pass, no executed case:** ${a(e.untested)}
|
|
8506
8736
|
`:`- \u26A0\uFE0F **UNTESTED:** none
|
|
8507
8737
|
`,e.notApplicable.length&&(i+=`- \u2796 **not applicable** (no such axis in this export's public API): ${a(e.notApplicable)}
|
|
8508
|
-
`);let
|
|
8738
|
+
`);let n=b.knownGaps.filter(o=>o.targets.includes(e.name));for(let o of n)i+=`- \u{1F50E} **tracked known gap** \`${o.id}\`: ${o.cases.join(" \xB7 ")}
|
|
8509
8739
|
`;return i+="\nSee `preview/frame-coverage.ledger.json` and `docs/FRAME-COVERAGE-LEDGER.md` for the written reason behind every cell.\n\n",i}function Et(e){let t=b.totals;if(!e||!e.trim()){let i=`# Preview contract coverage (issue #163)
|
|
8510
8740
|
|
|
8511
8741
|
`;i+=`> ${b.warning}
|
|
@@ -8517,19 +8747,19 @@ ${Fe}`),o}var Ct=new Map(b.dimensions.map(e=>[e.id,e.title]));function Me(e,t){i
|
|
|
8517
8747
|
|
|
8518
8748
|
`,i+=`## Contract dimensions
|
|
8519
8749
|
|
|
8520
|
-
`;for(let
|
|
8750
|
+
`;for(let n of b.dimensions)i+=`- \`${n.id}\` \u2014 ${n.title}
|
|
8521
8751
|
`;i+=`
|
|
8522
8752
|
## Tracked known gaps
|
|
8523
8753
|
|
|
8524
|
-
`;for(let
|
|
8525
|
-
`;return i+='\nCall `get_frame_coverage name="Button"` for one export. **Never** report a dimension as supported because an example renders it.\n',i}let a=K(e);if(!a){let i=V.filter(
|
|
8754
|
+
`;for(let n of b.knownGaps)i+=`- \`${n.id}\` \u2014 ${n.targets.join(", ")}: ${n.cases.join(" \xB7 ")}
|
|
8755
|
+
`;return i+='\nCall `get_frame_coverage name="Button"` for one export. **Never** report a dimension as supported because an example renders it.\n',i}let a=K(e);if(!a){let i=V.filter(n=>n.name.toLowerCase().includes(e.trim().toLowerCase())).slice(0,8).map(n=>n.name);return`No frame-coverage entry for "${e}" \u2014 its contract is **UNTESTED** by definition.
|
|
8526
8756
|
`+(i.length?`Did you mean: ${i.join(", ")}?
|
|
8527
8757
|
`:"")+"Call `get_frame_coverage` with no name for the repo-wide summary.\n"}return`# ${a.name} \u2014 contract coverage
|
|
8528
8758
|
|
|
8529
8759
|
**Group:** ${a.group}
|
|
8530
|
-
${Me(a,a.name)}`}function At(e){let t=$();if(t&&!Y(t))return ze(t,"get_pattern");let a=B(e);if(!a){let i=
|
|
8531
|
-
`;for(let
|
|
8532
|
-
`;return
|
|
8760
|
+
${Me(a,a.name)}`}function At(e){let t=$();if(t&&!Y(t))return ze(t,"get_pattern");let a=B(e);if(!a){let i=ne(e);if(i.length===0)return`Pattern "${e}" not found.`;let n=`Pattern "${e}" not found. Closest:
|
|
8761
|
+
`;for(let o of i)n+=`- ${o.name} \u2014 ${o.tagline}
|
|
8762
|
+
`;return n}return`# Pattern: ${a.name}
|
|
8533
8763
|
|
|
8534
8764
|
${a.tagline}
|
|
8535
8765
|
|
|
@@ -8551,9 +8781,9 @@ ${a.body}
|
|
|
8551
8781
|
|
|
8552
8782
|
${a.concept}
|
|
8553
8783
|
|
|
8554
|
-
`;return i+=`**Values:** ${a.values.map(
|
|
8784
|
+
`;return i+=`**Values:** ${a.values.map(n=>`\`${n}\``).join(" | ")}
|
|
8555
8785
|
|
|
8556
|
-
`,i+=`**Used by:** ${a.usedBy.map(
|
|
8786
|
+
`,i+=`**Used by:** ${a.usedBy.map(n=>`\`${n}\``).join(", ")}
|
|
8557
8787
|
|
|
8558
8788
|
`,a.notes&&(i+=`**Notes:** ${a.notes}
|
|
8559
8789
|
`),i}let t=`# Prop vocabulary
|
|
@@ -8567,21 +8797,21 @@ Values: ${a.values.map(i=>`\`${i}\``).join(" | ")}
|
|
|
8567
8797
|
|
|
8568
8798
|
`;return t}function Dt(e){let t=e?M(e):z;if(t.length===0)return`No tokens${e?` in "${e}"`:""}.`;let a=`# Design tokens${e?` \u2014 ${e}`:""}
|
|
8569
8799
|
|
|
8570
|
-
`,i=t.reduce((o
|
|
8800
|
+
`,i=t.reduce((n,o)=>((n[o.category]??=[]).push(o),n),{});for(let[n,o]of Object.entries(i)){a+=`## ${n}
|
|
8571
8801
|
|
|
8572
8802
|
| Name | Role | Tier |
|
|
8573
8803
|
|---|---|---|
|
|
8574
|
-
`;for(let r of
|
|
8804
|
+
`;for(let r of o)a+=`| \`${r.name}\` | ${r.role} | ${r.tier} |
|
|
8575
8805
|
`;a+=`
|
|
8576
8806
|
`}return a}function Ne(e,t){if(!e.trim())return"Describe the task (e.g. 'design a premium agency hero', 'audit existing settings page').";let a=le(e,t),i=`# Routing "${e}"${t?.consumerOnly?" (consumer)":""}
|
|
8577
8807
|
|
|
8578
|
-
`;for(let
|
|
8579
|
-
${
|
|
8580
|
-
`,
|
|
8581
|
-
`);return i+='\nFetch with: `get_skill_section skill="X" section="Y"`',i}function Rt(e){let t=e.trim().toLowerCase();if(!t)return"Describe your use case.";let a=[],i=(
|
|
8808
|
+
`;for(let n of a)i+=`- **skill:** \`${n.skill}\`, **section:** \`${n.section}\`
|
|
8809
|
+
${n.why}
|
|
8810
|
+
`,n.alsoSee?.length&&(i+=` _Also see:_ ${n.alsoSee.map(o=>`\`${o}\``).join(", ")}
|
|
8811
|
+
`);return i+='\nFetch with: `get_skill_section skill="X" section="Y"`',i}function Rt(e){let t=e.trim().toLowerCase();if(!t)return"Describe your use case.";let a=[],i=(o,r,s,h=2)=>{o.some(f=>t.includes(f))&&a.push({component:r,rationale:s,score:h})};if(i(["form","submit","validation","register","sign up"],"Form + FormField","RHF + zod composition.",5),i(["table","rows","columns"],"DataTable / Table","DataTable for chrome (toolbar+pagination+batch). Table for slim primitive.",5),i(["modal","dialog","confirm"],"Dialog",'Controlled open/onOpenChange. A destructive confirm is variant="destructive" on DialogRoot or DialogContent (alertdialog role, outside click does not dismiss).',4),i(["drawer","side panel","sheet"],"Sheet","Side panel for filters/settings.",4),i(["toast","notification"],"toast / Toaster","Sonner-backed.",4),i(["loading","saving","spinner"],"Spinner / Form loading prop","Spinner=active work, Skeleton=init fetch.",3),i(["alert","banner"],"Alert","tone \xD7 variant (default/banner/callout). Only for something the user must act on; not a page decoration.",3),i(["select","dropdown"],"Select","One control: showSearch for type-to-filter, loadOptions for async options; no separate combobox component exists.",3),i(["filter"],"Toolbar/ToolbarGroup + pattern 'filter-bar' (\u2192 data-table-page); many filters \u2192 pattern 'list-filters'; other layouts (saved views, faceted sidebar, docked panel\u2026) \u2192 pattern 'filter-bar-layouts'","Standalone Toolbar filter bar above a table; it WRAPS and never scrolls sideways (rule #51). Past 2\u20134 filters, the rest go behind ONE 'More filters' Sheet.",4),i(["search form","\u691C\u7D22","query filter","advanced search","condition builder","filter logic"],"SearchForm + pattern 'search-form'; field \xD7 operator rows \u2192 pattern 'condition-builder'",'Submit-driven search: SearchForm (\u691C\u7D22\u6761\u4EF6\u30D5\u30A9\u30FC\u30E0 \u2014 aligned columns, \u691C\u7D22/\u30AF\u30EA\u30A2, \u8A73\u7D30\u6761\u4EF6\u3092\u958B\u304F, \u691C\u7D22\u6761\u4EF6 summary). A condition builder (kintone / Salesforce filter logic) is a composition in a role="search" Form.',4),i(["delete","destructive"],"Pattern 'confirm-destructive'",'Dialog variant="destructive" + typed-name confirm.',4),!a.length)return`No direct match for "${e}". Try \`list_primitives\` or \`search_components\`.`;a.sort((o,r)=>r.score-o.score);let n=`# Suggestions for "${e}"
|
|
8582
8812
|
|
|
8583
|
-
`;for(let
|
|
8584
|
-
`;return
|
|
8813
|
+
`;for(let o of a)n+=`- **${o.component}** \u2014 ${o.rationale}
|
|
8814
|
+
`;return n}function zt(e,t="core"){let a=e.trim().toLowerCase();if(!a)return Pe(void 0,t);let i=a.split(/\s+/).filter(l=>l.length>=2),n=i.length?i:[a],r=R(w,t).map(l=>{let c=l.name.toLowerCase(),d=l.tagline.toLowerCase(),T=(l.useCases??[]).join(" ").toLowerCase(),D=(l.usage??[]).join(" ").toLowerCase(),y=(l.related??[]).join(" ").toLowerCase(),g=l.props.map(v=>v.name.toLowerCase()),x=(l.subParts??[]).join(" ").toLowerCase(),Q=(l.absorbed??[]).join(" ").toLowerCase(),m=0;c===a&&(m+=100),Q.split(" ").includes(a)&&(m+=95),x.split(" ").includes(a)&&(m+=90);for(let v of n)c.includes(v)&&(m+=5),Q.includes(v)&&(m+=5),x.includes(v)&&(m+=4),d.includes(v)&&(m+=3),T.includes(v)&&(m+=2),D.includes(v)&&(m+=1),y.includes(v)&&(m+=1),l.group.includes(v)&&(m+=1),g.some(je=>je.includes(v))&&(m+=1);return{c:l,score:m}}).filter(l=>l.score>0).sort((l,c)=>c.score-l.score).slice(0,12),s=ae(e),h=t==="core"?R(w,"lab").filter(l=>n.some(c=>l.name.toLowerCase().includes(c)||l.tagline.toLowerCase().includes(c)||(l.absorbed??[]).join(" ").toLowerCase().includes(c))):[],f=h.length?`
|
|
8585
8815
|
_Also in the opt-in \`@godxjp/ui/lab\` tier (not in the default context): ${h.map(l=>`**${l.name}**`).join(", ")} \u2014 pass tier="lab" to search it, or \`get_component\` by name._
|
|
8586
8816
|
`:"";if(!r.length&&!s.length)return`No matches for "${e}". Try \`list_primitives\`, \`list_utilities\`, or a broader term (e.g. a use-case word like "date", "select", "confirm").${f}`;let u=`# Search "${e}" \u2014 ${r.length} component match${r.length===1?"":"es"}`;u+=s.length?`, ${s.length} utility match${s.length===1?"":"es"}
|
|
8587
8817
|
|
|
@@ -8593,9 +8823,9 @@ ${Fe}`),s.length){u+=`
|
|
|
8593
8823
|
## Utilities \u2014 not components, no props
|
|
8594
8824
|
|
|
8595
8825
|
`;for(let l of s.slice(0,12))u+=`- **${l.name}** (${l.kind}, \`@godxjp/ui${l.subpaths[0]==="."?"":l.subpaths[0].slice(1)}\`) \u2014 ${l.tagline}
|
|
8596
|
-
`}return u}function It(e){let t=[],a=(o
|
|
8826
|
+
`}return u}function It(e){let t=[],a=(n,o)=>{n.test(e)&&t.push(o)};if(a(/<button[\s>]/,"Use `<Button>` instead of raw `<button>` (rule 29)."),a(/<input[\s>]/,"Use `<Input>` instead of raw `<input>` (rule 29)."),a(/<select[\s>]/,"Use `<Select>` instead of raw `<select>` (rule 29)."),a(/<Select\b[^>]*\bonChange=/,"Select value changes use `onValueChange`, not `onChange`."),a(/<textarea[\s>]/,"Use `<Textarea>` instead of raw `<textarea>` (rule 29)."),a(/<(table|thead|tbody)[\s>]/,"Use `<DataTable>` instead of a hand-rolled `<table>` (rule 29)."),a(/bg-(red|blue|green|yellow|gray|slate|zinc|neutral|stone|orange|amber|lime|emerald|teal|cyan|sky|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/,"Use semantic token utilities (`bg-primary`/`bg-destructive`) not raw color scales (rule 2)."),a(/\b(?:ml|mr|pl|pr|left|right)-(?:\d|\[|auto|px|full|screen)|\b(?:rounded-[lr]|border-[lr]|text-(?:left|right))\b/,"Physical direction class \u2014 use logical CSS (`ms-/me-`, `ps-/pe-`, `start-/end-`, `rounded-s/e`, `text-start/end`) so the UI flips correctly under RTL (rule: logical CSS)."),a(/size=["']default["']/,'`size="default"` is not in the controlled vocabulary \u2014 use `size` \u2208 xs|sm|md|lg.'),a(/\btext-\[[0-9.]+px\]/,"Arbitrary text size `text-[Npx]` bypasses the golden type scale \u2014 use `<Text size>` / `<Heading level>` (rule 42)."),a(/<Tag[\s\S]*?color=["']error["']/i,'Tag `color="error"` \u2192 `"destructive"` (v5.0, PR #60).'),a(/<Badge[\s\S]*?variant=["']error["']/i,'Badge `variant="error"` \u2192 `"destructive"` (v5.0, PR #63).'),a(/(Flex|Space|Grid|Masonry)[\s\S]*?(gap|size)=["']middle["']/i,'`"middle"` \u2192 `"default"` for Flex/Space/Grid/Masonry (v5.0).'),a(/<IconButton[\s\S]*?size=["']default["']/i,'IconButton `size="default"` \u2192 `"md"` (v5.0).'),a(/<SegmentedControl[\s\S]*?size=["']sm["']/i,'SegmentedControl `size="sm"` \u2192 `"small"` (v5.0).'),a(/<PageContent[\s\S]*?padding=["'](compact|comfortable)["']/i,'PageContent `padding="compact"/"comfortable"` \u2192 `"tight"/"cozy"` (v5.0).'),a(/<Pagination[\s\S]*?justify=["']between["']/i,'Pagination `justify="between"` \u2192 `"space-between"` (v5.0).'),/<IconButton(?![^>]*aria-label)/i.test(e)&&!/asChild/i.test(e)&&t.push("`<IconButton>` should have `aria-label` (rule 6 \u2014 WCAG)."),/cell:\s*\(\{?\s*row\s*\}?\)\s*=>/i.test(e)&&/export\s+const\s+\w+\s*:\s*Story/i.test(e)&&(/parameters[\s\S]{0,200}source[\s\S]{0,100}code:/i.test(e)||t.push("Stories with function-valued cell renderers MUST override `parameters.docs.source.code` (rule 34).")),/text-(red|blue|green|yellow)-\d{2,3}\b/.test(e)&&t.push("Hard-coded color scales \u2014 use semantic tokens. Tells AI-slop palette (rule 2 + anti-AI-tells.visual.rainbow-chip-wall)."),/h-\[?100vh\]?/.test(e)&&t.push("`100vh` causes iOS Safari viewport jump \u2014 use `min-h-[100dvh]` (redesign.layout / soft.absolute-zero)."),/className=["'][^"']*(?:shadow-md|shadow-lg|shadow-xl)["']/.test(e)&&t.push("Tailwind heavy shadows are an AI tell \u2014 use ultra-diffuse low-opacity (< 0.05) or tinted shadows (soft.absolute-zero, minimalist)."),/\b(?:Inter|Roboto|Helvetica|Open\s+Sans)\b/i.test(e)&&t.push("Banned default fonts (Inter/Roboto/Helvetica/Open Sans). Use Geist/Clash Display/PP Editorial New (soft.absolute-zero, minimalist.negative-constraints)."),/Acme|NovaCore|Flowbit|Quantix|VeloPay|John\s+Doe|Jane\s+Smith|Lorem\s+Ipsum/i.test(e)&&t.push("Generic placeholder content (Acme/NovaCore/John Doe/Lorem Ipsum). Use believable real-sounding names (anti-AI-tells.copy)."),t.length===0)return"\u2705 No issues found against the heuristic checks.";let i=`# Lint findings \u2014 ${t.length} issue${t.length===1?"":"s"}
|
|
8597
8827
|
|
|
8598
|
-
`;for(let
|
|
8828
|
+
`;for(let n of t)i+=`- ${n}
|
|
8599
8829
|
`;return i+=`
|
|
8600
8830
|
Note: heuristic only \u2014 not a substitute for the full CI gate.
|
|
8601
8831
|
`,i}var Be=k.length,qe=[{uri:"godx-ui://compatibility",name:"Package compatibility",description:"MCP package/server version and the compatible @godxjp/ui release range.",mimeType:"application/json"},{uri:"godx-ui://components",name:"All components",description:"Full component catalog as JSON \u2014 name, group, tagline, props, example, rules.",mimeType:"application/json"},{uri:"godx-ui://prop-vocabulary",name:"Shared prop vocabulary",description:"Cross-cutting prop types (SizeProp, StatusProp, ColorProp, LoadingProp, \u2026) as JSON.",mimeType:"application/json"},{uri:"godx-ui://tokens",name:"All design tokens",description:"Every CSS variable + role + value + axis as JSON.",mimeType:"application/json"},{uri:"godx-ui://rules",name:`Cardinal rules (${Be})`,description:`The ${Be} binding rules from CLAUDE.md as Markdown.`,mimeType:"text/markdown"},{uri:"godx-ui://patterns",name:"Code patterns",description:"Canonical pattern catalog (registration-form, settings-page, data-table, \u2026) as JSON.",mimeType:"application/json"}];async function He(e){if(e==="godx-ui://compatibility")return JSON.stringify({mcpVersion:p.version,serverVersion:p.version,compatibleUi:p.godxUiCompatibility,policy:"UI and MCP are released from the same source commit and minor release train."},null,2);if(e==="godx-ui://components")return JSON.stringify(w,null,2);if(e.startsWith("godx-ui://components/")){let t=e.slice(21),a=F(t);if(!a)throw new Error(`Component not found: ${t}`);return Pt(a)}if(e==="godx-ui://prop-vocabulary")return JSON.stringify(C,null,2);if(e==="godx-ui://tokens")return JSON.stringify(z,null,2);if(e.startsWith("godx-ui://tokens/")){let t=e.slice(17);return JSON.stringify(M(t),null,2)}if(e==="godx-ui://rules"){let t=`# Cardinal rules (${k.length})
|
|
@@ -8637,14 +8867,14 @@ ${e.example}
|
|
|
8637
8867
|
|
|
8638
8868
|
${N.map(r=>`${r.name} \u2014 ${String(r.description??"").split(/(?<=\.)\s/)[0]}`).join(`
|
|
8639
8869
|
`)}
|
|
8640
|
-
`,stderr:""};if(!N.some(
|
|
8870
|
+
`,stderr:""};if(!N.some(o=>o.name===t))return{code:1,stdout:"",stderr:`unknown tool: ${t} (godx-ui-mcp call with no tool lists them)
|
|
8641
8871
|
`};let i={};if(a!==void 0){try{i=JSON.parse(a)}catch{return{code:2,stdout:"",stderr:`arguments must be a JSON object, got: ${a}
|
|
8642
8872
|
`}}if(i===null||typeof i!="object"||Array.isArray(i))return{code:2,stdout:"",stderr:`arguments must be a JSON object, got: ${a}
|
|
8643
|
-
`}}let
|
|
8644
|
-
`)?
|
|
8873
|
+
`}}let n=await U(t,i);return{code:0,stdout:n.endsWith(`
|
|
8874
|
+
`)?n:`${n}
|
|
8645
8875
|
`,stderr:""}}var Wt=`Building UI with @godxjp/ui. Before any JSX:
|
|
8646
8876
|
1. get_design \u2014 DESIGN.md: tokens, scarcity rules, screen anatomy, do/don't.
|
|
8647
8877
|
2. route_consumer_task \u2192 compose-a-screen for app screens (marketing skills are for landing pages only).
|
|
8648
8878
|
3. get_exemplar for the closest archetype (no argument lists them: list, detail, editor, reader, settings, dashboard, approval-queue) \u2014 match its structure.
|
|
8649
8879
|
4. search_components before get_component; never guess a prop.
|
|
8650
|
-
After building: ui-audit at zero errors, then run \`npx @godxjp/ui visual-audit <url> <routes>\` (320/390/768/1024/1280/844\xD7390) to zero machine failures, inspect every screenshot at every width region by region (including regions you must not change), no top-N or round cap; report "probes passed; independent acceptance pending" \u2014 never accept your own screen.`;async function jt(){let e=new Lt({name:"godx-ui-mcp",version:p.version},{capabilities:{tools:{},resources:{}},instructions:Wt});e.setRequestHandler(qt,async()=>({tools:N})),e.setRequestHandler(Mt,async a=>{let{name:i,arguments:
|
|
8880
|
+
After building: ui-audit at zero errors, then run \`npx @godxjp/ui visual-audit <url> <routes>\` (320/390/768/1024/1280/844\xD7390) to zero machine failures, inspect every screenshot at every width region by region (including regions you must not change), no top-N or round cap; report "probes passed; independent acceptance pending" \u2014 never accept your own screen.`;async function jt(){let e=new Lt({name:"godx-ui-mcp",version:p.version},{capabilities:{tools:{},resources:{}},instructions:Wt});e.setRequestHandler(qt,async()=>({tools:N})),e.setRequestHandler(Mt,async a=>{let{name:i,arguments:n}=a.params;return{content:[{type:"text",text:await U(i,n??{})}]}}),e.setRequestHandler(Bt,async()=>({resources:qe})),e.setRequestHandler(Ht,async a=>{let{uri:i}=a.params,n=await He(i);return{contents:[{uri:i,mimeType:i.endsWith(".json")?"application/json":"text/markdown",text:n}]}});let t=new Ft;await e.connect(t),console.error("[godx-ui-mcp] connected (stdio)")}process.argv[2]==="call"?We(process.argv.slice(3)).then(({code:e,stdout:t,stderr:a})=>{a&&process.stderr.write(a),process.stdout.write(t,()=>process.exit(e))},e=>{console.error("[godx-ui-mcp] call failed:",e),process.exit(1)}):jt().catch(e=>{console.error("[godx-ui-mcp] fatal:",e),process.exit(1)});
|