@happyvertical/smrt-svelte 0.40.21 → 0.40.23

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/AGENTS.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  Top-of-stack Svelte 5 integration layer for SMRT: the app `Provider`, auth / AI hooks, browser AI (STT/TTS/LLM), forms, server-side i18n, and the domain-aware composites (module, workspace). The domain-agnostic UI primitives, i18n client, theme system, and module UI registry now live in `@happyvertical/smrt-ui` (#1582) — import those from there (e.g. `@happyvertical/smrt-ui/ui`, `@happyvertical/smrt-ui/i18n`). The agent-admin shells (`AgentAdminPanel` / `AgentAdminTabs` / `AgentSettingsShell`) moved to `@happyvertical/smrt-agents` (#1589) so this package no longer depends on `smrt-agents` — import them from `@happyvertical/smrt-agents/svelte/admin` (side-effect-free) or `@happyvertical/smrt-agents/svelte`.
4
4
 
5
+ ## Modules
6
+
7
+ Per-module semantics live in sibling module docs — read the one for the
8
+ subpath you are editing. This file keeps what holds in every module.
9
+
10
+ | Module | Scope | Module doc |
11
+ |---|---|---|
12
+ | `src/components/` | what stayed here after the smrt-ui split, the L3 gap primitives, and the import-convention table for picking a barrel | [agents/components.md](agents/components.md) |
13
+ | `src/i18n/` (`./i18n` + `./i18n/server`) | `defineMessages` / `useI18n` / `<Trans>` / `buildI18nSnapshot`, the template-vs-render split, and hardcoded-string enforcement | [agents/i18n.md](agents/i18n.md) |
14
+ | `src/themes/` + `src/theme/` | which theme system is canonical and the full `--smrt-*` design-token vocabulary with its alias rules | [agents/themes.md](agents/themes.md) |
15
+ | `src/test-support/` + `__tests__/` | the golden-test harness and pattern for Svelte component tests | [agents/testing.md](agents/testing.md) |
16
+ | `src/components/settings/` (`./settings`) | `SettingsCatalog`, `paginateSettingsCatalog`, and the summary-vs-detail scalability contract | [agents/settings.md](agents/settings.md) |
17
+ | `src/components/workspace/` (`./workspace` + `./web`) | the AdminShell family and its principles, the legacy ToolsDock surface, the `./web` activity-feed and `updateAvailable` adapters, and server-side dock gates | [agents/workspace.md](agents/workspace.md) |
18
+
5
19
  ## The UI split — primitive-adoption contract (#1589)
6
20
 
7
21
  SMRT's shared UI primitives are split by concern: **`smrt-ui` owns the
@@ -68,113 +82,6 @@ Wraps app in `+layout.svelte`. Provides auth state, permissions, WebSocket, and
68
82
  - **Adapters**: STT (browser-speech, whisper-cpp, whisper-wasm), TTS (browser-synthesis), LLM (webllm, transformers-llm)
69
83
  - Cache API: `getCachedSTT()`, `getCachedTTS()`, `getCachedLLM()`, `getCacheStats()`, `clearAllCaches()`
70
84
 
71
- ## Components
72
-
73
- The domain-agnostic primitives (`ui`, `layout`, `feedback`, `nav`, `display`,
74
- `calendar`, `chat`, `permissions`, **`roles`/`memberships`**, `theme`) and the
75
- i18n client / module registry moved to `@happyvertical/smrt-ui` — import them
76
- from there (`@happyvertical/smrt-ui/{ui,layout,feedback,…}`). This package keeps
77
- the top-of-stack, domain-aware pieces:
78
-
79
- | Category | Components |
80
- |----------|------------|
81
- | AI | `Provider`, `AILoadingOverlay`, `CapabilityGate`, `DownloadProgress`, `STTTest`, `VoiceInput` |
82
- | Forms (`/forms`) | `TextInput`, `Select`, `MoneyInput`, `DateTimeInput`, `Toggle`, `FileUpload`, `AddressInput`, + more (AI-wired inputs use the hooks/browser-ai here) |
83
- | Module | `ModulePanel` |
84
- | Settings (`/settings`) | `SettingsCatalog`, `paginateSettingsCatalog` |
85
- | Workspace (`/workspace`) | `AdminShell`, `ShellState`, `TenantNav`, focus tools, settings, activities, and system/app panels |
86
- | Legacy workspace (`/workspace/legacy`) | First-generation `ToolsDock` compatibility surface during AdminShell migration |
87
-
88
- ### Gap primitives & S10 consolidation (L3 #1422)
89
-
90
- L3 added the generic primitives domain packages were re-rolling, so S10 (#1415)
91
- has a consolidation target: `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`
92
- (menu-button), and `Tree` (flat-DOM ARIA tree, generalizes `NavTree`) under
93
- `./ui`; plus `MessageBubble`, `ReactionPicker`, `TypingIndicator` under the
94
- `./chat` subpath. Each ships with design tokens, keyboard + ARIA a11y, JSDoc'd
95
- props, a golden test, and a playground page (`playground/.../primitives`).
96
-
97
- **Adoption-only for S10** — these already meet the library bar; S10 should
98
- migrate domain re-rolls *onto* them rather than build new primitives:
99
-
100
- - **`FileUpload`** (`./forms`) — the canonical upload input; replace ad-hoc
101
- drop zones.
102
- - **`Modal` + forms** (`./feedback` + `./forms`) — compose for dialogs; no
103
- bespoke modal shells.
104
- - **`ConfirmDialog`** (`./feedback`) — the standard confirm/destructive-action
105
- flow.
106
- - **`Card`** (`./ui`) — the standard surface/container; retire local card CSS.
107
-
108
- ### Import convention (S10 #1415)
109
-
110
- Domain packages **consume** these primitives; they do not re-roll them. The
111
- duplication of Modal/Form/Button/Avatar across packages is the root cause of
112
- inconsistent a11y, tokens, and states downstream — fix it by importing from the
113
- library. Which barrel for what:
114
-
115
- | Need | Import from |
116
- |------|-------------|
117
- | Buttons, cards, badges, avatars, chips, skeletons, tooltips, dropdowns, trees, pagination | `@happyvertical/smrt-svelte/ui` (or the package root) |
118
- | Provider-free base inputs — `Input`, `Select`, `Textarea`, `Toggle`, `FormGroup` | `@happyvertical/smrt-ui/forms` (also re-exported from `@happyvertical/smrt-svelte/forms`) |
119
- | Provider-free `Form` (plain `<form>` wrapper) | `@happyvertical/smrt-ui/forms` **only** — `@happyvertical/smrt-svelte/forms` exports the *rich* Provider-backed `Form` under that name, so import the plain one straight from smrt-ui |
120
- | Provider-backed inputs — `TextInput`, `NumberInput`, `MoneyInput`, date/measurement/address inputs, `CheckboxInput`, file upload, the rich `Form` | `@happyvertical/smrt-svelte/forms` |
121
- | `Modal`, `ConfirmDialog`, `LoadingOverlay`, `ProgressBar` | `@happyvertical/smrt-svelte/feedback` |
122
- | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState` | `@happyvertical/smrt-svelte/layout` |
123
- | Chat message bubble, reaction picker, typing indicator | `@happyvertical/smrt-svelte/chat` |
124
- | Admin shell, tenant navigation, focus tools, settings, and activities | `@happyvertical/smrt-svelte/workspace` |
125
- | First-generation ToolsDock during AdminShell migration | `@happyvertical/smrt-svelte/workspace/legacy` |
126
- | Server-paged settings search, selection, and list/detail layout | `@happyvertical/smrt-svelte/settings` |
127
-
128
- The package root re-exports `./ui`, `./forms`, etc., so `from
129
- '@happyvertical/smrt-svelte'` also works; prefer the specific subpath in domain
130
- code for tree-shaking and clarity.
131
-
132
- **Consolidating an existing re-roll** — two patterns:
133
-
134
- 1. **Direct use** (preferred for new code and when the local API already matches):
135
- delete the local component, import the library primitive at each call site.
136
- 2. **Thin adapter** (when a package has an established, differing prop vocabulary
137
- or a `ModuleUIRegistry` registration to preserve): keep the local file but
138
- reduce it to a wrapper that maps the package's props onto the library
139
- component — no duplicated markup/styles/logic. Example:
140
- `chat/.../shared/Avatar.svelte` maps `avatarUrl`→`src` and `onlineStatus`'s
141
- `dnd`→the library's `busy`, delegating everything else.
142
-
143
- **Missing a primitive or prop?** Add it upstream in `smrt-svelte`, don't re-roll
144
- downstream (e.g. the library `Avatar` gained an image-error→initials fallback
145
- while consolidating chat's avatar).
146
-
147
- ## i18n (`./i18n` + `./i18n/server`, Sweep S13 #1418)
148
-
149
- Routes user-facing strings through `@happyvertical/smrt-languages`. The server
150
- pre-resolves a per-locale dictionary of **templates**; the client reads it
151
- synchronously and interpolates `{var}` placeholders with its own dependency-free
152
- `renderTemplate` (`src/i18n/render.ts`, parity-tested against languages — the
153
- client never bundles the heavy languages package). No async in render. The
154
- languages root is imported only by the Node-only `/i18n/server` subpath. See
155
- `docs/content/architecture/i18n.md`.
156
-
157
- - **`defineMessages({ key: englishDefault })`** — register a package's English
158
- code defaults (key namespace `<package>.<component>.<descriptor>`; smrt-svelte
159
- primitives use `ui.`). Returns a typed key map. Client-safe (no languages
160
- root import). smrt-svelte's own catalog is `src/i18n/strings.ts`.
161
- - **`useI18n()` → `{ locale, t }`** and **`<Trans key vars />`** — equal
162
- first-class APIs (`t` for attributes like `placeholder`/`aria-label`, `<Trans>`
163
- for element bodies). Resolution order: snapshot template → registered default
164
- → the key itself (never blank). Both work outside a `<Provider>` (fall back to
165
- registered defaults) so primitives stay usable in isolation/tests.
166
- - **`<Provider i18n={snapshot}>`** puts the store on context; the prop is
167
- seeded synchronously (SSR-safe) and a locale switch (reassigning `i18n`)
168
- re-renders every `t` / `<Trans>`.
169
- - **`buildI18nSnapshot({ locale, tenantId, db })`** (`./i18n/server`, Node-only)
170
- — a consumer's load function calls it for the request locale and passes the
171
- result to `<Provider>`. It seeds the languages registry from `defineMessages`
172
- defaults, then resolves each key through the override/tenant/locale chain.
173
- - Enforcement: `scripts/check-hardcoded-strings.mjs` (`pnpm
174
- check:hardcoded-strings`) flags hardcoded prose in `.svelte` markup —
175
- report-only until a package's extraction completes, then add it to the
176
- script's `STRICT_PACKAGES`. Phase 1 extracted `DataTable` as the pilot.
177
-
178
85
  ## Permission Action
179
86
 
180
87
  ```svelte
@@ -182,22 +89,6 @@ languages root is imported only by the Node-only `/i18n/server` subpath. See
182
89
  <div use:permission={{ slug: 'articles.delete', permissions: userPermissions, hideOnly: true }}>Delete</div>
183
90
  ```
184
91
 
185
- ## Themes
186
-
187
- Two theme systems: `src/theme/` (simple ThemeProvider with design tokens) and `src/themes/` (full preset system with material/glass/studio, CSS generation, runtime switching). **`src/themes/` is canonical** — it is the only path that delivers the complete preset-aware `--smrt-*` token surface (colors + typography + spacing + radius + elevation + motion) across material/glass/studio. `src/theme/` is the simpler/legacy provider; it emits the same CSS variable vocabulary from its single built-in scale for backward compatibility, but it does not support preset switching or preset-specific values.
188
-
189
- ### Design-token vocabulary (issue #1431)
190
-
191
- Components consume a Material-3 vocabulary. To keep one vocabulary that always resolves, the canonical names are emitted **plus** additive aliases — never rename canonical tokens:
192
-
193
- - **Radius**: canonical `none|sm|md|lg|xl|2xl|3xl|full`; aliases `extra-small|small|medium|large|extra-large`.
194
- - **Spacing**: canonical numeric scale `0…24`; aliases `xs|sm|md|lg|xl|2xl|3xl` mapped onto numeric values.
195
- - **Motion**: canonical `instant|fast|normal|slow|slower`; aliases `short1…long4` (M3 ms scale).
196
- - **Typography**: per-variant `-size|-line-height|-weight|-tracking|-font-family` **plus** a `-font` CSS-shorthand alias (`weight size/line-height family`).
197
- - **Helpers**: `--smrt-font-family-mono`, named `--smrt-typography-weight-{normal,medium,semibold,bold}`, and `--smrt-z-index-{dropdown…tooltip}` (incl. `dialog`).
198
-
199
- Single source of truth: `src/themes/shared.ts` (alias maps) → emitted by `src/themes/css-generator.ts` (JS `ThemeProvider`), mirrored into the static preset CSS (`src/themes/styles/*.css`) and the simple provider (`src/theme/tokens.ts`). `scripts/check-svelte-tokens.mjs` (CI + `pnpm check:svelte-tokens`) fails on any consumed-but-unemitted `--smrt-*` token; `src/themes/__tests__/token-aliases.test.ts` pins the emitted set. Don't introduce new `--smrt-*` names in components without emitting them from a delivery path.
200
-
201
92
  ## Key Files
202
93
 
203
94
  - `src/Provider.svelte` -- root component, state initialization
@@ -208,30 +99,6 @@ Single source of truth: `src/themes/shared.ts` (alias maps) → emitted by `src/
208
99
  - `src/browser-ai/` -- STT/TTS/LLM adapters, capability detection (bundled, not external)
209
100
  - `src/registry/` -- ModuleUIRegistry for cross-package component discovery
210
101
 
211
- ## Component testing (golden tests)
212
-
213
- Component test harness (sweep L4, #1423): `@testing-library/svelte` + `@testing-library/jest-dom` + `@testing-library/user-event` + `axe-core`, wired through `src/test-support/setup.ts` (jest-dom matchers, Testing Library auto-cleanup, a jsdom `<dialog>` `showModal`/`close` polyfill). The smrt-vitest plugin appends its own setup to `setupFiles` — it merges, so don't remove the entry.
214
-
215
- **Golden test pattern** — render → assert role/name/state → drive with `user-event` → prove axe-clean. `src/components/ui/__tests__/Button.test.ts` is the reference:
216
-
217
- ```ts
218
- import { render, screen } from '@testing-library/svelte';
219
- import userEvent from '@testing-library/user-event';
220
- import { expectNoA11yViolations } from '../../../test-support/a11y';
221
-
222
- render(Component, { props: { /* … */ } });
223
- const el = screen.getByRole('button', { name: 'Save' });
224
- await userEvent.click(el);
225
- expect(el).toHaveAttribute('aria-busy', 'true');
226
- const { container } = render(Component, { props });
227
- await expectNoA11yViolations(container); // axe; color-contrast off (jsdom has no paint)
228
- ```
229
-
230
- - **Snippet props** (`children`, cell/header renderers): build with `createRawSnippet(() => ({ render: () => '<span>…</span>' }))`.
231
- - **Hook-dependent components** (anything calling `useAppState`/`useSTT`/`useAuth` — they throw outside `<Provider>`): `vi.mock` the hook module with stub defaults. See `src/components/forms/__tests__/Form.test.ts`.
232
- - **Form-input a11y** (programmatic labels, `aria-describedby`, axe-clean for `Input`/`TextInput`/etc.) is L1's deliverable (#1420) on top of this harness — bare primitives like `Input` get behavior tests here, labelled axe coverage there.
233
- - Existing reference suites: Button, Input, Modal, DataTable, Form. The pattern is what sweep S11 (#1416) rolls out repo-wide.
234
-
235
102
  ## Dependencies
236
103
 
237
104
  - `@happyvertical/smrt-types` (shared types) — includes the identity data contracts (`User`, `Role`, `Membership`, `Tenant`) the role/membership components type against, so no dependency on `smrt-users` / `smrt-profiles` is needed
@@ -239,127 +106,3 @@ await expectNoA11yViolations(container); // axe; color-contrast off (jsdom has n
239
106
  - `@happyvertical/smrt-languages` is a hard `dependency` (not an optional peer): the Node-only `/i18n/server` subpath imports its resolver. The browser bundle still excludes it — the client `/i18n` layer never imports the languages root, so it tree-shakes out.
240
107
  - `@happyvertical/logger` (SDK) is a `dependency` — the console logger used for voice/AI error reporting in the form components. Consume it **only** through `src/internal/logger.ts`, never `createLogger()` at module scope: `createLogger()` reads `HAVE_LOGGER_LEVEL` from `process.env`, so a top-level call throws `ReferenceError: process is not defined` in the browser and kills client-side hydration under `vite dev` (prod builds tree-shake/define it away, so this only bites in dev). The `internal/logger` wrapper constructs the logger lazily and falls back to a bare `ConsoleLogger` when `process.env` is absent, keeping this browser-reachable module (imported by `Provider` + the form primitives) safe.
241
108
  - Peer (all optional): `svelte` >=5.18.2, plus the browser-AI engines (`@huggingface/transformers`, `@mlc-ai/web-llm`, `@remotion/whisper-web`, `@xenova/transformers`) and `chrono-node`.
242
-
243
- ## Scalable settings catalog (`./settings`)
244
-
245
- `SettingsCatalog` is the shared search/browse/select/edit shell for large
246
- code-first or database-backed settings registries. Its interface accepts a
247
- server-produced `SettingsCatalogPage`: callers keep transport, authorization,
248
- and domain-specific editor forms, while the module owns compact result rows,
249
- GET search, query-preserving selection links, bounded pagination, result counts,
250
- empty states, and responsive list/detail layout.
251
-
252
- `paginateSettingsCatalog()` is the optional server helper for definitions that
253
- already live in memory (prompt and language registries). It searches before
254
- paging, clamps pages, caps rendered slices at 100 rows, and selects only one
255
- detail item. Database-backed callers should query their own page and construct
256
- the same `SettingsCatalogPage` interface directly rather than loading every row.
257
-
258
- The summary-row type and selected-detail type may differ: list pages can remain
259
- cheap while only the selected definition is fully resolved. This is the
260
- scalability contract; do not resolve every settings editor before passing data
261
- to the catalog.
262
-
263
- ## AdminShell workspace surface
264
-
265
- The `./workspace` subpath (`src/components/workspace/`) is the canonical
266
- AdminShell family for SMRT admin web apps. It exports the four-edge shell
267
- contract (`AdminShell`, `ShellState`, settings, hotkeys, focus tools,
268
- activities, tenant nav, app/system panels) from `workspace/admin-shell/`.
269
-
270
- The first-generation workspace family (`WorkspaceShell`, `RoleShell`,
271
- `NavTree`, `Breadcrumbs`, `ToolsDock`) remains in source as migration reference
272
- only. Do not re-export it from the public `./workspace` barrel just to preserve
273
- compatibility. Applications that still need ToolsDock during migration may use
274
- the explicit `@happyvertical/smrt-svelte/workspace/legacy` subpath; keep legacy
275
- additions isolated there so the canonical workspace surface remains AdminShell.
276
-
277
- **Principles**:
278
- - SvelteKit-agnostic core — no `$app/state` or `$app/navigation` imports
279
- - SSR-safe public shell import/render path; browser listeners and localStorage
280
- activate after mount
281
- - No token bridges — consume `var(--smrt-color-*)` directly
282
- - App-owned configuration for hidden edges, push/overlay presentation, and
283
- exclusivity groups
284
- - User-owned preferences persist as sparse `ShellSettingsDelta` values
285
- - Focus tools may register imperatively through `ShellState` or declaratively
286
- through Svelte helpers
287
- - Shell activities are client-side records; server jobs, polling, WebSockets,
288
- and `smrt-web` SSE can feed them through app adapters
289
-
290
- See `src/components/workspace/MIGRATION.md` for the old-to-new concept map.
291
-
292
- ### Live activity feed adapter (`./web`, #1779)
293
-
294
- `activityFeed({ collection, map, shell })` (from `@happyvertical/smrt-svelte/web`)
295
- bridges a `@happyvertical/smrt-web` live collection into a `ShellState` activity
296
- registry: it subscribes the collection through `liveCollection`, reactively maps
297
- each row → `ShellActivity` via the app-supplied editorial `map`, and drives
298
- `upsertActivity` / `updateActivity` / `removeActivity` as rows appear, change,
299
- and vanish (a row mapping to `null` is excluded / retracted). Returns a disposer
300
- that removes exactly the activities it created. Must be called during component
301
- init (installs a `$effect`); the subscription tears down on unmount.
302
-
303
- It lives behind the opt-in `./web` entry — which pulls the TanStack client-data
304
- engine — and is **never** imported under `components/workspace/`, so the
305
- AdminShell core (`./workspace`) stays transport-agnostic and TanStack-free (epic
306
- #1766). The pure diff core is `ActivityFeedReconciler` (engine-free, unit-tested
307
- against a real `ShellState`). Demo: `playground/.../admin-shell-activity-feed`.
308
-
309
- ### `updateAvailable` binding (`./web`, #1764)
310
-
311
- `useUpdateAvailable({ state, updated? })` (from `@happyvertical/smrt-svelte/web`)
312
- is the Svelte 5 reactive wrapper over smrt-web's framework-free `UpdateState`
313
- (from `createUpdateState()`). It surfaces `updateAvailable` / `bundle` /
314
- `contract` reactively (`$state`/`$derived`) for a toast or reload prompt, and
315
- wires SvelteKit's native `updated` store as the **bundle** signal. The
316
- **contract** signal (a manifest-hash change across loads, or a live `_events`
317
- manifest-frame mismatch latched by smrt-web on reconnect) is surfaced as-is.
318
-
319
- `updated` is passed IN as a reactive accessor (`() => updated.current` on modern
320
- `$app/state`, or a `$derived` over the legacy `$updated` store) rather than
321
- imported here — `$app/*` only resolves inside a SvelteKit app, so a library that
322
- imported it could not build/test standalone; the other `./web` adapters take
323
- runtime input the same way. Must be called during component init (installs
324
- `$effect`s that subscribe to the primitive and watch `updated`); both tear down
325
- on unmount. Construct ONE `createUpdateState()` per app (it owns the durable
326
- last-seen-hash bookkeeping) and pass its `manifestHash` from
327
- `@happyvertical/smrt-virt-web`. Browser-safe, engine-free (no `@tanstack/*`
328
- type). NOTE: `.svelte.ts` runes tests may not run under the local Darwin/vite8
329
- toolchain (CI is the gate) — the binding is covered by typecheck + svelte-check +
330
- a light unit test.
331
-
332
- ### Dock availability gates (server-side)
333
-
334
- `ToolDef.gates?: string[]` declares the gates a tool must pass to be visible.
335
- Convention: `<prefix>:<identifier>` (e.g. `permission:articles.publish`,
336
- `feature:video-tools`, `myapp:show-jobs`). `composeDockAvailability` from
337
- `@happyvertical/smrt-svelte/workspace/server` evaluates them — register one
338
- evaluator per prefix, throws on unknown prefixes (loud-fail beats silent-leak),
339
- AND semantics across a tool's gates. Node-safe, no Svelte imports.
340
-
341
- The framework does NOT ship built-in evaluators — every prefix the dock sees
342
- must have a caller-supplied evaluator in the map (otherwise composition
343
- throws). `permission:` and `feature:` are recommended conventions for
344
- ecosystem cohesion (consumers typically wire `PermissionResolver` from
345
- smrt-users and `FeatureResolver` from smrt-features as those evaluators), but
346
- they're not reserved — apps may pick any namespace. App-specific gates
347
- should use a dedicated namespace (e.g. `myapp:`) to avoid colliding with
348
- future built-ins.
349
-
350
- Recommended pattern: in the consumer's `+server.ts` endpoint that backs
351
- `fetchAvailability`, wrap `PermissionResolver` (smrt-users) and `FeatureResolver`
352
- (smrt-features) as evaluators and pass them in. Tools without `gates` stay
353
- unconditionally visible (back-compat). Anytown's hand-coded
354
- `apps/dashboard/src/lib/server/content-tool-dock.ts` is a candidate for
355
- migration in a follow-up.
356
-
357
- Legacy ToolsDock treats availability fetch failures as degraded presentation
358
- state: it preserves the current context's last successful tool IDs, labels,
359
- and badges. Before the first success, or after a context change, it falls back
360
- to registered-tool metadata so contextual values do not cross boundaries.
361
- Consumers can read `dock.availabilityError` to surface the current context's
362
- failure; a context change or later successful refresh clears the signal, and
363
- success applies the new snapshot.
364
- Availability is never an authorization boundary—server operations must still
365
- enforce permissions.
@@ -0,0 +1,81 @@
1
+ # smrt-svelte/components
2
+
3
+ Module semantics for `src/components/`. Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## Components
8
+
9
+ The domain-agnostic primitives (`ui`, `layout`, `feedback`, `nav`, `display`,
10
+ `calendar`, `chat`, `permissions`, **`roles`/`memberships`**, `theme`) and the
11
+ i18n client / module registry moved to `@happyvertical/smrt-ui` — import them
12
+ from there (`@happyvertical/smrt-ui/{ui,layout,feedback,…}`). This package keeps
13
+ the top-of-stack, domain-aware pieces:
14
+
15
+ | Category | Components |
16
+ |----------|------------|
17
+ | AI | `Provider`, `AILoadingOverlay`, `CapabilityGate`, `DownloadProgress`, `STTTest`, `VoiceInput` |
18
+ | Forms (`/forms`) | `TextInput`, `Select`, `MoneyInput`, `DateTimeInput`, `Toggle`, `FileUpload`, `AddressInput`, + more (AI-wired inputs use the hooks/browser-ai here) |
19
+ | Module | `ModulePanel` |
20
+ | Settings (`/settings`) | `SettingsCatalog`, `paginateSettingsCatalog` |
21
+ | Workspace (`/workspace`) | `AdminShell`, `ShellState`, `TenantNav`, focus tools, settings, activities, and system/app panels |
22
+ | Legacy workspace (`/workspace/legacy`) | First-generation `ToolsDock` compatibility surface during AdminShell migration |
23
+
24
+ ### Gap primitives & S10 consolidation (L3 #1422)
25
+
26
+ L3 added the generic primitives domain packages were re-rolling, so S10 (#1415)
27
+ has a consolidation target: `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`
28
+ (menu-button), and `Tree` (flat-DOM ARIA tree, generalizes `NavTree`) under
29
+ `./ui`; plus `MessageBubble`, `ReactionPicker`, `TypingIndicator` under the
30
+ `./chat` subpath. Each ships with design tokens, keyboard + ARIA a11y, JSDoc'd
31
+ props, a golden test, and a playground page (`playground/.../primitives`).
32
+
33
+ **Adoption-only for S10** — these already meet the library bar; S10 should
34
+ migrate domain re-rolls *onto* them rather than build new primitives:
35
+
36
+ - **`FileUpload`** (`./forms`) — the canonical upload input; replace ad-hoc
37
+ drop zones.
38
+ - **`Modal` + forms** (`./feedback` + `./forms`) — compose for dialogs; no
39
+ bespoke modal shells.
40
+ - **`ConfirmDialog`** (`./feedback`) — the standard confirm/destructive-action
41
+ flow.
42
+ - **`Card`** (`./ui`) — the standard surface/container; retire local card CSS.
43
+
44
+ ### Import convention (S10 #1415)
45
+
46
+ Domain packages **consume** these primitives; they do not re-roll them. The
47
+ duplication of Modal/Form/Button/Avatar across packages is the root cause of
48
+ inconsistent a11y, tokens, and states downstream — fix it by importing from the
49
+ library. Which barrel for what:
50
+
51
+ | Need | Import from |
52
+ |------|-------------|
53
+ | Buttons, cards, badges, avatars, chips, skeletons, tooltips, dropdowns, trees, pagination | `@happyvertical/smrt-svelte/ui` (or the package root) |
54
+ | Provider-free base inputs — `Input`, `Select`, `Textarea`, `Toggle`, `FormGroup` | `@happyvertical/smrt-ui/forms` (also re-exported from `@happyvertical/smrt-svelte/forms`) |
55
+ | Provider-free `Form` (plain `<form>` wrapper) | `@happyvertical/smrt-ui/forms` **only** — `@happyvertical/smrt-svelte/forms` exports the *rich* Provider-backed `Form` under that name, so import the plain one straight from smrt-ui |
56
+ | Provider-backed inputs — `TextInput`, `NumberInput`, `MoneyInput`, date/measurement/address inputs, `CheckboxInput`, file upload, the rich `Form` | `@happyvertical/smrt-svelte/forms` |
57
+ | `Modal`, `ConfirmDialog`, `LoadingOverlay`, `ProgressBar` | `@happyvertical/smrt-svelte/feedback` |
58
+ | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState` | `@happyvertical/smrt-svelte/layout` |
59
+ | Chat message bubble, reaction picker, typing indicator | `@happyvertical/smrt-svelte/chat` |
60
+ | Admin shell, tenant navigation, focus tools, settings, and activities | `@happyvertical/smrt-svelte/workspace` |
61
+ | First-generation ToolsDock during AdminShell migration | `@happyvertical/smrt-svelte/workspace/legacy` |
62
+ | Server-paged settings search, selection, and list/detail layout | `@happyvertical/smrt-svelte/settings` |
63
+
64
+ The package root re-exports `./ui`, `./forms`, etc., so `from
65
+ '@happyvertical/smrt-svelte'` also works; prefer the specific subpath in domain
66
+ code for tree-shaking and clarity.
67
+
68
+ **Consolidating an existing re-roll** — two patterns:
69
+
70
+ 1. **Direct use** (preferred for new code and when the local API already matches):
71
+ delete the local component, import the library primitive at each call site.
72
+ 2. **Thin adapter** (when a package has an established, differing prop vocabulary
73
+ or a `ModuleUIRegistry` registration to preserve): keep the local file but
74
+ reduce it to a wrapper that maps the package's props onto the library
75
+ component — no duplicated markup/styles/logic. Example:
76
+ `chat/.../shared/Avatar.svelte` maps `avatarUrl`→`src` and `onlineStatus`'s
77
+ `dnd`→the library's `busy`, delegating everything else.
78
+
79
+ **Missing a primitive or prop?** Add it upstream in `smrt-svelte`, don't re-roll
80
+ downstream (e.g. the library `Avatar` gained an image-error→initials fallback
81
+ while consolidating chat's avatar).
package/agents/i18n.md ADDED
@@ -0,0 +1,36 @@
1
+ # smrt-svelte/i18n
2
+
3
+ Module semantics for `src/i18n/` (`./i18n` + `./i18n/server`). Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## i18n (`./i18n` + `./i18n/server`, Sweep S13 #1418)
8
+
9
+ Routes user-facing strings through `@happyvertical/smrt-languages`. The server
10
+ pre-resolves a per-locale dictionary of **templates**; the client reads it
11
+ synchronously and interpolates `{var}` placeholders with its own dependency-free
12
+ `renderTemplate` (`src/i18n/render.ts`, parity-tested against languages — the
13
+ client never bundles the heavy languages package). No async in render. The
14
+ languages root is imported only by the Node-only `/i18n/server` subpath. See
15
+ `docs/content/architecture/i18n.md`.
16
+
17
+ - **`defineMessages({ key: englishDefault })`** — register a package's English
18
+ code defaults (key namespace `<package>.<component>.<descriptor>`; smrt-svelte
19
+ primitives use `ui.`). Returns a typed key map. Client-safe (no languages
20
+ root import). smrt-svelte's own catalog is `src/i18n/strings.ts`.
21
+ - **`useI18n()` → `{ locale, t }`** and **`<Trans key vars />`** — equal
22
+ first-class APIs (`t` for attributes like `placeholder`/`aria-label`, `<Trans>`
23
+ for element bodies). Resolution order: snapshot template → registered default
24
+ → the key itself (never blank). Both work outside a `<Provider>` (fall back to
25
+ registered defaults) so primitives stay usable in isolation/tests.
26
+ - **`<Provider i18n={snapshot}>`** puts the store on context; the prop is
27
+ seeded synchronously (SSR-safe) and a locale switch (reassigning `i18n`)
28
+ re-renders every `t` / `<Trans>`.
29
+ - **`buildI18nSnapshot({ locale, tenantId, db })`** (`./i18n/server`, Node-only)
30
+ — a consumer's load function calls it for the request locale and passes the
31
+ result to `<Provider>`. It seeds the languages registry from `defineMessages`
32
+ defaults, then resolves each key through the override/tenant/locale chain.
33
+ - Enforcement: `scripts/check-hardcoded-strings.mjs` (`pnpm
34
+ check:hardcoded-strings`) flags hardcoded prose in `.svelte` markup —
35
+ report-only until a package's extraction completes, then add it to the
36
+ script's `STRICT_PACKAGES`. Phase 1 extracted `DataTable` as the pilot.
@@ -0,0 +1,25 @@
1
+ # smrt-svelte/settings catalog
2
+
3
+ Module semantics for `src/components/settings/` (`./settings`). Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## Scalable settings catalog (`./settings`)
8
+
9
+ `SettingsCatalog` is the shared search/browse/select/edit shell for large
10
+ code-first or database-backed settings registries. Its interface accepts a
11
+ server-produced `SettingsCatalogPage`: callers keep transport, authorization,
12
+ and domain-specific editor forms, while the module owns compact result rows,
13
+ GET search, query-preserving selection links, bounded pagination, result counts,
14
+ empty states, and responsive list/detail layout.
15
+
16
+ `paginateSettingsCatalog()` is the optional server helper for definitions that
17
+ already live in memory (prompt and language registries). It searches before
18
+ paging, clamps pages, caps rendered slices at 100 rows, and selects only one
19
+ detail item. Database-backed callers should query their own page and construct
20
+ the same `SettingsCatalogPage` interface directly rather than loading every row.
21
+
22
+ The summary-row type and selected-detail type may differ: list pages can remain
23
+ cheap while only the selected definition is fully resolved. This is the
24
+ scalability contract; do not resolve every settings editor before passing data
25
+ to the catalog.
@@ -0,0 +1,29 @@
1
+ # smrt-svelte/component testing
2
+
3
+ Module semantics for `src/test-support/` + `__tests__/`. Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## Component testing (golden tests)
8
+
9
+ Component test harness (sweep L4, #1423): `@testing-library/svelte` + `@testing-library/jest-dom` + `@testing-library/user-event` + `axe-core`, wired through `src/test-support/setup.ts` (jest-dom matchers, Testing Library auto-cleanup, a jsdom `<dialog>` `showModal`/`close` polyfill). The smrt-vitest plugin appends its own setup to `setupFiles` — it merges, so don't remove the entry.
10
+
11
+ **Golden test pattern** — render → assert role/name/state → drive with `user-event` → prove axe-clean. `src/components/ui/__tests__/Button.test.ts` is the reference:
12
+
13
+ ```ts
14
+ import { render, screen } from '@testing-library/svelte';
15
+ import userEvent from '@testing-library/user-event';
16
+ import { expectNoA11yViolations } from '../../../test-support/a11y';
17
+
18
+ render(Component, { props: { /* … */ } });
19
+ const el = screen.getByRole('button', { name: 'Save' });
20
+ await userEvent.click(el);
21
+ expect(el).toHaveAttribute('aria-busy', 'true');
22
+ const { container } = render(Component, { props });
23
+ await expectNoA11yViolations(container); // axe; color-contrast off (jsdom has no paint)
24
+ ```
25
+
26
+ - **Snippet props** (`children`, cell/header renderers): build with `createRawSnippet(() => ({ render: () => '<span>…</span>' }))`.
27
+ - **Hook-dependent components** (anything calling `useAppState`/`useSTT`/`useAuth` — they throw outside `<Provider>`): `vi.mock` the hook module with stub defaults. See `src/components/forms/__tests__/Form.test.ts`.
28
+ - **Form-input a11y** (programmatic labels, `aria-describedby`, axe-clean for `Input`/`TextInput`/etc.) is L1's deliverable (#1420) on top of this harness — bare primitives like `Input` get behavior tests here, labelled axe coverage there.
29
+ - Existing reference suites: Button, Input, Modal, DataTable, Form. The pattern is what sweep S11 (#1416) rolls out repo-wide.
@@ -0,0 +1,21 @@
1
+ # smrt-svelte/themes
2
+
3
+ Module semantics for `src/themes/` + `src/theme/`. Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## Themes
8
+
9
+ Two theme systems: `src/theme/` (simple ThemeProvider with design tokens) and `src/themes/` (full preset system with material/glass/studio, CSS generation, runtime switching). **`src/themes/` is canonical** — it is the only path that delivers the complete preset-aware `--smrt-*` token surface (colors + typography + spacing + radius + elevation + motion) across material/glass/studio. `src/theme/` is the simpler/legacy provider; it emits the same CSS variable vocabulary from its single built-in scale for backward compatibility, but it does not support preset switching or preset-specific values.
10
+
11
+ ### Design-token vocabulary (issue #1431)
12
+
13
+ Components consume a Material-3 vocabulary. To keep one vocabulary that always resolves, the canonical names are emitted **plus** additive aliases — never rename canonical tokens:
14
+
15
+ - **Radius**: canonical `none|sm|md|lg|xl|2xl|3xl|full`; aliases `extra-small|small|medium|large|extra-large`.
16
+ - **Spacing**: canonical numeric scale `0…24`; aliases `xs|sm|md|lg|xl|2xl|3xl` mapped onto numeric values.
17
+ - **Motion**: canonical `instant|fast|normal|slow|slower`; aliases `short1…long4` (M3 ms scale).
18
+ - **Typography**: per-variant `-size|-line-height|-weight|-tracking|-font-family` **plus** a `-font` CSS-shorthand alias (`weight size/line-height family`).
19
+ - **Helpers**: `--smrt-font-family-mono`, named `--smrt-typography-weight-{normal,medium,semibold,bold}`, and `--smrt-z-index-{dropdown…tooltip}` (incl. `dialog`).
20
+
21
+ Single source of truth: `src/themes/shared.ts` (alias maps) → emitted by `src/themes/css-generator.ts` (JS `ThemeProvider`), mirrored into the static preset CSS (`src/themes/styles/*.css`) and the simple provider (`src/theme/tokens.ts`). `scripts/check-svelte-tokens.mjs` (CI + `pnpm check:svelte-tokens`) fails on any consumed-but-unemitted `--smrt-*` token; `src/themes/__tests__/token-aliases.test.ts` pins the emitted set. Don't introduce new `--smrt-*` names in components without emitting them from a delivery path.
@@ -0,0 +1,109 @@
1
+ # smrt-svelte/workspace / AdminShell
2
+
3
+ Module semantics for `src/components/workspace/` (`./workspace` + `./web`). Package orientation, the cross-module
4
+ invariants, and the traps that apply before editing anything live in
5
+ [../AGENTS.md](../AGENTS.md) — read that first.
6
+
7
+ ## AdminShell workspace surface
8
+
9
+ The `./workspace` subpath (`src/components/workspace/`) is the canonical
10
+ AdminShell family for SMRT admin web apps. It exports the four-edge shell
11
+ contract (`AdminShell`, `ShellState`, settings, hotkeys, focus tools,
12
+ activities, tenant nav, app/system panels) from `workspace/admin-shell/`.
13
+
14
+ The first-generation workspace family (`WorkspaceShell`, `RoleShell`,
15
+ `NavTree`, `Breadcrumbs`, `ToolsDock`) remains in source as migration reference
16
+ only. Do not re-export it from the public `./workspace` barrel just to preserve
17
+ compatibility. Applications that still need ToolsDock during migration may use
18
+ the explicit `@happyvertical/smrt-svelte/workspace/legacy` subpath; keep legacy
19
+ additions isolated there so the canonical workspace surface remains AdminShell.
20
+
21
+ **Principles**:
22
+ - SvelteKit-agnostic core — no `$app/state` or `$app/navigation` imports
23
+ - SSR-safe public shell import/render path; browser listeners and localStorage
24
+ activate after mount
25
+ - No token bridges — consume `var(--smrt-color-*)` directly
26
+ - App-owned configuration for hidden edges, push/overlay presentation, and
27
+ exclusivity groups
28
+ - User-owned preferences persist as sparse `ShellSettingsDelta` values
29
+ - Focus tools may register imperatively through `ShellState` or declaratively
30
+ through Svelte helpers
31
+ - Shell activities are client-side records; server jobs, polling, WebSockets,
32
+ and `smrt-web` SSE can feed them through app adapters
33
+
34
+ See `src/components/workspace/MIGRATION.md` for the old-to-new concept map.
35
+
36
+ ### Live activity feed adapter (`./web`, #1779)
37
+
38
+ `activityFeed({ collection, map, shell })` (from `@happyvertical/smrt-svelte/web`)
39
+ bridges a `@happyvertical/smrt-web` live collection into a `ShellState` activity
40
+ registry: it subscribes the collection through `liveCollection`, reactively maps
41
+ each row → `ShellActivity` via the app-supplied editorial `map`, and drives
42
+ `upsertActivity` / `updateActivity` / `removeActivity` as rows appear, change,
43
+ and vanish (a row mapping to `null` is excluded / retracted). Returns a disposer
44
+ that removes exactly the activities it created. Must be called during component
45
+ init (installs a `$effect`); the subscription tears down on unmount.
46
+
47
+ It lives behind the opt-in `./web` entry — which pulls the TanStack client-data
48
+ engine — and is **never** imported under `components/workspace/`, so the
49
+ AdminShell core (`./workspace`) stays transport-agnostic and TanStack-free (epic
50
+ #1766). The pure diff core is `ActivityFeedReconciler` (engine-free, unit-tested
51
+ against a real `ShellState`). Demo: `playground/.../admin-shell-activity-feed`.
52
+
53
+ ### `updateAvailable` binding (`./web`, #1764)
54
+
55
+ `useUpdateAvailable({ state, updated? })` (from `@happyvertical/smrt-svelte/web`)
56
+ is the Svelte 5 reactive wrapper over smrt-web's framework-free `UpdateState`
57
+ (from `createUpdateState()`). It surfaces `updateAvailable` / `bundle` /
58
+ `contract` reactively (`$state`/`$derived`) for a toast or reload prompt, and
59
+ wires SvelteKit's native `updated` store as the **bundle** signal. The
60
+ **contract** signal (a manifest-hash change across loads, or a live `_events`
61
+ manifest-frame mismatch latched by smrt-web on reconnect) is surfaced as-is.
62
+
63
+ `updated` is passed IN as a reactive accessor (`() => updated.current` on modern
64
+ `$app/state`, or a `$derived` over the legacy `$updated` store) rather than
65
+ imported here — `$app/*` only resolves inside a SvelteKit app, so a library that
66
+ imported it could not build/test standalone; the other `./web` adapters take
67
+ runtime input the same way. Must be called during component init (installs
68
+ `$effect`s that subscribe to the primitive and watch `updated`); both tear down
69
+ on unmount. Construct ONE `createUpdateState()` per app (it owns the durable
70
+ last-seen-hash bookkeeping) and pass its `manifestHash` from
71
+ `@happyvertical/smrt-virt-web`. Browser-safe, engine-free (no `@tanstack/*`
72
+ type). NOTE: `.svelte.ts` runes tests may not run under the local Darwin/vite8
73
+ toolchain (CI is the gate) — the binding is covered by typecheck + svelte-check +
74
+ a light unit test.
75
+
76
+ ### Dock availability gates (server-side)
77
+
78
+ `ToolDef.gates?: string[]` declares the gates a tool must pass to be visible.
79
+ Convention: `<prefix>:<identifier>` (e.g. `permission:articles.publish`,
80
+ `feature:video-tools`, `myapp:show-jobs`). `composeDockAvailability` from
81
+ `@happyvertical/smrt-svelte/workspace/server` evaluates them — register one
82
+ evaluator per prefix, throws on unknown prefixes (loud-fail beats silent-leak),
83
+ AND semantics across a tool's gates. Node-safe, no Svelte imports.
84
+
85
+ The framework does NOT ship built-in evaluators — every prefix the dock sees
86
+ must have a caller-supplied evaluator in the map (otherwise composition
87
+ throws). `permission:` and `feature:` are recommended conventions for
88
+ ecosystem cohesion (consumers typically wire `PermissionResolver` from
89
+ smrt-users and `FeatureResolver` from smrt-features as those evaluators), but
90
+ they're not reserved — apps may pick any namespace. App-specific gates
91
+ should use a dedicated namespace (e.g. `myapp:`) to avoid colliding with
92
+ future built-ins.
93
+
94
+ Recommended pattern: in the consumer's `+server.ts` endpoint that backs
95
+ `fetchAvailability`, wrap `PermissionResolver` (smrt-users) and `FeatureResolver`
96
+ (smrt-features) as evaluators and pass them in. Tools without `gates` stay
97
+ unconditionally visible (back-compat). Anytown's hand-coded
98
+ `apps/dashboard/src/lib/server/content-tool-dock.ts` is a candidate for
99
+ migration in a follow-up.
100
+
101
+ Legacy ToolsDock treats availability fetch failures as degraded presentation
102
+ state: it preserves the current context's last successful tool IDs, labels,
103
+ and badges. Before the first success, or after a context change, it falls back
104
+ to registered-tool metadata so contextual values do not cross boundaries.
105
+ Consumers can read `dock.availabilityError` to surface the current context's
106
+ failure; a context change or later successful refresh clears the signal, and
107
+ success applies the new snapshot.
108
+ Availability is never an authorization boundary—server operations must still
109
+ enforce permissions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-svelte",
3
- "version": "0.40.21",
3
+ "version": "0.40.23",
4
4
  "description": "Svelte 5 components for SMRT user management - auth, users, tenants, roles, permissions, groups",
5
5
  "type": "module",
6
6
  "smrtRawPrimitives": "strict",
@@ -75,7 +75,8 @@
75
75
  "files": [
76
76
  "dist",
77
77
  "CLAUDE.md",
78
- "AGENTS.md"
78
+ "AGENTS.md",
79
+ "agents"
79
80
  ],
80
81
  "keywords": [
81
82
  "smrt",
@@ -104,10 +105,10 @@
104
105
  "@tanstack/db": "^0.6.14",
105
106
  "@tanstack/svelte-db": "^0.1.91",
106
107
  "esm-env": "^1.2.2",
107
- "@happyvertical/smrt-languages": "0.40.21",
108
- "@happyvertical/smrt-types": "0.40.21",
109
- "@happyvertical/smrt-ui": "0.40.21",
110
- "@happyvertical/smrt-web": "0.40.21"
108
+ "@happyvertical/smrt-languages": "0.40.23",
109
+ "@happyvertical/smrt-types": "0.40.23",
110
+ "@happyvertical/smrt-ui": "0.40.23",
111
+ "@happyvertical/smrt-web": "0.40.23"
111
112
  },
112
113
  "peerDependencies": {
113
114
  "@huggingface/transformers": ">=3.8.1",