@jskit-ai/agent-docs 0.1.27 → 0.1.28

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 (40) hide show
  1. package/DISTR_AGENT.md +1 -0
  2. package/guide/agent/app-extras/assistant.md +2 -2
  3. package/guide/agent/app-extras/mobile-capacitor.md +10 -3
  4. package/guide/agent/app-extras/realtime.md +11 -11
  5. package/guide/agent/app-setup/a-more-interesting-shell.md +52 -44
  6. package/guide/agent/app-setup/authentication.md +40 -22
  7. package/guide/agent/app-setup/console.md +5 -5
  8. package/guide/agent/app-setup/database-layer.md +15 -15
  9. package/guide/agent/app-setup/initial-scaffolding.md +37 -5
  10. package/guide/agent/app-setup/multi-homing.md +28 -28
  11. package/guide/agent/app-setup/users.md +22 -22
  12. package/guide/agent/app-setup/working-with-the-jskit-cli.md +13 -6
  13. package/guide/agent/generators/advanced-cruds.md +111 -68
  14. package/guide/agent/generators/crud-generators.md +5 -5
  15. package/guide/agent/generators/ui-generators.md +15 -13
  16. package/guide/agent/index.md +1 -1
  17. package/package.json +1 -1
  18. package/patterns/INDEX.md +4 -1
  19. package/patterns/client-requests.md +23 -6
  20. package/patterns/crud-repository-mapping.md +1 -1
  21. package/patterns/crud-scaffolding.md +10 -1
  22. package/patterns/filters.md +28 -10
  23. package/patterns/generated-ui-contract-tracking.md +62 -0
  24. package/patterns/page-scaffolding.md +17 -0
  25. package/patterns/placements.md +60 -0
  26. package/patterns/surfaces.md +5 -1
  27. package/patterns/ui-testing.md +3 -0
  28. package/reference/autogen/KERNEL_MAP.md +30 -1
  29. package/reference/autogen/packages/assistant.md +1 -1
  30. package/reference/autogen/packages/crud-ui-generator.md +19 -8
  31. package/reference/autogen/packages/kernel.md +38 -6
  32. package/reference/autogen/packages/realtime.md +1 -0
  33. package/reference/autogen/packages/shell-web.md +35 -3
  34. package/reference/autogen/packages/ui-generator.md +26 -5
  35. package/reference/autogen/packages/users-core.md +2 -0
  36. package/reference/autogen/packages/users-web.md +92 -3
  37. package/reference/autogen/packages/workspaces-web.md +3 -1
  38. package/reference/autogen/tooling/jskit-cli.md +24 -8
  39. package/templates/app/AGENTS.md +1 -0
  40. package/workflow/bootstrap.md +1 -0
package/DISTR_AGENT.md CHANGED
@@ -31,6 +31,7 @@ Core rules:
31
31
  - Keep that checkpoint compact. Do not expand it into a long preamble unless the developer asks for detail.
32
32
  - When a request involves JSKIT UI, routing, surfaces, CRUDs, filters, placements, live actions, or similar implementation details, scan `patterns/INDEX.md` for matching keywords and read only the relevant pattern files.
33
33
  - Reuse existing JSKIT helpers and runtime seams before adding new local helpers.
34
+ - If `npm install` is run while the app is using local JSKIT checkout links, run `npm run devlinks` immediately afterward.
34
35
  - A freshly scaffolded JSKIT app can still be in Stage 1. If platform decisions are not settled yet, continue with the initialize workflow before adding runtime packages.
35
36
  - Do not treat a missing `config.tenancyMode` line or an untouched minimal scaffold as a final tenancy decision.
36
37
  - Do not install tenancy-sensitive packages until Stage 1 decisions are complete and the chosen tenancy is written into `config/public.js`.
@@ -455,7 +455,7 @@ npm run dev
455
455
  npm run server
456
456
  ```
457
457
 
458
- After sign-in, the app should now expose these assistant routes:
458
+ After sign-in, the app should expose these assistant routes:
459
459
 
460
460
  - `/console/assistant`
461
461
  - `/w/your-personal-slug/admin/assistant`
@@ -534,7 +534,7 @@ That is what lets one app host several assistants at once without their AI setti
534
534
 
535
535
  ### `.env` gains one AI block per assistant surface
536
536
 
537
- The app now gets env keys such as:
537
+ The app gets env keys such as:
538
538
 
539
539
  ```dotenv
