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