@godxjp/ui-mcp 0.21.3 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -77,7 +77,8 @@ var COMPONENTS = [
77
77
  "DO: Pass `breadcrumb` as an ordered array of `{ label, to? }` objects from root to current page. The last item is automatically rendered without a link and receives `aria-current='page'`; earlier items with `to` become router `<Link>` elements. Never hand-roll a breadcrumb nav inside a PageContainer.",
78
78
  "DON'T: Use `density` to change individual control sizes \u2014 it cascades spacing across the entire page subtree. Set it once per page (e.g. `density='compact'` for data-dense list pages) and let all child components inherit it. Do not apply density classes manually.",
79
79
  "DON'T: Confuse PageContainer's prop names with the old PageHeader's prop names \u2014 PageContainer uses `subtitle` (not `description`) and `extra` (not `actions`). If you see those legacy names in old code, migrate them to PageContainer.",
80
- "DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this."
80
+ "DO: Leave `fill` off (the default) for ordinary pages \u2014 the body is content-height and top-packed, so a short page on a tall viewport leaves no stretched empty void below the content (the page background simply spans the shell). Only set `fill` when the body itself should occupy the full remaining height: a full-height DataTable, a SplitPane, or a chat surface whose message list scrolls and whose composer is pinned to the bottom via `footer` + `stickyFooter`. DON'T add a manual `min-h-screen` / `flex-1` wrapper or a spacer div to fight or fake this.",
81
+ "DO: Know the header draws NO bottom divider by default \u2014 it is governed by the semantic token `--page-header-divider` (default `none`). A service theme opts in once, globally, with `--page-header-divider: 1px solid hsl(var(--border));` in its theme CSS. Never re-create the divider with a `border-b` utility on the header or a `<Separator>` under the title; `variant='ghost'` stays divider-less regardless of the token."
81
82
  ],