540
540
  CONSOLE_ASSISTANT_AI_PROVIDER=openai
@@ -36,6 +36,9 @@ npx jskit mobile android doctor
36
36
  ```
37
37
 
38
38
  If doctor passes, the app is ready for the normal mobile workflow.
39
+ The `jskit mobile android ...` commands expect the mobile runtime package to be
40
+ installed in `package.json` and recorded in `.jskit/lock.json`; they do not
41
+ install `@jskit-ai/mobile-capacitor` for you.
39
42
 
40
43
  ## What you need on your machine
41
44
 
@@ -126,6 +129,10 @@ This:
126
129
  - builds the JSKIT web app into `dist/`
127
130
  - runs `cap sync android`
128
131
 
132
+ The refresh step requires the managed files from the package install to exist.
133
+ If `capacitor.config.json` or `.jskit/mobile-capacitor.md` is missing, install
134
+ the package again with `npx jskit add package @jskit-ai/mobile-capacitor`.
135
+
129
136
  ### Create the local tunnel
130
137
 
131
138
  ```bash
@@ -177,7 +184,7 @@ Use it when you want a clean signed-out state.
177
184
 
178
185
  ## The command set
179
186
 
180
- These are the mobile commands the package adds:
187
+ These are the CLI mobile commands used by the package workflow:
181
188
 
182
189
  - `jskit mobile android devices`
183
190
  - list visible Android devices
@@ -288,8 +295,8 @@ The package manages:
288
295
  - `MainActivity.java` or `MainActivity.kt`
289
296
  - the managed deep-link block in `AndroidManifest.xml`
290
297
 
291
- `jskit mobile android sync` is expected to refresh those files from
292
- `config.mobile`.
298
+ After the package is installed, `jskit mobile android sync` refreshes those
299
+ managed files from `config.mobile`.
293
300
 
294
301
  ### Why the tunnel exists
295
302
 
@@ -21,13 +21,13 @@ The first command records the runtime package in the app and updates the existin
21
21
 
22
22
  Unlike the database, users, console, and workspace chapters, this one does **not** need `npm run db:migrate`. `realtime` does not add schema files. It is transport infrastructure, not persistence.
23
23
 
24
- ## What changes now
24
+ ## What the package adds
25
25
 
26
- Installing `realtime` changes the app in three important ways.
26
+ Installing `realtime` adds three app capabilities.
27
27
 
28
28
  ### The app gets a realtime transport
29
29
 
30
- The server now mounts a socket.io runtime on the same Fastify server that already serves your JSKIT surfaces. The browser gets a matching socket.io client runtime through the normal client boot process.
30
+ The server mounts a socket.io runtime on the same Fastify server that already serves your JSKIT surfaces. The browser gets a matching socket.io client runtime through the normal client boot process.
31
31
 
32
32
  That means later modules, or your own app code, can stop thinking only in terms of request/response HTTP flows. They can start publishing live events and listening for them in Vue.
33
33
 
@@ -35,7 +35,7 @@ That means later modules, or your own app code, can stop thinking only in terms
35
35
 
36
36
  The package also uses the shell scaffolding that already exists.
37
37
 
38
- `realtime` appends a placement entry into `src/placement.js` that targets `shell-layout:top-right`, so the shell starts showing a small status dot without you having to create a new page for it.
38
+ `realtime` appends a placement entry into `src/placement.js` that targets the semantic `shell.status` placement, so the shell shows a small status dot without you having to create a new page for it. In the default shell topology, `shell.status` renders through the concrete `shell-layout:top-right` outlet.
39
39
 
40
40
  That dot is:
41
41
 
@@ -52,7 +52,7 @@ The app already had a browser dev server on `5173` and a backend runtime on `300
52
52
 
53
53
  So one of the main values of this package is that you do **not** have to hand-edit Vite config just to make socket.io work in local development.
54
54
 
55
- ### There are still no new pages
55
+ ### There are no realtime pages
56
56
 
57
57
  This is worth saying clearly because it can otherwise feel surprising.
58
58
 
@@ -75,7 +75,7 @@ npm run server
75
75
 
76
76
  Then open `http://localhost:5173/home`.
