@vegastack/design 0.8.33 → 0.8.35

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.
@@ -0,0 +1,332 @@
1
+ // `vegastack-design owned-files` — list the registry-owned files installed in a consuming project.
2
+ //
3
+ // A consumer keeps registry copies out of its own gates: lint ignores, the unused-code scan, the
4
+ // design audit, the drift check. Kept by hand, those lists drift from each other and from what is
5
+ // installed (the Regent app had three, each missing a different `@lib` file). This prints the one
6
+ // list they can all read, derived from what is actually on disk.
7
+ //
8
+ // A file is registry-owned when the registry index names its install target and the file exists
9
+ // there — an `@ui/`, `@lib/`, `@hooks/` or `@components/` target resolved through components.json
10
+ // `aliases`, or a block's project-relative target — or when it carries the provenance header the
11
+ // registry stamps (`// @vegastack <name>@<version> sha256-…`). `shadcn add` strips that header on
12
+ // install, so the index is what finds most copies; `--offline` (or `--index <file>`) avoids the
13
+ // network. The index is fetched exactly the way `check-updates` fetches it: same config, same
14
+ // credential headers, same trusted-origin and no-redirect rules.
15
+ //
16
+ // Read-only: it never writes, installs or edits.
17
+
18
+ import {
19
+ existsSync,
20
+ lstatSync,
21
+ readFileSync,
22
+ readdirSync,
23
+ realpathSync,
24
+ } from "node:fs";
25
+ import { join, relative, resolve, sep } from "node:path";
26
+ import {
27
+ fetchRegistryIndex,
28
+ ITEM_NAME_RE,
29
+ PROVENANCE_RE,
30
+ terminalText,
31
+ } from "./check-updates.mjs";
32
+
33
+ const USAGE = `vegastack-design owned-files — list the registry-owned files installed in this project
34
+
35
+ Usage: vegastack-design owned-files [options]
36
+
37
+ Prints the project-relative path of every file the VegaStack registry installed here, one per
38
+ line, so lint ignores, knip and drift checks can read one generated list.
39
+
40
+ A file is registry-owned when the registry index names its install target (resolved through
41
+ components.json aliases) and the file exists, or when it carries the registry's provenance
42
+ header (// @vegastack <name>@<version> sha256-…). shadcn add strips that header, so the index
43
+ finds most copies; --offline reports header-carrying files only.
44
+
45
+ Options:
46
+ --dir <path> Project root holding components.json (default: the current directory)
47
+ --json Print { files: string[], items: { name, type, version, files[] }[] }
48
+ --offline Do not read the registry index; report header-carrying files only
49
+ --index <file> Read the registry index from a local registry.json instead of fetching it
50
+ --registry <url> Override the registry URL template (…/{name}.json)
51
+ -h, --help Show this help
52
+
53
+ Read-only. Exit codes: 0 = listed · 2 = bad usage, no components.json, or no readable index`;
54
+
55
+ const SKIP_DIRS = new Set([
56
+ "node_modules",
57
+ ".next",
58
+ ".git",
59
+ "dist",
60
+ "build",
61
+ "out",
62
+ ".turbo",
63
+ "coverage",
64
+ ]);
65
+ const SOURCE_FILE = /\.(?:[cm]?[jt]sx?|css)$/;
66
+
67
+ class UsageError extends Error {}
68
+
69
+ function parseArgs(argv) {
70
+ const opts = {
71
+ dir: ".",
72
+ json: false,
73
+ offline: false,
74
+ index: null,
75
+ registry: null,
76
+ help: false,
77
+ };
78
+ for (let i = 0; i < argv.length; i += 1) {
79
+ const a = argv[i];
80
+ if (a === "-h" || a === "--help") opts.help = true;
81
+ else if (a === "--json") opts.json = true;
82
+ else if (a === "--offline") opts.offline = true;
83
+ else if (a === "--dir" || a === "--index" || a === "--registry") {
84
+ const value = argv[i + 1];
85
+ if (!value || value.startsWith("-"))
86
+ throw new UsageError(`${a} requires a value`);
87
+ i += 1;
88
+ if (a === "--dir") opts.dir = value;
89
+ else if (a === "--index") opts.index = value;
90
+ else opts.registry = value;
91
+ } else throw new UsageError(`unknown option: ${a}`);
92
+ }
93
+ if (opts.offline && (opts.index || opts.registry))
94
+ throw new UsageError("--offline reads no index; drop --index/--registry");
95
+ return opts;
96
+ }
97
+
98
+ /** `@/components/ui` → `<root>/components/ui`, or `<root>/src/components/ui` when only that exists. */
99
+ function aliasDir(root, alias) {
100
+ if (typeof alias !== "string" || alias.length === 0) return null;
101
+ const rel = alias.replace(/^(?:@|~)\//, "");
102
+ const direct = resolve(root, rel);
103
+ if (existsSync(direct)) return direct;
104
+ const src = resolve(root, "src", rel);
105
+ return existsSync(src) ? src : direct;
106
+ }
107
+
108
+ const inside = (dir, path) => path === dir || path.startsWith(dir + sep);
109
+
110
+ /** Whether `path` is under `root` once every symlink on the way is resolved. */
111
+ function reallyInside(root, path) {
112
+ try {
113
+ return inside(realpathSync(root), realpathSync(path));
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Where a registry file's `target` lands in this project: `@ui/…`, `@lib/…`, `@hooks/…` and
121
+ * `@components/…` through components.json aliases; a block's bare target (`components/email/x.ts`)
122
+ * under `src/` when the project has one, else under the root — shadcn's own placement.
123
+ */
124
+ export function resolveTarget(root, aliases, target) {
125
+ if (typeof target !== "string" || target.length === 0) return null;
126
+ const placeholder = /^@(ui|lib|hooks|components)\/(.+)$/.exec(target);
127
+ let candidates;
128
+ if (placeholder) {
129
+ const dir = aliasDir(
130
+ root,
131
+ aliases?.[placeholder[1]] ??
132
+ { ui: "@/components/ui", lib: "@/lib", hooks: "@/hooks" }[
133
+ placeholder[1]
134
+ ],
135
+ );
136
+ if (!dir) return null;
137
+ candidates = [resolve(dir, placeholder[2])];
138
+ } else if (target.startsWith("@")) {
139
+ return null; // a placeholder this command does not know (`@utils/…`)
140
+ } else {
141
+ const rel = target.replace(/^~\//, "");
142
+ candidates = [resolve(root, "src", rel), resolve(root, rel)];
143
+ }
144
+ for (const path of candidates) {
145
+ // Lexically AND physically inside the project: a `..` in a target, or a symlinked alias dir
146
+ // or ancestor pointing elsewhere, never lists (or reads) a file outside it. A symlinked file
147
+ // itself is not a registry copy either.
148
+ if (!inside(root, path) || !reallyInside(root, path)) continue;
149
+ try {
150
+ if (lstatSync(path).isFile()) return path;
151
+ } catch {
152
+ // not installed here
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /** Every source file under `dir` (no symlinks, no build output) whose first line is a provenance header. */
159
+ function headeredFiles(dir, out = new Map()) {
160
+ let entries;
161
+ try {
162
+ entries = readdirSync(dir, { withFileTypes: true });
163
+ } catch {
164
+ return out;
165
+ }
166
+ for (const e of entries) {
167
+ if (SKIP_DIRS.has(e.name) || e.isSymbolicLink()) continue;
168
+ const full = join(dir, e.name);
169
+ if (e.isDirectory()) headeredFiles(full, out);
170
+ else if (e.isFile() && SOURCE_FILE.test(e.name)) {
171
+ let first;
172
+ try {
173
+ first = readFileSync(full, "utf8").split("\n", 1)[0].replace(/\r$/, "");
174
+ } catch {
175
+ continue;
176
+ }
177
+ const m = PROVENANCE_RE.exec(first);
178
+ if (m) out.set(full, { name: m[1], version: m[2] });
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+
184
+ /**
185
+ * The registry-owned files under `root`: `{ files, items }`, every path project-relative with `/`.
186
+ * `index` is the registry index's `items` (or null for headers only).
187
+ */
188
+ export function ownedFiles(root, componentsJson, index) {
189
+ const aliases = componentsJson?.aliases ?? {};
190
+ const owners = new Map(); // absolute path → { name, type, version }
191
+
192
+ // Headers first: they name the item and the version that was installed.
193
+ const roots = ["ui", "lib", "hooks", "components"]
194
+ .map((kind) => aliasDir(root, aliases[kind]))
195
+ .filter(
196
+ (d) =>
197
+ d &&
198
+ existsSync(d) &&
199
+ inside(root, d) &&
200
+ reallyInside(root, d) &&
201
+ !lstatSync(d).isSymbolicLink(),
202
+ );
203
+ const searched = roots.filter(
204
+ (d, i) => !roots.some((o, j) => j !== i && d !== o && inside(o, d)),
205
+ );
206
+ for (const d of [...new Set(searched)])
207
+ for (const [path, header] of headeredFiles(d))
208
+ owners.set(path, { ...header, type: null });
209
+
210
+ for (const item of index ?? []) {
211
+ if (
212
+ !item ||
213
+ typeof item !== "object" ||
214
+ !ITEM_NAME_RE.test(item.name ?? "") ||
215
+ !Array.isArray(item.files)
216
+ )
217
+ continue;
218
+ for (const file of item.files) {
219
+ const path = resolveTarget(root, aliases, file?.target);
220
+ if (!path) continue;
221
+ const known = owners.get(path);
222
+ if (known && known.name !== item.name) continue; // the header (or an earlier item) wins
223
+ owners.set(path, {
224
+ name: item.name,
225
+ type: typeof item.type === "string" ? item.type : null,
226
+ version: known?.version ?? null,
227
+ });
228
+ }
229
+ }
230
+
231
+ const items = new Map();
232
+ for (const [path, owner] of owners) {
233
+ const rel = relative(root, path).split(sep).join("/");
234
+ const entry = items.get(owner.name) ?? {
235
+ name: owner.name,
236
+ type: owner.type,
237
+ version: null,
238
+ files: [],
239
+ };
240
+ entry.type ??= owner.type;
241
+ entry.version ??= owner.version;
242
+ entry.files.push(rel);
243
+ items.set(owner.name, entry);
244
+ }
245
+ const sorted = [...items.values()]
246
+ .map((item) => ({ ...item, files: item.files.sort() }))
247
+ .sort((a, b) => a.name.localeCompare(b.name));
248
+ return { files: sorted.flatMap((i) => i.files).sort(), items: sorted };
249
+ }
250
+
251
+ export async function main(argv = []) {
252
+ let opts;
253
+ try {
254
+ opts = parseArgs(argv);
255
+ } catch (err) {
256
+ console.error(`owned-files: ${terminalText(err.message)}\n`);
257
+ console.error(USAGE);
258
+ return 2;
259
+ }
260
+ if (opts.help) {
261
+ console.log(USAGE);
262
+ return 0;
263
+ }
264
+ const root = resolve(opts.dir);
265
+ const cjPath = join(root, "components.json");
266
+ // JSON errors are reported without the parser's message: it quotes a prefix of the file, and
267
+ // components.json (or a saved index) may hold a credential where a quote went missing.
268
+ const readJsonFile = (path, what) => {
269
+ let text;
270
+ try {
271
+ text = readFileSync(path, "utf8");
272
+ } catch (err) {
273
+ throw new Error(
274
+ `could not read ${what}: ${terminalText(err.code ?? "error")}`,
275
+ );
276
+ }
277
+ try {
278
+ return JSON.parse(text);
279
+ } catch {
280
+ throw new Error(`${what} is not valid JSON`);
281
+ }
282
+ };
283
+ let componentsJson;
284
+ try {
285
+ componentsJson = readJsonFile(cjPath, terminalText(cjPath));
286
+ } catch (err) {
287
+ console.error(
288
+ `✗ ${err.message} — owned-files resolves install targets through its aliases`,
289
+ );
290
+ return 2;
291
+ }
292
+
293
+ let index = null;
294
+ let source = "headers";
295
+ if (opts.index) {
296
+ try {
297
+ const what = `the index ${terminalText(opts.index)}`;
298
+ index = readJsonFile(resolve(opts.index), what)?.items;
299
+ if (!Array.isArray(index)) throw new Error(`${what} has no items array`);
300
+ source = terminalText(opts.index);
301
+ } catch (err) {
302
+ console.error(`✗ ${err.message}`);
303
+ return 2;
304
+ }
305
+ } else if (!opts.offline) {
306
+ try {
307
+ const fetched = await fetchRegistryIndex({
308
+ cwd: root,
309
+ registry: opts.registry,
310
+ componentsJson,
311
+ });
312
+ index = fetched.items;
313
+ source = fetched.url;
314
+ } catch (err) {
315
+ console.error(`✗ ${terminalText(err.message)}`);
316
+ console.error(
317
+ " pass --offline to list header-carrying files only, or --index <registry.json>",
318
+ );
319
+ return 2;
320
+ }
321
+ }
322
+
323
+ const result = ownedFiles(root, componentsJson, index);
324
+ if (opts.json) {
325
+ console.log(JSON.stringify({ source, ...result }, null, 2));
326
+ return 0;
327
+ }
328
+ for (const file of result.files) console.log(file);
329
+ if (result.files.length === 0)
330
+ console.error(`no registry-owned files found (index: ${source})`);
331
+ return 0;
332
+ }
@@ -6,7 +6,8 @@
6
6
  // verify Verify a registry item's integrity before/after `shadcn add` (Sigstore + hash).
7
7
  // skills Install the bundled VegaStack agent skills into the consuming project.
8
8
  // doctor Check a consuming project's setup (PostCSS plugin, preset import, registry,
9
- // retired design-system vocabulary in its own source).
9
+ // retired design-system vocabulary and DS composition in its own source).
10
+ // owned-files List the registry-owned files installed in the project (read-only).
10
11
  //
11
12
  // The bin is named `vegastack-design` (NOT `vegastack`) so it never collides with a platform CLI.
12
13
  // `check-updates` is imported in-process; `verify` is spawned (it's the standalone, hash-parity-tested
@@ -26,6 +27,7 @@ Commands:
26
27
  verify Verify a registry item's integrity (pre/post \`shadcn add\`)
27
28
  skills Install the VegaStack agent skills (Claude Code + Codex)
28
29
  doctor Check this project's setup and report what is wrong
30
+ owned-files List the registry-owned files installed in this project
29
31
 
30
32
  Run \`vegastack-design <command> --help\` for command options.
31
33
  -v, --version Print version
@@ -70,6 +72,12 @@ if (cmd === "doctor") {
70
72
  process.exit(main(rest));
71
73
  }
72
74
 
75
+ if (cmd === "owned-files") {
76
+ // imported in-process (our own code, read-only; reads the registry index like check-updates)
77
+ const { main } = await import(new URL("./owned-files.mjs", HERE).href);
78
+ process.exit(await main(rest));
79
+ }
80
+
73
81
  if (cmd === "verify") {
74
82
  // spawn the standalone verifier untouched; mark the dispatch so it skips its deprecation notice.
75
83
  const verifier = fileURLToPath(new URL("./verify-registry-item.mjs", HERE));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vegastack/design",
3
- "version": "0.8.33",
3
+ "version": "0.8.35",
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": {
@@ -70,7 +70,7 @@
70
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
- "@vegastack/design-tokens": "^0.8.17",
73
+ "@vegastack/design-tokens": "^0.8.35",
74
74
  "clsx": "^2.1.1",
75
75
  "tailwind-merge": "^3.6.0",
76
76
  "tsconfig-paths": "^4.2.0",
@@ -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
- **168 components**, plus 467 animated-icon items, 16 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-grid-selection`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-optimistic-action`, `use-overflow`, `use-platform`, `use-tabs-swipe`, `use-view-transition`), 15 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`, `email-kit`, `share-01`), 68 chart blocks across 7 families, and 21 data libs (`code-highlight`, `date-time`, `grid-clipboard`, `geo-data`, `emoji-data`, `drag-item`, `text-anchor`, `text-anchor-doc`, `text-edit-grammar`, `embed-providers`, `text-edit-schema`, `text-edit-markdown`, `file-kind`, `upload-progress`, `media-probe`, `page-layout`, `tile-overlay`, `icon-data`, `icon-components`, `picker-search`, `picker-preferences`) — 755 registry items in total.
6
+ **171 components**, plus 467 animated-icon items, 16 hooks (`use-animation-replay`, `use-announcer`, `use-async-search`, `use-drag-reorder`, `use-file-drop`, `use-grid-selection`, `use-inline-edit`, `use-list-nav`, `use-media-query`, `use-mobile`, `use-modal-inert`, `use-optimistic-action`, `use-overflow`, `use-platform`, `use-tabs-swipe`, `use-view-transition`), 15 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`, `email-kit`, `share-01`), 68 chart blocks across 7 families, and 21 data libs (`code-highlight`, `date-time`, `grid-clipboard`, `geo-data`, `emoji-data`, `drag-item`, `text-anchor`, `text-anchor-doc`, `text-edit-grammar`, `embed-providers`, `text-edit-schema`, `text-edit-markdown`, `file-kind`, `upload-progress`, `media-probe`, `page-layout`, `tile-overlay`, `icon-data`, `icon-components`, `picker-search`, `picker-preferences`) — 758 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
@@ -55,7 +55,7 @@ starts with `icon-` is a component and never an icon.
55
55
 
56
56
  ## Display
57
57
 
58
- - **`activity-feed`** — A record's history, Linear style: comment threads and activity events ("Priya changed status from Open to Done · 2h") in one list, with an All / Comments / Activity filter, an order toggle, collapsible event groups, a New divider, a floating Jump to latest, J/K/X keyboard navigation and a skeleton.
58
+ - **`activity-feed`** — A record's history, Linear style: comment threads and activity events ("Priya changed status from Open to Done · 2h") in one list, with an All / Comments / Activity filter, an order toggle, one actor's runs of changes as one line (a chain of one field's values, or "made 3 changes" expanding), collapsible event groups, a New divider, a floating Jump to latest, J/K/X keyboard navigation and a skeleton.
59
59
  - **`block-view`** — The frame TextEdit and MarkdownView draw a fenced block in when a blockViews entry renders its language: label, Preview / Code, split on a wide editable block, Expand with pan and pinch, and a ⋯ menu.
60
60
  - **`chip`** — The one labelled pill primitive — 10 decorative hues, two tiers, an optional selection rung, and a real 24x24 remove control. Behind Tag and FilterChip; ComboboxChip is Base UI's own chip, not this primitive.
61
61
  - **`code-block`** — A code panel with a language header and copy affordance — the shared code surface for chat transcripts, docs, and examples.
@@ -63,8 +63,10 @@ starts with `icon-` is a component and never an icon.
63
63
  - **`comments`** — A record's comments, Linear style: thread cards (first comment, Show N more replies, indented replies over hairlines, a flat reply row with the viewer's avatar), a bordered composer with a toolbar row and file drop, day labels, a list with a count, order toggle, Load earlier, skeleton and empty states, edit/delete/copy link/copy text, emoji reactions and an attachments slot.
64
64
  - **`diff-view`** — What changed between two texts — changed lines, then the words inside them, removed struck through and added tinted — with the sanctioned diff engine in its own lazily loaded chunk; line-only above 200 KB.
65
65
  - **`entry-list`** — A short, compact list of plain entries in a sheet, dialog or popover: a line of text, its muted details and small actions at its end.
66
+ - **`member-list`** — The people in a team, a space or a share list — an add row (picker · optional role · Add) and 36px rows (avatar · name over a muted sub line · role chip menu or text · ⋯ with Remove) that scroll after eight.
66
67
  - **`mermaid-block`** — A mermaid diagram drawn in the browser with the engine loaded lazily and its configuration pinned (strict security, SVG labels, dagre), in token colours for light and dark, keeping the last diagram that worked on an error — plus renderMermaid, useMermaidSvg and the ready blockViews entry for TextEdit and MarkdownView.
67
68
  - **`meta-line`** — An icon + text meta line under a record title: wrapping facts such as type, date and time, duration and owner, in the muted ink.
69
+ - **`qr-code`** — A QR code for a value, drawn as one crisp SVG path — dark modules on a light rounded tile in both themes, with the standard quiet zone — encoded during render (server or client) by uqr; an empty or unencodable value draws the empty tile.
68
70
  - **`reactions`** — Slack-style emoji reactions: pills with counts that toggle, a hover card naming who reacted, and an add-reaction button with quick reactions.
69
71
  - **`stat`** — A labelled value block — muted label over a value, honest faint empty state, optional delta line. Two scales.
70
72
  - **`tag-group`** — Hue-tinted label chips on the 10-hue tag palette, with +N overflow collapsing and removable tags.
@@ -176,7 +178,8 @@ starts with `icon-` is a component and never an icon.
176
178
  - **`resizable`** — Accessible resizable panel groups and layouts with keyboard support — horizontal or vertical, nestable, with an optional visible grip.
177
179
  - **`scroll-area`** — Augments native scroll functionality for custom, cross-browser styling — a viewport, an auto-hiding scrollbar, and a corner.
178
180
  - **`separator`** — A thin rule dividing content — horizontal or vertical, built on Base UI.
179
- - **`settings-row`** — A borders-only settings layout — titled sections, bordered cards, and label-plus-control rows.
181
+ - **`settings-layout`** — The two-pane Settings frame — a 220px sticky section nav with current-page marking and counts, and one 960px content column where forms cap at 720px left-aligned and lists fill; on a phone the nav is the first screen and pages push in with a back link.
182
+ - **`settings-row`** — A borders-only settings layout — titled sections, bordered cards, label-plus-control rows whose controls share one width and right edge (controlSize), and a settings × channels switch matrix with its column names once.
180
183
 
181
184
  ## Media
182
185
 
@@ -184,7 +187,7 @@ starts with `icon-` is a component and never an icon.
184
187
  - **`file-card`** — One file as a card row: thumbnail or type icon, name, description lines and a ⋯ actions menu — opening the file viewer, or the whole card a link (download, navigate); with UploadCard for a file uploading into the same list.
185
188
  - **`file-viewer`** — A full-screen dark overlay for stored files — images with a blur-up, zoom and pan; PDFs rendered page by page with a lazily loaded pdf.js over range requests; video and audio in the system players; the first megabyte of text, CSV (as a table) and Markdown; app-parsed previews through loadPreview; a file card with a thumbnail and Download for everything else — paged with arrows, side buttons or a swipe.
186
189
  - **`image`** — A presentational framed image with aspect-ratio, rounding, a loading skeleton, and an error fallback.
187
- - **`inbox`** — The notification Inbox: frame with header actions, All | Unread chips, sticky day groups, full-bleed rows with avatar or icon and an unread tint (no dot), rich titles, action chips, a fixed right column with the time over the hover actions (read toggle and menu), and empty, loading and error states.
190
+ - **`inbox`** — The notification Inbox: frame with header actions, For you · Updates · Archived tabs with an Unread toggle, sticky day groups, full-bleed rows grouped per record (stacked actors, a count that expands to the events), inline Allow / Decline or an answered line, J / K triage with an active row driving a peek, a fixed right column with the time over the hover actions (read toggle and menu), and empty, loading and error states.
188
191
  - **`level-meter`** — A segmented live input-level meter for a recording — Web Audio RMS from a MediaStream, or a level you drive — with warning and clipping zones.
189
192
  - **`media-player-controls`** — The shared media transport — play/pause, skip, seek, elapsed/duration, mute + volume, playback speed, and one keyboard shortcut map (useMediaShortcuts) — composed by Audio Player and Video Player.
190
193
  - **`notification-bell`** — A bell icon button with an unread-count badge overlay, plus the shared NotificationDot unread marker. Presentational — the app supplies the count.