@vegastack/design 0.7.99 → 0.7.101

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.
@@ -64,6 +64,7 @@ import {
64
64
  writeFileSync,
65
65
  } from "node:fs";
66
66
  import { tmpdir } from "node:os";
67
+ import { parseEnv } from "node:util";
67
68
  import { pathToFileURL } from "node:url";
68
69
  import { join, resolve, basename, relative, isAbsolute, sep } from "node:path";
69
70
  import { createHash } from "node:crypto";
@@ -71,6 +72,28 @@ import tsconfigPaths from "tsconfig-paths";
71
72
 
72
73
  const { loadConfig: loadTsconfigPaths } = tsconfigPaths;
73
74
 
75
+ /** Load credential values only; checkout-local files never control trust anchors. */
76
+ export function loadRegistryCredentials(cwd, env = process.env) {
77
+ const loaded = {};
78
+ for (const name of [".env", ".env.local"]) {
79
+ let contents;
80
+ try {
81
+ contents = readFileSync(join(cwd, name), "utf8");
82
+ } catch (error) {
83
+ if (error.code === "ENOENT") continue;
84
+ throw error;
85
+ }
86
+ const values = parseEnv(contents);
87
+ for (const key of ["CF_ACCESS_CLIENT_ID", "CF_ACCESS_CLIENT_SECRET"]) {
88
+ if (values[key] !== undefined) loaded[key] = values[key];
89
+ }
90
+ }
91
+ for (const [key, value] of Object.entries(loaded)) {
92
+ if (env[key] === undefined) env[key] = value;
93
+ }
94
+ return env;
95
+ }
96
+
74
97
  // ── canonical item hash (INLINED from tooling/registry-hash.mjs — keep IDENTICAL) ──
75
98
  // Covers the WHOLE item (not just file content); meta.integrity is excluded so the hash
76
99
  // is self-describing and stable. The provenance header (`// @vegastack <name>@<version>
@@ -947,6 +970,7 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
947
970
  /\/$/,
948
971
  "",
949
972
  );
973
+ loadRegistryCredentials(process.cwd());
950
974
  const headers = requestHeaders();
951
975
 
952
976
  const tempDir = mkdtempSync(join(tmpdir(), "vegastack-verify-"));
@@ -134,14 +134,32 @@ var FLOATING = {
134
134
  };
135
135
  function mergeRefs(...refs) {
136
136
  return (node) => {
137
- for (const ref of refs) {
138
- if (ref == null) continue;
137
+ const cleanups = refs.map((ref) => {
138
+ if (ref == null) return void 0;
139
139
  if (typeof ref === "function") {
140
- ref(node);
141
- } else {
142
- ref.current = node;
140
+ const cleanup = ref(node);
141
+ return typeof cleanup === "function" ? cleanup : () => ref(null);
143
142
  }
144
- }
143
+ ref.current = node;
144
+ return () => {
145
+ ref.current = null;
146
+ };
147
+ });
148
+ let cleaned = false;
149
+ return () => {
150
+ if (cleaned) return;
151
+ cleaned = true;
152
+ const errors = [];
153
+ for (const cleanup of cleanups) {
154
+ try {
155
+ cleanup?.();
156
+ } catch (error) {
157
+ errors.push(error);
158
+ }
159
+ }
160
+ if (errors.length)
161
+ throw new AggregateError(errors, "Merged ref cleanup failed");
162
+ };
145
163
  };
146
164
  }
147
165
 
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  cn
4
- } from "./chunk-4YF3V35A.js";
4
+ } from "./chunk-R6IZOGT5.js";
5
5
 
6
6
  // src/icons/create-animated-icon.tsx
7
7
  import * as React from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  cn
3
- } from "../chunk-4YF3V35A.js";
3
+ } from "../chunk-R6IZOGT5.js";
4
4
 
5
5
  // src/icons/icon.tsx
6
6
  import "react";
package/dist/index.cjs CHANGED
@@ -163,14 +163,32 @@ var FLOATING = {
163
163
  };
