@handamade/psi-mcp 0.6.0 → 0.7.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @handamade/psi-mcp
2
2
 
3
- MCP server and agent-context generator for the Psi (Ψ) design system. Documents the system — the 13 React 19 components, 3 composition patterns (D47) that recipe them together with gap-annotated compose trees, every semantic token with its OKLCH derivation formula resolved across all four themes, and the usage guidance — through two read-only tools. It does not write code.
3
+ MCP server and agent-context generator for the Psi (Ψ) design system. Documents the system — the 15 React 19 components, 3 composition patterns (D47) that recipe them together with gap-annotated compose trees, every semantic token with its OKLCH derivation formula resolved across all four themes, and the usage guidance — through two read-only tools. It does not write code.
4
4
 
5
5
  ## Connect
6
6
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0",
2
+ "version": "0.7.1",
3
3
  "themes": [
4
4
  "light",
5
5
  "dark",
@@ -162,7 +162,43 @@
162
162
  }
163
163
  ],
164
164
  "slots": [],
165
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Card\n\nContent container with an optional edge-to-edge media slot; `stacked` (media above body) or `featured` (media beside body) layout.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `variant` | `\"stacked\" \\| \"featured\"` | stacked | no | stacked = media above body; featured = media beside body (~1.6fr/1fr), stacks under md. |\n| `media` | `ReactNode` | — | no | Media slot rendered edge-to-edge (img, AspectRatio, …). |\n| `hoverLift` | `boolean` | false | no | Lift on hover: translateY(-6px) over --psi-duration-350. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Theming\n\nOverride `--psi-card-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n## Variant guidance\n\n- **accent** — Primary action, draws attention. Submit, CTA, main action in a group.\n- **accent-subtle** — Accent tone, lower visual weight. Selected state, active filter, soft CTA.\n- **neutral** — Default, structurally present. Secondary actions, toolbar buttons.\n- **neutral-subtle** — Minimal chrome. Inline actions, table row actions.\n- **ghost** — No visible container until hover. Icon-only triggers, compact toolbars.\n- **outline** — Bordered ghost — visible structure, no fill until hover. Marketing CTA, download button; hover fills accent.\n- **danger** — Destructive action. Delete, remove, disconnect.\n- **danger-subtle** — Destructive context, low urgency. Warning badges, soft destructive hints.\n- **success | warning** — Status communication (Tag only). Status badges, labels.\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
165
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Card\n\nContent container with an optional edge-to-edge media slot; `stacked` (media above body) or `featured` (media beside body) layout.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `variant` | `\"stacked\" \\| \"featured\"` | stacked | no | stacked = media above body; featured = media beside body (~1.6fr/1fr), stacks under md. |\n| `media` | `ReactNode` | — | no | Media slot rendered edge-to-edge (img, AspectRatio, …). |\n| `hoverLift` | `boolean` | false | no | Lift on hover: translateY(-6px) over --psi-duration-350. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Theming\n\nOverride `--psi-card-*` custom properties at any scope.\n\n## Variant guidance\n\n- **accent** — Primary action, draws attention. Submit, CTA, main action in a group.\n- **accent-subtle** — Accent tone, lower visual weight. Selected state, active filter, soft CTA.\n- **neutral** — Default, structurally present. Secondary actions, toolbar buttons.\n- **neutral-subtle** — Minimal chrome. Inline actions, table row actions.\n- **ghost** — No visible container until hover. Icon-only triggers, compact toolbars.\n- **outline** — Bordered ghost — visible structure, no fill until hover. Marketing CTA, download button; hover fills accent.\n- **danger** — Destructive action. Delete, remove, disconnect.\n- **danger-subtle** — Destructive context, low urgency. Warning badges, soft destructive hints.\n- **success | warning** — Status communication (Tag only). Status badges, labels.\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
166
+ },
167
+ {
168
+ "name": "Panel",
169
+ "description": "Elevated surface panel (D51): secondary background, faint hairline, radius-12 — the shared --psi-surface-* recipe Dialog's panel also binds. Not a Card: no media slot, no hover lift, opaque by design.",
170
+ "props": [
171
+ {
172
+ "name": "padding",
173
+ "type": "16 | 24",
174
+ "required": false,
175
+ "default": 24,
176
+ "description": "Inner padding in px."
177
+ },
178
+ {
179
+ "name": "ref",
180
+ "type": "Ref<HTMLDivElement>",
181
+ "required": false,
182
+ "default": null,
183
+ "description": "Forwarded ref to the root element."
184
+ },
185
+ {
186
+ "name": "className",
187
+ "type": "string",
188
+ "required": false,
189
+ "default": null,
190
+ "description": "Additional CSS class name(s) merged onto the component's root element."
191
+ }
192
+ ],
193
+ "slots": [
194
+ {
195
+ "name": "body",
196
+ "accepts": {},
197
+ "cardinality": "1..*",
198
+ "order": 1
199
+ }
200
+ ],
201
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Panel\n\nElevated surface panel (D51): secondary background, faint hairline, radius-12 — the shared --psi-surface-* recipe Dialog's panel also binds. Not a Card: no media slot, no hover lift, opaque by design.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `padding` | `16 \\| 24` | 24 | no | Inner padding in px. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Skipped — Panel itself is not focusable; focus moves through its children. |\n\nPlain <div> container with no implicit role. Pass aria-* host props if the panel should announce as a region.\n\n## Theming\n\nOverride `--psi-panel-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
166
202
  },