77
77
 
78
- The important visible change is in the top-right of the shell. You should now see the realtime status dot alongside the other shell controls.
78
+ The visible check is the shell status area. You should see the realtime status dot alongside the other shell controls.
79
79
 
80
80
  If the websocket connects successfully, the dot is green. If the backend is unavailable or the socket is reconnecting, the dot is red. Hovering it shows the current status text.
81
81
 
@@ -159,7 +159,7 @@ After the install, the app has:
159
159
 
160
160
  That one entry is what lets the browser dev server proxy websocket traffic correctly during local development.
161
161
 
162
- ### `src/placement.js` grows one new shell placement
162
+ ### `src/placement.js` includes the shell status placement
163
163
 
164
164
  The package appends this placement:
165
165
 
@@ -216,9 +216,9 @@ That is the right level of abstraction for this package:
216
216
 
217
217
  ## Summary
218
218
 
219
- This chapter does not make the app look radically different, but it adds an important new capability.
219
+ This chapter does not make the app look radically different, but it adds an important capability.
220
220
 
221
- - the backend can now host a realtime socket server
222
- - the frontend can now keep one shared websocket connection alive
223
- - the shell now exposes a live connection indicator
221
+ - the backend hosts a realtime socket server
222
+ - the frontend keeps one shared websocket connection alive
223
+ - the shell exposes a live connection indicator
224
224
  - later modules can build live behavior on top of that transport without inventing their own websocket setup
@@ -40,18 +40,18 @@ That is why the intended flow is:
40
40
 
41
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
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`.
43
+ Open `http://localhost:5173/` in the browser. The app lands in the `home` surface inside a real shell with an app bar, a navigation drawer, and a settings route at `/home/settings`.
44
44
 
45
- Two important things have changed compared with the older starter shell.
45
+ Two shell pieces are worth noticing first.
46
46
 
47
- - Navigation now lives in the drawer itself. `Home` and `Settings` are real shell menu entries from the start.
47
+ - Navigation lives in the drawer itself. `Home` and `Settings` are real shell menu entries from the start.
48
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
49
 
50
50
  Open `http://localhost:5173/home/settings` in the browser to see that nested settings shell immediately:
51
51
 
52
52
  ## Module features
53
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.
54
+ The most important idea in `shell-web` is that the app 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
55
 
56
56
  Start by asking JSKIT what public placement targets already exist:
57
57
 
@@ -59,12 +59,12 @@ Start by asking JSKIT what public placement targets already exist:
59
59
  npx jskit list-placements
60
60
  ```
61
61
 
62
- In a fresh `shell-web` app, the result looks like this:
62
+ In a fresh `shell-web` app, the result includes entries like these:
63
63
 
64
64
  ```text
65
65
  Available placements:
66
66
  - shell.primary-nav (default): Primary top-level navigation for the current surface.
67
- - compact -> shell-layout:primary-menu
67
+ - compact -> shell-layout:primary-bottom-nav
68
68
  - medium -> shell-layout:primary-menu
69
69
  - expanded -> shell-layout:primary-menu
70
70
  - page.section-nav [owner:home-settings]: Navigation between child pages in the home settings section.
@@ -95,6 +95,11 @@ Those concrete target names come from real `ShellOutlet` elements in the app. Se
95
95
  />
96
96
  ...
97
97
  <ShellOutlet target="shell-layout:secondary-menu" />
98
+ ...
99
+ <ShellOutlet target="shell-layout:primary-bottom-nav" />
100
+ ...
101
+ <ShellOutlet target="shell-layout:supporting-bottom-sheet" />
102
+ <ShellOutlet target="shell-layout:supporting-side-panel" />
98
103
  ```
99
104
 
100
105
  And the settings page introduces its own nested outlet in `src/pages/home/settings.vue`:
@@ -105,7 +110,7 @@ And the settings page introduces its own nested outlet in `src/pages/home/settin
105
110
 
106
111
  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.
107
112
 
108
- Just as importantly, `shell-web` already uses that placement system itself. The starter app is not only exposing semantic 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.
113
+ Just as importantly, `shell-web` uses that placement system itself. The starter app exposes semantic placement targets and seeds real placement entries into them. `Home` and `Settings` land in primary navigation, and `General` lands in the nested settings menu. The shell is exercising the same extension contract that later package and generator output uses.
109
114
 
