@happyvertical/smrt-svelte 0.37.8 → 0.37.10
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 +22 -86
- package/README.md +14 -10
- package/dist/components/workspace/MIGRATION.md +55 -0
- package/dist/components/workspace/README.md +22 -25
- package/dist/components/workspace/__tests__/AdminShell.test.js +55 -0
- package/dist/components/workspace/__tests__/admin-shell-hotkeys.test.js +34 -0
- package/dist/components/workspace/__tests__/admin-shell-settings.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +110 -0
- package/dist/components/workspace/__tests__/index.test.js +15 -24
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +2 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -1
- package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +1 -1
- package/dist/components/workspace/admin-shell/ActivityBadge.svelte +25 -0
- package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts +9 -0
- package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ActivityItem.svelte +85 -0
- package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts +8 -0
- package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ActivityList.svelte +41 -0
- package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts +10 -0
- package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ActivityToasts.svelte +140 -0
- package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts +16 -0
- package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +691 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +24 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/AppScopePanel.svelte +94 -0
- package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/HotkeyInput.svelte +74 -0
- package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellCorner.svelte +25 -0
- package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts +9 -0
- package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellDockTool.svelte +19 -0
- package/dist/components/workspace/admin-shell/ShellDockTool.svelte.d.ts +7 -0
- package/dist/components/workspace/admin-shell/ShellDockTool.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte +146 -0
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts +4 -0
- package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte +81 -0
- package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts +7 -0
- package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/SystemScopePanel.svelte +118 -0
- package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts +9 -0
- package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/SystemStatusChips.svelte +71 -0
- package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts +8 -0
- package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +101 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +10 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/context.d.ts +6 -0
- package/dist/components/workspace/admin-shell/context.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/context.js +16 -0
- package/dist/components/workspace/admin-shell/hotkeys.d.ts +14 -0
- package/dist/components/workspace/admin-shell/hotkeys.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/hotkeys.js +62 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +14 -0
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/settings.js +139 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +47 -0
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/state.svelte.js +252 -0
- package/dist/components/workspace/admin-shell/types.d.ts +137 -0
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +25 -11
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +24 -10
- package/dist/i18n/strings.workspace.d.ts +30 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +31 -0
- package/dist/internal/logger.d.ts +13 -6
- package/dist/internal/logger.d.ts.map +1 -1
- package/dist/internal/logger.js +33 -8
- package/dist/internal/logger.test.js +54 -0
- package/dist/web/__tests__/harness.svelte +25 -0
- package/dist/web/__tests__/harness.svelte.d.ts +15 -0
- package/dist/web/__tests__/harness.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/live-collection.svelte.test.js +184 -0
- package/dist/web/index.d.ts +13 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +12 -0
- package/dist/web/live-collection.svelte.d.ts +132 -0
- package/dist/web/live-collection.svelte.d.ts.map +1 -0
- package/dist/web/live-collection.svelte.js +173 -0
- package/package.json +13 -4
package/AGENTS.md
CHANGED
|
@@ -233,99 +233,35 @@ await expectNoA11yViolations(container); // axe; color-contrast off (jsdom has n
|
|
|
233
233
|
- `@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
|
|
234
234
|
- `@happyvertical/smrt-ui` (UI runtime: primitives, theme system, i18n client, module registry) is a hard `dependency`. The agent-admin shells that used to type against `@happyvertical/smrt-agents/ui` moved to `@happyvertical/smrt-agents/svelte` (#1589), so `smrt-agents` is no longer a dependency here — this drops smrt-svelte below smrt-agents in the package DAG.
|
|
235
235
|
- `@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.
|
|
236
|
-
- `@happyvertical/logger` (SDK) is a `dependency` — the
|
|
236
|
+
- `@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.
|
|
237
237
|
- 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`.
|
|
238
238
|
|
|
239
|
-
##
|
|
239
|
+
## AdminShell workspace surface
|
|
240
240
|
|
|
241
|
-
The `./workspace` subpath (`src/components/workspace/`)
|
|
242
|
-
|
|
243
|
-
`
|
|
244
|
-
|
|
241
|
+
The `./workspace` subpath (`src/components/workspace/`) is the canonical
|
|
242
|
+
AdminShell family for SMRT admin web apps. It exports the four-edge shell
|
|
243
|
+
contract (`AdminShell`, `ShellState`, settings, hotkeys, focus tools,
|
|
244
|
+
activities, tenant nav, app/system panels) from `workspace/admin-shell/`.
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
The first-generation workspace family (`WorkspaceShell`, `RoleShell`,
|
|
247
|
+
`NavTree`, `Breadcrumbs`, `ToolsDock`) remains in source as migration reference
|
|
248
|
+
only. Do not re-export it from the public `./workspace` barrel just to preserve
|
|
249
|
+
compatibility.
|
|
248
250
|
|
|
249
251
|
**Principles**:
|
|
250
|
-
- SvelteKit-agnostic — no `$app/state` or `$app/navigation` imports
|
|
251
|
-
- SSR-safe
|
|
252
|
+
- SvelteKit-agnostic core — no `$app/state` or `$app/navigation` imports
|
|
253
|
+
- SSR-safe public shell import/render path; browser listeners and localStorage
|
|
254
|
+
activate after mount
|
|
252
255
|
- No token bridges — consume `var(--smrt-color-*)` directly
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
`@deprecated`; prefer the granular pair. The `'dock:*'` prefix is reserved
|
|
263
|
-
for built-ins; consumer events should pick a different namespace.
|
|
264
|
-
- `WorkspaceShell` exposes `bind:mobileNavOpen` so consumers can lift the drawer
|
|
265
|
-
state. Pair it with `<NavTree onNavigate={() => mobileNavOpen = false} />` to
|
|
266
|
-
close the drawer on navigation without any DOM querying.
|
|
267
|
-
- `ToolDef.iconComponent?: Component` renders a custom icon (lucide-svelte etc.)
|
|
268
|
-
inside the rail glyph (and as a leading glyph in topbar layout). Takes
|
|
269
|
-
precedence over `icon: string`, then `label.charAt(0)` as last resort.
|
|
270
|
-
- `dock.refreshAvailability()` forces a re-run of `fetchAvailability` with the
|
|
271
|
-
current context. `setContext()` short-circuits on strict-equal references —
|
|
272
|
-
use refresh when a side-channel event (websocket, button) signals availability
|
|
273
|
-
or badges changed without a context change.
|
|
274
|
-
- Typed `defineToolsDock<TData, TActions>`. The factory's two generics flow
|
|
275
|
-
into `fetchAvailability`'s `ctx` param and through `ToolsDockContext<TData, TActions>`
|
|
276
|
-
for tool components. Inside a tool, type `context` locally:
|
|
277
|
-
`let { context }: { context: ToolsDockContext<MyData, MyActions> | null } = $props();`.
|
|
278
|
-
`context?.actions?.foo()` is then fully typed — no per-consumer redeclaration.
|
|
279
|
-
`ToolDef` itself is no longer generic (stored as a homogeneous `ToolDef[]`);
|
|
280
|
-
the consumer-side cast at registration is gone, the typed surface lives on
|
|
281
|
-
the component's `context` prop instead.
|
|
282
|
-
- Layout positioning. `<ToolsDock layout='topbar'>` renders its own
|
|
283
|
-
`position: fixed` panel — **do not also use `<WorkspaceShell>`'s `inspector`
|
|
284
|
-
snippet** in that mode (the two panels overlap with no z-index coordination).
|
|
285
|
-
`'rail'` layout is safe to compose alongside `inspector` — its panel sits
|
|
286
|
-
inside the dock's own aside.
|
|
287
|
-
|
|
288
|
-
### RoleShell
|
|
289
|
-
|
|
290
|
-
Opinionated thin wrapper for multi-role admin shells. Pass a `RoleConfig[]` list
|
|
291
|
-
and the current role id; renders `<WorkspaceShell>` + `<NavTree>` + `<Breadcrumbs>`
|
|
292
|
-
wired together. Role colors flow through as `--smrt-role-color` CSS custom property.
|
|
293
|
-
|
|
294
|
-
```svelte
|
|
295
|
-
<script lang="ts">
|
|
296
|
-
import { page } from '$app/state';
|
|
297
|
-
import { RoleShell } from '@happyvertical/smrt-svelte/workspace';
|
|
298
|
-
import AccountMenu from '$lib/AccountMenu.svelte';
|
|
299
|
-
import { ROLE_CONFIGS } from '$lib/roles';
|
|
300
|
-
|
|
301
|
-
let { data, children } = $props();
|
|
302
|
-
let mobileNavOpen = $state(false);
|
|
303
|
-
</script>
|
|
304
|
-
|
|
305
|
-
<RoleShell
|
|
306
|
-
roles={ROLE_CONFIGS}
|
|
307
|
-
currentRole={data.currentRole}
|
|
308
|
-
currentPath={page.url.pathname}
|
|
309
|
-
bind:mobileNavOpen
|
|
310
|
-
>
|
|
311
|
-
{#snippet sidebarFooter()}
|
|
312
|
-
<AccountMenu user={data.user} />
|
|
313
|
-
{/snippet}
|
|
314
|
-
{@render children?.()}
|
|
315
|
-
</RoleShell>
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
The `{@render children?.()}` call is the Svelte 5 idiom for rendering a
|
|
319
|
-
layout's child route content — replace with the equivalent slot/render call
|
|
320
|
-
for your framework if you're not using SvelteKit's `+layout.svelte` flow.
|
|
321
|
-
|
|
322
|
-
The shell intentionally doesn't know about specific role IDs — consumers pick
|
|
323
|
-
whatever set their app needs. Use this for role-based admin dashboards; use
|
|
324
|
-
`<WorkspaceShell>` directly for non-role apps.
|
|
325
|
-
|
|
326
|
-
See epic [happyvertical/smrt#1226](https://github.com/happyvertical/smrt/issues/1226) for context;
|
|
327
|
-
implementations land via #1227 (`WorkspaceShell`), #1228 (`NavTree`/`Breadcrumbs`), and #1229
|
|
328
|
-
(`ToolsDock` + registry).
|
|
256
|
+
- App-owned configuration for hidden edges, push/overlay presentation, and
|
|
257
|
+
exclusivity groups
|
|
258
|
+
- User-owned preferences persist as sparse `ShellSettingsDelta` values
|
|
259
|
+
- Focus tools may register imperatively through `ShellState` or declaratively
|
|
260
|
+
through Svelte helpers
|
|
261
|
+
- Shell activities are client-side records; server jobs, polling, WebSockets,
|
|
262
|
+
and `smrt-web` SSE can feed them through app adapters
|
|
263
|
+
|
|
264
|
+
See `src/components/workspace/MIGRATION.md` for the old-to-new concept map.
|
|
329
265
|
|
|
330
266
|
### Dock availability gates (server-side)
|
|
331
267
|
|
package/README.md
CHANGED
|
@@ -80,40 +80,44 @@ pnpm add @happyvertical/smrt-svelte
|
|
|
80
80
|
</ThemeProvider>
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
### Workspace
|
|
83
|
+
### Admin Workspace
|
|
84
84
|
|
|
85
85
|
```svelte
|
|
86
86
|
<script lang="ts">
|
|
87
87
|
import { manifest } from '$lib/smrt-manifest';
|
|
88
88
|
import {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
AdminShell,
|
|
90
|
+
TenantNav,
|
|
91
|
+
tenantNavFromManifest,
|
|
91
92
|
} from '@happyvertical/smrt-svelte/workspace';
|
|
92
93
|
|
|
93
94
|
let { children } = $props();
|
|
94
95
|
|
|
95
|
-
const sections =
|
|
96
|
+
const sections = tenantNavFromManifest(manifest, {
|
|
96
97
|
sectionHints: {
|
|
97
98
|
'@happyvertical/smrt-content': 'Content',
|
|
98
99
|
'@happyvertical/smrt-profiles': 'Profiles',
|
|
99
100
|
},
|
|
100
101
|
});
|
|
101
102
|
|
|
102
|
-
const roles = [{ id: 'admin', label: 'Admin', sections }];
|
|
103
103
|
</script>
|
|
104
104
|
|
|
105
|
-
<
|
|
105
|
+
<AdminShell title="Admin">
|
|
106
|
+
{#snippet tenantPanel()}
|
|
107
|
+
<TenantNav items={sections} currentHref="/admin/articles" />
|
|
108
|
+
{/snippet}
|
|
109
|
+
|
|
106
110
|
{@render children?.()}
|
|
107
|
-
</
|
|
111
|
+
</AdminShell>
|
|
108
112
|
```
|
|
109
113
|
|
|
110
114
|
Filter the same manifest by role permissions when only a subset of resources
|
|
111
115
|
should be visible:
|
|
112
116
|
|
|
113
117
|
```ts
|
|
114
|
-
import {
|
|
118
|
+
import { tenantNavFromManifest } from '@happyvertical/smrt-svelte/workspace';
|
|
115
119
|
|
|
116
|
-
const editorSections =
|
|
120
|
+
const editorSections = tenantNavFromManifest(manifest, {
|
|
117
121
|
permittedResources: [
|
|
118
122
|
'@happyvertical/smrt-content:Article',
|
|
119
123
|
'@happyvertical/smrt-content:Document',
|
|
@@ -137,7 +141,7 @@ const editorSections = navTreeFromManifest(manifest, {
|
|
|
137
141
|
| `@happyvertical/smrt-svelte/ui` | UI primitives (Button, Card, Badge, Pagination) |
|
|
138
142
|
| `@happyvertical/smrt-svelte/themes` | ThemeProvider, presets (material/glass/studio), CSS generation |
|
|
139
143
|
| `@happyvertical/smrt-svelte/registry` | ModuleUIRegistry for agent admin panels |
|
|
140
|
-
| `@happyvertical/smrt-svelte/workspace` |
|
|
144
|
+
| `@happyvertical/smrt-svelte/workspace` | AdminShell, ShellState, tenant nav, focus tools, settings, activities, and system/app panels |
|
|
141
145
|
| `@happyvertical/smrt-svelte/browser-ai` | Browser AI client (STT/TTS/LLM adapters, capability detection) |
|
|
142
146
|
| `@happyvertical/smrt-svelte/browser-ai/svelte` | Svelte AI components (VoiceInput, CapabilityGate, etc.) |
|
|
143
147
|
| `@happyvertical/smrt-svelte/styles/tokens.css` | Design tokens CSS |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# AdminShell Migration
|
|
2
|
+
|
|
3
|
+
The first-generation workspace family (`WorkspaceShell`, `RoleShell`,
|
|
4
|
+
`NavTree`, `Breadcrumbs`, `ToolsDock`) is no longer the public
|
|
5
|
+
`@happyvertical/smrt-svelte/workspace` surface. Treat those source files as a
|
|
6
|
+
migration reference while moving apps to `AdminShell`.
|
|
7
|
+
|
|
8
|
+
## Concept Map
|
|
9
|
+
|
|
10
|
+
| First-generation concept | AdminShell concept |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `WorkspaceShell` outer layout | `AdminShell` four-edge layout |
|
|
13
|
+
| `RoleShell` wrapper | App-owned config + `TenantNav` |
|
|
14
|
+
| `NavTree` | `TenantNav` fed by app data or `tenantNavFromManifest()` |
|
|
15
|
+
| `ToolsDock` / `defineToolsDock` | Focus edge tools through `ShellState.registerFocusTool()` or `ShellDockTool` |
|
|
16
|
+
| Dock localStorage | Sparse `ShellSettingsDelta` through `ShellSettingsAdapter` |
|
|
17
|
+
| App-specific job panels | `SystemScopePanel` fed by app stores/endpoints |
|
|
18
|
+
|
|
19
|
+
## Minimal Shape
|
|
20
|
+
|
|
21
|
+
```svelte
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import {
|
|
24
|
+
AdminShell,
|
|
25
|
+
TenantNav,
|
|
26
|
+
SystemStatusChips,
|
|
27
|
+
} from '@happyvertical/smrt-svelte/workspace';
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<AdminShell title="My App">
|
|
31
|
+
{#snippet tenantPanel()}
|
|
32
|
+
<TenantNav items={navItems} currentHref={currentPath} />
|
|
33
|
+
{/snippet}
|
|
34
|
+
|
|
35
|
+
{#snippet systemBar()}
|
|
36
|
+
<SystemStatusChips chips={statusChips} />
|
|
37
|
+
{/snippet}
|
|
38
|
+
|
|
39
|
+
{@render children?.()}
|
|
40
|
+
</AdminShell>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- `hidden` means the app omitted an edge; users normally persist only
|
|
46
|
+
collapsed/expanded panel deltas.
|
|
47
|
+
- Push versus overlay is an app decision per edge.
|
|
48
|
+
- Expanded panels are independent by default. Use matching `exclusiveGroup`
|
|
49
|
+
values on panel config to make panels mutually exclusive.
|
|
50
|
+
- Hotkeys use `KeyboardEvent.code` so the WASD cluster remains physical across
|
|
51
|
+
keyboard layouts.
|
|
52
|
+
- URL synchronization is intentionally adapter-owned. The shell exposes
|
|
53
|
+
serializable state and commands; apps decide their route/query shape.
|
|
54
|
+
- Shell activities are client-side records. Server jobs, polling, WebSockets,
|
|
55
|
+
and `smrt-web` SSE live updates can all feed them through adapters.
|
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
# workspace/
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
SvelteKit-agnostic, SSR-safe, and carry no domain coupling — they slot into
|
|
5
|
-
any Svelte 5 app that wants a sidebar + topbar + tools-dock layout.
|
|
3
|
+
Canonical AdminShell family for SMRT admin web apps.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
`@happyvertical/smrt-svelte/workspace` now exports the four-edge AdminShell
|
|
6
|
+
contract:
|
|
8
7
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- `ToolsDock`, `defineToolsDock`, `useToolsDock` — [#1229](https://github.com/happyvertical/smrt/issues/1229)
|
|
8
|
+
- App scope on the top edge
|
|
9
|
+
- Tenant scope on the left edge
|
|
10
|
+
- Focus scope on the right edge
|
|
11
|
+
- System scope on the bottom edge
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
The shell owns geometry, keyboard behavior, settings deltas, responsive
|
|
14
|
+
presentation, focus tool registration, activity registry/watch components, and
|
|
15
|
+
optional SMRT-fed content seams. It does not depend on jobs, users, tenancy, or
|
|
16
|
+
SvelteKit route APIs; apps pass data, endpoints, and permission-filtered nav in.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Main Exports
|
|
18
19
|
|
|
19
|
-
- `
|
|
20
|
-
- `
|
|
20
|
+
- `AdminShell`
|
|
21
|
+
- `ShellState` / `createShellState`
|
|
22
|
+
- `ShellSettingsPanel`, `HotkeyInput`, `ShortcutsOverlay`
|
|
23
|
+
- `ShellDockTool`, `TenantNav`
|
|
24
|
+
- `ActivityBadge`, `ActivityList`, `ActivityItem`, `ActivityToasts`
|
|
25
|
+
- `AppScopePanel`, `SystemStatusChips`, `SystemScopePanel`
|
|
26
|
+
- `tenantNavFromManifest`
|
|
21
27
|
|
|
22
|
-
##
|
|
28
|
+
## Migration
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- `ToolsDock` + `defineToolsDock` + `useToolsDock` (#1229) — right-rail tools dock + registry
|
|
27
|
-
|
|
28
|
-
## Principles
|
|
29
|
-
|
|
30
|
-
- **SvelteKit-agnostic** — no `$app/state` or `$app/navigation` imports
|
|
31
|
-
- **SSR-safe** — guard all `window` / `localStorage` access
|
|
32
|
-
- **No token bridges** — consume `var(--smrt-color-*)` tokens directly
|
|
33
|
-
- **No domain coupling** — domain-specific tools live in consumer packages
|
|
34
|
-
- **Extensible tool IDs** — tool IDs are arbitrary strings, not an enum
|
|
30
|
+
See [MIGRATION.md](./MIGRATION.md) for the first-generation workspace migration
|
|
31
|
+
map.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createRawSnippet, flushSync, mount, tick, unmount } from 'svelte';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
import AdminShell from '../admin-shell/AdminShell.svelte';
|
|
4
|
+
function textSnippet(text) {
|
|
5
|
+
return createRawSnippet(() => ({
|
|
6
|
+
render: () => `<span>${text}</span>`,
|
|
7
|
+
}));
|
|
8
|
+
}
|
|
9
|
+
let container;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
container = document.createElement('div');
|
|
12
|
+
document.body.appendChild(container);
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
container.remove();
|
|
16
|
+
});
|
|
17
|
+
describe('AdminShell', () => {
|
|
18
|
+
it('renders the four edge shell and body content', () => {
|
|
19
|
+
const component = mount(AdminShell, {
|
|
20
|
+
target: container,
|
|
21
|
+
props: {
|
|
22
|
+
title: 'Ops',
|
|
23
|
+
children: textSnippet('main work'),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
expect(container.querySelector('.smrt-admin-shell')).not.toBeNull();
|
|
28
|
+
expect(container.querySelector('header')?.textContent).toContain('Ops');
|
|
29
|
+
expect(container.querySelector('main')?.textContent).toContain('main work');
|
|
30
|
+
expect(container.querySelector('[role="navigation"]')).not.toBeNull();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
unmount(component);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it('toggles a physical-code panel hotkey', async () => {
|
|
37
|
+
const component = mount(AdminShell, {
|
|
38
|
+
target: container,
|
|
39
|
+
props: {
|
|
40
|
+
children: textSnippet('main work'),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
const shell = container.querySelector('.smrt-admin-shell');
|
|
45
|
+
expect(shell?.getAttribute('data-top-state')).toBe('collapsed');
|
|
46
|
+
await tick();
|
|
47
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyW' }));
|
|
48
|
+
flushSync();
|
|
49
|
+
expect(shell?.getAttribute('data-top-state')).toBe('expanded');
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
unmount(component);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { hotkeyMatchesEvent, shellActionFromKeyboardEvent, } from '../admin-shell/hotkeys.js';
|
|
3
|
+
import { resolveShellConfig } from '../admin-shell/settings.js';
|
|
4
|
+
function keyEvent(init) {
|
|
5
|
+
return new KeyboardEvent('keydown', init);
|
|
6
|
+
}
|
|
7
|
+
describe('AdminShell hotkeys', () => {
|
|
8
|
+
it('matches physical KeyboardEvent.code bindings', () => {
|
|
9
|
+
expect(hotkeyMatchesEvent({ code: 'KeyW' }, keyEvent({ code: 'KeyW' }))).toBe(true);
|
|
10
|
+
expect(hotkeyMatchesEvent({ code: 'KeyW' }, keyEvent({ code: 'KeyZ' }))).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
it('maps WASD codes to shell edges', () => {
|
|
13
|
+
const { panels } = resolveShellConfig();
|
|
14
|
+
expect(shellActionFromKeyboardEvent(keyEvent({ code: 'KeyW' }), panels)).toEqual({ type: 'toggle-panel', edge: 'top' });
|
|
15
|
+
expect(shellActionFromKeyboardEvent(keyEvent({ code: 'KeyA' }), panels)).toEqual({ type: 'toggle-panel', edge: 'left' });
|
|
16
|
+
expect(shellActionFromKeyboardEvent(keyEvent({ code: 'KeyS' }), panels)).toEqual({ type: 'toggle-panel', edge: 'bottom' });
|
|
17
|
+
expect(shellActionFromKeyboardEvent(keyEvent({ code: 'KeyD' }), panels)).toEqual({ type: 'toggle-panel', edge: 'right' });
|
|
18
|
+
});
|
|
19
|
+
it('suppresses panel hotkeys in editable controls', () => {
|
|
20
|
+
const input = document.createElement('input');
|
|
21
|
+
const event = keyEvent({ code: 'KeyW' });
|
|
22
|
+
Object.defineProperty(event, 'target', { value: input });
|
|
23
|
+
expect(shellActionFromKeyboardEvent(event, resolveShellConfig().panels)).toBe(null);
|
|
24
|
+
});
|
|
25
|
+
it('keeps the shortcuts overlay available on question mark', () => {
|
|
26
|
+
expect(shellActionFromKeyboardEvent(keyEvent({ key: '?', code: 'Slash', shiftKey: true }), resolveShellConfig().panels)).toEqual({ type: 'show-shortcuts' });
|
|
27
|
+
});
|
|
28
|
+
it('does not open shortcuts when typing ? in an editable control', () => {
|
|
29
|
+
const input = document.createElement('input');
|
|
30
|
+
const event = keyEvent({ key: '?', code: 'Slash', shiftKey: true });
|
|
31
|
+
Object.defineProperty(event, 'target', { value: input });
|
|
32
|
+
expect(shellActionFromKeyboardEvent(event, resolveShellConfig().panels)).toBe(null);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mergeShellSettingsDelta, resolveHotkey, resolveInitialPanelState, resolveShellConfig, } from '../admin-shell/settings.js';
|
|
3
|
+
describe('AdminShell settings', () => {
|
|
4
|
+
it('treats false app config as a hidden edge', () => {
|
|
5
|
+
const config = resolveShellConfig({ right: false });
|
|
6
|
+
expect(config.panels.right.initial).toBe('hidden');
|
|
7
|
+
expect(resolveInitialPanelState('right', config.panels.right)).toBe('hidden');
|
|
8
|
+
expect(resolveHotkey('right', config.panels.right)).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
it('resolves user panel deltas over app defaults', () => {
|
|
11
|
+
const config = resolveShellConfig({
|
|
12
|
+
left: { initial: 'collapsed' },
|
|
13
|
+
});
|
|
14
|
+
expect(resolveInitialPanelState('left', config.panels.left, {
|
|
15
|
+
panels: { left: 'expanded' },
|
|
16
|
+
})).toBe('expanded');
|
|
17
|
+
});
|
|
18
|
+
it('merges sparse deltas without replacing unrelated keys', () => {
|
|
19
|
+
expect(mergeShellSettingsDelta({ hotkeysEnabled: true, panels: { left: 'expanded' } }, { keymap: { top: { code: 'KeyQ' } } })).toEqual({
|
|
20
|
+
hotkeysEnabled: true,
|
|
21
|
+
panels: { left: 'expanded' },
|
|
22
|
+
keymap: { top: { code: 'KeyQ' } },
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createShellState } from '../admin-shell/state.svelte.js';
|
|
3
|
+
describe('ShellState', () => {
|
|
4
|
+
it('allows panels to expand independently by default', () => {
|
|
5
|
+
const shell = createShellState();
|
|
6
|
+
shell.expandPanel('top');
|
|
7
|
+
shell.expandPanel('bottom');
|
|
8
|
+
expect(shell.panels.top).toBe('expanded');
|
|
9
|
+
expect(shell.panels.bottom).toBe('expanded');
|
|
10
|
+
});
|
|
11
|
+
it('closes peers in the same exclusivity group', () => {
|
|
12
|
+
const shell = createShellState({
|
|
13
|
+
config: {
|
|
14
|
+
top: { exclusiveGroup: 'vertical' },
|
|
15
|
+
bottom: { exclusiveGroup: 'vertical' },
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
shell.expandPanel('top');
|
|
19
|
+
shell.expandPanel('bottom');
|
|
20
|
+
expect(shell.panels.top).toBe('collapsed');
|
|
21
|
+
expect(shell.panels.bottom).toBe('expanded');
|
|
22
|
+
});
|
|
23
|
+
it('registers and unregisters focus tools', () => {
|
|
24
|
+
const shell = createShellState();
|
|
25
|
+
const unregister = shell.registerFocusTool({
|
|
26
|
+
id: 'activity',
|
|
27
|
+
label: 'Activity',
|
|
28
|
+
});
|
|
29
|
+
expect(shell.focusTools.map((tool) => tool.id)).toEqual(['activity']);
|
|
30
|
+
expect(shell.activeFocusToolId).toBe('activity');
|
|
31
|
+
unregister();
|
|
32
|
+
expect(shell.focusTools).toEqual([]);
|
|
33
|
+
expect(shell.activeFocusToolId).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
it('re-homes activities to system when their edge is hidden', () => {
|
|
36
|
+
const shell = createShellState({ config: { right: false } });
|
|
37
|
+
shell.upsertActivity({
|
|
38
|
+
id: 'encode-1',
|
|
39
|
+
label: 'Encode',
|
|
40
|
+
kind: 'video-encode',
|
|
41
|
+
scope: 'focus',
|
|
42
|
+
status: 'running',
|
|
43
|
+
});
|
|
44
|
+
expect(shell.listActivities()[0]?.edge).toBe('bottom');
|
|
45
|
+
expect(shell.activityBadge('bottom').count).toBe(1);
|
|
46
|
+
});
|
|
47
|
+
it('emits lifecycle events for activity updates', () => {
|
|
48
|
+
const shell = createShellState();
|
|
49
|
+
const listener = vi.fn();
|
|
50
|
+
shell.watchActivities(listener);
|
|
51
|
+
shell.upsertActivity({
|
|
52
|
+
id: 'import-1',
|
|
53
|
+
label: 'Import',
|
|
54
|
+
kind: 'import',
|
|
55
|
+
scope: 'system',
|
|
56
|
+
status: 'running',
|
|
57
|
+
});
|
|
58
|
+
shell.updateActivity('import-1', { status: 'completed' });
|
|
59
|
+
expect(listener).toHaveBeenCalledWith(expect.objectContaining({ type: 'transition' }));
|
|
60
|
+
});
|
|
61
|
+
it('keeps an exclusivity-closed peer collapsed after settings re-resolve', () => {
|
|
62
|
+
const shell = createShellState({
|
|
63
|
+
config: {
|
|
64
|
+
top: { exclusiveGroup: 'vertical' },
|
|
65
|
+
bottom: { exclusiveGroup: 'vertical' },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
shell.expandPanel('top');
|
|
69
|
+
shell.expandPanel('bottom');
|
|
70
|
+
// Re-applying settings (as hydrate / app-default changes do) must not
|
|
71
|
+
// resurrect the peer the exclusivity rule collapsed.
|
|
72
|
+
shell.applySettings({});
|
|
73
|
+
expect(shell.panels.top).toBe('collapsed');
|
|
74
|
+
expect(shell.panels.bottom).toBe('expanded');
|
|
75
|
+
});
|
|
76
|
+
it('re-homes to a visible edge when both the scope edge and system are hidden', () => {
|
|
77
|
+
const shell = createShellState({ config: { right: false, bottom: false } });
|
|
78
|
+
shell.upsertActivity({
|
|
79
|
+
id: 'encode-2',
|
|
80
|
+
label: 'Encode',
|
|
81
|
+
kind: 'video-encode',
|
|
82
|
+
scope: 'focus',
|
|
83
|
+
status: 'running',
|
|
84
|
+
});
|
|
85
|
+
const edge = shell.listActivities()[0]?.edge;
|
|
86
|
+
expect(edge).toBeDefined();
|
|
87
|
+
expect(shell.panels[edge]).not.toBe('hidden');
|
|
88
|
+
});
|
|
89
|
+
it('flags progress-only updates as upsert with a previous snapshot', () => {
|
|
90
|
+
const shell = createShellState();
|
|
91
|
+
const events = [];
|
|
92
|
+
shell.watchActivities((event) => {
|
|
93
|
+
if (event.type !== 'remove') {
|
|
94
|
+
events.push(`${event.type}:${event.previous ? 'has-prev' : 'new'}`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
shell.upsertActivity({
|
|
98
|
+
id: 'encode-3',
|
|
99
|
+
label: 'Encode',
|
|
100
|
+
kind: 'video-encode',
|
|
101
|
+
scope: 'system',
|
|
102
|
+
status: 'running',
|
|
103
|
+
progress: 10,
|
|
104
|
+
});
|
|
105
|
+
shell.updateActivity('encode-3', { progress: 40 });
|
|
106
|
+
// A new activity is a notify-worthy "started"; a progress-only tick is an
|
|
107
|
+
// upsert carrying `previous`, which the toaster filters out.
|
|
108
|
+
expect(events).toEqual(['upsert:new', 'upsert:has-prev']);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -1,37 +1,28 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smoke test: the workspace subpath barrel must export the new
|
|
3
|
-
* NavTree and Breadcrumbs components (plus existing types).
|
|
4
|
-
*/
|
|
5
1
|
import { describe, expect, it } from 'vitest';
|
|
6
2
|
import * as workspace from '../index.js';
|
|
7
3
|
describe('workspace barrel', () => {
|
|
8
|
-
it('exports
|
|
9
|
-
expect(workspace.
|
|
4
|
+
it('exports AdminShell', () => {
|
|
5
|
+
expect(workspace.AdminShell).toBeDefined();
|
|
10
6
|
});
|
|
11
|
-
it('exports
|
|
12
|
-
expect(workspace.
|
|
7
|
+
it('exports shell state helpers', () => {
|
|
8
|
+
expect(workspace.createShellState).toBeDefined();
|
|
9
|
+
expect(workspace.resolveShellConfig).toBeDefined();
|
|
13
10
|
});
|
|
14
|
-
it('exports
|
|
15
|
-
expect(workspace.
|
|
11
|
+
it('exports settings and activity components', () => {
|
|
12
|
+
expect(workspace.ShellSettingsPanel).toBeDefined();
|
|
13
|
+
expect(workspace.ActivityList).toBeDefined();
|
|
16
14
|
});
|
|
17
|
-
it('exports
|
|
18
|
-
expect(workspace.
|
|
19
|
-
expect(typeof workspace.
|
|
15
|
+
it('exports tenant nav helper from the manifest implementation', () => {
|
|
16
|
+
expect(workspace.tenantNavFromManifest).toBeDefined();
|
|
17
|
+
expect(typeof workspace.tenantNavFromManifest).toBe('function');
|
|
20
18
|
});
|
|
21
19
|
it('exports pluralizeClassName helper', () => {
|
|
22
20
|
expect(workspace.pluralizeClassName).toBeDefined();
|
|
23
21
|
expect(typeof workspace.pluralizeClassName).toBe('function');
|
|
24
22
|
});
|
|
25
|
-
it('
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// (No runtime export; the `import type` above is the real check.)
|
|
30
|
-
const _typeCheck = {
|
|
31
|
-
isOpen: false,
|
|
32
|
-
activeTool: null,
|
|
33
|
-
context: null,
|
|
34
|
-
};
|
|
35
|
-
expect(_typeCheck.isOpen).toBe(false);
|
|
23
|
+
it('does not export first-generation shell components as first-class API', () => {
|
|
24
|
+
expect('WorkspaceShell' in workspace).toBe(false);
|
|
25
|
+
expect('RoleShell' in workspace).toBe(false);
|
|
26
|
+
expect('ToolsDock' in workspace).toBe(false);
|
|
36
27
|
});
|
|
37
28
|
});
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* If TypeScript ever rejects this file under strict checks, the regression
|
|
24
24
|
* from review thread #1239/PRRT_kwDOQDruXs6CbBcK has returned.
|
|
25
25
|
*/
|
|
26
|
-
import { type DefineToolsDockOptions, type
|
|
26
|
+
import { type DefineToolsDockOptions, type ToolsDockInstance } from '../../tools-dock/define-tools-dock.svelte.js';
|
|
27
|
+
import type { ToolDef, ToolsDockApi, ToolsDockContext } from '../../types.js';
|
|
27
28
|
import type { MyActions, MyData } from './typed-tool-types.js';
|
|
28
29
|
/** Type-only export: a tool registry that uses the typed component. */
|
|
29
30
|
export declare const typedTool: ToolDef;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-typed-tool.d.ts","sourceRoot":"","sources":["../../../../../src/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,
|
|
1
|
+
{"version":3,"file":"register-typed-tool.d.ts","sourceRoot":"","sources":["../../../../../src/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,KAAK,sBAAsB,EAE3B,KAAK,iBAAiB,EACvB,MAAM,8CAA8C,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/D,uEAAuE;AACvE,eAAO,MAAM,SAAS,EAAE,OAQvB,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,YAAY,EAAE,sBAAsB,CAAC,MAAM,EAAE,SAAS,CAElE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAO5D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,gBAK5B,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,SAAS,CAAC,SAAS,MAAM,CAAC,GACrE,CAAC,GACD,KAAK,CAAC;AAEV;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC,CAmC3E"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* If TypeScript ever rejects this file under strict checks, the regression
|
|
24
24
|
* from review thread #1239/PRRT_kwDOQDruXs6CbBcK has returned.
|
|
25
25
|
*/
|
|
26
|
-
import { defineToolsDock, } from '../../
|
|
26
|
+
import { defineToolsDock, } from '../../tools-dock/define-tools-dock.svelte.js';
|
|
27
27
|
import TypedTool from './TypedTool.svelte';
|
|
28
28
|
/** Type-only export: a tool registry that uses the typed component. */
|
|
29
29
|
export const typedTool = {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Badge } from '@happyvertical/smrt-ui/ui';
|
|
3
|
+
import { useAdminShell } from './context.js';
|
|
4
|
+
import type { PanelEdge } from './types.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
edge: PanelEdge;
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { edge, label = 'Running activities' }: Props = $props();
|
|
12
|
+
const shell = useAdminShell();
|
|
13
|
+
const badge = $derived(shell.activityBadge(edge));
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
{#if badge.count > 0 || badge.hasFailed}
|
|
17
|
+
<Badge
|
|
18
|
+
variant={badge.hasFailed ? 'error' : 'primary'}
|
|
19
|
+
size="sm"
|
|
20
|
+
title={label}
|
|
21
|
+
aria-label={`${label}: ${badge.count}`}
|
|
22
|
+
>
|
|
23
|
+
{badge.progress !== null ? `${Math.round(badge.progress)}%` : badge.count}
|
|
24
|
+
</Badge>
|
|
25
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PanelEdge } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
edge: PanelEdge;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ActivityBadge: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ActivityBadge = ReturnType<typeof ActivityBadge>;
|
|
8
|
+
export default ActivityBadge;
|
|
9
|
+
//# sourceMappingURL=ActivityBadge.svelte.d.ts.map
|