167
203
  {
168
204
  "name": "Input",
@@ -189,6 +225,13 @@
189
225
  "default": null,
190
226
  "description": "Forwarded ref to the underlying `<input>` element."
191
227
  },
228
+ {
229
+ "name": "placeholder",
230
+ "type": "string",
231
+ "required": false,
232
+ "default": null,
233
+ "description": "Native placeholder text shown while the field is empty (only on components whose host element supports it)."
234
+ },
192
235
  {
193
236
  "name": "className",
194
237
  "type": "string",
@@ -198,7 +241,7 @@
198
241
  }
199
242
  ],
200
243
  "slots": [],
201
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Input\n\nSingle-line text input with pixel-true heights (24–48) and an error state. Inside a Field, id/aria-describedby/aria-invalid/required are wired automatically (D49).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `size` | `24 \\| 32 \\| 40 \\| 48` | 32 | no | Height in px (24 \\| 32 \\| 40 \\| 48). |\n| `error` | `boolean` | false | no | Show error styling. Inside a Field, the Field's error also lights this. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input>` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Focuses the native <input>; focus ring on the field. |\n\nerror sets a red border only. Inside a Field, aria-invalid and aria-describedby are wired automatically (D49); standalone, pair them yourself.\n\n## Theming\n\nOverride `--psi-input-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
244
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Input\n\nSingle-line text input with pixel-true heights (24–48) and an error state. Inside a Field, id/aria-describedby/aria-invalid/required are wired automatically (D49).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `size` | `24 \\| 32 \\| 40 \\| 48` | 32 | no | Height in px (24 \\| 32 \\| 40 \\| 48). |\n| `error` | `boolean` | false | no | Show error styling. Inside a Field, the Field's error also lights this. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input>` element. |\n| `placeholder` | `string` | — | no | Native placeholder text shown while the field is empty (only on components whose host element supports it). |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Focuses the native <input>; focus ring on the field. |\n\nerror sets a red border only. Inside a Field, aria-invalid and aria-describedby are wired automatically (D49); standalone, pair them yourself.\n\n## Theming\n\nOverride `--psi-input-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
202
245
  },
203
246
  {
204
247
  "name": "Select",
@@ -317,7 +360,7 @@
317
360
  "order": 3
318
361
  }
319
362
  ],
