@happyvertical/smrt-template-sveltekit 0.38.3 → 0.38.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-template-sveltekit",
3
- "version": "0.38.3",
3
+ "version": "0.38.4",
4
4
  "description": "SvelteKit project template with SMRT framework integration",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -29,7 +29,7 @@
29
29
  "directory": "packages/template-sveltekit"
30
30
  },
31
31
  "peerDependencies": {
32
- "@happyvertical/smrt-core": "0.38.3"
32
+ "@happyvertical/smrt-core": "0.38.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "vitest": "^4.1.9"
@@ -39,8 +39,11 @@ A SvelteKit application with SMRT framework integration for rapid development of
39
39
  │ │ └── tenancy.ts # Pluggable tenant resolver
40
40
  │ └── routes/
41
41
  │ ├── api/ # Auto-generated API routes (don't edit!)
42
+ │ ├── +layout.server.ts # Builds AdminShell tenant nav server-side (manifest → nav)
43
+ │ ├── +layout.svelte # WASD AdminShell chrome wrapping every page
44
+ │ ├── settings/ # ShellSettingsPanel (panel layout + hotkeys)
42
45
  │ ├── +page.server.ts # Home page server load + demo mutation (reference pattern)
43
- │ └── +page.svelte # Home page
46
+ │ └── +page.svelte # Home page (renders inside AdminShell)
44
47
  ├── smrt.config.ts # Root SMRT config
45
48
  ├── vite.config.ts # Vite + SMRT plugin config
46
49
  └── svelte.config.js # SvelteKit config
@@ -226,6 +229,84 @@ same REST route segment as `depends`/`invalidate`). Add
226
229
  `@happyvertical/smrt-web` and `@happyvertical/smrt-svelte` to the project's
227
230
  dependencies before using this pattern.
228
231
 
232
+ ## Workspace shell (AdminShell)
233
+
234
+ This template renders every page inside the **WASD AdminShell** — a four-edge
235
+ admin layout from `@happyvertical/smrt-svelte/workspace`. Each edge is a scope
236
+ (top = app, left = tenant, right = focus, bottom = system) toggled with
237
+ <kbd>W</kbd>/<kbd>A</kbd>/<kbd>S</kbd>/<kbd>D</kbd> (press <kbd>?</kbd> for the
238
+ shortcuts). It is wired in the root layout:
239
+
240
+ - `src/routes/+layout.server.ts` builds the left "tenant" nav **server-side**
241
+ with `tenantNavFromManifest()` from the SMRT manifest (`.smrt/manifest.json`)
242
+ and returns it as layout data — no client-side nav fetch.
243
+ - `src/routes/+layout.svelte` wraps `{@render children()}` in `<AdminShell>`
244
+ (inside `<ThemeProvider>` — see **Theming** below), feeding that nav to
245
+ `<TenantNav>`, a brand/app panel on top, and a couple of status chips on the
246
+ bottom.
247
+ - `src/routes/settings/+page.svelte` drops in `<ShellSettingsPanel>` so users
248
+ can toggle panels and remap the hotkeys; the choices persist in
249
+ `localStorage` under the shell's `storageKey`.
250
+
251
+ **The shell preserves the server-load pattern above.** AdminShell's public core
252
+ is SSR-safe: it renders statically on the server and only activates hotkeys /
253
+ `localStorage` after mount. The active page still renders inside the shell's
254
+ `<main>` and keeps its own `+page.server.ts` load and `invalidate()` refresh —
255
+ the shell is chrome around the page, not a replacement for its data flow. The
256
+ home page's `depends('smrt:items')` / `invalidate('smrt:items')` cycle works
257
+ unchanged.
258
+
259
+ ### Theming
260
+
261
+ The layout wraps the shell in **`<ThemeProvider>`** from
262
+ `@happyvertical/smrt-ui/themes` — the standard SMRT theming wrapper (the same
263
+ pattern the reference `@happyvertical/smrt-content` app uses):
264
+
265
+ ```svelte
266
+ <script lang="ts">
267
+ import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
268
+ // @font-face rules for the SMRT type stack (Space Grotesk / Inter /
269
+ // JetBrains Mono woff2). ThemeProvider supplies the token *variables*;
270
+ // this loads the font *files*. Bundled — no CDN request.
271
+ import '@happyvertical/smrt-ui/themes/styles/fonts.css';
272
+ </script>
273
+
274
+ <ThemeProvider colorScheme="system" persist={true}>
275
+ <!-- AdminShell + every page render themed inside here -->
276
+ </ThemeProvider>
277
+ ```
278
+
279
+ - ThemeProvider injects the **entire `--smrt-*` token set** (colors, typography,
280
+ spacing, radius, elevation, motion, z-index) as an inline style it computes
281
+ during render — so the tokens are present in the SSR HTML with **no unstyled
282
+ flash**, and it re-resolves them on the client. You do **not** need to import
283
+ `styles/tokens.css` or `themes/styles/*.css` separately; the provider is the
284
+ runtime source of truth.
285
+ - `colorScheme="system"` follows the OS light/dark preference; `persist` saves a
286
+ user's explicit switch to `localStorage`. Switch presets/scheme at runtime
287
+ with `ThemeSwitcher` / `ColorSchemeToggle` (also from `.../themes`), or read
288
+ the current theme via the theme context.
289
+ - The one thing the provider does not carry is the font **files** — hence the
290
+ `themes/styles/fonts.css` import above. Drop it and the type stack degrades to
291
+ `system-ui` / `ui-monospace` automatically.
292
+
293
+ To pin a specific look instead of following the OS, set
294
+ `colorScheme="light"` (or `"dark"`) and `preset="material"` (or `"glass"` /
295
+ `"studio"` / `"smrt"`).
296
+
297
+ The status chips ship static (`Local`, `Ready`). To make them (and the focus
298
+ edge) live — job counts, dispatch depth, connection state — feed real values
299
+ through `systemFeed` (`@happyvertical/smrt-svelte/workspace/live`) or
300
+ `activityFeed` (`@happyvertical/smrt-svelte/web`).
301
+
302
+ - **Migration guide** (old `WorkspaceShell`/`RoleShell` → `AdminShell`, and why
303
+ adoption is additive):
304
+ [`@happyvertical/smrt-svelte` → `src/components/workspace/MIGRATION.md`](https://github.com/happyvertical/smrt/blob/main/packages/smrt-svelte/src/components/workspace/MIGRATION.md)
305
+ - **Four-scope demo** (all edges, focus tools, activities) and the live-feed
306
+ variants, in the smrt-svelte playground:
307
+ `playground/src/routes/admin-shell`, `admin-shell-activity-feed`, and
308
+ `admin-shell-system-feed`.
309
+
229
310
  ## Multi-tenancy
230
311
 
231
312
  This template ships with multi-tenancy pre-wired. Out of the box you get:
@@ -20,8 +20,10 @@
20
20
  "vite": "^8.1.3"
21
21
  },