164
164
  function mergeRefs(...refs) {
165
165
  return (node) => {
166
- for (const ref of refs) {
167
- if (ref == null) continue;
166
+ const cleanups = refs.map((ref) => {
167
+ if (ref == null) return void 0;
168
168
  if (typeof ref === "function") {
169
- ref(node);
170
- } else {
171
- ref.current = node;
169
+ const cleanup = ref(node);
170
+ return typeof cleanup === "function" ? cleanup : () => ref(null);
172
171
  }
173
- }
172
+ ref.current = node;
173
+ return () => {
174
+ ref.current = null;
175
+ };
176
+ });
177
+ let cleaned = false;
178
+ return () => {
179
+ if (cleaned) return;
180
+ cleaned = true;
181
+ const errors = [];
182
+ for (const cleanup of cleanups) {
183
+ try {
184
+ cleanup?.();
185
+ } catch (error) {
186
+ errors.push(error);
187
+ }
188
+ }
189
+ if (errors.length)
190
+ throw new AggregateError(errors, "Merged ref cleanup failed");
191
+ };
174
192
  };
175
193
  }
176
194
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -193,7 +193,7 @@ declare const FLOATING: {
193
193
  * (only ref objects the caller already holds), so it is server-safe like `cn`.
194
194
  *
195
195
  * **Not memoized.** Calling it produces a NEW function every time, and React detaches a
196
- * changed ref callback (calls it with `null`) and reattaches it on every render. Wrap the
196
+ * changed ref callback (runs its cleanup; legacy callbacks receive `null`) and reattaches it on every render. Wrap the
197
197
  * CALL at the call site when the inputs are stable:
198
198
  *
199
199
  * @example
package/dist/index.d.ts CHANGED
@@ -193,7 +193,7 @@ declare const FLOATING: {
193
193
  * (only ref objects the caller already holds), so it is server-safe like `cn`.
194
194
  *
195
195
  * **Not memoized.** Calling it produces a NEW function every time, and React detaches a
196
- * changed ref callback (calls it with `null`) and reattaches it on every render. Wrap the
196
+ * changed ref callback (runs its cleanup; legacy callbacks receive `null`) and reattaches it on every render. Wrap the
197
197
  * CALL at the call site when the inputs are stable:
198
198
  *
199
199
  * @example
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  mergeRefs,
6
6
  prose,
7
7
  proseClassName
8
- } from "./chunk-4YF3V35A.js";
8
+ } from "./chunk-R6IZOGT5.js";
9
9
  export {
10
10
  FLOATING,
11
11
  TIMINGS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegastack/design",
3
- "version": "0.7.99",
3
+ "version": "0.7.101",
4
4
  "description": "VegaStack design system — cn utility, icon runtime, Tailwind v4 preset, and the vegastack-design CLI (tokens ship separately as @vegastack/design-tokens)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -67,7 +67,7 @@
67
67
  "lint": "eslint . && node ../../tooling/design-lint.mjs src && pnpm run verify",
68
68
  "verify": "node ../../tooling/verify-preset-source.mjs",
69
69
  "typecheck": "tsc --noEmit",
70
- "test": "node test/compare.test.mjs && node test/check-updates.test.mjs && node test/skills-install.test.mjs && node test/doctor.test.mjs"
70
+ "test": "node test/compare.test.mjs && node test/check-updates.test.mjs && node test/skills-install.test.mjs && node test/doctor.test.mjs && node --test test/merge-refs.test.mjs test/registry-credentials.test.mjs"
71
71
  },