320
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Field\n\nLabeled form-row wrapper: label above, control, one message line below — description normally, error when set (aria-live). Auto-wires id/aria-describedby/aria-invalid into Input and Select (D49).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `label` | `ReactNode` | — | no | Label content; renders a <label> (or <legend> in group mode). |\n| `description` | `ReactNode` | — | no | Helper line under the control; replaced by error when error is set. |\n| `error` | `ReactNode` | — | no | Error content; when truthy it replaces the description and switches the field (and a wrapped Input/Select) into error state. |\n| `required` | `boolean` | false | no | Renders the required marker and flows `required` to the control. |\n| `group` | `boolean` | false | no | Group mode: fieldset/legend wrapping several self-labeled controls (Checkbox/Switch); the message describes the whole group. |\n| `htmlFor` | `string` | — | no | Override the generated control id (pair it with the same id on the control). |\n| `ref` | `Ref<HTMLDivElement \\| HTMLFieldSetElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Focus moves to the wrapped control; the label is announced with it. |\n\nWires label association, aria-describedby and aria-invalid into a wrapped Input/Select automatically; the message line is aria-live=polite. Group mode renders fieldset/legend.\n\n## Theming\n\nOverride `--psi-field-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
363
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Field\n\nLabeled form-row wrapper: label above, control, one message line below — description normally, error when set (aria-live). Auto-wires id/aria-describedby/aria-invalid into Input and Select (D49).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `label` | `ReactNode` | — | no | Label content; renders a <label> (or <legend> in group mode). |\n| `description` | `ReactNode` | — | no | Helper line under the control; replaced by error when error is set. |\n| `error` | `ReactNode` | — | no | Error content; when truthy it replaces the description and switches the field (and a wrapped Input/Select) into error state. |\n| `required` | `boolean` | false | no | Renders the required marker and flows `required` to the control. |\n| `group` | `boolean` | false | no | Group mode: fieldset/legend wrapping several self-labeled controls (Checkbox/Switch); the message describes the whole group. |\n| `htmlFor` | `string` | — | no | Override the generated control id (pair it with the same id on the control). |\n| `ref` | `Ref<HTMLDivElement \\| HTMLFieldSetElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Focus moves to the wrapped control; the label is announced with it. |\n\nWires label association, aria-describedby and aria-invalid into a wrapped Input/Select automatically; the message line is aria-live=polite. Group mode renders fieldset/legend.\n\n## Theming\n\nOverride `--psi-field-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
321
364
  },
322
365
  {
323
366
  "name": "Dialog",
@@ -411,7 +454,7 @@
411
454
  "order": 3
412
455
  }
413
456
  ],
414
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Dialog\n\nModal dialog on the native <dialog> top layer: focus trap, aria-modal and focus restore come from the platform; title/footer slots, Esc/backdrop/ close-button dismissal via onClose(reason) (D50).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showModal()/close(). |\n| `onClose` | `(reason: \"esc\" \\| \"backdrop\" \\| \"close-button\") => void` | — | yes | Called on every dismissal attempt with its source; the consumer flips `open`. |\n| `title` | `ReactNode` | — | no | Heading; renders an <h2> wired to aria-labelledby. Without it, pass aria-label. |\n| `footer` | `ReactNode` | — | no | Action row (Buttons — one accent per group, danger for destructive). |\n| `width` | `400 \\| 560 \\| 720` | 560 | no | Panel width in px (400 \\| 560 \\| 720). |\n| `dismissible` | `boolean` | true | no | false = no close button, Esc and backdrop swallowed — footer is the only exit. |\n| `ref` | `Ref<HTMLDialogElement>` | — | no | Forwarded ref to the underlying <dialog> element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Esc | Dismisses via onClose('esc') when dismissible; swallowed otherwise. |\n| Tab | Focus is trapped inside by the native <dialog> top layer; restored on close. |\n\nRendered with showModal(): aria-modal, inert background and focus restore come from the platform. title wires aria-labelledby; without title, pass aria-label. Backdrop click dismisses only when dismissible.\n\n## Theming\n\nOverride `--psi-dialog-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
457
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Dialog\n\nModal dialog on the native <dialog> top layer: focus trap, aria-modal and focus restore come from the platform; title/footer slots, Esc/backdrop/ close-button dismissal via onClose(reason) (D50).\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `open` | `boolean` | — | yes | Controlled open state; syncs to showModal()/close(). |\n| `onClose` | `(reason: \"esc\" \\| \"backdrop\" \\| \"close-button\") => void` | — | yes | Called on every dismissal attempt with its source; the consumer flips `open`. |\n| `title` | `ReactNode` | — | no | Heading; renders an <h2> wired to aria-labelledby. Without it, pass aria-label. |\n| `footer` | `ReactNode` | — | no | Action row (Buttons — one accent per group, danger for destructive). |\n| `width` | `400 \\| 560 \\| 720` | 560 | no | Panel width in px (400 \\| 560 \\| 720). |\n| `dismissible` | `boolean` | true | no | false = no close button, Esc and backdrop swallowed — footer is the only exit. |\n| `ref` | `Ref<HTMLDialogElement>` | — | no | Forwarded ref to the underlying <dialog> element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Esc | Dismisses via onClose('esc') when dismissible; swallowed otherwise. |\n| Tab | Focus is trapped inside by the native <dialog> top layer; restored on close. |\n\nRendered with showModal(): aria-modal, inert background and focus restore come from the platform. title wires aria-labelledby; without title, pass aria-label. Backdrop click dismisses only when dismissible.\n\n## Theming\n\nOverride `--psi-dialog-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
415
458
  },