110
115
  The shell also ships with a few app-owned component tokens that it can use as default link renderers. You can inspect those too:
111
116
 
@@ -123,9 +128,9 @@ Showing link-item tokens only (token must end with "link-item"). Tip: use --all
123
128
  - local.main.ui.tab-link-item [app:packages/main/src/client/providers/MainClientProvider.js]
124
129
  ```
125
130
 
126
- 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.
131
+ The shell knows about three local link-item tokens. They are app-owned components registered by the local package, and topology uses them when an outlet needs to render links or tabs.
127
132
 
128
- At this point the shell is already using placements itself. The next step is to add one of our own.
133
+ At this point the shell is using placements itself. The next step is to add one of our own.
129
134
 
130
135
  ### Adding generic elements directly
131
136
 
@@ -135,7 +140,7 @@ To add a small UI element to the shell itself:
135
140
  npx jskit generate ui-generator placed-element --name "Alerts Widget"
136
141
  ```
137
142
 
138
- 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.status`. 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.
143
+ That command creates a Vue component under `src/components/` (in this case `src/components/AlertsWidgetElement.vue`), registers a local token for it, and adds a placement entry targeting `shell.status`. After running it, refresh the home page in the browser. The shell renders the app-owned widget inside one of its named placement targets.
139
144
 
140
145
  In this app, there is no need to pass `--surface`: since the app only has one enabled surface, JSKIT can infer it automatically.
141
146
 
@@ -159,15 +164,15 @@ npx jskit generate ui-generator page home/settings/profile/index.vue --name "Pro
159
164
 
160
165
  This is a more interesting example than the widget case. JSKIT creates the page file, notices that `src/pages/home/settings.vue` owns the settings section navigation, and adds the preferred semantic menu entry there automatically. You do not have to write that placement entry by hand.
161
166
 
162
- 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.
167
+ Open `/home/settings/profile` in the browser. The settings shell shows a second real child page and a second real menu entry created by the same page-generation command. `General` comes 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.
163
168
 
164
- Now add a second sibling page:
169
+ Add a second sibling page:
165
170
 
166
171
  ```bash
167
172
  npx jskit generate ui-generator page home/settings/notifications/index.vue --name "Notifications"
168
173
  ```
169
174
 
170
- 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 `page.section-nav` owner, so the links simply stack in the menu for free.
175
+ Open `/home/settings/notifications` in the browser. You 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 `page.section-nav` owner, so the links stack in the menu for free.
171
176
 
172
177
  The order is also easy to reason about:
173
178
 
@@ -179,7 +184,7 @@ The order is also easy to reason about:
179
184
  In JSKIT's file-based routing, a page file can act as a layout if it renders a `RouterView`.
180
185
 
181
186
  - `src/pages/home/settings.vue` owns the settings shell and wraps its child routes.
182
- - `src/pages/home/settings/index.vue` is now just a redirect, so `/home/settings` lands on `/home/settings/general`.
187
+ - `src/pages/home/settings/index.vue` is just a redirect, so `/home/settings` lands on `/home/settings/general`.
183
188
  - `src/pages/home/settings/general/index.vue` is the first real child page created by the starter shell.
184
189
  - `src/pages/home/settings/profile/index.vue` becomes `/home/settings/profile` and still renders inside the layout from `settings.vue`.
185
190
 
@@ -248,13 +253,13 @@ Later in the guide, `jskit doctor` will help catch the second mistake automatica
248
253
 
249
254
  ### Component tokens
250
255
 
251
- If you rerun the token listing now, you will see that only the widget command created a new app-owned component token:
256
+ If you rerun the token listing, the widget command is the only command in this chapter that created an app-owned component token:
252
257
 
253
258
  ```bash
254
259
  npx jskit list-component-tokens --all --prefix local.main.
255
260
  ```
256
261
 
257
- The output now includes:
262
+ The output includes:
258
263
 
259
264
  ```text
260
265
  - local.main.ui.element.alerts-widget
@@ -279,7 +284,7 @@ That is the first real example of JSKIT behaving like an extension system rather
279
284
 
280
285
  ## What `shell-web` changes in the app
281
286
 
282
- The most interesting files now look roughly like this:
287
+ The most interesting files look roughly like this:
283
288
 
