@jskit-ai/agent-docs 0.1.1

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.
Files changed (73) hide show
  1. package/DISTR_AGENT.md +25 -0
  2. package/guide/agent/app-extras/assistant.md +636 -0
  3. package/guide/agent/app-extras/realtime.md +223 -0
  4. package/guide/agent/app-setup/a-more-interesting-shell.md +643 -0
  5. package/guide/agent/app-setup/authentication.md +948 -0
  6. package/guide/agent/app-setup/console.md +316 -0
  7. package/guide/agent/app-setup/database-layer.md +775 -0
  8. package/guide/agent/app-setup/initial-scaffolding.md +714 -0
  9. package/guide/agent/app-setup/multi-homing.md +655 -0
  10. package/guide/agent/app-setup/users.md +355 -0
  11. package/guide/agent/app-setup/working-with-the-jskit-cli.md +983 -0
  12. package/guide/agent/generators/advanced-cruds.md +923 -0
  13. package/guide/agent/generators/crud-generators.md +556 -0
  14. package/guide/agent/generators/intro.md +63 -0
  15. package/guide/agent/generators/ui-generators.md +648 -0
  16. package/guide/agent/index.md +39 -0
  17. package/guide/human/app-extras/assistant.md +695 -0
  18. package/guide/human/app-extras/realtime.md +270 -0
  19. package/guide/human/app-setup/a-more-interesting-shell.md +734 -0
  20. package/guide/human/app-setup/authentication.md +963 -0
  21. package/guide/human/app-setup/console.md +352 -0
  22. package/guide/human/app-setup/database-layer.md +822 -0
  23. package/guide/human/app-setup/initial-scaffolding.md +738 -0
  24. package/guide/human/app-setup/multi-homing.md +795 -0
  25. package/guide/human/app-setup/users.md +404 -0
  26. package/guide/human/app-setup/working-with-the-jskit-cli.md +997 -0
  27. package/guide/human/generators/advanced-cruds.md +923 -0
  28. package/guide/human/generators/crud-generators.md +556 -0
  29. package/guide/human/generators/intro.md +109 -0
  30. package/guide/human/generators/ui-generators.md +665 -0
  31. package/guide/human/index.md +39 -0
  32. package/package.json +28 -0
  33. package/reference/autogen/KERNEL_MAP.md +536 -0
  34. package/reference/autogen/README.md +44 -0
  35. package/reference/autogen/packages/agent-docs.md +13 -0
  36. package/reference/autogen/packages/assistant-core.md +310 -0
  37. package/reference/autogen/packages/assistant-runtime.md +219 -0
  38. package/reference/autogen/packages/assistant.md +73 -0
  39. package/reference/autogen/packages/auth-core.md +352 -0
  40. package/reference/autogen/packages/auth-provider-supabase-core.md +223 -0
  41. package/reference/autogen/packages/auth-web.md +267 -0
  42. package/reference/autogen/packages/console-core.md +116 -0
  43. package/reference/autogen/packages/console-web.md +37 -0
  44. package/reference/autogen/packages/crud-core.md +283 -0
  45. package/reference/autogen/packages/crud-server-generator.md +220 -0
  46. package/reference/autogen/packages/crud-ui-generator.md +154 -0
  47. package/reference/autogen/packages/database-runtime-mysql.md +61 -0
  48. package/reference/autogen/packages/database-runtime-postgres.md +39 -0
  49. package/reference/autogen/packages/database-runtime.md +216 -0
  50. package/reference/autogen/packages/http-runtime.md +213 -0
  51. package/reference/autogen/packages/kernel.md +1350 -0
  52. package/reference/autogen/packages/realtime.md +95 -0
  53. package/reference/autogen/packages/shell-web.md +349 -0
  54. package/reference/autogen/packages/storage-runtime.md +39 -0
  55. package/reference/autogen/packages/ui-generator.md +101 -0
  56. package/reference/autogen/packages/uploads-image-web.md +76 -0
  57. package/reference/autogen/packages/uploads-runtime.md +85 -0
  58. package/reference/autogen/packages/users-core.md +307 -0
  59. package/reference/autogen/packages/users-web.md +473 -0
  60. package/reference/autogen/packages/workspaces-core.md +415 -0
  61. package/reference/autogen/packages/workspaces-web.md +372 -0
  62. package/reference/autogen/tooling/config-eslint.md +52 -0
  63. package/reference/autogen/tooling/create-app.md +194 -0
  64. package/reference/autogen/tooling/jskit-catalog.md +27 -0
  65. package/reference/autogen/tooling/jskit-cli.md +624 -0
  66. package/reference/autogen/tooling/test-support.md +27 -0
  67. package/reference/autogen/tooling/testUtils.md +31 -0
  68. package/templates/APP_BLUEPRINT.md +57 -0
  69. package/workflow/app-state.md +33 -0
  70. package/workflow/bootstrap.md +24 -0
  71. package/workflow/feature-delivery.md +21 -0
  72. package/workflow/review.md +22 -0
  73. package/workflow/scoping.md +26 -0
