@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 +14 -271
- package/agents/components.md +81 -0
- package/agents/i18n.md +36 -0
- package/agents/settings.md +25 -0
- package/agents/testing.md +29 -0
- package/agents/themes.md +21 -0
- package/agents/workspace.md +109 -0
- package/package.json +7 -6
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.
|
package/agents/themes.md
ADDED
|
@@ -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.
|
|
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.
|
|
108
|
-
"@happyvertical/smrt-types": "0.40.
|
|
109
|
-
"@happyvertical/smrt-ui": "0.40.
|
|
110
|
-
"@happyvertical/smrt-web": "0.40.
|
|
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",
|