22
22
  "dependencies": {
23
- "@happyvertical/smrt-core": "^0.38.3",
24
- "@happyvertical/smrt-tenancy": "^0.38.3",
25
- "@happyvertical/smrt-users": "^0.38.3"
23
+ "@happyvertical/smrt-core": "^0.38.4",
24
+ "@happyvertical/smrt-svelte": "^0.38.4",
25
+ "@happyvertical/smrt-tenancy": "^0.38.4",
26
+ "@happyvertical/smrt-ui": "^0.38.4",
27
+ "@happyvertical/smrt-users": "^0.38.4"
26
28
  }
27
29
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Root layout server load — builds the AdminShell tenant navigation.
3
+ *
4
+ * The WASD AdminShell (`src/routes/+layout.svelte`) is the default chrome for
5
+ * this app. Its left "tenant" rail is driven by a nav tree derived from the
6
+ * SMRT manifest, and that tree is built HERE — server-side — rather than with a
7
+ * client-side fetch. This mirrors the home page's data pattern
8
+ * (`+page.server.ts`): data is produced during SSR, serialized into the initial
9
+ * HTML, and hydrated on the client with no duplicate request.
10
+ *
11
+ * `tenantNavFromManifest()` is a pure function (data in → data out). It reads
12
+ * the same generated manifest the runtime uses (`.smrt/manifest.json`, written
13
+ * by `smrtPlugin()` and already loaded by `src/lib/server/smrt.ts`), so the nav
14
+ * stays in sync with your `@smrt()` classes automatically instead of being
15
+ * hand-maintained.
16
+ *
17
+ * This load reads no URL/params and declares no `depends()`, so after its
18
+ * initial SSR run SvelteKit does not re-run it on client navigations — only on
19
+ * a full reload or `invalidateAll()`. The manifest-derived nav is therefore
20
+ * effectively static for the session, which is fine (the manifest is fixed at
21
+ * build time). It is independent of each page's own load, so the home page's
22
+ * `depends('smrt:items')` / `invalidate('smrt:items')` refresh flow is
23
+ * untouched by this file.
24
+ */
25
+
26
+ import { existsSync } from 'node:fs';
27
+ import { join } from 'node:path';
28
+
29
+ import { loadManifestFromPathSync } from '@happyvertical/smrt-core/manifest';
30
+ import {
31
+ type ShellNavItem,
32
+ type SmrtManifestLike,
33
+ tenantNavFromManifest,
34
+ } from '@happyvertical/smrt-svelte/workspace';
35
+ import type { LayoutServerLoad } from './$types';
36
+
37
+ export const load: LayoutServerLoad = async () => {
38
+ // Same manifest file `src/lib/server/smrt.ts` seeds the registry from. Reading
39
+ // it here returns the parsed object we hand to the pure nav builder; if it is
40
+ // not generated yet (fresh checkout before the first `vite dev`/`vite build`)
41
+ // we fall back to an empty rail rather than failing the whole layout.
42
+ const manifestPath = join(process.cwd(), '.smrt', 'manifest.json');
43
+
44
+ let nav: ShellNavItem[] = [];
45
+ if (existsSync(manifestPath)) {
46
+ const manifest = loadManifestFromPathSync(manifestPath);
47
+ if (manifest) {
48
+ // `tenantNavFromManifest` reads a documented subset of the manifest
49
+ // (`SmrtManifestLike`). The core `SmartObjectManifest` carries richer
50
+ // field types (e.g. `ApiConfig` has no index signature), so narrow it to
51
+ // the structural shape the pure helper consumes.
52
+ //
53
+ // `basePath: ''` emits page-style hrefs (`/items`) for the developer to
54
+ // wire to their own list routes. `sectionHints` groups classes by
55
+ // package into readable section titles — extend it as you add packages.
56
+ // `NavSection[]` is structurally a superset of `ShellNavItem[]`, so it
57
+ // feeds straight into <TenantNav items={nav}>.
58
+ nav = tenantNavFromManifest(manifest as unknown as SmrtManifestLike, {
59
+ basePath: '',
60
+ sectionHints: {
61
+ '@happyvertical/smrt-content': 'Content',
62
+ '@happyvertical/smrt-users': 'Users',
63
+ },
64
+ });
65
+ }
66
+ }
67
+
68
+ return { nav };
69
+ };
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import {
4
+ AdminShell,
5
+ AppScopePanel,
6
+ type ShellStatusChip,
7
+ SystemStatusChips,
8
+ TenantNav,
9
+ } from '@happyvertical/smrt-svelte/workspace';
10
+ import { ThemeProvider } from '@happyvertical/smrt-ui/themes';
11
+ // Self-hosted @font-face rules for the SMRT type stack (Space Grotesk /
12
+ // Inter / JetBrains Mono woff2). `<ThemeProvider>` supplies every `--smrt-*`
13
+ // token variable at runtime, but not the font FILES — this import loads them
14
+ // so `--smrt-font-family` renders as the real stack instead of falling back
15
+ // to system-ui. Bundled with the app; no CDN request.
16
+ import '@happyvertical/smrt-ui/themes/styles/fonts.css';
17
+ import type { LayoutProps } from './$types';
18
+
19
+ // `data.nav` is built server-side in `+layout.server.ts` from the SMRT
20
+ // manifest and hydrated here — no client-side nav fetch. `children` is the
21
+ // active page, which renders inside AdminShell's `<main>` and keeps its own
22
+ // server load + `invalidate` flow (see `+page.server.ts`).
23
+ let { data, children }: LayoutProps = $props();
24
+
25
+ // Both wrappers are SSR-safe. ThemeProvider emits its `--smrt-*` token
26
+ // variables as an inline style computed during render (no `window`), so the
27
+ // tokens are present in the server HTML with no unstyled flash; it only reads
28
+ // `matchMedia` after mount to resolve `colorScheme="system"`. AdminShell's
29
+ // public core likewise renders statically and only wires WASD hotkeys /
30
+ // localStorage persistence after mount. Nothing here reads `window` or
31
+ // `localStorage` at module or render time. `$app/state`'s `page` is populated
32
+ // on the server too, so `currentHref` is correct on first paint.
33
+ const currentHref = $derived(page.url.pathname);
34
+
35
+ // Minimal, static system chips. Full live wiring (job counts, dispatch
36
+ // depth, connection state) is a documented next step: feed real values
37
+ // through `systemFeed` (`@happyvertical/smrt-svelte/workspace/live`) or
38
+ // `activityFeed` (`@happyvertical/smrt-svelte/web`)
39
+ // — see the migration guide and the playground `admin-shell-system-feed`
40
+ // demo. Keeping these static here avoids loading the client engine on every
41
+ // page of the scaffold.
42
+ const statusChips: ShellStatusChip[] = [
43
+ { id: 'env', label: 'Local', tone: 'info' },
44
+ { id: 'connection', label: 'Ready', tone: 'success' },
45
+ ];
46
+ </script>
47
+
48
+ <!--
49
+ ThemeProvider is the standard SMRT theming wrapper (mirrors the reference app
50
+ `@happyvertical/smrt-content` → `src/routes/+layout.svelte`). It injects the
51
+ full `--smrt-*` token set the shell and pages consume, and `colorScheme="system"`
52
+ follows the OS light/dark preference at runtime; `persist` remembers a user's
53
+ explicit choice. Wrap it OUTERMOST so AdminShell and every page render themed.
54
+ -->
55
+ <ThemeProvider colorScheme="system" persist={true}>
56
+ <AdminShell title="SMRT App" subtitle="SvelteKit" storageKey="smrt-app-shell">
57
+ {#snippet appPanel()}
58
+ <AppScopePanel
59
+ appName="SMRT App"
60
+ tenantName="Local development"
61
+ environment="local"
62
+ showSettings={false}
63
+ >
64
+ {#snippet docs()}
65
+ <nav class="app-scope-links">
66
+ <a href="/settings">Shell settings</a>
67
+ </nav>
68
+ {/snippet}
69
+ </AppScopePanel>
70
+ {/snippet}
71
+
72
+ {#snippet tenantPanel()}
73
+ <TenantNav items={data.nav} {currentHref} />
74
+ {/snippet}
75
+
76
+ {#snippet systemBar()}
77
+ <div class="system-bar">
78
+ <SystemStatusChips chips={statusChips} />
79
+ <a class="system-bar__settings" href="/settings">Settings</a>
80
+ </div>
81
+ {/snippet}
82
+
83
+ {@render children()}
84
+ </AdminShell>
85
+ </ThemeProvider>
86
+
87
+ <style>
88
+ :global(body) {
89
+ margin: 0;
90
+ }
91
+
92
+ .app-scope-links {
93
+ display: grid;
94
+ gap: var(--smrt-spacing-1);
95
+ }
96
+
97
+ .app-scope-links a,
98
+ .system-bar__settings {
99
+ color: var(--smrt-color-primary);
100
+ text-decoration: none;
101
+ }
102
+
103
+ .system-bar {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: space-between;
107
+ gap: var(--smrt-spacing-3);
108
+ inline-size: 100%;
109
+ }
110
+ </style>
@@ -16,7 +16,14 @@
16
16
  <title>SMRT SvelteKit App</title>
17
17
  </svelte:head>
18
18
 
19
- <main>
19
+ <!--
20
+ This page renders inside the root layout's <AdminShell> `<main>`
21
+ (`src/routes/+layout.svelte`). Its server load + `invalidate('smrt:items')`
22
+ refresh flow is unchanged by the shell — the shell is chrome around the
23
+ page, not a replacement for its data loading. A plain wrapper element (not a
24
+ second <main>) keeps the HTML valid.
25
+ -->
26
+ <div class="page">
20
27
  <h1>Welcome to SMRT + SvelteKit</h1>
21
28
 
22
29
  <section>
@@ -94,10 +101,10 @@
94
101
  <li>API routes are auto-generated in <code>src/routes/api/</code></li>
95
102
  </ul>
96
103
  </section>
97
- </main>
104
+ </div>
98
105
 
99
106
  <style>
100
- main {
107
+ .page {
101
108
  max-width: 800px;
102
109
  margin: 0 auto;
103
110
  padding: 2rem;
@@ -0,0 +1,41 @@
1
+ <script lang="ts">
2
+ import { ShellSettingsPanel } from '@happyvertical/smrt-svelte/workspace';
3
+
4
+ // This page renders inside the root layout's <AdminShell>, so the shell
5
+ // context set by AdminShell is available to descendants — ShellSettingsPanel
6
+ // reads it via useAdminShell() with no extra wiring. It lets users toggle
7
+ // panel visibility and remap the WASD hotkeys; changes persist through the
8
+ // shell's `storageKey` (localStorage), applied client-side after mount.
9
+ </script>
10
+
11
+ <svelte:head>
12
+ <title>Settings · SMRT App</title>
13
+ </svelte:head>
14
+
15
+ <section class="settings">
16
+ <header>
17
+ <h1>Settings</h1>
18
+ <p>Adjust the workspace shell — panel layout and keyboard shortcuts.</p>
19
+ </header>
20
+
21
+ <ShellSettingsPanel />
22
+ </section>
23
+
24
+ <style>
25
+ .settings {
26
+ display: grid;
27
+ gap: var(--smrt-spacing-6, 1.5rem);
28
+ max-width: 720px;
29
+ padding: var(--smrt-spacing-8, 2rem);
30
+ }
31
+
32
+ .settings header {
33
+ display: grid;
34
+ gap: var(--smrt-spacing-2, 0.5rem);
35
+ }
36
+
37
+ .settings h1,
38
+ .settings p {
39
+ margin: 0;
40
+ }
41
+ </style>