416
459
  {
417
460
  "name": "Checkbox",
@@ -440,7 +483,7 @@
440
483
  }
441
484
  ],
442
485
  "slots": [],
443
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Checkbox\n\nNative checkbox with a built-in label; for independent binary choices.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native <input type=checkbox> underneath (visually hidden). |\n\n\n## Theming\n\nOverride `--psi-checkbox-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
486
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Checkbox\n\nNative checkbox with a built-in label; for independent binary choices.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native <input type=checkbox> underneath (visually hidden). |\n\n\n## Theming\n\nOverride `--psi-checkbox-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
444
487
  },
445
488
  {
446
489
  "name": "Switch",
@@ -469,12 +512,19 @@
469
512
  }
470
513
  ],
471
514
  "slots": [],
472
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Switch\n\nOn/off toggle (`role=\"switch\"`) with a built-in label; for settings that take effect immediately.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\" role=\"switch\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native checkbox input with role=\"switch\"; announced as a switch, not a checkbox. |\n\n\n## Theming\n\nOverride `--psi-switch-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
515
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Switch\n\nOn/off toggle (`role=\"switch\"`) with a built-in label; for settings that take effect immediately.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label text. |\n| `ref` | `Ref<HTMLInputElement>` | — | no | Forwarded ref to the underlying `<input type=\"checkbox\" role=\"switch\">` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Space | Toggles. Native checkbox input with role=\"switch\"; announced as a switch, not a checkbox. |\n\n\n## Theming\n\nOverride `--psi-switch-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
473
516
  },
