@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,734 @@
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
+ ## Recap from previous chapters
10
+
11
+ To get back to the same starting point as the end of the previous chapter, run:
12
+
13
+ ```bash
14
+ npx @jskit-ai/create-app exampleapp --tenancy-mode none
15
+ cd exampleapp
16
+ npm install
17
+ ```
18
+
19
+ If you are already continuing from the previous chapter, you are already in the right place and can skip that setup.
20
+
21
+ ## Installing `shell-web`
22
+
23
+ From inside `exampleapp`, run:
24
+
25
+ ```bash
26
+ npx jskit add package shell-web
27
+ npm install
28
+ ```
29
+
30
+
31
+ `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.
32
+
33
+ To see the result, run:
34
+
35
+ ```bash
36
+ npm run dev
37
+ npm run server
38
+ ```
39
+
40
+ 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.
41
+
42
+ <DocsTerminalTip label="Important" title="Install It Early">
43
+ `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.
44
+
45
+ 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.
46
+
47
+ That is why the intended flow is:
48
+
49
+ 1. scaffold the app
50
+ 2. install `shell-web`
51
+ 3. start personalizing the shell
52
+
53
+ 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.
54
+ </DocsTerminalTip>
55
+
56
+ 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`.
57
+
58
+ <figure class="docs-browser-shot">
59
+ <div class="docs-browser-shot__bar">
60
+ <div class="docs-browser-shot__dots" aria-hidden="true">
61
+ <span></span>
62
+ <span></span>
63
+ <span></span>
64
+ </div>
65
+ <div class="docs-browser-shot__address">http://localhost:5173/</div>
66
+ </div>
67
+ <img
68
+ src="/images/guide/a-more-interesting-shell/a-more-interesting-shell-home.png"
69
+ alt="Example app after installing shell-web, showing the shell layout and starter home page"
70
+ />
71
+ </figure>
72
+
73
+ Two important things have changed compared with the older starter shell.
74
+
75
+ - Navigation now lives in the drawer itself. `Home` and `Settings` are real shell menu entries from the start.
76
+ - `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.
77
+
78
+ Open `http://localhost:5173/home/settings` in the browser to see that nested settings shell immediately:
79
+
80
+ <figure class="docs-browser-shot">
81
+ <div class="docs-browser-shot__bar">
82
+ <div class="docs-browser-shot__dots" aria-hidden="true">
83
+ <span></span>
84
+ <span></span>
85
+ <span></span>
86
+ </div>
87
+ <div class="docs-browser-shot__address">http://localhost:5173/home/settings/general</div>
88
+ </div>
89
+ <img
90
+ src="/images/guide/a-more-interesting-shell/a-more-interesting-shell-settings-general.png"
91
+ alt="Example app home settings page after installing shell-web, showing the seeded General child page and nested settings menu"
92
+ />
93
+ </figure>
94
+
95
+ ## Module features
96
+
97
+ 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.
98
+
99
+ Start by asking JSKIT what placement targets already exist:
100
+
101
+ ```bash
102
+ npx jskit list-placements
103
+ ```
104
+
105
+ In a fresh `shell-web` app, the result looks like this:
106
+
107
+ ```text
108
+ Available placements:
109
+ - home-settings:primary-menu [src/pages/home/settings.vue]
110
+ - shell-layout:primary-menu (default) [src/components/ShellLayout.vue]
111
+ - shell-layout:secondary-menu [src/components/ShellLayout.vue]
112
+ - shell-layout:top-left [src/components/ShellLayout.vue]
113
+ - shell-layout:top-right [src/components/ShellLayout.vue]
114
+ ```
115
+
116
+ 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.
117
+
118
+ Those target names come from real `ShellOutlet` elements in the app. See `src/components/ShellLayout.vue`:
119
+
120
+ ```html
121
+ ...
122
+ <ShellOutlet target="shell-layout:top-left" />
123
+ ...
124
+ <ShellOutlet target="shell-layout:top-right" />
125
+ ...
126
+ <ShellOutlet
127
+ target="shell-layout:primary-menu"
128
+ default
129
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
130
+ />
131
+ ...
132
+ <ShellOutlet
133
+ target="shell-layout:secondary-menu"
134
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
135
+ />
136
+ ```
137
+
138
+ And the settings page introduces its own nested outlet in `src/pages/home/settings.vue`:
139
+
140
+ ```html
141
+ <ShellOutlet
142
+ target="home-settings:primary-menu"
143
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
144
+ />
145
+ ```
146
+
147
+ 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.
148
+
149
+ 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.
150
+
151
+ The shell also ships with a few app-owned component tokens that it can use as default link renderers. You can inspect those too:
152
+
153
+ ```bash
154
+ npx jskit list-component-tokens --prefix local.main.
155
+ ```
156
+
157
+ The starter result looks like this:
158
+
159
+ ```text
160
+ Available placement component tokens:
161
+ Showing link-item tokens only (token must end with "link-item"). Tip: use --all for full token list.
162
+ - local.main.ui.menu-link-item [app:packages/main/src/client/providers/MainClientProvider.js]
163
+ - 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]
164
+ - local.main.ui.tab-link-item [app:packages/main/src/client/providers/MainClientProvider.js]
165
+ ```
166
+
167
+ 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.
168
+
169
+ At this point the shell is already using placements itself. The next step is to add one of our own.
170
+
171
+
172
+ ### Adding generic elements directly
173
+
174
+ To add a small UI element to the shell itself:
175
+
176
+ ```bash
177
+ npx jskit generate ui-generator placed-element --name "Alerts Widget"
178
+ ```
179
+
180
+ 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.
181
+
182
+ <figure class="docs-browser-shot">
183
+ <div class="docs-browser-shot__bar">
184
+ <div class="docs-browser-shot__dots" aria-hidden="true">
185
+ <span></span>
186
+ <span></span>
187
+ <span></span>
188
+ </div>
189
+ <div class="docs-browser-shot__address">http://localhost:5173/home</div>
190
+ </div>
191
+ <img
192
+ src="/images/guide/a-more-interesting-shell/a-more-interesting-shell-alerts-widget.png"
193
+ alt="Example app home page after generating Alerts Widget, showing the widget rendered in the shell"
194
+ />
195
+ </figure>
196
+
197
+ In this app, there is no need to pass `--surface`: since the app only has one enabled surface, JSKIT can infer it automatically.
198
+
199
+ ### Adding a page with automatic menu placement
200
+
201
+ 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.
202
+
203
+ The source path in the `list-placements` output helps you reason about where child pages belong. When you see:
204
+
205
+ ```text
206
+ - home-settings:primary-menu [src/pages/home/settings.vue]
207
+ ```
208
+
209
+ 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.
210
+
211
+
212
+ Now use the settings host the way it is normally meant to be used: add a real child page under it.
213
+
214
+ ```bash
215
+ npx jskit generate ui-generator page home/settings/profile/index.vue --name "Profile"
216
+ ```
217
+
218
+ 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.
219
+
220
+ 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.
221
+
222
+ <figure class="docs-browser-shot">
223
+ <div class="docs-browser-shot__bar">
224
+ <div class="docs-browser-shot__dots" aria-hidden="true">
225
+ <span></span>
226
+ <span></span>
227
+ <span></span>
228
+ </div>
229
+ <div class="docs-browser-shot__address">http://localhost:5173/home/settings/profile</div>
230
+ </div>
231
+ <img
232
+ src="/images/guide/a-more-interesting-shell/a-more-interesting-shell-settings-profile.png"
233
+ alt="Example app home settings page after generating the Profile child page, showing the automatic settings menu entry and child page content"
234
+ />
235
+ </figure>
236
+
237
+ Now add a second sibling page:
238
+
239
+ ```bash
240
+ npx jskit generate ui-generator page home/settings/notifications/index.vue --name "Notifications"
241
+ ```
242
+
243
+ 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.
244
+
245
+ The order is also easy to reason about:
246
+
247
+ - `General` comes first because `shell-web` seeds it with a lower order than generated child pages.
248
+ - `Profile` and `Notifications` both use the generator's default order, so between those two the menu keeps source order.
249
+
250
+ <figure class="docs-browser-shot">
251
+ <div class="docs-browser-shot__bar">
252
+ <div class="docs-browser-shot__dots" aria-hidden="true">
253
+ <span></span>
254
+ <span></span>
255
+ <span></span>
256
+ </div>
257
+ <div class="docs-browser-shot__address">http://localhost:5173/home/settings/notifications</div>
258
+ </div>
259
+ <img
260
+ src="/images/guide/a-more-interesting-shell/a-more-interesting-shell-settings-stacked-links.png"
261
+ alt="Example app home settings page after generating Profile and Notifications child pages, showing the stacked automatic settings menu entries"
262
+ />
263
+ </figure>
264
+
265
+ <DocsTerminalTip label="Routing" title="Child Pages Under Layouts">
266
+ In JSKIT's file-based routing, a page file can act as a layout if it renders a `RouterView`.
267
+
268
+ - `src/pages/home/settings.vue` owns the settings shell and wraps its child routes.
269
+ - `src/pages/home/settings/index.vue` is now just a redirect, so `/home/settings` lands on `/home/settings/general`.
270
+ - `src/pages/home/settings/general/index.vue` is the first real child page created by the starter shell.
271
+ - `src/pages/home/settings/profile/index.vue` becomes `/home/settings/profile` and still renders inside the layout from `settings.vue`.
272
+
273
+ 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.
274
+
275
+ 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`.
276
+ </DocsTerminalTip>
277
+
278
+ <DocsTerminalTip label="Icons" title="Menu Metadata Is Not The Same As Vue Icon Props">
279
+ When you start customizing generated pages and menu links, icons are one of the first details you usually add.
280
+
281
+ There are two different paths to keep straight:
282
+
283
+ 1. placement and menu metadata such as `src/placement.js`
284
+ 2. direct Vuetify icon props inside normal `.vue` components
285
+
286
+ 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:
287
+
288
+ ```js
289
+ addPlacement({
290
+ id: "home.settings.profile.link",
291
+ target: "home-settings:primary-menu",
292
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
293
+ props: {
294
+ label: "Profile",
295
+ to: "./profile",
296
+ icon: "mdi-account-circle-outline"
297
+ }
298
+ });
299
+ ```
300
+
301
+ But do not copy that same string into a normal Vue component:
302
+
303
+ ```vue
304
+ <v-list-item prepend-icon="mdi-account-circle-outline" />
305
+ ```
306
+
307
+ JSKIT apps use Vuetify's SVG MDI renderer, so direct Vue icon props should use an `@mdi/js` path or a Vuetify alias instead:
308
+
309
+ ```vue
310
+ <script setup>
311
+ import { mdiAccountCircleOutline } from "@mdi/js";
312
+ </script>
313
+
314
+ <v-list-item :prepend-icon="mdiAccountCircleOutline" />
315
+ ```
316
+
317
+ So the practical rule is:
318
+
319
+ - editing `src/placement.js` or other shell menu metadata: `mdi-*` strings are fine
320
+ - editing a normal `.vue` file: use `@mdi/js` or a Vuetify alias such as `$close`
321
+
322
+ Later in the guide, `jskit doctor` will help catch the second mistake automatically.
323
+ </DocsTerminalTip>
324
+
325
+
326
+ ### Component tokens
327
+
328
+ If you rerun the token listing now, you will see that only the widget command created a new app-owned component token:
329
+
330
+ ```bash
331
+ npx jskit list-component-tokens --all --prefix local.main.
332
+ ```
333
+
334
+ The output now includes:
335
+
336
+ ```text
337
+ - local.main.ui.element.alerts-widget
338
+ - local.main.ui.menu-link-item
339
+ - local.main.ui.surface-aware-menu-link-item
340
+ - local.main.ui.tab-link-item
341
+ ```
342
+
343
+ 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.
344
+
345
+ So the shell story in this chapter is:
346
+
347
+ - `ShellOutlet` defines named places where UI can appear
348
+ - `jskit list-placements` shows those places
349
+ - `shell-web` already uses those places for the starter `Home`, `Settings`, and `General` entries
350
+ - `jskit generate ui-generator placed-element ...` creates app-owned UI and places it into one of them
351
+ - `jskit generate ui-generator page ...` can also discover a parent outlet and add the right menu entry automatically
352
+ - repeating that page command for the same host gives you a stacked menu, still without hand-editing the host page
353
+ - nested pages can host placements too, not just the top shell
354
+
355
+ That is the first real example of JSKIT behaving like an extension system rather than just a scaffold generator.
356
+
357
+ ## What `shell-web` changes in the app
358
+
359
+ The most interesting files now look roughly like this:
360
+
361
+ ```text
362
+ src/
363
+ components/
364
+ AlertsWidgetElement.vue
365
+ ShellLayout.vue
366
+ menus/
367
+ MenuLinkItem.vue
368
+ SurfaceAwareMenuLinkItem.vue
369
+ TabLinkItem.vue
370
+ error.js
371
+ placement.js
372
+ pages/
373
+ home.vue
374
+ home/
375
+ index.vue
376
+ settings.vue
377
+ settings/
378
+ index.vue
379
+ general/
380
+ index.vue
381
+ profile/
382
+ index.vue
383
+ notifications/
384
+ index.vue
385
+ ```
386
+
387
+ This chapter is the first time the scaffold starts to feel layered instead of flat.
388
+
389
+ ### `package.json` and `.jskit/lock.json`
390
+
391
+ The first file worth reopening is still `package.json`. After `shell-web`, the most important new dependency entries are:
392
+
393
+ ```json
394
+ {
395
+ "dependencies": {
396
+ "@jskit-ai/shell-web": "0.x",
397
+ "@tanstack/vue-query": "^5.90.5",
398
+ "@mdi/js": "^7.4.47"
399
+ }
400
+ }
401
+ ```
402
+
403
+ 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.
404
+
405
+ 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.
406
+
407
+ 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.
408
+
409
+ 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.
410
+
411
+ ### The `home` surface gets a real wrapper
412
+
413
+ 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.
414
+
415
+ Before `shell-web`, `src/pages/home.vue` was only a tiny route owner with a `RouterView`. After installing `shell-web`, it becomes:
416
+
417
+ ```vue
418
+ <route lang="json">
419
+ {
420
+ "meta": {
421
+ "jskit": {
422
+ "surface": "home"
423
+ }
424
+ }
425
+ }
426
+ </route>
427
+
428
+ <script setup>
429
+ import ShellLayout from "@/components/ShellLayout.vue";
430
+ import { RouterView } from "vue-router";
431
+ </script>
432
+
433
+ <template>
434
+ <ShellLayout title="" subtitle="">
435
+ <RouterView />
436
+ </ShellLayout>
437
+ </template>
438
+ ```
439
+
440
+ 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`.
441
+
442
+ ### `src/placement.js` becomes the placement registry
443
+
444
+ After installing `shell-web`, the app gets a placement registry file:
445
+
446
+ ```js
447
+ import { createPlacementRegistry } from "@jskit-ai/shell-web/client/placement";
448
+
449
+ const registry = createPlacementRegistry();
450
+ const { addPlacement } = registry;
451
+
452
+ export { addPlacement };
453
+
454
+ export default function getPlacements() {
455
+ return registry.build();
456
+ }
457
+ ```
458
+
459
+ 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.
460
+
461
+ 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:
462
+
463
+ ```js
464
+ addPlacement({
465
+ id: "shell-web.home.menu.home",
466
+ target: "shell-layout:primary-menu",
467
+ surfaces: ["*"],
468
+ order: 50,
469
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
470
+ props: {
471
+ label: "Home",
472
+ surface: "home",
473
+ scopedSuffix: "/",
474
+ unscopedSuffix: "/",
475
+ exact: true
476
+ }
477
+ });
478
+
479
+ addPlacement({
480
+ id: "shell-web.home.menu.settings",
481
+ target: "shell-layout:primary-menu",
482
+ surfaces: ["home"],
483
+ order: 100,
484
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
485
+ props: {
486
+ label: "Settings",
487
+ surface: "home",
488
+ scopedSuffix: "/settings",
489
+ unscopedSuffix: "/settings"
490
+ }
491
+ });
492
+
493
+ addPlacement({
494
+ id: "shell-web.home.settings.general",
495
+ target: "home-settings:primary-menu",
496
+ surfaces: ["home"],
497
+ order: 100,
498
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
499
+ props: {
500
+ label: "General",
501
+ surface: "home",
502
+ scopedSuffix: "/settings/general",
503
+ unscopedSuffix: "/settings/general",
504
+ to: "./general"
505
+ }
506
+ });
507
+
508
+ addPlacement({
509
+ id: "ui-generator.element.alerts-widget",
510
+ target: "shell-layout:top-right",
511
+ surfaces: ["home"],
512
+ order: 155,
513
+ componentToken: "local.main.ui.element.alerts-widget"
514
+ });
515
+
516
+ addPlacement({
517
+ id: "ui-generator.page.home.settings.profile.link",
518
+ target: "home-settings:primary-menu",
519
+ surfaces: ["home"],
520
+ order: 155,
521
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
522
+ props: {
523
+ label: "Profile",
524
+ surface: "home",
525
+ scopedSuffix: "/settings/profile",
526
+ unscopedSuffix: "/settings/profile",
527
+ to: "./profile"
528
+ }
529
+ });
530
+
531
+ addPlacement({
532
+ id: "ui-generator.page.home.settings.notifications.link",
533
+ target: "home-settings:primary-menu",
534
+ surfaces: ["home"],
535
+ order: 155,
536
+ componentToken: "local.main.ui.surface-aware-menu-link-item",
537
+ props: {
538
+ label: "Notifications",
539
+ surface: "home",
540
+ scopedSuffix: "/settings/notifications",
541
+ unscopedSuffix: "/settings/notifications",
542
+ to: "./notifications"
543
+ }
544
+ });
545
+ ```
546
+
547
+ That snippet shows the full placement contract clearly:
548
+
549
+ - the target says where the UI should go
550
+ - the component token says what should render that entry
551
+ - `props.to` tells the generated menu link which child route to open
552
+ - `props.icon`, when you add one, belongs to menu metadata rather than direct Vuetify icon rendering
553
+ - the surface list says where it is active
554
+ - lower `order` values come first
555
+ - when multiple entries target the same outlet with the same order, the shell keeps their source order
556
+
557
+ 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.
558
+
559
+ So the shell itself remains stable. What changes is the registry that feeds it.
560
+
561
+ ### The local client provider publishes the app-owned components
562
+
563
+ 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`.
564
+
565
+ After the `shell-web` install and the `Alerts Widget` generator command, that file contains registrations like these:
566
+
567
+ ```js
568
+ import AlertsWidgetElement from "/src/components/AlertsWidgetElement.vue";
569
+
570
+ registerMainClientComponent("local.main.ui.element.alerts-widget", () => AlertsWidgetElement);
571
+
572
+ registerMainClientComponent("local.main.ui.menu-link-item", () => MenuLinkItem);
573
+ registerMainClientComponent("local.main.ui.surface-aware-menu-link-item", () => SurfaceAwareMenuLinkItem);
574
+ registerMainClientComponent("local.main.ui.tab-link-item", () => TabLinkItem);
575
+ ```
576
+
577
+ 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.
578
+
579
+ 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.
580
+
581
+ So the flow is:
582
+
583
+ 1. a placement entry names a component token
584
+ 2. the local client provider publishes that token
585
+ 3. the `ShellOutlet` resolves it at runtime
586
+
587
+ That is why the placement system feels dynamic even though the app still owns all of the concrete Vue files.
588
+
589
+ ### `App.vue` and `error.js` now support shell-level errors
590
+
591
+ The top-level app root changes too. `src/App.vue` is no longer just:
592
+
593
+ ```vue
594
+ <v-app>
595
+ <RouterView />
596
+ </v-app>
597
+ ```
598
+
599
+ It now also includes a shell error host:
600
+
601
+ ```vue
602
+ <script setup>
603
+ import { RouterView } from "vue-router";
604
+ import ShellErrorHost from "@jskit-ai/shell-web/client/components/ShellErrorHost";
605
+ </script>
606
+
607
+ <template>
608
+ <v-app>
609
+ <RouterView />
610
+ <ShellErrorHost />
611
+ </v-app>
612
+ </template>
613
+ ```
614
+
615
+ That host is backed by the new app-owned `src/error.js` file:
616
+
617
+ ```js
618
+ import { createDefaultErrorPolicy } from "@jskit-ai/shell-web/client/error";
619
+
620
+ export default Object.freeze({
621
+ defaultPresenterId: "material.snackbar",
622
+ policy: createDefaultErrorPolicy(),
623
+ presenters: []
624
+ });
625
+ ```
626
+
627
+ The idea is the same as with placements: `shell-web` provides the runtime, but the app owns the configuration file that the runtime reads.
628
+
629
+ ### The home page is no longer static
630
+
631
+ `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.
632
+
633
+ 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.
634
+
635
+ 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.
636
+
637
+ ### The first client stores appear
638
+
639
+ This is also the first chapter where an installed package starts exposing app-facing Pinia stores. `shell-web` exports these two:
640
+
641
+ ```js
642
+ import {
643
+ useShellLayoutStore,
644
+ useShellErrorPresentationStore
645
+ } from "@jskit-ai/shell-web/client";
646
+ ```
647
+
648
+ `useShellLayoutStore()` owns the shell drawer state:
649
+
650
+ - whether the drawer is open right now
651
+ - whether the drawer should open by default on load
652
+
653
+ `useShellErrorPresentationStore()` exposes the current banner, snackbar, and dialog presentation state behind `ShellErrorHost`.
654
+
655
+ The simplest direct store usage looks like this:
656
+
657
+ ```vue
658
+ <script setup>
659
+ import { computed } from "vue";
660
+ import { useShellLayoutStore } from "@jskit-ai/shell-web/client";
661
+
662
+ const shellLayout = useShellLayoutStore();
663
+
664
+ const drawerDefaultOpenModel = computed({
665
+ get() {
666
+ return shellLayout.drawerDefaultOpen;
667
+ },
668
+ set(value) {
669
+ shellLayout.setDrawerDefaultOpen(Boolean(value));
670
+ }
671
+ });
672
+ </script>
673
+ ```
674
+
675
+ 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.
676
+
677
+ 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.
678
+
679
+ 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.
680
+
681
+ That distinction is worth noticing early:
682
+
683
+ - runtime services still do the operational work
684
+ - Pinia stores are now the normal Vue-facing shared-state surface
685
+
686
+ ### The first settings route appears
687
+
688
+ `shell-web` also creates a settings shell for the `home` surface:
689
+
690
+ ```text
691
+ src/pages/home/settings.vue
692
+ src/pages/home/settings/index.vue
693
+ src/pages/home/settings/general/index.vue
694
+ ```
695
+
696
+ The important host file is still `src/pages/home/settings.vue`:
697
+
698
+ ```vue
699
+ <v-list nav density="comfortable" rounded="lg" border>
700
+ <ShellOutlet
701
+ target="home-settings:primary-menu"
702
+ default-link-component-token="local.main.ui.surface-aware-menu-link-item"
703
+ />
704
+ </v-list>
705
+
706
+ <RouterView />
707
+ ```
708
+
709
+ 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.
710
+
711
+ The starter shell now uses a real child-page structure right away:
712
+
713
+ - `src/pages/home/settings/index.vue` is only a redirect into the first child page
714
+ - `src/pages/home/settings/general/index.vue` is the first real settings page
715
+ - `src/placement.js` already seeds a `General` link into `home-settings:primary-menu`
716
+
717
+ 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.
718
+
719
+ ### What did not change
720
+
721
+ It is also worth being explicit about what `shell-web` does **not** do yet:
722
+
723
+ - it does not add authentication
724
+ - it does not add a database
725
+ - it does not add new surfaces
726
+ - it does not change the local server provider model
727
+
728
+ The app is still structurally simple. `shell-web` just makes that simple app behave like a shell instead of a loose page.
729
+
730
+ ## Summary
731
+
732
+ 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`.
733
+
734
+ 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.