@jskit-ai/agent-docs 0.1.3 → 0.1.5

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