@@ -0,0 +1,643 @@
1
+ <!-- Generated by `npm run agent-docs:build` from `docs/guide/app-setup/a-more-interesting-shell.md`. Do not edit manually. -->
2
+
3
+ # A more interesting shell
4
+
5
+ In the first chapter, the app was intentionally plain. That was useful because it let us see the smallest possible JSKIT scaffold without too many moving parts. In this chapter, we install `shell-web`, which is the package that turns that bare starting point into a real shell: a layout, navigation outlets, a first settings area, and a proper app-level error host.
6
+
7
+ This still is not authentication, not database work, and not multi-surface routing. The app remains small. But it starts to look and behave like something you could actually grow.
8
+
9
+ ## Installing `shell-web`
10
+
11
+ From inside `exampleapp`, run:
12
+
13
+ ```bash
14
+ npx jskit add package shell-web
15
+ npm install
16
+ ```
17
+
18
+ `jskit add` rewrites part of the scaffold and records the installed runtime package in `.jskit/lock.json`. The following `npm install` is what actually downloads the new package and its supporting dependencies.
19
+
20
+ To see the result, run:
21
+
22
+ ```bash
23
+ npm run dev
24
+ npm run server
25
+ ```
26
+
27
+ This time you really want both processes running. The new home page fetches `/api/health`, so the browser-facing dev server on port `5173` expects the backend on port `3000` to be alive as well.
28
+
29
+ **Important: Install It Early**
30
+
31
+ `shell-web` is not just adding new files. It also **claims and replaces part of the original scaffold** so the app can switch from the plain starter layout to the real shell layout.
32
+
33
+ That replacement is intentionally strict: `shell-web` only takes over scaffold files if they are still **exactly** the same as the files that `create-app` originally wrote. If you have already edited those starter files, `shell-web` refuses to claim them instead of overwriting your work.
34
+
35
+ That is why the intended flow is:
36
+
37
+ 1. scaffold the app
38
+ 2. install `shell-web`
39
+ 3. start personalizing the shell
40
+
41
+ If you build directly on top of the plain scaffold first and only try to add `shell-web` later, the install may fail because those app-owned files no longer match the untouched scaffold baseline.
42
+
43
+ Open `http://localhost:5173/` in the browser. The app still lands in the `home` surface, but that surface is no longer just a card in the middle of the page. It is wrapped in a real shell with an app bar, a navigation drawer, and a settings route at `/home/settings`.
44
+
45
+ Two important things have changed compared with the older starter shell.
46
+
47
+ - Navigation now lives in the drawer itself. `Home` and `Settings` are real shell menu entries from the start.
48
+ - `Settings` is already a real nested section. Opening `/home/settings` redirects to `/home/settings/general`, and the left-side menu already contains a starter `General` entry.
49
+
50
+ Open `http://localhost:5173/home/settings` in the browser to see that nested settings shell immediately:
51
+
52
+ ## Module features
53
+
54
+ The most important new idea in `shell-web` is that the app now has _specific, named places_ where UI can be inserted later. JSKIT calls those places _placements_. In practice, this means later packages or generators do not have to rewrite the whole shell every time they want to add a menu entry, a widget, or a settings section.
55
+
56
+ Start by asking JSKIT what placement targets already exist:
57
+
58
+ ```bash
59
+ npx jskit list-placements
60
+ ```
61
+
62
+ In a fresh `shell-web` app, the result looks like this:
63
+
64
+ ```text
65
+ Available placements:
66
+ - home-settings:primary-menu [src/pages/home/settings.vue]
67
+ - shell-layout:primary-menu (default) [src/components/ShellLayout.vue]
68
+ - shell-layout:secondary-menu [src/components/ShellLayout.vue]
69
+ - shell-layout:top-left [src/components/ShellLayout.vue]
70
+ - shell-layout:top-right [src/components/ShellLayout.vue]
71
+ ```
72
+
73
+ This command lists placement targets, not the content inside them. That is why the output is about target names and source files. Later, when you place things into the shell, this list stays the same unless you add or remove a `ShellOutlet` in source.
74
+
75
+ Those target names come from real `ShellOutlet` elements in the app. See `src/components/ShellLayout.vue`:
76
+
77
+ ```html
78
+ ...
79
+ <ShellOutlet target="shell-layout:top-left" />
80
+ ...
81
+ <ShellOutlet target="shell-layout:top-right" />
82
+ ...
83
+ <ShellOutlet
84
+ target="shell-layout:primary-menu"
85
+ default
86
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
87
+ />
88
+ ...
89
+ <ShellOutlet
90
+ target="shell-layout:secondary-menu"
91
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
92
+ />
93
+ ```
94
+
95
+ And the settings page introduces its own nested outlet in `src/pages/home/settings.vue`:
96
+
97
+ ```html
98
+ <ShellOutlet
99
+ target="home-settings:primary-menu"
100
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
101
+ />
102
+ ```
103
+
104
+ That nested example matters. It shows that the shell is not the only place that can host placements. A page inside the shell can define its own insertion point too. That is how JSKIT can later build menus inside sections such as settings without rewriting the whole shell.
105
+
106
+ Just as importantly, `shell-web` already uses that placement system itself. The starter app is not only exposing placement targets; it is also seeding real placement entries into them. The drawer gets `Home` and `Settings`, and the nested settings menu gets `General`. That is why the shell already feels real before you generate anything of your own.
107
+
108
+ The shell also ships with a few app-owned component tokens that it can use as default link renderers. You can inspect those too:
109
+
110
+ ```bash
111
+ npx jskit list-component-tokens --prefix local.main.
112
+ ```
113
+
114
+ The starter result looks like this:
115
+
116
+ ```text
117
+ Available placement component tokens:
118
+ Showing link-item tokens only (token must end with "link-item"). Tip: use --all for full token list.
119
+ - local.main.ui.menu-link-item [app:packages/main/src/client/providers/MainClientProvider.js]
120
+ - local.main.ui.surface-aware-menu-link-item [app:packages/main/src/client/providers/MainClientProvider.js, app:src/placement.js, package:@jskit-ai/shell-web:templates/src/placement.js]
121
+ - local.main.ui.tab-link-item [app:packages/main/src/client/providers/MainClientProvider.js]
122
+ ```
123
+
124
+ So before we add anything of our own, the shell already knows about three local link-item tokens. They are app-owned components registered by the local package, and the shell uses them when an outlet needs to render links or tabs.
125
+
126
+ At this point the shell is already using placements itself. The next step is to add one of our own.
127
+
128
+ ### Adding generic elements directly
129
+
130
+ To add a small UI element to the shell itself:
131
+
132
+ ```bash
133
+ npx jskit generate ui-generator placed-element --name "Alerts Widget"
134
+ ```
135
+
136
+ That command creates a Vue component under `src/components/` (in this case `src/components/AlertsWidgetElement.vue`), registers a new local token for it, and adds a placement entry targeting `shell-layout:top-right`. After running it, refresh the home page in the browser. The shell now has a real app-owned widget living inside one of its named placement targets.
137
+
138
+ In this app, there is no need to pass `--surface`: since the app only has one enabled surface, JSKIT can infer it automatically.
139
+
140
+ ### Adding a page with automatic menu placement
141
+
142
+ The settings host uses the same placement machinery, but the normal way to grow it is not by dropping a free-standing widget there. The more interesting case is adding a child page and letting JSKIT wire the menu entry for you.
143
+
144
+ The source path in the `list-placements` output helps you reason about where child pages belong. When you see:
145
+
146
+ ```text
147
+ - home-settings:primary-menu [src/pages/home/settings.vue]
148
+ ```
149
+
150
+ you know that the outlet lives in the settings host page. So if you want a child page to appear in that menu, you should create it under that part of the route tree instead of treating the menu like a generic widget area. For example, `src/pages/home/settings/profile/index.vue` belongs to that settings section, so JSKIT can wire its preferred menu entry into `home-settings:primary-menu` automatically.
151
+
152
+ Now use the settings host the way it is normally meant to be used: add a real child page under it.
153
+
154
+ ```bash
155
+ npx jskit generate ui-generator page home/settings/profile/index.vue --name "Profile"
156
+ ```
157
+
158
+ This is a more interesting example than the widget case. JSKIT creates the page file, notices that `src/pages/home/settings.vue` owns the `home-settings:primary-menu` outlet, and adds the preferred menu entry there automatically. You do not have to write that placement entry by hand.
159
+
160
+ Open `/home/settings/profile` in the browser. The settings shell now shows a second real child page and a second real menu entry created by the same page-generation command. `General` was already there from `shell-web`; `Profile` is the first additional settings page you add yourself. This is the important part of the chapter: the exact same placement system works both at the top shell level and inside a page-owned nested outlet.
161
+
162
+ Now add a second sibling page:
163
+
164
+ ```bash
165
+ npx jskit generate ui-generator page home/settings/notifications/index.vue --name "Notifications"
166
+ ```
167
+
168
+ Open `/home/settings/notifications` in the browser. You now get a third settings menu entry without touching `settings.vue`, without writing a second menu component, and without hand-editing `src/placement.js`. JSKIT appends another placement entry targeting the same `home-settings:primary-menu` outlet, so the links simply stack in the menu for free.
169
+
170
+ The order is also easy to reason about:
171
+
172
+ - `General` comes first because `shell-web` seeds it with a lower order than generated child pages.
173
+ - `Profile` and `Notifications` both use the generator's default order, so between those two the menu keeps source order.
174
+
175
+ **Routing: Child Pages Under Layouts**
176
+
177
+ In JSKIT's file-based routing, a page file can act as a layout if it renders a `RouterView`.
178
+
179
+ - `src/pages/home/settings.vue` owns the settings shell and wraps its child routes.
180
+ - `src/pages/home/settings/index.vue` is now just a redirect, so `/home/settings` lands on `/home/settings/general`.
181
+ - `src/pages/home/settings/general/index.vue` is the first real child page created by the starter shell.
182
+ - `src/pages/home/settings/profile/index.vue` becomes `/home/settings/profile` and still renders inside the layout from `settings.vue`.
183
+
184
+ This is why the `home-settings:primary-menu` outlet from `list-placements` is such a useful clue: it tells you which page is acting as the host.
185
+
186
+ Even an `index.vue` page can have children. If you want an index page to stay visible while child routes render underneath it, put those children under an `index/` directory such as `src/pages/home/settings/profile/index/details.vue`.
187
+
188
+ **Icons: Menu Metadata Is Not The Same As Vue Icon Props**
189
+
190
+ When you start customizing generated pages and menu links, icons are one of the first details you usually add.
191
+
192
+ There are two different paths to keep straight:
193
+
194
+ 1. placement and menu metadata such as `src/placement.js`
195
+ 2. direct Vuetify icon props inside normal `.vue` components
196
+
197
+ In placement metadata, raw `mdi-*` strings are acceptable because the shell menu runtime normalizes them before Vuetify renders the icon. A menu entry like this is fine:
198
+
199
+ ```js
200
+ addPlacement({
201
+ id: "home.settings.profile.link",
202
+ target: "home-settings:primary-menu",
203
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
204
+ props: {
205
+ label: "Profile",
206
+ to: "./profile",
207
+ icon: "mdi-account-circle-outline"
208
+ }
209
+ });
210
+ ```
211
+
212
+ But do not copy that same string into a normal Vue component:
213
+
214
+ ```vue
215
+ <v-list-item prepend-icon="mdi-account-circle-outline" />
216
+ ```
217
+
218
+ JSKIT apps use Vuetify's SVG MDI renderer, so direct Vue icon props should use an `@mdi/js` path or a Vuetify alias instead:
219
+
220
+ ```vue
221
+ <script setup>
222
+ import { mdiAccountCircleOutline } from "@mdi/js";
223
+ </script>
224
+
225
+ <v-list-item :prepend-icon="mdiAccountCircleOutline" />
226
+ ```
227
+
228
+ So the practical rule is:
229
+
230
+ - editing `src/placement.js` or other shell menu metadata: `mdi-*` strings are fine
231
+ - editing a normal `.vue` file: use `@mdi/js` or a Vuetify alias such as `$close`
232
+
233
+ Later in the guide, `jskit doctor` will help catch the second mistake automatically.
234
+
235
+ ### Component tokens
236
+
237
+ If you rerun the token listing now, you will see that only the widget command created a new app-owned component token:
238
+
239
+ ```bash
240
+ npx jskit list-component-tokens --all --prefix local.main.
241
+ ```
242
+
243
+ The output now includes:
244
+
245
+ ```text
246
+ - local.main.ui.element.alerts-widget
247
+ - local.main.ui.menu-link-item
248
+ - local.main.ui.surface-aware-menu-link-item
249
+ - local.main.ui.tab-link-item
250
+ ```
251
+
252
+ That difference is useful. `npx jskit generate ui-generator placed-element` creates a new component token. `page` reuses the existing default link-item token for the parent outlet and adds a placement entry for the new route.
253
+
254
+ So the shell story in this chapter is:
255
+
256
+ - `ShellOutlet` defines named places where UI can appear
257
+ - `jskit list-placements` shows those places
258
+ - `shell-web` already uses those places for the starter `Home`, `Settings`, and `General` entries
259
+ - `jskit generate ui-generator placed-element ...` creates app-owned UI and places it into one of them
260
+ - `jskit generate ui-generator page ...` can also discover a parent outlet and add the right menu entry automatically
261
+ - repeating that page command for the same host gives you a stacked menu, still without hand-editing the host page
262
+ - nested pages can host placements too, not just the top shell
263
+
264
+ That is the first real example of JSKIT behaving like an extension system rather than just a scaffold generator.
265
+
266
+ ## What `shell-web` changes in the app
267
+
268
+ The most interesting files now look roughly like this:
269
+
270
+ ```text
271
+ src/
272
+ components/
273
+ AlertsWidgetElement.vue
274
+ ShellLayout.vue
275
+ menus/
276
+ MenuLinkItem.vue
277
+ SurfaceAwareMenuLinkItem.vue
278
+ TabLinkItem.vue
279
+ error.js
280
+ placement.js
281
+ pages/
282
+ home.vue
283
+ home/
284
+ index.vue
285
+ settings.vue
286
+ settings/
287
+ index.vue
288
+ general/
289
+ index.vue
290
+ profile/
291
+ index.vue
292
+ notifications/
293
+ index.vue
294
+ ```
295
+
296
+ This chapter is the first time the scaffold starts to feel layered instead of flat.
297
+
298
+ ### `package.json` and `.jskit/lock.json`
299
+
300
+ The first file worth reopening is still `package.json`. After `shell-web`, the most important new dependency entries are:
301
+
302
+ ```json
303
+ {
304
+ "dependencies": {
305
+ "@jskit-ai/shell-web": "0.x",
306
+ "@tanstack/vue-query": "^5.90.5",
307
+ "@mdi/js": "^7.4.47"
308
+ }
309
+ }
310
+ ```
311
+
312
+ The important part is not just that `@jskit-ai/shell-web` appears. The package also brings in Vue Query and icon data because the starter shell now includes a live health check and shell-specific UI elements.
313
+
314
+ It is also worth noticing what does **not** happen here. The `placed-element` and `page` commands from this chapter mutate app-owned files, but they do not add a permanent runtime dependency to `package.json`. They are tooling actions, not runtime package installs.
315
+
316
+ The lock file becomes more interesting too. In the first chapter, `.jskit/lock.json` only knew about `@local/main`. Now it also records `@jskit-ai/shell-web` and the exact files and text mutations that package introduced.
317
+
318
+ That is worth noticing because this is the first chapter where JSKIT is no longer just scaffolding a base app. It is now applying a runtime package that owns concrete changes in your app tree.
319
+
320
+ ### The `home` surface gets a real wrapper
321
+
322
+ The surface itself did not change. `home` is still the same surface defined in `config/public.js`. What changed is the page tree inside it.
323
+
324
+ Before `shell-web`, `src/pages/home.vue` was only a tiny route owner with a `RouterView`. After installing `shell-web`, it becomes:
325
+
326
+ ```vue
327
+ <route lang="json">
328
+ {
329
+ "meta": {
330
+ "jskit": {
331
+ "surface": "home"
332
+ }
333
+ }
334
+ }
335
+ </route>
336
+
337
+ <script setup>
338
+ import ShellLayout from "@/components/ShellLayout.vue";
339
+ import { RouterView } from "vue-router";
340
+ </script>
341
+
342
+ <template>
343
+ <ShellLayout title="" subtitle="">
344
+ <RouterView />
345
+ </ShellLayout>
346
+ </template>
347
+ ```
348
+
349
+ That one change explains a lot. The `home` surface is no longer just a place where pages live. It is now a shell-wrapped surface. Every child page under `src/pages/home/` renders inside that app-owned `ShellLayout`.
350
+
351
+ ### `src/placement.js` becomes the placement registry
352
+
353
+ After installing `shell-web`, the app gets a placement registry file:
354
+
355
+ ```js
356
+ import { createPlacementRegistry } from "@jskit-ai/shell-web/client/placement";
357
+
358
+ const registry = createPlacementRegistry();
359
+ const { addPlacement } = registry;
360
+
361
+ export { addPlacement };
362
+
363
+ export default function getPlacements() {
364
+ return registry.build();
365
+ }
366
+ ```
367
+
368
+ That file is the app-owned seam for placements. `shell-web` owns the runtime that can render placements, but the app owns the registry source that lists what should appear in those targets.
369
+
370
+ After the `shell-web` install plus the `placed-element` and `page` commands from this chapter, the bottom of the file now contains real placement entries:
371
+
372
+ ```js
373
+ addPlacement({
374
+ id: "shell-web.home.menu.home",
375
+ target: "shell-layout:primary-menu",
376
+ surfaces: ["*"],
377
+ order: 50,
378
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
379
+ props: {
380
+ label: "Home",
381
+ surface: "home",
382
+ scopedSuffix: "/",
383
+ unscopedSuffix: "/",
384
+ exact: true
385
+ }
386
+ });
387
+
388
+ addPlacement({
389
+ id: "shell-web.home.menu.settings",
390
+ target: "shell-layout:primary-menu",
391
+ surfaces: ["home"],
392
+ order: 100,
393
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
394
+ props: {
395
+ label: "Settings",
396
+ surface: "home",
397
+ scopedSuffix: "/settings",
398
+ unscopedSuffix: "/settings"
399
+ }
400
+ });
401
+
402
+ addPlacement({
403
+ id: "shell-web.home.settings.general",
404
+ target: "home-settings:primary-menu",
405
+ surfaces: ["home"],
406
+ order: 100,
407
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
408
+ props: {
409
+ label: "General",
410
+ surface: "home",
411
+ scopedSuffix: "/settings/general",
412
+ unscopedSuffix: "/settings/general",
413
+ to: "./general"
414
+ }
415
+ });
416
+
417
+ addPlacement({
418
+ id: "ui-generator.element.alerts-widget",
419
+ target: "shell-layout:top-right",
420
+ surfaces: ["home"],
421
+ order: 155,
422
+ componentToken: "local.main.ui.element.alerts-widget"
423
+ });
424
+
425
+ addPlacement({
426
+ id: "ui-generator.page.home.settings.profile.link",
427
+ target: "home-settings:primary-menu",
428
+ surfaces: ["home"],
429
+ order: 155,
430
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
431
+ props: {
432
+ label: "Profile",
433
+ surface: "home",
434
+ scopedSuffix: "/settings/profile",
435
+ unscopedSuffix: "/settings/profile",
436
+ to: "./profile"
437
+ }
438
+ });
439
+
440
+ addPlacement({
441
+ id: "ui-generator.page.home.settings.notifications.link",
442
+ target: "home-settings:primary-menu",
443
+ surfaces: ["home"],
444
+ order: 155,
445
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
446
+ props: {
447
+ label: "Notifications",
448
+ surface: "home",
449
+ scopedSuffix: "/settings/notifications",
450
+ unscopedSuffix: "/settings/notifications",
451
+ to: "./notifications"
452
+ }
453
+ });
454
+ ```
455
+
456
+ That snippet shows the full placement contract clearly:
457
+
458
+ - the target says where the UI should go
459
+ - the component token says what should render that entry
460
+ - `props.to` tells the generated menu link which child route to open
461
+ - `props.icon`, when you add one, belongs to menu metadata rather than direct Vuetify icon rendering
462
+ - the surface list says where it is active
463
+ - lower `order` values come first
464
+ - when multiple entries target the same outlet with the same order, the shell keeps their source order
465
+
466
+ That is why the settings menu now shows `General` first, followed by `Profile` and `Notifications`: `General` is seeded by `shell-web` with a lower order, while the two generated pages share the same later order and keep their source order.
467
+
468
+ So the shell itself remains stable. What changes is the registry that feeds it.
469
+
470
+ ### The local client provider publishes the app-owned components
471
+
472
+ The placement registry only points at tokens. Those tokens still need to resolve to real Vue components somewhere. That happens in the app-local client provider in `packages/main/src/client/providers/MainClientProvider.js`.
473
+
474
+ After the `shell-web` install and the `Alerts Widget` generator command, that file contains registrations like these:
475
+
476
+ ```js
477
+ import AlertsWidgetElement from "/src/components/AlertsWidgetElement.vue";
478
+
479
+ registerMainClientComponent("local.main.ui.element.alerts-widget", () => AlertsWidgetElement);
480
+
481
+ registerMainClientComponent("local.main.ui.menu-link-item", () => MenuLinkItem);
482
+ registerMainClientComponent("local.main.ui.surface-aware-menu-link-item", () => SurfaceAwareMenuLinkItem);
483
+ registerMainClientComponent("local.main.ui.tab-link-item", () => TabLinkItem);
484
+ ```
485
+
486
+ This is the same app-local provider seam from the previous chapter, but now you can see why it matters. The provider is what lets the placement runtime resolve app-owned components by token instead of hard-coding imports inside the shell runtime.
487
+
488
+ The `Profile` and `Notifications` pages did not need to add another provider registration because they reuse the existing `local.main.ui.surface-aware-menu-link-item` token for their menu entries.
489
+
490
+ So the flow is:
491
+
492
+ 1. a placement entry names a component token
493
+ 2. the local client provider publishes that token
494
+ 3. the `ShellOutlet` resolves it at runtime
495
+
496
+ That is why the placement system feels dynamic even though the app still owns all of the concrete Vue files.
497
+
498
+ ### `App.vue` and `error.js` now support shell-level errors
499
+
500
+ The top-level app root changes too. `src/App.vue` is no longer just:
501
+
502
+ ```vue
503
+ <v-app>
504
+ <RouterView />
505
+ </v-app>
506
+ ```
507
+
508
+ It now also includes a shell error host:
509
+
510
+ ```vue
511
+ <script setup>
512
+ import { RouterView } from "vue-router";
513
+ import ShellErrorHost from "@jskit-ai/shell-web/client/components/ShellErrorHost";
514
+ </script>
515
+
516
+ <template>
517
+ <v-app>
518
+ <RouterView />
519
+ <ShellErrorHost />
520
+ </v-app>
521
+ </template>
522
+ ```
523
+
524
+ That host is backed by the new app-owned `src/error.js` file:
525
+
526
+ ```js
527
+ import { createDefaultErrorPolicy } from "@jskit-ai/shell-web/client/error";
528
+
529
+ export default Object.freeze({
530
+ defaultPresenterId: "material.snackbar",
531
+ policy: createDefaultErrorPolicy(),
532
+ presenters: []
533
+ });
534
+ ```
535
+
536
+ The idea is the same as with placements: `shell-web` provides the runtime, but the app owns the configuration file that the runtime reads.
537
+
538
+ ### The home page is no longer static
539
+
540
+ `src/pages/home/index.vue` is also more interesting now. It is no longer just a welcome card. It uses Vue Query to fetch `/api/health` and display the result in the UI.
541
+
542
+ That is why this chapter is the point where running both `npm run dev` and `npm run server` stops feeling optional. The page now expects the backend to be alive.
543
+
544
+ This matters because it is the first tiny example of the frontend and backend participating in the same shell. The request itself is simple, but the behavior is more realistic than the empty starter card from the previous chapter.
545
+
546
+ ### The first client stores appear
547
+
548
+ This is also the first chapter where an installed package starts exposing app-facing Pinia stores. `shell-web` exports these two:
549
+
550
+ ```js
551
+ import {
552
+ useShellLayoutStore,
553
+ useShellErrorPresentationStore
554
+ } from "@jskit-ai/shell-web/client";
555
+ ```
556
+
557
+ `useShellLayoutStore()` owns the shell drawer state:
558
+
559
+ - whether the drawer is open right now
560
+ - whether the drawer should open by default on load
561
+
562
+ `useShellErrorPresentationStore()` exposes the current banner, snackbar, and dialog presentation state behind `ShellErrorHost`.
563
+
564
+ The simplest direct store usage looks like this:
565
+
566
+ ```vue
567
+ <script setup>
568
+ import { computed } from "vue";
569
+ import { useShellLayoutStore } from "@jskit-ai/shell-web/client";
570
+
571
+ const shellLayout = useShellLayoutStore();
572
+
573
+ const drawerDefaultOpenModel = computed({
574
+ get() {
575
+ return shellLayout.drawerDefaultOpen;
576
+ },
577
+ set(value) {
578
+ shellLayout.setDrawerDefaultOpen(Boolean(value));
579
+ }
580
+ });
581
+ </script>
582
+ ```
583
+
584
+ That is the raw shared store behind the starter `General` settings page. It is just normal Pinia state and actions: read `drawerDefaultOpen`, write it back through `setDrawerDefaultOpen(...)`, and the shell reacts.
585
+
586
+ The same store also exposes the live drawer state through `drawerOpen`, plus `setDrawerOpen(...)` and `toggleDrawer()` for components that need to control the drawer directly instead of only changing its default preference.
587
+
588
+ You do not need either store very often in this chapter, because `shell-web` already mounts the shell and error host for you. The starter `General` settings page uses the higher-level `useShellLayoutState()` helper instead of talking to `useShellLayoutStore()` directly, because that helper combines the store-backed drawer state with the current route and surface context that `ShellLayout` also needs.
589
+
590
+ That distinction is worth noticing early:
591
+
592
+ - runtime services still do the operational work
593
+ - Pinia stores are now the normal Vue-facing shared-state surface
594
+
595
+ ### The first settings route appears
596
+
597
+ `shell-web` also creates a settings shell for the `home` surface:
598
+
599
+ ```text
600
+ src/pages/home/settings.vue
601
+ src/pages/home/settings/index.vue
602
+ src/pages/home/settings/general/index.vue
603
+ ```
604
+
605
+ The important host file is still `src/pages/home/settings.vue`:
606
+
607
+ ```vue
608
+ <v-list nav density="comfortable" rounded="lg" border>
609
+ <ShellOutlet
610
+ target="home-settings:primary-menu"
611
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
612
+ />
613
+ </v-list>
614
+
615
+ <RouterView />
616
+ ```
617
+
618
+ This file matters for the same reason as `ShellLayout.vue`: it creates another named extension point instead of hard-coding a finished settings UI. The difference is that this one lives inside a page, not at the top shell level.
619
+
620
+ The starter shell now uses a real child-page structure right away:
621
+
622
+ - `src/pages/home/settings/index.vue` is only a redirect into the first child page
623
+ - `src/pages/home/settings/general/index.vue` is the first real settings page
624
+ - `src/placement.js` already seeds a `General` link into `home-settings:primary-menu`
625
+
626
+ That is what makes the page-generation examples in this chapter important. They are not inventing a new pattern. They are extending the exact same host-and-child-page structure that `shell-web` already uses for its own starter `General` page.
627
+
628
+ ### What did not change
629
+
630
+ It is also worth being explicit about what `shell-web` does **not** do yet:
631
+
632
+ - it does not add authentication
633
+ - it does not add a database
634
+ - it does not add new surfaces
635
+ - it does not change the local server provider model
636
+
637
+ The app is still structurally simple. `shell-web` just makes that simple app behave like a shell instead of a loose page.
638
+
639
+ ## Summary
640
+
641
+ After this chapter, the app is still small, but it is no longer flat. `shell-web` adds an app-owned shell layout, a placement registry, a shell error host, menu-link tokens in the local client provider, real drawer navigation, and the first nested settings section under `home`.
642
+
643
+ More importantly, this chapter is where placements stop being theory. The shell already uses them for `Home`, `Settings`, and the starter `General` settings page. Then you inspect the available targets, place real UI into the outer shell, and add more child settings pages that automatically land in the nested settings menu. That is the first real example of JSKIT working as an extension system rather than just a scaffold generator.