72
72
  "dependencies": {
73
73
  "@vegastack/design-tokens": "^0.7.80",
@@ -18,15 +18,16 @@ the icon runtime, the Tailwind v4 preset, and the `vegastack-design` CLI.
18
18
 
19
19
  **Invoking the CLI.** The bin is `vegastack-design` but the package is `@vegastack/design`, so a
20
20
  bare `npx vegastack-design …` in a project that does not already have it installed would try to
21
- fetch an unrelated, unscoped package from npm. Use one of these instead — the first once it is a
22
- local dependency (it resolves from `node_modules` and never contacts the registry), the second for a
23
- standalone one-off:
21
+ fetch an unrelated, unscoped package from npm. Use the installed CLI so the project's lockfile
22
+ selects its approved version:
24
23
 
25
24
  ```bash
26
25
  pnpm exec vegastack-design <command>
27
- npx --package=@vegastack/design vegastack-design <command>
28
26
  ```
29
27
 
28
+ A standalone invocation must explicitly pin an approved `@vegastack/design` version; do not
29
+ resolve a different verifier version during the preflight/install/post-write sequence.
30
+
30
31
  `@vegastack/ui` is private and is never installed downstream — components arrive by copy-in.
31
32
 
32
33
  ## 2. Tailwind v4 CSS
@@ -52,12 +53,51 @@ inside the published builds — the icon runtime, and the compiled Toaster (its
52
53
  and status-tint classes live in `@vegastack/ui/dist`, and Tailwind has to be told to generate them).
53
54
  Your own application and component source is scanned by Tailwind as usual.
54
55
 
55
- ## 3. Wrap the app root in the provider
56
+ ## 3. Configure registry access
57
+
58
+ Components come from a private registry behind Cloudflare Access service tokens.
59
+
60
+ If the project has no `components.json` yet, create one first — pick the **base** style, never
61
+ `radix`, because VegaStack components are Base UI:
62
+
63
+ ```bash
64
+ pnpm dlx shadcn@4.21.0 init --base base
65
+ ```
66
+
67
+ Then add the registry block to `components.json`:
68
+
69
+ ```json
70
+ {
71
+ "registries": {
72
+ "@vegastack": {
73
+ "url": "https://design.vegastack.com/r/{name}.json",
74
+ "headers": {
75
+ "CF-Access-Client-Id": "${CF_ACCESS_CLIENT_ID}",
76
+ "CF-Access-Client-Secret": "${CF_ACCESS_CLIENT_SECRET}"
77
+ }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ Store credentials in your secret manager or ignored `.env.local`. The installed verifier loads
84
+ only `CF_ACCESS_CLIENT_ID` and `CF_ACCESS_CLIENT_SECRET` from `.env` then `.env.local`; an existing
85
+ process value wins. Registry origin and signer overrides are accepted only from the operator's
86
+ process environment, never a checkout-local dotenv file. Do not source the whole dotenv file.
87
+
88
+ Configure this registry before the first provider/component add. Inspect before writing anything:
56
89
 
57
90
  ```bash
58
- npx shadcn@latest add @vegastack/provider
91
+ pnpm dlx shadcn@4.21.0 add @vegastack/button --dry-run
59
92
  ```
60
93
 
94
+ External and client projects are **tokenless** — components are copied in during development, and
95
+ the shipped application holds zero VegaStack credentials.
96
+
97
+ ## 4. Wrap the app root in the provider
98
+
99
+ Install `provider` using the complete fail-closed procedure in step 5 before adding this wrapper.
100
+
61
101
  This copies `VegaStackProvider` and `useVegaStackTheme` into your project, composing the `toast`
62
102
  Toaster item.
63
103
 
@@ -80,8 +120,7 @@ putting `className="isolate"` on your `<body>` yourself. Without it, popups can
80
120
  chrome or mis-position in stacking-context-heavy layouts.
81
121
 
82
122
  **Stop iOS focus zoom.** iOS Safari zooms into any focused field under 16px. `base.css` renders
83
- text-entry controls at 16px on touch pointers, and the viewport should cap the scale as well, which
84
- stops focus zoom while pinch-zoom keeps working on iOS. In Next.js, in the root layout:
123
+ text-entry controls at 16px on touch pointers. Keep zoom unrestricted; do not add a maximum-scale cap. In Next.js, in the root layout:
85
124
 
86
125
  ```tsx
87
126
  import type { Viewport } from "next";
@@ -89,46 +128,9 @@ import type { Viewport } from "next";
89
128
  export const viewport: Viewport = {
90
129
  width: "device-width",
91
130
  initialScale: 1,
92
- maximumScale: 1,
93
131
  };
94
132
  ```
95
133
 
96
- ## 4. Configure registry access
97
-
98
- Components come from a private registry behind Cloudflare Access service tokens.
99
-
100
- If the project has no `components.json` yet, create one first — pick the **base** style, never
101
- `radix`, because VegaStack components are Base UI:
102
-
103
- ```bash
104
- pnpm dlx shadcn@latest init --base base
105
- ```
106
-
107
- Then add the registry block to `components.json`:
108
-
109
- ```json
110
- {
111
- "registries": {
112
- "@vegastack": {
113
- "url": "https://design.vegastack.com/r/{name}.json",
114
- "headers": {
115
- "CF-Access-Client-Id": "${CF_ACCESS_CLIENT_ID}",
116
- "CF-Access-Client-Secret": "${CF_ACCESS_CLIENT_SECRET}"
117
- }
118
- }
119
- }
120
- }
121
- ```
122
-
123
- Put the credentials in `.env.local`. Inspect before writing anything:
124
-
125
- ```bash
126
- pnpm dlx shadcn@latest add @vegastack/button --dry-run
127
- ```
128
-
129
- External and client projects are **tokenless** — components are copied in during development, and
130
- the shipped application holds zero VegaStack credentials.
131
-
132
134
  ## 5. Add a component (fail-closed, three steps)
133
135
 
134
136
  This is the only safe way to add a component. Do not collapse it to a bare `shadcn add`: that leaves
@@ -140,16 +142,16 @@ a time-of-check/time-of-use gap, because `shadcn add` re-fetches the item after
140
142
  DIR="$(mktemp -d "${TMPDIR:-/tmp}/vegastack-verify.XXXXXX")"; ITEM="$DIR/button.json"
141
143
 
142
144
  # 1) PRE-WRITE — verify signature + hash, and save the trusted bytes for step 3.
143
- npx --package=@vegastack/design vegastack-design verify --save "$ITEM" button
145
+ pnpm exec vegastack-design verify --save "$ITEM" button
144
146
 
145
147
  # Retain the verified digest in the parent shell BEFORE shadcn or dependency code runs.
146
148
  EXPECTED="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")).meta.integrity)' "$ITEM")"
147
149
 
148
150
  # 2) COPY-IN — shadcn writes the files, rewriting import aliases per your components.json.
149
- pnpm dlx shadcn@latest add @vegastack/button
151
+ pnpm dlx shadcn@4.21.0 add @vegastack/button
150
152
 
151
153
  # 3) POST-WRITE — prove the copied files match the SAVED item. Exits 1 on any tampering.
152
- npx --package=@vegastack/design vegastack-design verify \
154
+ pnpm exec vegastack-design verify \
153
155
  --post-write --item "$ITEM" --expected-integrity "$EXPECTED" --target-dir .
154
156
  ```
155
157
 
@@ -176,7 +178,7 @@ Never override a `--color-*` variable — that is the build-inlined bridge, not
176
178
  Copy-in means no automatic updates.
177
179
 
178
180
  ```bash
179
- npx --package=@vegastack/design vegastack-design check-updates # ⬆ update · ≈ drift · ✓ up to date
181
+ pnpm exec vegastack-design check-updates # ⬆ update · ≈ drift · ✓ up to date
180
182
  ```
181
183
 
182
184
  Then per stale component: `shadcn add @vegastack/<name> --diff` to review, `--overwrite` to apply,
@@ -190,7 +192,7 @@ Add the Renovate preset `github>VegaStack/renovate-config` so additive token bum
190
192
  If this project's agents do not already have them:
191
193
 
192
194
  ```bash
193
- npx --package=@vegastack/design vegastack-design skills install
195
+ pnpm exec vegastack-design skills install
194
196
  ```
195
197
 
196
198
  Writes the public VegaStack skills into `.claude/skills/` and `.agents/skills/`.
@@ -83,7 +83,7 @@ rg -n '(focus|focus-visible|focus-within|data-focused|popup-open)[^ "]*:border-(
83
83
  - **a focus border** — a border colour under a focus or open-popup variant (`focus:border-ring/70`,
84
84
  `focus-within:border-ring`, `data-popup-open:border-input`, `not-focus:aria-invalid:…`) in app
85
85
  code. A border never changes colour on focus or while active (FOC-14) — the DS's own text-entry
86
- components are the one exception (a subtle `border-ring/40` focus border, no fill) and carry it
86
+ components are the one exception (a subtle `border-ring/50` focus border, no fill) and carry it
87
87
  themselves; only an invalid field's destructive border shows, in every state. **error**
88
88
  - a status FILL used as a text ink on that family's own tint — `bg-destructive/10 text-destructive`
89
89
  measures 3.98:1. The readable half is `text-destructive-text`. **error**
@@ -79,12 +79,10 @@ link` (upstream's set, verbatim). `destructive` is a soft tint, not a solid red
79
79
  need a search field (it is the preset `country-select` and `region-select` are built from — reach
80
80
  for it before composing `combobox` by hand); **`combobox`** directly only for free text,
81
81
  suggestions or multi-select chips.
82
- - **One view-switch rule.** A form value is a **`radio-group`**. An immediate view or scope switch
83
- over the same content (Mine | Team, All | Unread, Grid | List) is a single-select
84
- **`toggle-group`** that always keeps one item pressed — `deselectable={false}` — with
85
- `spacing={0}` for 2–5 options inline (`wrap` when they can outgrow the row). Swapping in-page regions is
86
- **`tabs`**; moving between URLs is navigation — links, not `tabs` (a route-tabs recipe is
87
- not shipped yet).
82
+ - **One view-switch rule.** Form choices use `radio-group`; toolbar scope switches use default
83
+ `Tabs`, and Grid | List | Board uses `ViewToggle` (which composes Tabs). `ToggleGroup` is for
84
+ pressed action/value choices. URL navigation uses links and the route-tabs recipe.
85
+
88
86
  - **Empty is tiered** — nothing yet, no matches ("Clear filters"), couldn't load (`role="alert"`,
89
87
  "Try again"), blocked. Pick the tier from the empty-state foundation
90
88
  (<https://design.vegastack.com/docs/foundations/empty-states>); never leave a region blank.
@@ -243,7 +241,7 @@ on shadcn's `neutral` base. Always use the utility, never a raw value.
243
241
  | Fill | `bg-primary` (solid action, every checked control) · `bg-secondary` (soft) · `bg-muted` (well, track, skeleton) · `bg-accent` (hover) |
244
242
  | Text | `text-foreground` · `text-muted-foreground` · `text-{primary,secondary,accent,card,popover}-foreground` |
245
243
  | Status | `bg-{destructive,success,warning,info}` · `-foreground` (ink ON the fill) · `-text` (ink on the page or on the family's own tint) |
246
- | Border | `border-border` · `border-input` — there are no rings; focus is a global background tint (Tabs alone keep a ring) |
244
+ | Border | `border-border` · `border-input` — there are no rings; focus is a global background tint (Tabs included) |
247
245
  | Radius | `rounded-{sm,md,lg,xl,2xl}` — all derived from the single `--radius` |
248
246
  | Type | Tailwind's own `text-{xs…7xl}`. `text-sm` is 14px, `text-base` is 16px. Line-height and letter-spacing above `text-base` come from the theme — never write `tracking-*`, an arbitrary `text-[13px]`, or `uppercase` |
249
247
  | Font | `font-sans` `font-mono` `font-serif` `font-heading` |
@@ -290,10 +288,10 @@ heading down `n` levels (capped at h6) so the outline never skips back up.
290
288
 
291
289
  **Markdown is read with `MarkdownView` and edited with `TextEdit format="markdown"`** — never a
292
290
  textarea, a local prose class, or a hand-rolled editor. For in-place editing, swap the view for
293
- `<TextEdit format="markdown" variant="ghost" onSave onCancel />`: ghost is borderless with no inset,
294
- so the text does not move, and Save/Cancel sit at the toolbar's end (⌘/Ctrl+Enter saves, Esc
295
- cancels). Pick `toolbar="minimal"` for comments, `"standard"` (default) for notes and summaries,
296
- `"full"` for documents; the schema follows the toolbar. Keep `variant="outline"` inside forms.
291
+ `<TextEdit format="markdown" variant="document" onCommit={save} />` for inline document editing.
292
+ Use `variant="boxed"` inside forms and `variant="composer"` for submission composers.
293
+ Commit through `onCommit`; `onRevert` observes a revert. The editor owns its contextual toolbar;
294
+ there are no `toolbar`, `onSave` or `onCancel` props.
297
295
 
298
296
  `muted`, `accent` and `secondary` share one value in this base, and all three are kept: name the one
299
297
  whose ROLE you mean, so a consumer can retune one without moving the others.
@@ -357,7 +355,11 @@ contract.
357
355
  players); schedules → `formatDateRange` / `formatTimeOfDay`; feed/inbox headers → `groupByDay`.
358
356
  Pass the viewer's `timeZone` (the `tz` cookie via `getTimeZone(cookie, orgZone)` on the server,
359
357
  `useTimeZone()` on the client; render `TimeZoneScript` + `TimeZoneProvider` once at the root).
360
- API/JSON output stays ISO.
358
+ Pass a serialized request timestamp as `TimeZoneProvider.referenceNow`; groups, due badges and
359
+ picker presets read the same live epoch clock via `useDateTimeNow()` from `relative-time`.
360
+ `DateTime` and `DueLabel` share it too; explicit `DateTime.options.now` stays controlled.
361
+ It is `undefined` during SSR without that reference; render affected date content pending rather
362
+ than reading a separate render-time clock. API/JSON output stays ISO.
361
363
 
362
364
  ## Do / Don't
363
365
 
@@ -3,7 +3,7 @@
3
3
  <!-- GENERATED — do not hand-edit. Regenerated from the design system's component contract,
4
4
  which is the authority for membership and counts. -->
5
5
 
6
- **145 components**, plus 467 animated-icon items, 13 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-overflow`, `use-platform`, `use-tabs-swipe`), 14 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `page-editor-01`, `command-search-01`, `list-page-01`, `library-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`, `share-01`), 68 chart blocks across 7 families, and 11 data libs (`code-highlight`, `date-time`, `geo-data`, `emoji-data`, `drag-item`, `text-anchor`, `text-anchor-doc`, `file-kind`, `media-probe`, `page-layout`, `tile-overlay`) — 718 registry items in total.
6
+ **149 components**, plus 467 animated-icon items, 13 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-overflow`, `use-platform`, `use-tabs-swipe`), 14 starter blocks (`app-shell-01`, `board-01`, `issue-detail-01`, `page-editor-01`, `command-search-01`, `list-page-01`, `library-01`, `login-01`, `notifications-01`, `review-split-01`, `settings-01`, `settings-02`, `status-pages-01`, `share-01`), 68 chart blocks across 7 families, and 11 data libs (`code-highlight`, `date-time`, `geo-data`, `emoji-data`, `drag-item`, `text-anchor`, `text-anchor-doc`, `file-kind`, `media-probe`, `page-layout`, `tile-overlay`) — 722 registry items in total.
7
7
 
8
8
  Install any of them with `shadcn add @vegastack/<name>`. Animated icons install as
9
9
  `@vegastack/icon-<name>`; the bare name is reserved for components, so a component whose name
@@ -23,6 +23,7 @@ starts with `icon-` is a component and never an icon.
23
23
  - **`calendar`** — A date-field calendar on React DayPicker — single, multiple and range selection.
24
24
  - **`checkbox`** — A binary (or indeterminate) toggle on Base UI Checkbox, with a 24px invisible hit area (A11Y-2).
25
25
  - **`chip-input`** — Free-token entry field — Enter/comma/paste commits chips, Backspace removes, per-chip validation marks invalid entries instead of dropping them. InputGroup field chrome + real Tag chips.
26
+ - **`choice-card`** — A radio option drawn as a card — icon, title, description and the radio — in a group that is two across from @sm and stacked below. A thin wrapper over the Field choice-card pattern.
26
27
  - **`color-picker`** — A swatch-triggered popover presenting a grid of preset colors — pick one, fire onValueChange, mark the selection.
27
28
  - **`combobox`** — A filterable listbox behind a text input — grouped items, an announced empty state and a chips mode.
28
29
  - **`country-select`** — A searchable country picker returning the ISO 3166-1 alpha-2 code, with flag + name. A thin wrapper over SearchableSelect fed by the geo-data item.
@@ -47,6 +48,7 @@ starts with `icon-` is a component and never an icon.
47
48
  - **`searchable-select`** — The Select-shaped Combobox preset: a full-width trigger, an in-panel search field, a tick on the selected row (matched by key, so async options tick too), a standard person option (name plus muted email, both searched) and an optional clear control. Single or multiple, controlled through value/onValueChange.
48
49
  - **`select`** — A dropdown for one value — trigger, grouped scrollable popup and item-aligned positioning.
49
50
  - **`slider`** — A number or range over a continuous track — horizontal or vertical, any number of thumbs.
51
+ - **`space-picker`** — SpaceChip — a space as a quiet property chip (tile, name, chevron) — and SpacePicker, that chip opening a searchable list of spaces with My space first, a check on the current one and disabled rows that say why.
50
52
  - **`switch`** — An on/off toggle for instant settings — two sizes and a 24px invisible hit area (A11Y-2).
51
53
  - **`textarea`** — A styled native textarea that grows with its content; focus darkens its border subtly with an ease and paints no fill.
52
54
 
@@ -108,6 +110,7 @@ starts with `icon-` is a component and never an icon.
108
110
 
109
111
  ## Overlay
110
112
 
113
+ - **`access-chip`** — How far one record reaches as one focusable chip: the space tile and name, a lock and Only invited, My space, or a space the viewer cannot see (someone's My space, a private space) — with a globe when published and the full sentence in a tooltip. Clicking opens Share.
111
114
  - **`alert-dialog`** — A modal that interrupts for a decision — an optional media slot, two sizes and an action/cancel footer.
112
115
  - **`context-menu`** — The same menu vocabulary opened by right-click, positioned at the pointer.
113
116
  - **`dialog`** — A modal overlay — backdrop, centred popup, a plain right-aligned footer and an optional close button; opens onto the first field or the popup, never the close ×.
@@ -117,6 +120,7 @@ starts with `icon-` is a component and never an icon.
117
120
  - **`panel-search`** — The sticky, box-free search row a filtering popup puts at the top of its panel (decision OVL-11) — shared by EmojiPicker and ShortcutOverlay.
118
121
  - **`permission-menu`** — An access level on a sharing row — a ghost trigger reading the level, a menu of levels with one-line descriptions and a check on the current one, an optional destructive Remove access, and a read-only text mode for built-in rows.
119
122
  - **`popover`** — An anchored, dismissible surface for secondary content, with a header, title and description.
123
+ - **`responsive-dialog`** — One overlay API that is a centred Dialog from 768px up and a bottom Sheet with a drag handle and a safe-area footer below it — the same header, body and footer in both.
120
124
  - **`sheet`** — A panel that slides in from any edge — Dialog semantics with a side, a header and a footer; opens onto the first field, never the close ×; a left sheet can dock beside the sidebar rail (beside).
121
125
  - **`shortcut-overlay`** — The ?-triggered dialog listing keyboard shortcuts, rendered from a declaration registry (keys, label, category, when) — grouped, filterable, platform-aware via use-platform + Kbd.
122
126
  - **`tooltip`** — A floating label on hover or focus, portaled inside the theme scope (OVL-13).