284
289
  ```text
285
290
  src/
@@ -317,19 +322,18 @@ The first file worth reopening is still `package.json`. After `shell-web`, the m
317
322
  {
318
323
  "dependencies": {
319
324
  "@jskit-ai/shell-web": "0.x",
320
- "@tanstack/vue-query": "^5.90.5",
321
325
  "@mdi/js": "^7.4.47"
322
326
  }
323
327
  }
324
328
  ```
325
329
 
326
- 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.
330
+ The important part is not just that `@jskit-ai/shell-web` appears. The package brings in shell runtime code and icon data; Vue Query is already owned by the base app bootstrap so every client package uses the same query client.
327
331
 
328
332
  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.
329
333
 
330
334
  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.
331
335
 
332
- 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.
336
+ That is worth noticing because this is the first chapter where JSKIT applies a runtime package that owns concrete changes in your app tree.
333
337
 
334
338
  ### The `home` surface gets a real wrapper
335
339
 
@@ -360,7 +364,7 @@ import { RouterView } from "vue-router";
360
364
  </template>
361
365
  ```
362
366
 
363
- 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`.
367
+ That one change explains a lot. The `home` surface is a shell-wrapped surface. Every child page under `src/pages/home/` renders inside that app-owned `ShellLayout`.
364
368
 
365
369
  ### `src/placement.js` becomes the placement registry
366
370
 