474
517
  {
475
518
  "name": "Tag",
476
519
  "description": "Compact label for status or category; semantic variants, optional subtle tint and dismiss button.",
477
520
  "props": [
521
+ {
522
+ "name": "children",
523
+ "type": "ReactNode",
524
+ "required": false,
525
+ "default": null,
526
+ "description": "Label content."
527
+ },
478
528
  {
479
529
  "name": "variant",
480
530
  "type": "\"neutral\" | \"accent\" | \"success\" | \"warning\" | \"danger\"",
@@ -512,7 +562,43 @@
512
562
  }
513
563
  ],
514
564
  "slots": [],
515
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tag\n\nCompact label for status or category; semantic variants, optional subtle tint and dismiss button.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `variant` | `\"neutral\" \\| \"accent\" \\| \"success\" \\| \"warning\" \\| \"danger\"` | neutral | no | Semantic color variant. |\n| `subtle` | `boolean` | false | no | Use tinted background with semantic foreground. |\n| `onDismiss` | `(() => void)` | — | no | When provided, renders a dismiss button that calls this handler. |\n| `ref` | `Ref<HTMLSpanElement>` | — | no | Forwarded ref to the underlying `<span>` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Enter / Space (dismiss button) | onDismiss renders a real <button>; keyboard-dismissible. |\n\nPassive label otherwise; not in the tab order without onDismiss.\n\n## Theming\n\nOverride `--psi-tag-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n## Variant guidance\n\n- **accent** — Primary action, draws attention. Submit, CTA, main action in a group.\n- **accent-subtle** — Accent tone, lower visual weight. Selected state, active filter, soft CTA.\n- **neutral** — Default, structurally present. Secondary actions, toolbar buttons.\n- **neutral-subtle** — Minimal chrome. Inline actions, table row actions.\n- **ghost** — No visible container until hover. Icon-only triggers, compact toolbars.\n- **outline** — Bordered ghost — visible structure, no fill until hover. Marketing CTA, download button; hover fills accent.\n- **danger** — Destructive action. Delete, remove, disconnect.\n- **danger-subtle** — Destructive context, low urgency. Warning badges, soft destructive hints.\n- **success | warning** — Status communication (Tag only). Status badges, labels.\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
565
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tag\n\nCompact label for status or category; semantic variants, optional subtle tint and dismiss button.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `children` | `ReactNode` | — | no | Label content. |\n| `variant` | `\"neutral\" \\| \"accent\" \\| \"success\" \\| \"warning\" \\| \"danger\"` | neutral | no | Semantic color variant. |\n| `subtle` | `boolean` | false | no | Use tinted background with semantic foreground. |\n| `onDismiss` | `(() => void)` | — | no | When provided, renders a dismiss button that calls this handler. |\n| `ref` | `Ref<HTMLSpanElement>` | — | no | Forwarded ref to the underlying `<span>` element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Enter / Space (dismiss button) | onDismiss renders a real <button>; keyboard-dismissible. |\n\nPassive label otherwise; not in the tab order without onDismiss.\n\n## Theming\n\nOverride `--psi-tag-*` custom properties at any scope.\n\n## Variant guidance\n\n- **accent** — Primary action, draws attention. Submit, CTA, main action in a group.\n- **accent-subtle** — Accent tone, lower visual weight. Selected state, active filter, soft CTA.\n- **neutral** — Default, structurally present. Secondary actions, toolbar buttons.\n- **neutral-subtle** — Minimal chrome. Inline actions, table row actions.\n- **ghost** — No visible container until hover. Icon-only triggers, compact toolbars.\n- **outline** — Bordered ghost — visible structure, no fill until hover. Marketing CTA, download button; hover fills accent.\n- **danger** — Destructive action. Delete, remove, disconnect.\n- **danger-subtle** — Destructive context, low urgency. Warning badges, soft destructive hints.\n- **success | warning** — Status communication (Tag only). Status badges, labels.\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
566
+ },
567
+ {
568
+ "name": "Toolbar",
569
+ "description": "Horizontal grouping row for filter/search controls (D52). Wraps on overflow; zero JS. Deliberately NOT ARIA role=\"toolbar\" — that role contracts roving-tabindex arrow-key navigation, wrong for form controls. With aria-label it announces as role=\"group\".",
570
+ "props": [
571
+ {
572
+ "name": "gap",
573
+ "type": "8 | 12 | 16",
574
+ "required": false,
575
+ "default": 8,
576
+ "description": "Gap between controls in px."
577
+ },
578
+ {
579
+ "name": "ref",
580
+ "type": "Ref<HTMLDivElement>",
581
+ "required": false,
582
+ "default": null,
583
+ "description": "Forwarded ref to the root element."
584
+ },
585
+ {
586
+ "name": "className",
587
+ "type": "string",
588
+ "required": false,
589
+ "default": null,
590
+ "description": "Additional CSS class name(s) merged onto the component's root element."
591
+ }
592
+ ],
593
+ "slots": [
594
+ {
595
+ "name": "body",
596
+ "accepts": {},
597
+ "cardinality": "0..*",
598
+ "order": 1
599
+ }
600
+ ],
601
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Toolbar\n\nHorizontal grouping row for filter/search controls (D52). Wraps on overflow; zero JS. Deliberately NOT ARIA role=\"toolbar\" — that role contracts roving-tabindex arrow-key navigation, wrong for form controls. With aria-label it announces as role=\"group\".\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `gap` | `8 \\| 12 \\| 16` | 8 | no | Gap between controls in px. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the root element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab | Moves through the controls in DOM order — no roving tabindex (deliberately not role=toolbar, D52). |\n\nWith aria-label it renders role=group so the control cluster announces with a name; unlabeled it is a plain layout div.\n\n## Theming\n\nThis component has no `--psi-toolbar-*` tokens — its styling binds scale tokens only, so there is nothing component-scoped to override. Theme changes reach it through the semantic tokens of its children and surroundings.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
516
602
  },
517
603
  {
518
604
  "name": "Tooltip",
@@ -541,7 +627,7 @@
541
627
  }
542
628
  ],
543
629
  "slots": [],