82
83
  useCases: [
83
84
  "A master list page (e.g. invoices, journal entries, customers) where the header holds the page title, a 'New Invoice' button in `extra`, a breadcrumb trail, and a full-bleed DataTable as the body \u2014 use `variant='flush'` + `<PageContainer.Inset>` for the Toolbar above the table.",
@@ -519,6 +520,12 @@ export default function Shell() {
519
520
  defaultValue: "undefined",
520
521
  description: "Called when the search bar button (\u2318K) is clicked. Wire this to your command-palette or search dialog."
521
522
  },
523
+ {
524
+ name: "searchPlaceholder",
525
+ type: "string",
526
+ defaultValue: 'i18n "layout.topbar.searchPlaceholder" ("\u691C\u7D22\u2026")',
527
+ description: "Search-bar placeholder text \u2014 set a domain-specific hint (e.g. \u6848\u4EF6\u30FB\u53D7\u6CE8\u30FB\u9867\u5BA2\u3092\u691C\u7D22\u2026) instead of the generic default."
528
+ },
522
529
  {
523
530
  name: "onTweaksOpen",
524
531
  type: "() => void",
@@ -1315,7 +1322,7 @@ export function Grid({ rows }: { rows: Row[] }) {
1315
1322
  {
1316
1323
  name: "StatCard",
1317
1324
  group: "data-display",
1318
- tagline: "KPI tile. \u26A0\uFE0F StatCard IS ALREADY a bordered Card \u2014 render it DIRECTLY in ResponsiveGrid. NEVER wrap it in <Card>/<CardContent> (that double-borders it \u2192 looks too thick). NO accent prop (accent is a Card prop).",
1325
+ tagline: "KPI tile. \u26A0\uFE0F StatCard IS ALREADY a bordered Card \u2014 render it DIRECTLY in ResponsiveGrid. NEVER wrap it in <Card>/<CardContent> (that double-borders it \u2192 looks too thick). Use `accent` for a semantic leading-edge rail on a KPI needing attention.",
1319
1326
  props: [
1320
1327
  { name: "label", type: "ReactNode", required: true, description: "Metric name." },
1321
1328
  {
@@ -1336,13 +1343,24 @@ export function Grid({ rows }: { rows: Row[] }) {
1336
1343
  defaultValue: '"stacked"',
1337
1344
  description: "stacked = label over value; inline = label left / value right."
1338
1345
  },
1339
- { name: "align", type: '"start" | "end"', description: "Align the metric group." }
1346
+ { name: "align", type: '"start" | "end"', description: "Align the metric group." },
1347
+ {
1348
+ name: "inverse",
1349
+ type: "boolean",
1350
+ defaultValue: "false",
1351
+ description: "Flip delta tone semantics for metrics where lower is better (cost, error rate): '-' renders green, '+' renders red."
1352
+ },
1353
+ {
1354
+ name: "accent",
1355
+ type: '"primary" | "success" | "warning" | "info" | "attention" | "destructive"',
1356
+ description: "Semantic 3px leading-edge rail (forwarded to the underlying Card) \u2014 flags a KPI that needs attention (e.g. attention = backlog with a deadline, destructive = overdue)."
1357
+ }
1340
1358
  ],
1341
1359
  usage: [
1342
1360
  "DO place StatCard directly as a child of ResponsiveGrid \u2014 it renders its own bordered Card shell internally, so no wrapping <Card> or <CardContent> is needed or allowed. Wrapping creates a double border.",
1343
1361
  "DO pass `delta` as a sign-prefixed string (e.g. '+12%' or '-3%') to get automatic color tone: '+' renders text-success, '-' renders text-destructive. For metrics where a negative delta is good (e.g. cost reduction, error rate), pass `inverse` so the tone is flipped correctly.",
1344
1362
  "DO use `hint` for secondary context (e.g. '\u5148\u6708\u6BD4 +3%', 'last 30 days'). In the default `stacked` layout hint renders below the value; in `inline` layout it renders beside the label.",
1345
- "DO NOT add an `accent` prop \u2014 accent is a Card prop and StatCard does not expose it. Passing accent has no effect and creates a false expectation.",
1363
+ "DO use `accent` (not a className border) for the semantic leading-edge rail \u2014 `attention` for a non-destructive backlog needing action, `destructive` only for overdue/exceeded states. Most tiles in a grid stay rail-less; an all-accented grid says nothing.",
1346
1364
  "DO NOT hand-roll a KPI tile using a plain <Card><CardContent>. StatCard is the correct primitive and token-aligns the label/value/hint/delta slots automatically.",
1347
1365
  "WHILE data is loading, replace each StatCard with a <SkeletonStat /> at the same grid position \u2014 never render an empty value string or a spinner inside StatCard itself."
1348
1366
  ],
@@ -2675,6 +2693,184 @@ export function PrioritySelect({ value, onValueChange }) {
2675
2693
  storyPath: "data-entry/RadioGroup.stories.tsx",
2676
2694
  rules: [23]
2677
2695
  },
2696
+ {
2697
+ name: "MonthPicker",
2698
+ group: "data-entry",
2699
+ tagline: "Year/month (yyyy/MM) input with an Ant-Design-style month-grid popover \u2014 a year chevron header over a 3x4 grid of the twelve months. The input stays typeable; the grid is the visual affordance.",
2700
+ props: [
2701
+ {
2702
+ name: "value",
2703
+ type: "Date | undefined",
2704
+ description: "Controlled value \u2014 first day of the selected month. Pass undefined to clear."
2705
+ },
2706
+ {
2707
+ name: "defaultValue",
2708
+ type: "Date | undefined",
2709
+ description: "Uncontrolled initial value."
2710
+ },
2711
+ {
2712
+ name: "onValueChange",
2713
+ type: "(value: Date | undefined) => void",
2714
+ description: "Fires on a grid pick, on a complete typed yyyy/MM, and on clear."
2715
+ },
2716
+ {
2717
+ name: "placeholder",
2718
+ type: "string",
2719
+ defaultValue: '"yyyy/mm"',
2720
+ description: "Placeholder shown while empty."
2721
+ },
2722
+ {
2723
+ name: "disabled",
2724
+ type: "boolean",
2725
+ defaultValue: "false",
2726
+ description: "Disables the input, the clear button and the grid trigger."
2727
+ },
2728
+ {
2729
+ name: "className",
2730
+ type: "string",
2731
+ description: "Extra classes on the control shell (width/margin overrides)."
2732
+ },
2733
+ {
2734
+ name: "id",
2735
+ type: "string",
2736
+ description: "Wired to the inner input; auto-generated when omitted so the field always has an id."
2737
+ },
2738
+ {
2739
+ name: "name",
2740
+ type: "string",
2741
+ description: "Form field name \u2014 submits the display text (yyyy/MM)."
2742
+ },
2743
+ {
2744
+ name: "fromYear",
2745
+ type: "number",
2746
+ description: "Inclusive lower bound for the year navigation."
2747
+ },
2748
+ {
2749
+ name: "toYear",
2750
+ type: "number",
2751
+ description: "Inclusive upper bound for the year navigation."
2752
+ },
2753
+ {
2754
+ name: "allowClear",
2755
+ type: "boolean",
2756
+ defaultValue: "true",
2757
+ description: "Inline clear button while a value is set."
2758
+ }
2759
+ ],
2760
+ usage: [
2761
+ "DO use MonthPicker for every yyyy/MM (year-month) field \u2014 never a bare Input with a YYYY/MM helper text.",
2762
+ "DO wrap it in FormField like every other labelled control; FormField injects id/aria wiring.",
2763
+ "DO NOT compose two MonthPickers to fake a from~to range \u2014 that is MonthRangePicker (one control shell, like DateRangePicker)."
2764
+ ],
2765
+ related: [
2766
+ "DatePicker \u2014 full date (yyyy-MM-dd); MonthPicker when the day is meaningless (\u7DE0\u3081\u5E74\u6708, \u96C6\u8A08\u5E74\u6708, \u5546\u8AC7\u767A\u751F\u5E74\u6708).",
2767
+ "MonthRangePicker \u2014 use for a yyyy/MM from~to pair; one input-styled control, never two MonthPickers side-by-side."
2768
+ ],
2769
+ example: `import { useState } from "react";
2770
+ import { MonthPicker, FormField } from "@godxjp/ui/data-entry";
2771
+
2772
+ export function OrderMonthField() {
2773
+ const [ym, setYm] = useState<Date | undefined>(undefined);
2774
+
2775
+ return (
2776
+ <FormField label="\u53D7\u6CE8\u65E5\u5E74\u6708">
2777
+ <MonthPicker name="search_order_date_ym" value={ym} onValueChange={setYm} />
2778
+ </FormField>
2779
+ );
2780
+ }`,
2781
+ storyPath: "data-entry/MonthPicker.stories.tsx",
2782
+ rules: [3, 6, 13, 31, 43]
2783
+ },
2784
+ {
2785
+ name: "MonthRangePicker",
2786
+ group: "data-entry",
2787
+ tagline: "Year/month (yyyy/MM) RANGE rendered as ONE input-styled control `[ from \u2192 to \u2715 \u{1F4C5} ]` (Ant RangePicker convention, same shell as DateRangePicker) with an Ant-style month-grid popover. Both inputs stay typeable; picks are two-step with from \u2264 to always enforced.",
2788
+ props: [
2789
+ {
2790
+ name: "value",
2791
+ type: "DateRange | undefined",
2792
+ description: "Controlled range \u2014 both edges normalized to the first day of their month. Pass undefined to clear."
2793
+ },
2794
+ {
2795
+ name: "defaultValue",
2796
+ type: "DateRange | undefined",
2797
+ description: "Uncontrolled initial range."
2798
+ },
2799
+ {
2800
+ name: "onValueChange",
2801
+ type: "(value: DateRange | undefined) => void",
2802
+ description: "Fires on each grid step ({from, to: undefined} then the complete pair), on a complete typed yyyy/MM at either edge, and on clear. Never emits an inverted range \u2014 a backwards pick/typing is swapped so from \u2264 to."
2803
+ },
2804
+ {
2805
+ name: "placeholder",
2806
+ type: "string",
2807
+ defaultValue: '"yyyy/mm"',
2808
+ description: "Placeholder shown in both inputs while empty."
2809
+ },
2810
+ {
2811
+ name: "disabled",
2812
+ type: "boolean",
2813
+ defaultValue: "false",
2814
+ description: "Disables both inputs, the clear button and the grid trigger."
2815
+ },
2816
+ {
2817
+ name: "className",
2818
+ type: "string",
2819
+ description: "Extra classes on the control shell (width/margin overrides)."
2820
+ },
2821
+ {
2822
+ name: "id",
2823
+ type: "string",
2824
+ description: "Wired to the from input; the to input gets `${id}-to`. Auto-generated when omitted."
2825
+ },
2826
+ {
2827
+ name: "name",
2828
+ type: "string",
2829
+ description: "Form field name \u2014 emits the range as `${name}_from` / `${name}_to` yyyy/MM fields."
2830
+ },
2831
+ {
2832
+ name: "fromYear",
2833
+ type: "number",
2834
+ description: "Inclusive lower bound for the year navigation."
2835
+ },
2836
+ {
2837
+ name: "toYear",
2838
+ type: "number",
2839
+ description: "Inclusive upper bound for the year navigation."
2840
+ },
2841
+ {
2842
+ name: "allowClear",
2843
+ type: "boolean",
2844
+ defaultValue: "true",
2845
+ description: "Inline clear button (clears the WHOLE range) while a value is set."
2846
+ }
2847
+ ],
2848
+ usage: [
2849
+ "DO use MonthRangePicker for every yyyy/MM from~to pair \u2014 never two MonthPickers (or bare Inputs) separated by ~; a range is ONE control, exactly like DateRangePicker.",
2850
+ "DO rely on its built-in range validation: a backwards grid pick or typed pair is swap-normalized so the emitted range always satisfies from \u2264 to \u2014 do not re-validate order in the app.",
2851
+ "DO wrap it in FormField like every other labelled control; FormField injects id/aria wiring.",
2852
+ "Grid picks are two-step (from, then to) and reset-on-complete: picking while a complete range is held STARTS a new range, so the start month is never stuck."
2853
+ ],
2854
+ related: [
2855
+ "MonthPicker \u2014 single yyyy/MM value; MonthRangePicker when the field is a from~to pair (\u5546\u8AC7\u767A\u751F\u5E74\u6708\u306E\u7BC4\u56F2\u691C\u7D22, \u96C6\u8A08\u671F\u9593).",
2856
+ "DateRangePicker \u2014 full-date (yyyy-MM-dd) range with the same one-control shell; MonthRangePicker when the day is meaningless."
2857
+ ],
2858
+ example: `import { useState } from "react";
2859
+ import type { DateRange } from "react-day-picker";
2860
+ import { MonthRangePicker, FormField } from "@godxjp/ui/data-entry";
2861
+
2862
+ export function NegotiationYmField() {
2863
+ const [range, setRange] = useState<DateRange | undefined>(undefined);
2864
+
2865
+ return (
2866
+ <FormField label="\u5546\u8AC7\u767A\u751F\u5E74\u6708">
2867
+ <MonthRangePicker name="search_negotiation_ym" value={range} onValueChange={setRange} />
2868
+ </FormField>
2869
+ );
2870
+ }`,
2871
+ storyPath: "data-entry/MonthRangePicker.stories.tsx",
2872
+ rules: [3, 6, 13, 31, 43]
2873
+ },
2678
2874
  {
2679
2875
  name: "DatePicker",
2680
2876
  group: "data-entry",
@@ -7027,6 +7223,18 @@ var TOKENS = [
7027
7223
  },
7028
7224
  { name: "--info", category: "semantic", tier: "semantic", role: "Information status role." },
7029
7225
  { name: "--attention", category: "semantic", tier: "semantic", role: "Attention status role." },
7226
+ {
7227
+ name: "--page-header-divider",
7228
+ category: "semantic",
7229
+ tier: "semantic",
7230
+ role: "PageContainer header bottom divider. Default none; a service theme opts in with `1px solid hsl(var(--border))`."
7231
+ },
7232
+ {
7233
+ name: "--page-header-pad-bottom",
7234
+ category: "semantic",
7235
+ tier: "semantic",
7236
+ role: "PageContainer header bottom inset. Defaults to page top padding minus the section gap so the title band is vertically balanced."
7237
+ },
7030
7238
  { name: "--badge-space-*", category: "component", tier: "component", role: "Badge spacing." },
7031
7239
  {
7032
7240
  name: "--card-*",
@@ -7041,6 +7249,18 @@ var TOKENS = [
7041
7249
  role: "Shared form control heights, padding, icons, and focus chrome."
7042
7250
  },
7043
7251
  { name: "--table-*", category: "component", tier: "component", role: "Table row/cell sizing." },
7252
+ {
7253
+ name: "--form-label-width",
7254
+ category: "component",
7255
+ tier: "component",
7256
+ role: "Label column width in horizontal Form layout. Default max-content; a service theme sets it once (e.g. 110px) \u2014 the labelWidth prop overrides per form/field."
7257
+ },
7258
+ {
7259
+ name: "--form-label-gap",
7260
+ category: "component",
7261
+ tier: "component",
7262
+ role: "Label\u2194control column gap in horizontal Form layout. Default 16px (--space-4)."
7263
+ },
7044
7264
  {
7045
7265
  name: "--dialog-* / --alert-* / --skeleton-*",
7046
7266
  category: "component",
@@ -7052,6 +7272,715 @@ function tokensByCategory(category) {
7052
7272
  return TOKENS.filter((t) => t.category === category);
7053
7273
  }
7054
7274
 
7275
+ // src/data/component-tokens.generated.ts
7276
+ var COMPONENT_TOKENS = [
7277
+ {
7278
+ "name": "--badge-space-gap",
7279
+ "value": "var(--space-inline-xs)",
7280
+ "description": "Badge component tokens."
7281
+ },
7282
+ {
7283
+ "name": "--badge-space-x",
7284
+ "value": "var(--space-2)",
7285
+ "description": "Badge component tokens."
7286
+ },
7287
+ {
7288
+ "name": "--badge-space-y",
7289
+ "value": "var(--space-1)",
7290
+ "description": "Badge component tokens."
7291
+ },
7292
+ {
7293
+ "name": "--badge-font-size",
7294
+ "value": "var(--font-size-xs)",
7295
+ "description": "Small-by-design (badge/pill/counter). A knob (rule #45) so a service can * re-tune badge text without touching the global --font-size-xs step."
7296
+ },
7297
+ {
7298
+ "name": "--card-space-inset",
7299
+ "value": "var(--space-section-active)",
7300
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7301
+ },
7302
+ {
7303
+ "name": "--card-space-header-y",
7304
+ "value": "var(--space-stack-sm)",
7305
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7306
+ },
7307
+ {
7308
+ "name": "--card-space-body-y",
7309
+ "value": "var(--space-section-active)",
7310
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7311
+ },
7312
+ {
7313
+ "name": "--card-space-footer-y",
7314
+ "value": "var(--space-stack-sm)",
7315
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7316
+ },
7317
+ {
7318
+ "name": "--card-space-gap",
7319
+ "value": "var(--space-stack-xs)",
7320
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7321
+ },
7322
+ {
7323
+ "name": "--card-title-font-size",
7324
+ "value": "var(--font-size-base)",
7325
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7326
+ },
7327
+ {
7328
+ "name": "--card-title-line-height",
7329
+ "value": "var(--line-height-tight)",
7330
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7331
+ },
7332
+ {
7333
+ "name": "--card-title-font-weight",
7334
+ "value": "var(--font-weight-semibold)",
7335
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7336
+ },
7337
+ {
7338
+ "name": "--card-description-font-size",
7339
+ "value": "var(--font-size-sm)",
7340
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7341
+ },
7342
+ {
7343
+ "name": "--card-description-line-height",
7344
+ "value": "var(--line-height-normal)",
7345
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7346
+ },
7347
+ {
7348
+ "name": "--card-background",
7349
+ "value": "var(--card)",
7350
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7351
+ },
7352
+ {
7353
+ "name": "--card-border",
7354
+ "value": "var(--border)",
7355
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7356
+ },
7357
+ {
7358
+ "name": "--card-header-background",
7359
+ "value": "var(--muted)",
7360
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7361
+ },
7362
+ {
7363
+ "name": "--card-header-background-alpha",
7364
+ "value": "0.55",
7365
+ "description": "Card component tokens: card chrome derives from semantic layout tokens."
7366
+ },
7367
+ {
7368
+ "name": "--card-header-border-bottom",
7369
+ "value": "1px solid hsl(var(--card-border))",
7370
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7371
+ },
7372
+ {
7373
+ "name": "--card-radius",
7374
+ "value": "var(--radius)",
7375
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7376
+ },
7377
+ {
7378
+ "name": "--stat-card-label-font-size",
7379
+ "value": "var(--font-size-xs)",
7380
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7381
+ },
7382
+ {
7383
+ "name": "--stat-card-label-font-weight",
7384
+ "value": "var(--font-weight-medium)",
7385
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7386
+ },
7387
+ {
7388
+ "name": "--stat-card-label-letter-spacing",
7389
+ "value": "0.04em",
7390
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7391
+ },
7392
+ {
7393
+ "name": "--stat-card-value-font-size",
7394
+ "value": "var(--font-size-2xl)",
7395
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7396
+ },
7397
+ {
7398
+ "name": "--stat-card-value-line-height",
7399
+ "value": "1.1",
7400
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7401
+ },
7402
+ {
7403
+ "name": "--stat-card-value-font-weight",
7404
+ "value": "var(--font-weight-semibold)",
7405
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7406
+ },
7407
+ {
7408
+ "name": "--stat-card-hint-font-size",
7409
+ "value": "var(--font-size-xs)",
7410
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7411
+ },
7412
+ {
7413
+ "name": "--stat-card-gap",
7414
+ "value": "var(--space-stack-xs)",
7415
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7416
+ },
7417
+ {
7418
+ "name": "--stat-card-icon-size",
7419
+ "value": "2.25rem",
7420
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7421
+ },
7422
+ {
7423
+ "name": "--stat-card-delta-font-size",
7424
+ "value": "var(--font-size-xs)",
7425
+ "description": "Banded-header divider \u2014 tokenised (rule #44) so a service theme can make it * dashed / heavier / none without forking CSS. Pair with * --card-header-background-alpha: 0 for a quiet borderless-band header."
7426
+ },
7427
+ {
7428
+ "name": "--control-height-compact",
7429
+ "value": "1.75rem",
7430
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7431
+ },
7432
+ {
7433
+ "name": "--control-height-default",
7434
+ "value": "2rem",
7435
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7436
+ },
7437
+ {
7438
+ "name": "--control-height-comfortable",
7439
+ "value": "2.75rem",
7440
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7441
+ },
7442
+ {
7443
+ "name": "--control-padding-x-compact",
7444
+ "value": "var(--space-2)",
7445
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7446
+ },
7447
+ {
7448
+ "name": "--control-padding-x-default",
7449
+ "value": "var(--space-3)",
7450
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7451
+ },
7452
+ {
7453
+ "name": "--control-padding-x-comfortable",
7454
+ "value": "var(--space-4)",
7455
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7456
+ },
7457
+ {
7458
+ "name": "--control-height",
7459
+ "value": "var(--control-height-default)",
7460
+ "description": "Control primitive tokens: heights, horizontal padding, adjacent control sizes."
7461
+ },
7462
+ {
7463
+ "name": "--control-height-sm",
7464
+ "value": "calc(var(--control-height) - 0.25rem)",
7465
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7466
+ },
7467
+ {
7468
+ "name": "--control-height-lg",
7469
+ "value": "calc(var(--control-height) + 0.25rem)",
7470
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7471
+ },
7472
+ {
7473
+ "name": "--control-height-xs",
7474
+ "value": "calc(var(--control-height) - 0.5rem)",
7475
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7476
+ },
7477
+ {
7478
+ "name": "--control-padding-x",
7479
+ "value": "var(--control-padding-x-default)",
7480
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7481
+ },
7482
+ {
7483
+ "name": "--control-gap",
7484
+ "value": "var(--space-inline-sm)",
7485
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7486
+ },
7487
+ {
7488
+ "name": "--control-gap-sm",
7489
+ "value": "var(--space-inline-xs)",
7490
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7491
+ },
7492
+ {
7493
+ "name": "--control-radius",
7494
+ "value": "var(--radius)",
7495
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7496
+ },
7497
+ {
7498
+ "name": "--control-icon-size",
7499
+ "value": "1rem",
7500
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7501
+ },
7502
+ {
7503
+ "name": "--control-icon-size-sm",
7504
+ "value": "0.875rem",
7505
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7506
+ },
7507
+ {
7508
+ "name": "--control-focus-ring-width",
7509
+ "value": "2px",
7510
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7511
+ },
7512
+ {
7513
+ "name": "--checkbox-size",
7514
+ "value": "1rem",
7515
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7516
+ },
7517
+ {
7518
+ "name": "--checkbox-size-compact",
7519
+ "value": "0.875rem",
7520
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7521
+ },
7522
+ {
7523
+ "name": "--checkbox-size-comfortable",
7524
+ "value": "1.125rem",
7525
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7526
+ },
7527
+ {
7528
+ "name": "--choice-gap",
7529
+ "value": "var(--space-inline-sm)",
7530
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7531
+ },
7532
+ {
7533
+ "name": "--choice-group-gap-x",
7534
+ "value": "var(--space-6)",
7535
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7536
+ },
7537
+ {
7538
+ "name": "--choice-group-gap-y",
7539
+ "value": "var(--space-3)",
7540
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7541
+ },
7542
+ {
7543
+ "name": "--choice-description-gap",
7544
+ "value": "0.125rem",
7545
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7546
+ },
7547
+ {
7548
+ "name": "--choice-control-offset",
7549
+ "value": "0.125rem",
7550
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7551
+ },
7552
+ {
7553
+ "name": "--switch-width",
7554
+ "value": "2.25rem",
7555
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7556
+ },
7557
+ {
7558
+ "name": "--switch-width-compact",
7559
+ "value": "2rem",
7560
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7561
+ },
7562
+ {
7563
+ "name": "--switch-width-comfortable",
7564
+ "value": "2.5rem",
7565
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7566
+ },
7567
+ {
7568
+ "name": "--switch-height",
7569
+ "value": "1.25rem",
7570
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7571
+ },
7572
+ {
7573
+ "name": "--switch-height-compact",
7574
+ "value": "1.125rem",
7575
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7576
+ },
7577
+ {
7578
+ "name": "--switch-height-comfortable",
7579
+ "value": "1.375rem",
7580
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7581
+ },
7582
+ {
7583
+ "name": "--switch-thumb-size",
7584
+ "value": "1rem",
7585
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7586
+ },
7587
+ {
7588
+ "name": "--switch-thumb-size-compact",
7589
+ "value": "0.875rem",
7590
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7591
+ },
7592
+ {
7593
+ "name": "--switch-thumb-size-comfortable",
7594
+ "value": "1.125rem",
7595
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7596
+ },
7597
+ {
7598
+ "name": "--switch-thumb-translate",
7599
+ "value": "1rem",
7600
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7601
+ },
7602
+ {
7603
+ "name": "--switch-thumb-translate-compact",
7604
+ "value": "0.875rem",
7605
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7606
+ },
7607
+ {
7608
+ "name": "--switch-thumb-translate-comfortable",
7609
+ "value": "1.125rem",
7610
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7611
+ },
7612
+ {
7613
+ "name": "--slider-track-height",
7614
+ "value": "0.375rem",
7615
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7616
+ },
7617
+ {
7618
+ "name": "--slider-thumb-size",
7619
+ "value": "1rem",
7620
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7621
+ },
7622
+ {
7623
+ "name": "--color-picker-input-width",
7624
+ "value": "6.5rem",
7625
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7626
+ },
7627
+ {
7628
+ "name": "--command-list-max-height",
7629
+ "value": "min(300px, 50vh)",
7630
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7631
+ },
7632
+ {
7633
+ "name": "--command-input-padding-x",
7634
+ "value": "var(--space-3)",
7635
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7636
+ },
7637
+ {
7638
+ "name": "--command-group-padding",
7639
+ "value": "var(--space-1)",
7640
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7641
+ },
7642
+ {
7643
+ "name": "--command-item-padding-y",
7644
+ "value": "var(--space-2)",
7645
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7646
+ },
7647
+ {
7648
+ "name": "--command-item-padding-x",
7649
+ "value": "var(--space-2)",
7650
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7651
+ },
7652
+ {
7653
+ "name": "--search-input-edge-inset",
7654
+ "value": "var(--space-3)",
7655
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7656
+ },
7657
+ {
7658
+ "name": "--search-input-start-padding",
7659
+ "value": "calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",
7660
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7661
+ },
7662
+ {
7663
+ "name": "--search-input-end-padding",
7664
+ "value": "calc( var(--search-input-edge-inset) + var(--control-icon-size) + var(--control-gap) )",
7665
+ "description": "Adjacent control sizes, derived from the active --control-height. * Default/compact use a 4px/8px step; density overrides comfortable * (which steps 44\u219236\u219232 per the design's spacing-density spec)."
7666
+ },
7667
+ {
7668
+ "name": "--control-height-compact",
7669
+ "value": "2.75rem",
7670
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7671
+ },
7672
+ {
7673
+ "name": "--control-height-default",
7674
+ "value": "2.75rem",
7675
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7676
+ },
7677
+ {
7678
+ "name": "--choice-description-font-size",
7679
+ "value": "var(--font-size-xs)",
7680
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7681
+ },
7682
+ {
7683
+ "name": "--color-picker-hex-font-size",
7684
+ "value": "var(--font-size-xs)",
7685
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7686
+ },
7687
+ {
7688
+ "name": "--command-group-heading-font-size",
7689
+ "value": "var(--font-size-xs)",
7690
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7691
+ },
7692
+ {
7693
+ "name": "--search-input-label-font-size",
7694
+ "value": "var(--font-size-xs)",
7695
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7696
+ },
7697
+ {
7698
+ "name": "--tag-input-chip-font-size",
7699
+ "value": "var(--font-size-xs)",
7700
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7701
+ },
7702
+ {
7703
+ "name": "--toggle-sm-font-size",
7704
+ "value": "var(--font-size-xs)",
7705
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7706
+ },
7707
+ {
7708
+ "name": "--button-sm-font-size",
7709
+ "value": "var(--font-size-xs)",
7710
+ "description": "Rule #24 \u2014 on touch devices (coarse pointer) interactive controls keep a \u226544px tap target * regardless of density; desktop (fine pointer) keeps the compact heights above. --control-height * resolves through these via var(), so inputs/buttons/selects/table rows all bump together."
7711
+ },
7712
+ {
7713
+ "name": "--progress-label-font-size",
7714
+ "value": "var(--font-size-xs)",
7715
+ "description": "Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7716
+ },
7717
+ {
7718
+ "name": "--tree-item-title-font-size",
7719
+ "value": "var(--font-size-xs)",
7720
+ "description": "Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7721
+ },
7722
+ {
7723
+ "name": "--tree-item-description-font-size",
7724
+ "value": "var(--font-size-xs)",
7725
+ "description": "Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7726
+ },
7727
+ {
7728
+ "name": "--timeline-note-font-size",
7729
+ "value": "var(--font-size-xs)",
7730
+ "description": "Data-display component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7731
+ },
7732
+ {
7733
+ "name": "--password-strength-score-font-size",
7734
+ "value": "var(--font-size-xs)",
7735
+ "description": "Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7736
+ },
7737
+ {
7738
+ "name": "--password-strength-checklist-font-size",
7739
+ "value": "var(--font-size-xs)",
7740
+ "description": "Data-entry component tokens \u2014 small-by-design text knobs (rule #45/#46)."
7741
+ },
7742
+ {
7743
+ "name": "--dialog-space-x",
7744
+ "value": "var(--space-chrome-x)",
7745
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7746
+ },
7747
+ {
7748
+ "name": "--dialog-space-y",
7749
+ "value": "var(--space-chrome-y)",
7750
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7751
+ },
7752
+ {
7753
+ "name": "--dialog-space-inset",
7754
+ "value": "var(--dialog-space-y) var(--dialog-space-x)",
7755
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7756
+ },
7757
+ {
7758
+ "name": "--dialog-space-gap",
7759
+ "value": "var(--space-stack-md)",
7760
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7761
+ },
7762
+ {
7763
+ "name": "--dialog-close-space-offset",
7764
+ "value": "var(--space-4)",
7765
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7766
+ },
7767
+ {
7768
+ "name": "--alert-space-inset",
7769
+ "value": "var(--space-section-active)",
7770
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7771
+ },
7772
+ {
7773
+ "name": "--alert-space-gap",
7774
+ "value": "var(--space-inline-md)",
7775
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7776
+ },
7777
+ {
7778
+ "name": "--alert-inner-space-gap",
7779
+ "value": "var(--space-stack-sm)",
7780
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7781
+ },
7782
+ {
7783
+ "name": "--alert-dismiss-space-offset",
7784
+ "value": "var(--space-3)",
7785
+ "description": "Dialog inset defaults to the shared global chrome tokens (override --space-chrome-* once for the * whole system, or --dialog-space-x/-y for dialogs only)."
7786
+ },
7787
+ {
7788
+ "name": "--alert-bg-alpha",
7789
+ "value": "0.05",
7790
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7791
+ },
7792
+ {
7793
+ "name": "--alert-border-alpha",
7794
+ "value": "0.3",
7795
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7796
+ },
7797
+ {
7798
+ "name": "--empty-state-space-y",
7799
+ "value": "var(--space-10)",
7800
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7801
+ },
7802
+ {
7803
+ "name": "--empty-state-space-x",
7804
+ "value": "var(--space-6)",
7805
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7806
+ },
7807
+ {
7808
+ "name": "--skeleton-row-gap",
7809
+ "value": "var(--space-stack-sm)",
7810
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7811
+ },
7812
+ {
7813
+ "name": "--skeleton-cell-gap",
7814
+ "value": "var(--space-inline-lg)",
7815
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7816
+ },
7817
+ {
7818
+ "name": "--skeleton-card-inset",
7819
+ "value": "var(--space-section-active)",
7820
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7821
+ },
7822
+ {
7823
+ "name": "--skeleton-radius",
7824
+ "value": "var(--radius)",
7825
+ "description": "Soft (subtle) semantic tint ratios \u2014 themeable so a service can hit its exact spec * (a brand's success-bg/-border are often more present than the faint 5%/30% default)."
7826
+ },
7827
+ {
7828
+ "name": "--form-label-width",
7829
+ "value": "max-content",
7830
+ "description": "Width of the label column in horizontal/inline layout. A service theme sets * this once (e.g. 110px) to align every form to its design grid; the Form/ * FormField `labelWidth` prop overrides per form/field."
7831
+ },
7832
+ {
7833
+ "name": "--form-label-gap",
7834
+ "value": "var(--space-4)",
7835
+ "description": "Column gap between the label and its control in horizontal/inline layout."
7836
+ },
7837
+ {
7838
+ "name": "--pagination-gap",
7839
+ "value": "var(--space-inline-sm)",
7840
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7841
+ },
7842
+ {
7843
+ "name": "--pagination-item-gap",
7844
+ "value": "var(--space-inline-xs)",
7845
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7846
+ },
7847
+ {
7848
+ "name": "--pagination-size-width",
7849
+ "value": "5.5rem",
7850
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7851
+ },
7852
+ {
7853
+ "name": "--pagination-total-font-size",
7854
+ "value": "var(--font-size-sm)",
7855
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7856
+ },
7857
+ {
7858
+ "name": "--filter-bar-gap",
7859
+ "value": "var(--space-3)",
7860
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7861
+ },
7862
+ {
7863
+ "name": "--filter-bar-padding-y",
7864
+ "value": "var(--space-2)",
7865
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7866
+ },
7867
+ {
7868
+ "name": "--filter-label-font-size",
7869
+ "value": "var(--font-size-xs)",
7870
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7871
+ },
7872
+ {
7873
+ "name": "--filter-picker-width-sm",
7874
+ "value": "11rem",
7875
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7876
+ },
7877
+ {
7878
+ "name": "--filter-picker-width-md",
7879
+ "value": "14rem",
7880
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7881
+ },
7882
+ {
7883
+ "name": "--breadcrumb-font-size",
7884
+ "value": "var(--font-size-xs)",
7885
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7886
+ },
7887
+ {
7888
+ "name": "--menubar-shortcut-font-size",
7889
+ "value": "var(--font-size-xs)",
7890
+ "description": "Navigation primitive tokens: pagination, filters, compact pickers."
7891
+ },
7892
+ {
7893
+ "name": "--sidebar-section-label-font-size",
7894
+ "value": "var(--font-size-2xs)",
7895
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7896
+ },
7897
+ {
7898
+ "name": "--sidebar-product-tenant-font-size",
7899
+ "value": "var(--font-size-2xs)",
7900
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7901
+ },
7902
+ {
7903
+ "name": "--sidebar-badge-font-size",
7904
+ "value": "var(--font-size-2xs)",
7905
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7906
+ },
7907
+ {
7908
+ "name": "--sidebar-user-role-font-size",
7909
+ "value": "var(--font-size-2xs)",
7910
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7911
+ },
7912
+ {
7913
+ "name": "--sidebar-nav-sub-font-size",
7914
+ "value": "var(--font-size-xs)",
7915
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7916
+ },
7917
+ {
7918
+ "name": "--sidebar-flyout-title-font-size",
7919
+ "value": "var(--font-size-xs)",
7920
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7921
+ },
7922
+ {
7923
+ "name": "--topbar-chip-icon-font-size",
7924
+ "value": "var(--font-size-2xs)",
7925
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7926
+ },
7927
+ {
7928
+ "name": "--kbd-font-size",
7929
+ "value": "var(--font-size-2xs)",
7930
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7931
+ },
7932
+ {
7933
+ "name": "--sidebar-logo-mark-font-size",
7934
+ "value": "var(--font-size-xs)",
7935
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7936
+ },
7937
+ {
7938
+ "name": "--sidebar-avatar-font-size",
7939
+ "value": "var(--font-size-2xs)",
7940
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7941
+ },
7942
+ {
7943
+ "name": "--sidebar-user-name-font-size",
7944
+ "value": "var(--font-size-xs)",
7945
+ "description": "Shell (sidebar / topbar / kbd) component tokens \u2014 small-by-design text * knobs (rule #45/#46). A service re-tunes chrome text without moving the * global scale."
7946
+ },
7947
+ {
7948
+ "name": "--table-row-height-compact",
7949
+ "value": "1.75rem",
7950
+ "description": "Table component tokens: row height, cell padding."
7951
+ },
7952
+ {
7953
+ "name": "--table-row-height-default",
7954
+ "value": "2rem",
7955
+ "description": "Table component tokens: row height, cell padding."
7956
+ },
7957
+ {
7958
+ "name": "--table-row-height-comfortable",
7959
+ "value": "2.75rem",
7960
+ "description": "Table component tokens: row height, cell padding."
7961
+ },
7962
+ {
7963
+ "name": "--table-row-height",
7964
+ "value": "var(--table-row-height-default)",
7965
+ "description": "Table component tokens: row height, cell padding."
7966
+ },
7967
+ {
7968
+ "name": "--table-cell-padding-y",
7969
+ "value": "var(--space-2)",
7970
+ "description": "Table component tokens: row height, cell padding."
7971
+ },
7972
+ {
7973
+ "name": "--table-cell-space-x",
7974
+ "value": "var(--control-padding-x)",
7975
+ "description": "Table component tokens: row height, cell padding."
7976
+ },
7977
+ {
7978
+ "name": "--table-head-font-size",
7979
+ "value": "var(--font-size-xs)",
7980
+ "description": "Table component tokens: row height, cell padding."
7981
+ }
7982
+ ];
7983
+
7055
7984
  // src/data/rules.ts
7056
7985
  var CARDINAL_RULES = [
7057
7986
  {
@@ -7263,6 +8192,26 @@ var CARDINAL_RULES = [
7263
8192
  number: 42,
7264
8193
  title: "Props & Tokens Before Customization",
7265
8194
  body: "Before reaching for a Tailwind class, inline `style`, or extra CSS, you MUST first check whether the component already supports the need via a PROP, a design TOKEN, or a layout/typography PRIMITIVE. godx-ui is meant to be enough on its own (Ant-Design-style): `className` is for genuine one-offs only \u2014 never to redo what an API already does. Specifically: (1) NEVER hand-roll typography \u2014 no `text-[13px]`/`text-[11px]` arbitrary px (bypasses the golden type scale), no `font-medium`/`font-semibold`/`text-muted-foreground` on a raw `<span>`; use `<Text size tone weight tabular mono>` / `<Heading level>`. (2) NEVER hand-roll a trivial flex/grid wrapper; use `<Flex>` / `<ResponsiveGrid>` / `<PageContainer>`. (3) NEVER set a control's radius/height/colour with a utility when a `shape`/`size`/`tone`/token exists. If a real need has NO prop/token/primitive, that is a library GAP \u2014 file it (draft_bug_report), don't paper over it with ad-hoc Tailwind."
8195
+ },
8196
+ {
8197
+ number: 43,
8198
+ title: "Every form control goes through FormField",
8199
+ body: "Consumers MUST wrap every labelled form control (Input, Select, DatePicker, DateRangePicker, NumberInput, Radio.Group, Checkbox groups, range pairs, ...) in FormField \u2014 it owns the label (aria-labelledby, never a dangling <label for>), auto-generates/injects the control id, and wires aria-describedby/aria-errormessage/aria-invalid. Bare controls are the rare exception (e.g. a toolbar quick-filter with its own aria-label) and must carry id/name + aria-label themselves. Never hand-roll a label+control stack with Text/Label."
8200
+ },
8201
+ {
8202
+ number: 44,
8203
+ title: "Chrome is a token, default quiet",
8204
+ body: "Any decorative chrome a component draws \u2014 dividers, separator borders, and the padding that exists only to space that chrome \u2014 MUST read a token; never hard-code it in `src/styles/*.css` (a hard-coded `border-bottom: 1px solid hsl(var(--border))` leaves consumers no off-switch short of a variant fork). The DEFAULT is the quietest state (`none` / balanced rhythm); a service theme opts IN, e.g. `--page-header-divider: 1px solid hsl(var(--border))`. Born from real consumption: PageContainer's header divider was undisableable until tokenised."
8205
+ },
8206
+ {
8207
+ number: 45,
8208
+ title: "Every service-tunable constant gets a knob",
8209
+ body: 'When component CSS encodes a geometry choice that a service plausibly re-tunes to match its design handoff \u2014 form label column width, label\u2194control gap, header insets \u2014 it MUST be a documented component token (current value as the default). The theme sets it ONCE globally; props (`labelWidth`) override per instance; Form\u2192FormField priority stays intact. The test: "would a service theme.css want to change this to match its design grid?" If yes and the only route is forking CSS, that is a library gap \u2014 fix the library, don\'t patch the app. Born from real consumption: `--form-label-width` / `--form-label-gap` (design spec said 110px/8px; the values were prop-only and hard-coded `--space-4`).'
8210
+ },
8211
+ {
8212
+ number: 46,
8213
+ title: "Typography is tokens, default is base",
8214
+ body: "A UI framework gives consumers knobs: every font-size in `src/styles/*.css` MUST reference a token \u2014 the global modular scale `var(--font-size-{2xs|xs|sm|base|lg|xl|2xl})` or a per-component `var(--{component}-\u2026-font-size)` knob (rule #45) \u2014 never a hard-coded literal (`font-size: 12px` can't be re-themed). The DEFAULT body size is `--font-size-base`; components render body/UI text at `base`, not at the `sm` alias. Smaller-by-design text (badge, section label, caption) is a component token defaulting to a small step (`--badge-font-size: var(--font-size-xs)`), so a service re-tunes that part without moving the global scale. The `sm`/`xs` tokens stay for the explicit `<Text size>` API. Every component token is surfaced in the MCP `get_component` output (check:mcp-token-sync). Enforced by `check:typography`."
7266
8215
  }
7267
8216
  ];
7268
8217
  function findRule(num) {
@@ -8586,6 +9535,84 @@ minimal repro, expected vs actual, the installed version, and env \u2014 enough
8586
9535
  reproduce in one paste.`
8587
9536
  }
8588
9537
  ]
9538
+ },
9539
+ // ── app-performance (consumer) ─────────────────────────────────
9540
+ {
9541
+ id: "app-performance",
9542
+ audience: "consumer",
9543
+ name: "App performance \u2014 measure-first React perf with @godxjp/ui",
9544
+ whenToUse: "A screen built on @godxjp/ui feels slow, Chrome logs [Violation] handler warnings, typing lags in a filter pane, a panel toggle blocks, or the bundle is questioned. Distilled from a real audit (orders screen: keystroke 165ms \u2192 5ms, panel open 270ms \u2192 18ms, app bundle \u221227%). Measure FIRST \u2014 the library's per-control cost is small (~3ms/field dev); page architecture is almost always the culprit.",
9545
+ source: "@godxjp/ui MCP (consumer surface) \u2014 2026-06 exseli audit, verified numbers",
9546
+ sections: [
9547
+ {
9548
+ id: "measure-first",
9549
+ title: "Measure before touching anything",
9550
+ tagline: "Long tasks + a temporary React Profiler tell you WHO is slow \u2014 never guess.",
9551
+ body: `Three probes, run in the page (devtools console / playwright evaluate):
9552
+ 1) Long tasks \u2014 anything >50ms in a handler triggers Chrome's [Violation]:
9553
+ const t=[]; new PerformanceObserver(l=>l.getEntries().forEach(e=>t.push(Math.round(e.duration))))
9554
+ .observe({type:"longtask",buffered:true});
9555
+ 2) Attribution \u2014 wrap suspect sections temporarily:
9556
+ <Profiler id="sec" onRender={(id,phase,d)=>(window.__perf??=[]).push([id,phase,Math.round(d)])}>
9557
+ Compare each section's actualDuration against the total long task. In the reference audit
9558
+ 25 @godxjp/ui composite fields mounted in 79ms \u2014 the other ~150ms was the PAGE re-rendering
9559
+ everything else. Indict with numbers, then fix only what they indict.
9560
+ 3) Dev-mode caveat: development React is 3\u20135\xD7 slower; a one-off 60ms task at popup-open is
9561
+ normal. Re-measure AFTER the fix with the same probes and paste before/after numbers.`
9562
+ },
9563
+ {
9564
+ id: "filter-pane-memo",
9565
+ title: "Filter panes: per-field memo + stable setters",
9566
+ tagline: "Page-root state with no memo boundaries = the whole pane re-renders per keystroke.",
9567
+ body: `The classic failure: all search state lives at the page root, every field reads it
9568
+ inline \u2192 one keystroke re-renders ~30 fields + the results table (165ms/key measured).
9569
+ The proven fix shape:
9570
+ - module-level memo field units: const FText = memo(function FText({ id, label, k, value, onSet }) {
9571
+ return <FormField id={id} label={label}><Input id={id} value={value}
9572
+ onChange={(e) => onSet(k, e.target.value)} /></FormField>; });
9573
+ - ONE stable setter per state map: const setField = useCallback((key, value) =>
9574
+ setCond(c => ({ ...c, [key]: value })), []);
9575
+ - the results table in its OWN memo component; pagination pages WITHIN the submitted query
9576
+ (setSubmitted(prev => ({ ...prev, page }))) so its handler stays identity-stable.
9577
+ After: a keystroke re-renders exactly one field (5ms). Don't memo cheap leaves that re-render
9578
+ rarely \u2014 over-memoization is real overhead. react-compiler lint traps: no ref writes during
9579
+ render, no sync setState in effect bodies; the sanctioned latch is the guarded render-time set
9580
+ (if (cond && !state) setState(true)).`
9581
+ },
9582
+ {
9583
+ id: "heavy-panels",
9584
+ title: "Heavy hidden panels: defer, keep mounted, pre-mount at idle",
9585
+ tagline: "Never mount a 25-field subtree synchronously inside a click handler.",
9586
+ body: `A \u8A73\u7D30\u6761\u4EF6-style toggle that conditionally renders a large subtree blocks the click
9587
+ (~230ms measured). Three-layer fix, in order:
9588
+ 1) urgent state drives only the button chrome (chevron/aria-expanded);
9589
+ 2) const deferredOpen = useDeferredValue(open) gates the FIRST mount into a deferred lane;
9590
+ 3) keep it mounted afterwards and toggle visibility: <div className={open ? "contents" : "hidden"}>
9591
+ ("contents" keeps children in the parent flex/grid rhythm; field state lives in the page so
9592
+ hiding loses nothing);
9593
+ 4) optionally pre-mount during idle after first paint so even the FIRST open is instant:
9594
+ useEffect(() => { const idle = window.requestIdleCallback ?? (cb => setTimeout(cb, 300));
9595
+ const h = idle(() => setMounted(true)); return () => (window.cancelIdleCallback ?? clearTimeout)(h); }, []);
9596
+ Measured after: open 270ms \u2192 18ms, zero post-load long tasks.`
9597
+ },
9598
+ {
9599
+ id: "bundle-budget",
9600
+ title: "Code splitting + what an import costs",
9601
+ tagline: "lazy() every page; know the per-import budget before blaming the library.",
9602
+ body: `Route-level splitting is the default app shape: const Page = lazy(() => import("@/pages/Page"))
9603
+ behind ONE <Suspense fallback={<PageContainer title={<Skeleton \u2026/>}><Skeleton \u2026/></PageContainer>}>.
9604
+ Reference app: initial 929KB \u2192 675KB; the orders screen (day-picker + table stack, 122KB) and
9605
+ login (react-hook-form + zod, 93KB) load only when visited.
9606
+ Per-import minified budget of @godxjp/ui \u226513.10.0 (preserved-module dist \u2014 imports tree-shake
9607
+ for real): StatCard 30KB \xB7 Input 52 \xB7 Button 56 \xB7 DataTable 79 \xB7 Select 165 \xB7 DateRangePicker 207
9608
+ (genuinely needs react-day-picker + date-fns). A shared ~50KB floor is intrinsic (tailwind-merge
9609
+ + bundled 3-locale i18n) and amortizes across one vendor chunk. Virtualize lists only >100 rows \u2014
9610
+ DataTable at 50/page does not need it. The dist is bundler-oriented ESM (extensionless + JSON
9611
+ imports): fine for vite/webpack consumers; when testing against a local checkout run tests on an
9612
+ npm-pack TARBALL install, not a file: symlink (the symlink's nested node_modules creates a
9613
+ dual-React artifact under vitest).`
9614
+ }
9615
+ ]
8589
9616
  }
8590
9617
  ];
8591
9618
  function findSkill(id) {
@@ -8737,6 +9764,29 @@ function routeTask(task, opts) {
8737
9764
  "If @godxjp/ui itself is at fault, don't fake a workaround \u2014 file a detailed gh issue (use draft_bug_report).",
8738
9765
  ["design-to-page/report-bug"]
8739
9766
  );
9767
+ route(
9768
+ [
9769
+ "slow",
9770
+ "performance",
9771
+ "perf",
9772
+ "violation",
9773
+ "long task",
9774
+ "lag",
9775
+ "janky",
9776
+ "re-render",
9777
+ "rerender",
9778
+ "bundle size",
9779
+ "code splitting",
9780
+ "tree-shak",
9781
+ "ch\u1EADm",
9782
+ "l\u1ED7i hi\u1EC7u n\u0103ng",
9783
+ "\u91CD\u3044"
9784
+ ],
9785
+ "app-performance",
9786
+ "measure-first",
9787
+ "Measure FIRST (longtask + temporary Profiler), then apply the matching proven fix \u2014 page architecture, not the library, is almost always the culprit.",
9788
+ ["app-performance/filter-pane-memo", "app-performance/heavy-panels", "app-performance/bundle-budget"]
9789
+ );
8740
9790
  const filtered = opts?.consumerOnly ? matches.filter((m) => {
8741
9791
  const sk = findSkill(m.skill);
8742
9792
  return !sk || sk.audience !== "core";
@@ -9952,6 +11002,62 @@ ${section.body}
9952
11002
 
9953
11003
  _Source: ${skill.source}_`;
9954
11004
  }
11005
+ var TOKEN_PREFIXES = {
11006
+ Badge: ["badge"],
11007
+ Button: ["button"],
11008
+ Toggle: ["toggle"],
11009
+ TagInput: ["tag-input"],
11010
+ Card: ["card"],
11011
+ StatCard: ["stat-card"],
11012
+ Table: ["table"],
11013
+ DataTable: ["table"],
11014
+ Dialog: ["dialog"],
11015
+ AlertDialog: ["dialog"],
11016
+ Sheet: ["dialog"],
11017
+ Drawer: ["dialog"],
11018
+ Alert: ["alert"],
11019
+ EmptyState: ["empty-state"],
11020
+ Skeleton: ["skeleton"],
11021
+ Pagination: ["pagination"],
11022
+ Toolbar: ["filter"],
11023
+ Breadcrumb: ["breadcrumb"],
11024
+ Menubar: ["menubar"],
11025
+ Progress: ["progress"],
11026
+ TreeSelect: ["tree"],
11027
+ Timeline: ["timeline"],
11028
+ PasswordStrength: ["password-strength"],
11029
+ PasswordInput: ["password-strength"],
11030
+ Checkbox: ["checkbox"],
11031
+ Switch: ["switch"],
11032
+ Slider: ["slider"],
11033
+ ColorPicker: ["color-picker"],
11034
+ Command: ["command"],
11035
+ Radio: ["choice"],
11036
+ RadioGroup: ["choice"],
11037
+ Field: ["choice"],
11038
+ AppShell: ["sidebar", "topbar"],
11039
+ Sidebar: ["sidebar"],
11040
+ Topbar: ["topbar"],
11041
+ Form: ["form"],
11042
+ FormField: ["form"],
11043
+ // Shared control sizing/typography — every plain control reads these.
11044
+ Input: ["control"],
11045
+ Textarea: ["control"],
11046
+ NumberInput: ["control"],
11047
+ Select: ["control", "search-input"],
11048
+ Cascader: ["control"],
11049
+ DatePicker: ["control"],
11050
+ TimePicker: ["control"],
11051
+ InputOTP: ["control"]
11052
+ };
11053
+ function componentTokensFor(name) {
11054
+ const prefixes = TOKEN_PREFIXES[name] ?? [
11055
+ name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()
11056
+ ];
11057
+ return COMPONENT_TOKENS.filter(
11058
+ (t) => prefixes.some((p) => t.name.startsWith(`--${p}-`))
11059
+ );
11060
+ }
9955
11061
  function getComponent(name) {
9956
11062
  const c = findComponent(name);
9957
11063
  if (!c) return `Component "${name}" not found. Use \`list_primitives\` to discover.`;
@@ -9977,6 +11083,20 @@ function getComponent(name) {
9977
11083
  `;
9978
11084
  for (const p of c.props) {
9979
11085
  out += `| \`${p.name}\` | \`${p.type}\` | ${p.required ? "\u2713" : ""} | ${p.defaultValue ? `\`${p.defaultValue}\`` : ""} | ${p.description} |
11086
+ `;
11087
+ }
11088
+ const tokens = componentTokensFor(c.name);
11089
+ if (tokens.length) {
11090
+ out += `
11091
+ ## Design tokens (theme knobs)
11092
+
11093
+ Override these in a service \`theme.css\` to re-tune ONLY this component (never hard-code or fork CSS \u2014 rules #44/#45/#46):
11094
+
11095
+ `;
11096
+ out += `| Token | Default | What it controls |
11097
+ |---|---|---|
11098
+ `;
11099
+ for (const t of tokens) out += `| \`${t.name}\` | \`${t.value}\` | ${t.description} |
9980
11100
  `;
9981
11101
  }
9982
11102
  if (c.usage && c.usage.length) {
@@ -10448,8 +11568,8 @@ ${c.example}
10448
11568
  // package.json
10449
11569
  var package_default = {
10450
11570
  name: "@godxjp/ui-mcp",
10451
- version: "0.21.3",
10452
- 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, 34 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).",
11571
+ version: "0.22.0",
11572
+ 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).",
10453
11573
  type: "module",
10454
11574
  main: "./dist/index.js",
10455
11575
  module: "./dist/index.js",