@@ -381,7 +385,7 @@ export default function getPlacements() {
381
385
 
382
386
  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.
383
387
 
384
- 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:
388
+ After the `shell-web` install plus the `placed-element` and `page` commands from this chapter, the bottom of the file contains real placement entries:
385
389
 
386
390
  ```js
387
391
  addPlacement({
@@ -483,7 +487,7 @@ That snippet shows the full placement contract clearly:
483
487
  - lower `order` values come first
484
488
  - when multiple entries target the same semantic placement with the same order, the shell keeps their source order
485
489
 
486
- 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.
490
+ That is why the settings menu 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.
487
491
 
488
492
  So the shell itself remains stable. What changes is the registry that feeds it.
489
493
 
@@ -503,7 +507,7 @@ registerMainClientComponent("local.main.ui.surface-aware-menu-link-item", () =>
503
507
  registerMainClientComponent("local.main.ui.tab-link-item", () => TabLinkItem);
504
508
  ```
505
509
 
506
- 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.
510
+ This is the same app-local provider seam from the previous chapter, and this chapter shows 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.
507
511
 
508
512
  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.
509
513
 
@@ -515,17 +519,9 @@ So the flow is:
515
519
 
516
520
  That is why the placement system feels dynamic even though the app still owns all of the concrete Vue files.
517
521
 
518
- ### `App.vue` and `error.js` now support shell-level errors
519
-
520
- The top-level app root changes too. `src/App.vue` is no longer just:
521
-
522
- ```vue
523
- <v-app>
524
- <RouterView />
525
- </v-app>
526
- ```
522
+ ### `App.vue` and `error.js` provide shell-level errors
527
523
 
528
- It now also includes a shell error host:
524
+ The top-level app root includes the normal route outlet and the shell error host:
529
525
 
530
526
  ```vue
531
527
  <script setup>
@@ -541,31 +537,43 @@ import ShellErrorHost from "@jskit-ai/shell-web/client/components/ShellErrorHost
541
537
  </template>
542
538
  ```
543
539
 
544
- That host is backed by the new app-owned `src/error.js` file:
540
+ That host is backed by the app-owned `src/error.js` file:
545
541
 
546
542
  ```js
547
543
  import { createDefaultErrorPolicy } from "@jskit-ai/shell-web/client/error";
548
544
 
549
545
  export default Object.freeze({
550
546
  defaultPresenterId: "material.snackbar",
551
- policy: createDefaultErrorPolicy(),
547
+ policy: createDefaultErrorPolicy({
548
+ resourceLoadChannel: "silent",
549
+ actionFeedbackChannel: "snackbar",
550
+ appRecoverableChannel: "banner",
551
+ blockingChannel: "dialog"
552
+ }),
552
553
  presenters: []
553
554
  });
554
555
  ```
555
556
 
556
557
  The idea is the same as with placements: `shell-web` provides the runtime, but the app owns the configuration file that the runtime reads.
557
558
 
558
- ### The home page is no longer static
559
+ The default error policy is intent-based. Runtime code reports what kind of error happened and the app-owned policy decides the presentation:
560
+
561
+ - `resource-load` uses `silent`; the screen keeps the load message and retry action local.
562
+ - `action-feedback` uses `snackbar`; saves, commands, and other user-triggered actions report through one lightweight feedback channel.
563
+ - `app-recoverable` uses `banner`; shell refresh and recoverable navigation failures stay visible without blocking the app.
564
+ - `blocking` uses `dialog`; unexpected UI failures and other blocking errors require explicit attention.
565
+
566
+ ### The home page talks to the backend
559
567
 
560
- `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.
568
+ `src/pages/home/index.vue` uses Vue Query to fetch `/api/health` and display the result in the UI.
561
569
 
562
- 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.
570
+ That is why this chapter is the point where running both `npm run dev` and `npm run server` matters. The page expects the backend to be alive.
563
571
 
564
572
  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.
565
573
 
566
574
  ### The first client stores appear
567
575
 
568
- This is also the first chapter where an installed package starts exposing app-facing Pinia stores. `shell-web` exports these two:
576
+ `shell-web` also exposes app-facing Pinia stores:
569
577
 
570
578
  ```js
571
579
  import {
@@ -607,10 +615,10 @@ The same store also exposes the live drawer state through `drawerOpen`, plus `se
607
615
 
608
616
  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.
609
617
 
610
- That distinction is worth noticing early:
618
+ That distinction is worth noticing:
611
619
 
612
620
  - runtime services still do the operational work
613
- - Pinia stores are now the normal Vue-facing shared-state surface
621
+ - Pinia stores are the normal Vue-facing shared-state surface
614
622
 
615
623
  ### The first settings route appears
616
624
 
@@ -634,7 +642,7 @@ The important host file is still `src/pages/home/settings.vue`:
634
642
 
635
643
  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.
636
644
 
637
- The starter shell now uses a real child-page structure right away:
645
+ The starter shell uses a real child-page structure right away:
638
646
 
639
647
  - `src/pages/home/settings/index.vue` is only a redirect into the first child page
640
648
  - `src/pages/home/settings/general/index.vue` is the first real settings page
@@ -665,6 +673,6 @@ The app is still structurally simple. `shell-web` just makes that simple app beh
665
673
 
666
674
  ## Summary
667
675
 
668
- 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`.
676
+ After this chapter, the app is still small, but it has a shell shape. `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`.
669
677
 
670
678
  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.
@@ -83,7 +83,7 @@ npm run server
83
83
 
84
84
  Then open `http://localhost:5173/auth/login` in the browser.
85
85
 
86
- The login page is now real, and it already contains several different auth modes behind the same card.
86
+ The login page is real, and it already contains several different auth modes behind the same card.
87
87
 
88
88
  - **Sign in** is the normal email-and-password flow.
89
89
  - **Register** creates a new Supabase auth user.
@@ -91,7 +91,7 @@ The login page is now real, and it already contains several different auth modes
91
91
  - **Use one-time code** switches to an email OTP login flow.
92
92
  - **Remember this account on this device** stores a small local hint in browser storage so the next visit can greet the last-used account and let the user keep that email preselected.
93
93
 
94
- If you go back to `http://localhost:5173/home`, the shell also has a small auth widget in the top-right corner. When you are signed out it shows a guest state and a menu entry that leads to `/auth/login`. After you sign in, the same placement changes to a sign-out menu.
94
+ If you go back to `http://localhost:5173/home`, the shell also has a small auth widget in the status area. When you are signed out it shows a guest state and a menu entry that leads to `/auth/login`. After you sign in, the same placement changes to a sign-out menu.
95
95
 
96
96
  ## Reading the screen carefully
97
97
 
@@ -165,7 +165,7 @@ JSKIT asks Supabase to send OTP login emails only for existing users. In other w
165
165
 
166
166
  That endpoint asks Supabase to send a password reset email. In this chapter's scaffold, this mode is only the **request** step.
167
167
 
168
- The backend already supports the later recovery endpoints too, but the chapter's simple app does **not** yet scaffold a dedicated app-owned `reset-password` page. So at this stage, the guide should be read as: the screen can request recovery emails now, while the full browser-side password-reset completion UI is still something you would add explicitly in the app.
168
+ The backend already supports the later recovery endpoints too, but the chapter's simple app does **not** yet scaffold a dedicated app-owned `reset-password` page. So at this stage, the guide should be read as: the screen can request recovery emails, while the full browser-side password-reset completion UI is still something you would add explicitly in the app.
169
169
 
170
170
  ### Remembered account behavior
171
171
 
@@ -184,7 +184,7 @@ On the next visit, the card can show a `Welcome back, ...` panel and a `Use anot
184
184
 
185
185
  The screen is also ready to show OAuth provider buttons, but only if providers are configured.
186
186
 
187
- Right now the chapter's `config.server.js` keeps this empty:
187
+ For this chapter, `config.server.js` keeps this empty:
188
188
 
189
189
  ```js
190
190
  config.auth = {
@@ -332,13 +332,22 @@ addPlacement({
332
332
  });
333
333
  ```
334
334
 
335
- And the page file itself is a normal page scaffold:
335
+ And the page file itself is a normal generated empty-state screen:
336
336
 
337
337
  ```vue
338
338
  <template>
339
- <section class="pa-4">
340
- <h1 class="text-h5 mb-2">Reports</h1>
341
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
339
+ <section class="generated-page-screen d-flex flex-column ga-4">
340
+ <header>
341
+ <p class="text-overline text-medium-emphasis mb-1">Screen</p>
342
+ <h1 class="generated-page-screen__title">Reports</h1>
343
+ </header>
344
+
345
+ <v-sheet rounded="lg" border class="generated-page-screen__empty-state">
346
+ <h2 class="text-h6 mb-2">No Reports activity yet</h2>
347
+ <p class="text-body-2 text-medium-emphasis mb-0">
348
+ Activity and actions for this screen will appear here.
349
+ </p>
350
+ </v-sheet>
342
351
  </section>
343
352
  </template>
344
353
  ```
@@ -364,9 +373,18 @@ To make the route require login, add a route guard block to `src/pages/home/repo
364
373
  </route>
365
374
 
366
375
  <template>
367
- <section class="pa-4">
368
- <h1 class="text-h5 mb-2">Reports</h1>
369
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
376
+ <section class="generated-page-screen d-flex flex-column ga-4">
377
+ <header>
378
+ <p class="text-overline text-medium-emphasis mb-1">Screen</p>
379
+ <h1 class="generated-page-screen__title">Reports</h1>
380
+ </header>
381
+
382
+ <v-sheet rounded="lg" border class="generated-page-screen__empty-state">
383
+ <h2 class="text-h6 mb-2">No Reports activity yet</h2>
384
+ <p class="text-body-2 text-medium-emphasis mb-0">
385
+ Activity and actions for this screen will appear here.
386
+ </p>
387
+ </v-sheet>
370
388
  </section>
371
389
  </template>
372
390
  ```
@@ -383,7 +401,7 @@ So after login, JSKIT can send the user back to the page they originally asked f
383
401
 
384
402
  ### Hide the menu entry when signed out
385
403
 
386
- The route is now protected, but the drawer link is still visible. That is expected. Route protection and shell visibility are separate concerns.
404
+ The route is protected, but the drawer link is still visible. That is expected. Route protection and shell visibility are separate concerns.
387
405
 
388
406
  To hide the `Reports` menu entry until the user is logged in, update the placement entry in `src/placement.js`:
389
407
 
@@ -407,7 +425,7 @@ addPlacement({
407
425
 
408
426
  The only new part is the `when(...)` line. That predicate is evaluated by the shell placement runtime using the auth context that `auth-web` injects from `/api/session`.
409
427
 
410
- So the behavior now becomes:
428
+ So the behavior becomes:
411
429
 
412
430
  - signed out:
413
431
  - the `Reports` drawer entry disappears
@@ -440,7 +458,7 @@ const auth = useAuthStore();
440
458
  </v-alert>
441
459
 
442
460
  <h1 class="text-h5 mb-2">Home</h1>
443
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
461
+ <p class="text-body-2 text-medium-emphasis">Your account session is active on this device.</p>
444
462
  </section>
445
463
  </template>
446
464
  ```
@@ -501,7 +519,7 @@ At this point the guide has shown three distinct layers of client state:
501
519
  - `shell-web` adds shell-facing stores such as `useShellLayoutStore()`
502
520
  - `auth-web` adds `useAuthStore()` for authentication state
503
521
 
504
- That progression is intentional. Packages keep their operational runtimes internally, but the app-facing shared state they surface to Vue code is now store-based.
522
+ That progression is intentional. Packages keep their operational runtimes internally, but the app-facing shared state they surface to Vue code is store-based.
505
523
 
506
524
  ## What `auth-base` adds to the app
507
525
 
@@ -528,7 +546,7 @@ Three things are worth noticing immediately.
528
546
 
529
547
  - `auth-provider-supabase-core` is the provider-specific runtime.
530
548
  - `auth-web` is the part that adds the web routes and the default auth UI.
531
- - there is now an `auth` surface-specific dev/build script family, just as `home` already had.
549
+ - there is an `auth` surface-specific dev/build script family, just as `home` already had.
532
550
 
533
551
  The provider command also writes a new `.env` file:
534
552
 
@@ -541,7 +559,7 @@ APP_PUBLIC_URL=http://localhost:5173
541
559
 
542
560
  This is the bridge between the scaffold and your real Supabase project. `APP_PUBLIC_URL` matters because auth emails and callback flows need to know which browser URL they should return to.
543
561
 
544
- Public routing config changes too. `config/public.js` now has a second surface:
562
+ Public routing config changes too. `config/public.js` has a second surface:
545
563
 
546
564
  ```js
547
565
  config.surfaceDefinitions.auth = {
@@ -568,7 +586,7 @@ config.auth = {
568
586
  };
569
587
  ```
570
588
 
571
- That small block explains a lot of the default login screen. The stock UI is ready for OAuth providers such as Google, but right now the provider list is empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
589
+ That small block explains a lot of the default login screen. The stock UI is ready for OAuth providers such as Google, but this chapter keeps the provider list empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
572
590
 
573
591
  The auth routes themselves are app-owned wrappers around the module-supplied default views. `src/pages/auth/login.vue` looks like this:
574
592
 
@@ -640,7 +658,7 @@ addPlacement({
640
658
  });
641
659
  ```
642
660
 
643
- This is the shell placement system from the previous chapter doing real work again. `auth-web` does not hard-code a permanent top-right button into `ShellLayout.vue`. Instead, it contributes a widget and two menu entries into named outlets, and those entries react to the current auth state.
661
+ This is the shell placement system from the previous chapter doing real work again. `auth-web` does not hard-code a permanent auth button into `ShellLayout.vue`. Instead, it contributes the profile widget into the semantic `shell.status` placement and contributes sign-in/sign-out links into the semantic `auth.profile-menu` placement. Topology maps those semantic targets to concrete outlets and renderers.
644
662
 
645
663
  So the auth story in this chapter is spread across clear responsibilities:
646
664
 
@@ -702,7 +720,7 @@ onBeforeUnmount(() => {
702
720
  </v-alert>
703
721
 
704
722
  <h1 class="text-h5 mb-2">Home</h1>
705
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
723
+ <p class="text-body-2 text-medium-emphasis">Your account session is active on this device.</p>
706
724
  </section>
707
725
  </template>
708
726
  ```
@@ -917,7 +935,7 @@ It is also why the shell widget can react cleanly to auth state without storing
917
935
 
918
936
  ### Authenticated Playwright testing with the dev auth bypass
919
937
 
920
- JSKIT now ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through Supabase.
938
+ JSKIT ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through Supabase.
921
939
 
922
940
  This is the standard path the agent should use for authenticated browser tests:
923
941
 
@@ -1083,6 +1101,6 @@ That is why the confirmation screen in the app should be understood as a **sessi
1083
1101
 
1084
1102
  ## Summary
1085
1103
 
1086
- After this chapter, the app can really authenticate against Supabase. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The provider-specific values now live in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1104
+ After this chapter, the app can really authenticate against Supabase. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The provider-specific values live in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1087
1105
 
1088
1106
  Just as importantly, the app is still deliberately incomplete. Authentication exists, but the database-backed user model does not. That separation is useful, because the next layer of the guide can explain users and persistence without having to also explain the first auth setup at the same time.