544
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tooltip\n\nShort hover/focus hint attached to a single trigger element.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `content` | `ReactNode` | — | yes | Tooltip content. |\n| `children` | `ReactElement<Record<string, unknown>, string \\| JSXElementConstructor<any>>` | — | yes | The trigger element (must accept ref, onMouseEnter/Leave, onFocus/Blur). |\n| `placement` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | top | no | Preferred placement. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab (focus trigger) | Shows immediately on focus (no delay). |\n| Escape | Dismisses while visible (WCAG 1.4.13). |\n\nHover opens after a short delay; content is linked via aria-describedby while visible. Trigger must accept onMouseEnter/Leave and onFocus/Blur props (cloned in automatically) — no ref forwarding required.\n\n## Theming\n\nOverride `--psi-tooltip-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
630
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# Tooltip\n\nShort hover/focus hint attached to a single trigger element.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `content` | `ReactNode` | — | yes | Tooltip content. |\n| `children` | `ReactElement<Record<string, unknown>, string \\| JSXElementConstructor<any>>` | — | yes | The trigger element (must accept ref, onMouseEnter/Leave, onFocus/Blur). |\n| `placement` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | top | no | Preferred placement. |\n\n## Keyboard & assistive tech\n\n| Keys | Behavior |\n|---|---|\n| Tab (focus trigger) | Shows immediately on focus (no delay). |\n| Escape | Dismisses while visible (WCAG 1.4.13). |\n\nHover opens after a short delay; content is linked via aria-describedby while visible. Trigger must accept onMouseEnter/Leave and onFocus/Blur props (cloned in automatically) — no ref forwarding required.\n\n## Theming\n\nOverride `--psi-tooltip-*` custom properties at any scope.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
545
631
  },
546
632
  {
547
633
  "name": "NavBar",
@@ -613,7 +699,7 @@
613
699
  }
614
700
  ],
615
701
  "slots": [],
616
- "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# AspectRatio\n\nFixed-aspect-ratio box that sizes its child media.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `ratio` | `number` | — | yes | Width/height ratio, e.g. 16/10 or 4/5. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the frame element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Theming\n\nOverride `--psi-aspect-ratio-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
702
+ "doc": "<!-- Generated by emit-docs.ts — do not edit. -->\n# AspectRatio\n\nFixed-aspect-ratio box that sizes its child media.\n\n## Props\n\n| Prop | Type | Default | Required | Description |\n|---|---|---|---|---|\n| `ratio` | `number` | — | yes | Width/height ratio, e.g. 16/10 or 4/5. |\n| `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the frame element. |\n| `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |\n\n## Theming\n\nThis component has no `--psi-aspect-ratio-*` tokens — its styling binds scale tokens only, so there is nothing component-scoped to override. Theme changes reach it through the semantic tokens of its children and surroundings.\n\n\n\n## Rules\n\n- One accent per visual group; everything else neutral or ghost.\n- danger only for actions with real consequences.\n- Sizes are px numbers (24|32|40|48), never S/M/L.\n- Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.\n- Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.\n- --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).\n- Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.\n- Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.\n"
617
703
  }
618
704
  ],
619
705
  "tokens": [
@@ -2204,11 +2290,9 @@
2204
2290
  "content": {
2205
2291
  "example-filter": "<active filter>"
2206
2292
  },
2207
- "gaps": [
2208
- "Toolbar"
2209
- ],
2210
- "blocked": true,
2211
- "preset": null
2293
+ "gaps": [],
2294
+ "blocked": false,
2295
+ "preset": "<Toolbar>\n <Input />\n <Select />\n <Tag><active filter></Tag>\n</Toolbar>\n"
2212
2296
  },
2213
2297
  {
2214
2298
  "id": "settings-form-row",
@@ -2447,7 +2531,8 @@
2447
2531
  "highlight": "accent-subtle",
2448
2532
  "meta": "neutral-subtle",
2449
2533
  "status": "success | warning | danger"
2450
- }
2534
+ },
2535
+ "tagApi": "On Tag, subtle is a boolean prop, not part of the variant union: accent-subtle is spelled variant=\"accent\" subtle (Tag variants: neutral | accent | success | warning | danger)."
2451
2536
  },
2452
2537
  "getting-started": {
2453
2538
  "install": "npm install @handamade/psi-tokens @handamade/psi-react",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handamade/psi-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "MCP server and agent-context generator for the Psi (Ψ) design system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,8 +32,8 @@
32
32
  "tsx": "^4.22.4",
33
33
  "typescript": "^5.7.0",
34
34
  "vitest": "^3.2.6",
35
- "@handamade/psi-react": "0.6.0",
36
- "@handamade/psi-tokens": "0.6.0"
35
+ "@handamade/psi-tokens": "0.7.1",
36
+ "@handamade/psi-react": "0.7.1"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsx scripts/build-index.ts && tsc -p tsconfig.build.json"