@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,753 +0,0 @@
1
- # Initial scaffolding
2
-
3
- In this first chapter, we are going to create the smallest useful JSKIT app, install its dependencies, run it locally, and read the scaffold it gives us. The goal of this chapter is to explain how to get started with JSKIT and to understand what the generator produced, which files matter, and why the project already has concepts like _surfaces_, a local runtime package, and a server even before we add any real features.
4
-
5
- Start in a working directory and run:
6
-
7
- ```bash
8
- npx @jskit-ai/create-app exampleapp --tenancy-mode none
9
- cd exampleapp
10
- npm install
11
- ```
12
-
13
- The first command creates a new folder called `exampleapp` and fills it with JSKIT's base shell template. The `exampleapp` name is used in a few template replacements, such as the package name and the browser title. The `--tenancy-mode none` flag tells JSKIT to start with the smallest routing model. In this mode, the app is not workspace-aware (more of this later in the guide, when multihoming is introduced). That keeps the first scaffold easier to read because there is no workspace slug handling yet.
14
-
15
- If you are working with an AI agent and want the agent to explain the platform options after the scaffold exists, there is also a safe guided path:
16
-
17
- ```bash
18
- npx @jskit-ai/create-app exampleapp
19
- cd exampleapp
20
- npm install
21
- ```
22
-
23
- That produces the same minimal scaffold shape, but without writing an explicit `config.tenancyMode` line yet. The important rule is that this does **not** mean tenancy is already decided. Before installing tenancy-sensitive packages such as `workspaces-core` or `workspaces-web`, the agent still needs to finish the Stage 1 platform conversation and then write the chosen tenancy into `config/public.js`. If workspace packages are installed while the app is still effectively on `none`, you fall into the recovery path described later in the multi-homing chapter.
24
-
25
- After creating the scaffolding (which comes with a package.json file), you will need to run `npm install` to install dependencies.
26
-
27
- <DocsTerminalTip title="Try Bash Completion!">
28
-
29
- Once `npm install` has finished, you can enable Bash completion for the JSKIT CLI. If you only want it for the current shell session, run:
30
-
31
- ```bash
32
- source <(npx jskit completion bash)
33
- ```
34
-
35
- If you want JSKIT completion to keep working in future Bash sessions as well, run:
36
-
37
- ```bash
38
- npx jskit completion bash --install
39
- ```
40
-
41
- That writes a small loader file into your home directory and updates `~/.bashrc` for you. To activate it in the current shell immediately, run `source ~/.bashrc`.
42
-
43
- Once completion is loaded, you can test it immediately.
44
-
45
- If you type:
46
-
47
- ```bash
48
- npx jskit li
49
- ```
50
-
51
- and press Tab twice, Bash will show completions such as `list`, `list-placements`, and `list-component-tokens`.
52
-
53
- If you type:
54
-
55
- ```bash
56
- npx jskit add p
57
- ```
58
-
59
- and press Tab, JSKIT will complete that subcommand argument to `package`.
60
-
61
- The `jskit` command is central in the use of JSKIT. The autocompletion will help speeding things up.
62
-
63
- </DocsTerminalTip>
64
-
65
- To see the app in the browser, the quickest path is:
66
-
67
- ```bash
68
- npm run dev
69
- ```
70
-
71
- Then open `http://localhost:5173/` in the browser. The starter screen is intentionally plain. That is a good thing. It proves the shell is wired correctly before we start adding packages.
72
-
73
- <figure class="docs-browser-shot">
74
- <div class="docs-browser-shot__bar">
75
- <div class="docs-browser-shot__dots" aria-hidden="true">
76
- <span></span>
77
- <span></span>
78
- <span></span>
79
- </div>
80
- <div class="docs-browser-shot__address">http://localhost:5173/</div>
81
- </div>
82
- <img
83
- src="/images/guide/initial-scaffolding/initial-scaffolding-home.png"
84
- alt="Freshly generated exampleapp starter screen in a browser window"
85
- />
86
- </figure>
87
-
88
- For the very first page, `npm run dev` is enough because the scaffold does not make any real API calls yet. For normal JSKIT development, though, you will usually keep a second terminal open and run the backend as well:
89
-
90
- ```bash
91
- npm run server
92
- ```
93
-
94
- That starts the Fastify server on port `3000`. As soon as you begin adding backend features, the frontend dev server will expect that backend to be there, because the Vite configuration proxies `/api` requests to the local server. A good habit is to treat `npm run dev` as the browser-facing process and `npm run server` as the app runtime behind it.
95
-
96
- If you want a fast sanity check that the backend is alive, open `http://localhost:3000/api/health` or request it from the terminal:
97
-
98
- ```bash
99
- curl http://localhost:3000/api/health
100
- ```
101
-
102
- You should get a small JSON response with `ok: true`.
103
-
104
- ## Reading the scaffold
105
-
106
- A fresh app has more structure than a plain Vue starter because JSKIT is preparing both a web shell and an application runtime from the beginning. The top-level layout looks roughly like this:
107
-
108
- ```text
109
- exampleapp/
110
- .jskit/
111
- config/
112
- packages/main/
113
- server/
114
- src/
115
- tests/
116
- package.json
117
- server.js
118
- vite.config.mjs
119
- ```
120
-
121
- The first file most people should read is `package.json`. It is the command center for the app. It tells you how to run the frontend (`npm run dev`), the backend (`npm run server`), the test suite, and the build. It also shows the most important dependencies that make the starter shell work: Vue, Vite, Fastify, the JSKIT kernel, and the HTTP runtime.
122
-
123
- The most important parts look like this:
124
-
125
- ```json
126
- {
127
- "scripts": {
128
- "server": "node ./bin/server.js",
129
- "server:all": "node ./bin/server.js",
130
- "server:home": "SERVER_SURFACE=home node ./bin/server.js",
131
- "devlinks": "jskit app link-local-packages",
132
- "dev": "vite",
133
- "dev:all": "vite",
134
- "dev:home": "VITE_SURFACE=home vite",
135
- "build": "vite build",
136
- "build:all": "vite build",
137
- "build:home": "VITE_SURFACE=home vite build",
138
- "test": "node --test",
139
- "test:client": "vitest run tests/client",
140
- "verify": "jskit app verify && npm run --if-present verify:app",
141
- "release": "jskit app release",
142
- "jskit:update": "jskit app update-packages"
143
- },
144
- "dependencies": {
145
- "@local/main": "file:packages/main",
146
- "@jskit-ai/kernel": "0.x",
147
- "@jskit-ai/http-runtime": "0.x",
148
- "fastify": "^5.7.4",
149
- "pinia": "^3.0.4",
150
- "vue": "^3.5.13",
151
- "vuetify": "^4.0.0"
152
- },
153
- "devDependencies": {
154
- "@jskit-ai/jskit-cli": "0.x",
155
- "vite": "^6.1.0",
156
- "vitest": "^4.0.18"
157
- }
158
- }
159
- ```
160
-
161
- There are two details worth noticing immediately. The dependency on `@local/main` points at `file:packages/main`, which means your app already contains its own local JSKIT package. The maintenance scripts are also useful to notice early, because they show an important ownership boundary in JSKIT.
162
-
163
- `verify`, `jskit:update`, `devlinks`, and `release` are intentionally thin wrappers. They stay in `package.json` because they are convenient app-local shortcuts, but the real implementation now lives in `jskit app ...`, not in copied scaffold scripts.
164
-
165
- That matters because JSKIT maintenance policy changes over time. If the scaffold copied a large shell script into every app, existing apps would freeze the old behavior forever. By delegating to `jskit app verify`, `jskit app update-packages`, `jskit app link-local-packages`, and `jskit app release`, the app keeps the nice `npm run` shortcuts while the maintained behavior stays in the installed CLI package.
166
-
167
- `jskit app verify` is worth noticing specifically. Linting, tests, and builds check your source code and runtime behavior. The JSKIT part of that flow runs `doctor`, which checks JSKIT-managed app state: installed package visibility, lock-file-backed managed files, and other JSKIT-specific health rules. It is there because a JSKIT app is not only code. It is also a descriptor-driven managed project.
168
-
169
- The surface-specific script names are also worth noticing early, even in this tiny app. `dev:home`, `server:home`, and `build:home` are the first concrete places where surface selection shows up in the scaffold. They work by setting `VITE_SURFACE=home` on the client side and `SERVER_SURFACE=home` on the server side. In this first chapter, where `home` is the only surface, those variants behave almost the same as the default commands. Later, once more surfaces exist, those scripts become the simplest way to run or build just one surface at a time.
170
-
171
-
172
- ### App surfaces in JSKIT
173
-
174
- A surface is JSKIT's name for a named slice of the application. They are a very important concept in JSKIT, since a surface can be built -- and deployed -- separately from each other. This is useful if for example you want the end-user interface _not_ to contain _any_ of the symbols/strings of the admin interface.
175
-
176
- Surfaces are defined in a very important file in JSKIT: `config/public.js`. This is the app's shared public configuration, used both by client and server. It's called "public" because it _will_ be read by the browser, and therefore it _will_ be available to the world. It defines the current tenancy mode, the default surface, and the list of surface definitions. In this first scaffold there is only one surface, `home`, which is the starter surface
177
-
178
- Even though we are using `--tenancy-mode none`, it will still be possible to add more surfaces. Every app starts with a single `home` surface, and later packages will expand that topology.
179
-
180
- Here is the part of `config/public.js` that sets that up:
181
-
182
- ```js
183
- import { surfaceAccessPolicies } from "./surfaceAccessPolicies.js";
184
-
185
- export const config = {};
186
- config.tenancyMode = "none";
187
-
188
- config.surfaceModeAll = "all";
189
- config.surfaceDefaultId = "home";
190
- config.webRootAllowed = "no";
191
- config.surfaceAccessPolicies = surfaceAccessPolicies;
192
- config.surfaceDefinitions = {};
193
- config.surfaceDefinitions.home = {
194
- id: "home",
195
- label: "Home",
196
- pagesRoot: "home",
197
- enabled: true,
198
- requiresAuth: false,
199
- requiresWorkspace: false,
200
- accessPolicyId: "public",
201
- origin: ""
202
- };
203
- ```
204
-
205
- Right next to that file is `config/surfaceAccessPolicies.js`. This is where the access rules for surfaces live. In the initial shell, `home` uses the `public` policy. You do not need to change these policies now, but you do need to know where they come from, because later packages will extend them.
206
-
207
- The starter policies are small enough to read in one glance:
208
-
209
- ```js
210
- export const surfaceAccessPolicies = {};
211
-
212
- surfaceAccessPolicies.public = {};
213
- ```
214
-
215
- That tells you one thing immediately: `home` is open. More specific policies only appear when later packages add them.
216
-
217
- ### The client side
218
-
219
- #### Client bootstrap
220
-
221
- The `src/` directory is the frontend application. `src/main.js` is the real boot file. It creates the Vue app, sets up the router, enables Vuetify, and builds a JSKIT surface runtime from `config/public.js`. That one file is worth reading carefully because it shows the main client-side contract of a JSKIT app: scaffold config is turned into a running client shell, with the surface runtime, router, installed client modules, and app boot all wired together.
222
-
223
- The important part looks like this:
224
-
225
- ```js
226
- import { createApp } from "vue";
227
- import { createPinia } from "pinia";
228
- import { createRouter, createWebHistory } from "vue-router/auto";
229
- import { routes } from "vue-router/auto-routes";
230
- import "vuetify/styles";
231
- import { createVuetify } from "vuetify";
232
- import * as components from "vuetify/components";
233
- import * as directives from "vuetify/directives";
234
- import { aliases as mdiAliases, mdi } from "vuetify/iconsets/mdi-svg";
235
- import { createSurfaceRuntime } from "@jskit-ai/kernel/shared/surface/runtime";
236
- import {
237
- bootstrapClientShellApp,
238
- createShellRouter
239
- } from "@jskit-ai/kernel/client";
240
- import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
241
- import App from "./App.vue";
242
- import NotFoundView from "./views/NotFound.vue";
243
- import { config } from "../config/public.js";
244
-
245
- const surfaceRuntime = createSurfaceRuntime({
246
- allMode: config.surfaceModeAll,
247
- surfaces: config.surfaceDefinitions,
248
- defaultSurfaceId: config.surfaceDefaultId
249
- });
250
-
251
- const surfaceMode = surfaceRuntime.normalizeSurfaceMode(import.meta.env.VITE_SURFACE);
252
- const { router, fallbackRoute } = createShellRouter({
253
- createRouter,
254
- history: createWebHistory(),
255
- routes,
256
- surfaceRuntime,
257
- surfaceMode,
258
- notFoundComponent: NotFoundView,
259
- guard: {
260
- surfaceDefinitions: config.surfaceDefinitions,
261
- defaultSurfaceId: config.surfaceDefaultId,
262
- webRootAllowed: config.webRootAllowed
263
- }
264
- });
265
-
266
- const vuetify = createVuetify({
267
- components,
268
- directives,
269
- theme: {
270
- defaultTheme: "light"
271
- },
272
- icons: {
273
- defaultSet: "mdi",
274
- aliases: mdiAliases,
275
- sets: { mdi }
276
- }
277
- });
278
- const pinia = createPinia();
279
-
280
- void bootstrapClientShellApp({
281
- createApp,
282
- rootComponent: App,
283
- appConfig: config,
284
- appPlugins: [pinia, vuetify],
285
- pinia,
286
- router,
287
- bootClientModules: bootInstalledClientModules,
288
- surfaceRuntime,
289
- surfaceMode,
290
- env: import.meta.env,
291
- fallbackRoute
292
- }).catch((error) => {
293
- console.error("Failed to bootstrap client app.", error);
294
- });
295
- ```
296
-
297
- The flow is simple once you read it in order: config in, runtime in memory, router built from that runtime, UI plugin installed, app bootstrapped.
298
-
299
- <DocsInDepth title="In depth" preview-height="15rem">
300
-
301
- `createSurfaceRuntime(...)` turns the static surface config into a small runtime registry that both the router and later client modules can query. In the starter app, the input data is basically this:
302
-
303
- ```js
304
- config.surfaceDefinitions = {
305
- home: {
306
- pagesRoot: "home", // this surface lives under src/pages/home
307
- requiresAuth: false,
308
- accessPolicyId: "public"
309
- }
310
- };
311
-
312
- config.surfaceDefaultId = "home"; // default surface
313
- config.surfaceModeAll = "all"; // unrestricted surface mode
314
- ```
315
-
316
- That means the `home` surface itself lives under `/home`. Because `home` is also the default surface and the starter scaffold sets `webRootAllowed = "no"`, visiting `/` redirects to `/home`.
317
-
318
- That gives the client a normalized surface runtime with answers to questions such as:
319
-
320
- ```js
321
- surfaceRuntime.DEFAULT_SURFACE_ID; // "home"
322
- surfaceRuntime.listEnabledSurfaceIds(); // ["home"]
323
- surfaceRuntime.resolveSurfaceFromPathname("/home"); // "home"
324
- ```
325
-
326
- `surfaceMode` is not another surface definition. It is the current viewing mode for the app. In a plain starter app, `VITE_SURFACE` is usually unset, so `surfaceMode` becomes `"all"`, meaning "do not restrict the router to one specific surface". Later, when you run surface-specific profiles, the same runtime can narrow the active routes to just one surface. In the scaffold scripts, `npm run dev:home` is simply setting `VITE_SURFACE=home`, while `npm run dev` and `npm run dev:all` leave the client in unrestricted `"all"` mode.
327
-
328
- `createShellRouter(...)` uses that `surfaceRuntime` object to assemble the actual router. Concretely, it does this:
329
-
330
- 1. Takes the generated Vue routes.
331
- 2. Adds a fallback not-found route.
332
- 3. Filters those routes down to the ones that belong to the current surface mode.
333
- 4. Calls Vue Router's real `createRouter(...)` with that filtered route list.
334
- 5. Installs a `beforeEach` guard for surface-aware redirects and access behavior.
335
-
336
- So this is not a separate routing system. It is a thin JSKIT wrapper around normal Vue Router setup, using the surface runtime to decide which routes should be active and which guard behavior should be applied.
337
-
338
- In the starter app, with only `home`, this is almost boring. It mostly means:
339
-
340
- - build the router
341
- - add not-found
342
- - enforce the `/` to `/home` redirect behavior
343
-
344
- `createPinia()` is the standard shared-state layer for the client app. JSKIT installs it from day 0 so later packages can expose Vue-facing stores without each app having to bolt Pinia on afterward.
345
-
346
- In the plain starter scaffold there are still no package-defined client stores to use yet. Pinia is already there so later packages can add them without changing the app bootstrap. In the next chapters, `shell-web` adds shell stores such as `useShellLayoutStore()`, and `auth-web` later adds `useAuthStore()`.
347
-
348
- `createVuetify(...)` is the ordinary UI plugin setup. There is nothing especially JSKIT-specific there; it just makes Vuetify components, directives, theme settings, and icon aliases available to the app before the router is mounted.
349
-
350
- `bootInstalledClientModules` is the extension seam, meaning "this is the point where later-installed JSKIT packages get to join client startup". The confusing part is that it is not a normal file in your app. In `src/main.js` you import it from:
351
-
352
- ```js
353
- import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
354
- ```
355
-
356
- That `virtual:` prefix is a Vite convention. It means there is no real file on disk with that name. Instead, JSKIT's Vite plugin generates that module for you during development and build.
357
-
358
- The startup path looks like this:
359
-
360
- 1. `src/main.js` imports `virtual:jskit-client-bootstrap`.
361
- 2. The JSKIT Vite plugin sees that import and generates a module on the fly.
362
- 3. That generated module imports the `.../client` entrypoint of each installed JSKIT package that has client-side behavior.
363
- 4. It wraps those imported modules into one function called `bootInstalledClientModules(...)`.
364
- 5. `bootstrapClientShellApp(...)` calls that function before the router is installed and before the app is mounted.
365
- 6. `bootClientModules(...)` then starts client providers and registers any client routes contributed by those installed packages.
366
-
367
- The generated module is roughly like this:
368
-
369
- ```js
370
- import * as authClient from "@jskit-ai/auth-web/client";
371
- import * as usersClient from "@jskit-ai/users-web/client";
372
- import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
373
-
374
- const installedClientModules = [
375
- { packageId: "@jskit-ai/auth-web", module: authClient },
376
- { packageId: "@jskit-ai/users-web", module: usersClient }
377
- ];
378
-
379
- async function bootInstalledClientModules(context = {}) {
380
- return bootClientModules({
381
- ...context,
382
- clientModules: installedClientModules
383
- });
384
- }
385
- ```
386
-
387
- That is why Vite is involved. The browser cannot safely discover installed packages by itself at runtime. It cannot scan `.jskit/lock.json`, inspect `node_modules`, and turn that into bundler-visible imports. Vite needs a normal import graph up front. The plugin creates that graph for the app.
388
-
389
- If this were plain Vue without that plugin, you would have to maintain the list yourself:
390
-
391
- ```js
392
- import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
393
- import * as authClient from "@jskit-ai/auth-web/client";
394
- import * as usersClient from "@jskit-ai/users-web/client";
395
-
396
- await bootClientModules({
397
- app,
398
- router,
399
- surfaceRuntime,
400
- surfaceMode,
401
- clientModules: [
402
- { packageId: "@jskit-ai/auth-web", module: authClient },
403
- { packageId: "@jskit-ai/users-web", module: usersClient }
404
- ]
405
- });
406
- ```
407
-
408
- JSKIT automates that step so adding or removing packages does not require hand-editing `src/main.js`.
409
-
410
- In a brand-new shell app, there are no extra installed client modules yet, so the generated function is effectively empty. Later, when you install packages with client providers or extra UI routes, this same hook is what starts those providers and registers those routes.
411
-
412
- `bootstrapClientShellApp(...)` is the final assembly step. It creates the Vue app, installs plugins such as Pinia and Vuetify, passes the Pinia instance explicitly into client-module boot, stores the client app config, runs `bootInstalledClientModules(...)`, attaches the fallback route if needed, installs the router, waits for the router to be ready, and only then mounts the app. That order matters because installed packages need a chance to extend the app before the first render happens.
413
-
414
- </DocsInDepth>
415
-
416
- #### The main package (client side)
417
-
418
- One more client-side piece is worth seeing before looking at page files: the starter app already has its own client provider. The app-local package declares it in `packages/main/package.descriptor.mjs` like this:
419
-
420
- ```js
421
- client: {
422
- providers: [
423
- {
424
- entrypoint: "src/client/providers/MainClientProvider.js",
425
- export: "MainClientProvider"
426
- }
427
- ]
428
- }
429
- ```
430
-
431
- That declaration is one of the things `bootClientModules(...)` uses. On the client, the lifecycle is:
432
-
433
- 1. collect the installed client modules
434
- 2. resolve the provider classes they declare
435
- 3. create the client runtime application container
436
- 4. run each provider's `register()` method
437
- 5. run each provider's `boot()` method, if it has one
438
-
439
- So yes: client providers use the same `register()`/`boot()` lifecycle pattern as server providers. In the scaffold, the app-local client provider starts like this:
440
-
441
- ```js
442
- const mainClientComponents = [];
443
-
444
- function registerMainClientComponent(token, resolveComponent) {
445
- mainClientComponents.push({ token, resolveComponent });
446
- }
447
-
448
- class MainClientProvider {
449
- static id = "local.main.client";
450
-
451
- register(app) {
452
- for (const { token, resolveComponent } of mainClientComponents) {
453
- app.singleton(token, resolveComponent);
454
- }
455
- }
456
- }
457
-
458
- export {
459
- MainClientProvider,
460
- registerMainClientComponent
461
- };
462
- ```
463
-
464
- The important idea is that this provider is not rendering UI directly. It is registering token-addressable client components into the application container. In the base scaffold, that list starts empty. Later package installs and generators can extend this file by adding imports and `registerMainClientComponent(...)` calls for app-owned client components. In other words, this file is the app's local registration seam.
465
-
466
- ```js
467
- import MenuLinkItem from "/src/components/menus/MenuLinkItem.vue";
468
-
469
- registerMainClientComponent("local.main.ui.menu-link-item", () => MenuLinkItem);
470
- ```
471
-
472
- Then `MainClientProvider.register(app)` publishes those into the client container with `app.singleton(...)`. Later packages and placement runtime code can ask for those components by token instead of importing app files directly.
473
-
474
- This code is intentionally small. `registerMainClientComponent(...)` is a private app-local registration hook, not a public validation API, so the scaffold keeps it minimal and lets obvious mistakes fail honestly when the provider is used.
475
-
476
- `MainClientProvider` does not define a `boot()` method yet, so the boot phase is effectively empty for this provider right now. But the lifecycle still supports it. If you later add `boot()`, JSKIT will run it after all client providers have finished `register()`.
477
-
478
- Inside `src/pages/` you will find both route owners and actual page components. The easy file to notice is `src/pages/home/index.vue`, because that is the page with visible content. The easy file to miss is `src/pages/home.vue`. That wrapper file contains route metadata that attaches the page tree to a JSKIT surface. When you later add more pages, that surface information is one of the things JSKIT uses to decide where a page belongs.
479
-
480
- The wrapper file is tiny, but it is doing an important job:
481
-
482
- ```vue
483
- <route lang="json">
484
- {
485
- "meta": {
486
- "jskit": {
487
- "surface": "home"
488
- }
489
- }
490
- }
491
- </route>
492
-
493
- <template>
494
- <RouterView />
495
- </template>
496
- ```
497
-
498
- This is why `src/pages/home/index.vue` becomes part of the `home` surface instead of just being "some route".
499
-
500
- <DocsTerminalTip label="Routing" title="How JSKIT Reads Route Metadata">
501
-
502
- The `<route lang="json">` block is not something `shell-web` or `auth-web` parses directly.
503
-
504
- During dev/build, the file-based Vue Router plugin scans `src/pages/` and turns that block into ordinary Vue Router route metadata. Later, when navigation happens, JSKIT reads that metadata from the matched route records.
505
-
506
- That is why a block like this can affect behavior without any extra code in the page component itself:
507
-
508
- - `meta.jskit.surface` tells JSKIT which surface the page tree belongs to
509
- - `meta.guard.policy` can later tell JSKIT whether the route should be treated as `public` or `authenticated`
510
-
511
- So the order is:
512
-
513
- 1. the router plugin reads the `<route>` block
514
- 2. the generated route record gets a normal `meta` object
515
- 3. JSKIT reads that `meta` during routing
516
-
517
- </DocsTerminalTip>
518
-
519
- `src/App.vue` is deliberately small. It is only the outer Vuetify app shell and a `RouterView`. That is another pattern you should get used to in JSKIT: the base scaffold stays thin, and most behavior is pushed toward packages, page files, and runtime providers.
520
-
521
- <DocsTerminalTip label="Container" title="App Methods">
522
-
523
- The `app` object in `register(app)` and `boot(app)` is JSKIT's application container.
524
-
525
- Defining values:
526
-
527
- - `app.singleton(token, factory)`: register one lazily created shared value for the whole app.
528
- - `app.bind(token, factory)`: register a factory that creates a fresh value every time the token is resolved.
529
- - `app.scoped(token, factory)`: register one value per child scope.
530
- - `app.instance(token, value)`: register an already-created value directly.
531
- - `app.tag(token, tagName)`: add a token to a named group.
532
-
533
- Typical examples look like this:
534
-
535
- ```js
536
- app.instance("appConfig", appConfig);
537
- app.singleton("local.main.ui.menu-link-item", () => TabLinkItem);
538
- app.bind("feature.clock", () => new Clock());
539
- app.tag("auth.login.component", "auth.ui");
540
- ```
541
-
542
- That means:
543
-
544
- - `instance(...)` is good for something you already created, such as loaded config.
545
- - `singleton(...)` is good for one shared runtime object or component resolver.
546
- - `bind(...)` is for "make a fresh one each time someone asks".
547
- - `tag(...)` lets you group related tokens so another part of the app can ask for the whole set later.
548
-
549
- Using values:
550
-
551
- - `app.make(token)`: resolve a token from the container.
552
- - `app.has(token)`: check whether a token is registered.
553
- - `app.createScope(scopeId)`: create a child scope.
554
- - `app.resolveTag(tagName)`: resolve all tokens in that group.
555
-
556
- Then later code can consume those registrations like this:
557
-
558
- ```js
559
- const authGuardRuntime = app.make("runtime.auth-guard.client");
560
-
561
- if (app.has("jskit.client.vue.app")) {
562
- const vueApp = app.make("jskit.client.vue.app");
563
- }
564
-
565
- const authUi = app.resolveTag("auth.ui");
566
- const requestScope = app.createScope("request:123");
567
- ```
568
-
569
- A scope is a real child container, not just a label. You can put request-local values into it and then resolve them from the scope itself:
570
-
571
- ```js
572
- app.singleton("logger", () => createLogger());
573
-
574
- const requestScope = app.createScope("request:123");
575
- requestScope.instance("request.id", "request:123");
576
-
577
- const logger = requestScope.make("logger"); // inherited from the parent app container
578
- const requestId = requestScope.make("request.id"); // local to this scope
579
- ```
580
-
581
- Here:
582
-
583
- - `make(...)` gets the thing behind a token.
584
- - `has(...)` lets you check before assuming a token exists.
585
- - `resolveTag(...)` gets every token in a named group.
586
- - `createScope(...)` gives you a child container when you need scoped values rather than app-wide ones, and that child container can then use `make(...)`, `has(...)`, `instance(...)`, and the other container methods too.
587
-
588
- </DocsTerminalTip>
589
-
590
- ### The server side
591
-
592
- The backend entry point is `server.js`, with `bin/server.js` acting as the small executable wrapper used by the npm scripts. `server.js` starts Fastify, registers a built-in `/api/health` route, loads the provider runtime, and decides how to serve the frontend. In development, you normally visit the Vite dev server on port `5173`. In a built app, this same server can also serve the compiled frontend.
593
-
594
- The core of that startup path looks like this:
595
-
596
- ```js
597
- async function createServer() {
598
- const app = Fastify({ logger: true });
599
- registerTypeBoxFormats();
600
- app.setValidatorCompiler(TypeBoxValidatorCompiler);
601
-
602
- app.get("/api/health", async () => {
603
- return {
604
- ok: true,
605
- app: "exampleapp"
606
- };
607
- });
608
-
609
- const runtimeEnv = resolveRuntimeEnv();
610
- const appRoot = path.resolve(process.cwd());
611
- const runtime = await tryCreateProviderRuntimeFromApp({
612
- appRoot,
613
- profile: resolveRuntimeProfileFromSurface({
614
- surfaceRuntime,
615
- serverSurface: runtimeEnv.SERVER_SURFACE
616
- }),
617
- env: runtimeEnv,
618
- logger: app.log,
619
- fastify: app
620
- });
621
-
622
- registerSurfaceRequestConstraint({
623
- fastify: app,
624
- surfaceRuntime,
625
- serverSurface: runtimeEnv.SERVER_SURFACE,
626
- globalUiPaths: resolveGlobalUiPaths(runtime?.globalUiPaths || [])
627
- });
628
-
629
- return app;
630
- }
631
- ```
632
-
633
- The health route is built in, but the more important idea is that the server is already prepared to validate HTTP input, load the JSKIT provider runtime from the app itself, and constrain requests by surface.
634
-
635
- You will also notice `config/server.js`. In the base shell it is intentionally almost empty. It is there to reserve a clear place for server-side configuration as backend features are added, without pretending the starter app already has server behavior it does not yet need.
636
-
637
- The small `server/lib/` directory exists to keep that server boot code tidy. `runtimeEnv.js` reads environment variables such as port and host. `surfaceRuntime.js` builds the same surface runtime that the client uses, so the server and browser agree on what surfaces exist. In the scaffold scripts, `npm run server:home` is simply setting `SERVER_SURFACE=home`, while `npm run server` and `npm run server:all` leave the server unrestricted.
638
-
639
- #### The main package (server side)
640
-
641
- The most unusual part of the scaffold, if you are new to JSKIT, is `packages/main/`. This is the app-local runtime package. It is not there by accident, and it is not just a convenience folder. JSKIT treats your app itself as a local package with a descriptor, client provider hooks, and server provider hooks. That is why the folder contains `package.descriptor.mjs` and a small `src/` tree of its own.
642
-
643
- You already saw the client-side provider in the client bootstrap path. The server side uses the same model: the descriptor tells JSKIT which provider class belongs to the local package, and the runtime calls `register()` and then `boot()`.
644
-
645
- The server part of that descriptor looks like this:
646
-
647
- ```js
648
- export default Object.freeze({
649
- packageVersion: 1,
650
- packageId: "@local/main",
651
- version: "0.1.0",
652
- kind: "runtime",
653
- runtime: {
654
- server: {
655
- providerEntrypoint: "src/server/index.js",
656
- providers: [
657
- {
658
- discover: {
659
- dir: "src/server/providers",
660
- pattern: "*Provider.js"
661
- }
662
- }
663
- ]
664
- }
665
- },
666
- metadata: {
667
- server: {
668
- routes: []
669
- }
670
- }
671
- });
672
- ```
673
-
674
- This is the moment where the scaffold stops looking like "just a Vue app". The app is declaring itself as a runtime package that JSKIT can discover, load, and mutate safely.
675
-
676
- For the server side, the main file to remember is `packages/main/src/server/providers/MainServiceProvider.js`. At the beginning it is almost empty, but that emptiness is useful. It means you already have a stable place to put backend runtime code as the app grows, instead of inventing ad hoc structure later.
677
-
678
- The server-side provider starts like this:
679
-
680
- ```js
681
- import { loadAppConfig } from "../support/loadAppConfig.js";
682
-
683
- class MainServiceProvider {
684
- static id = "local.main";
685
-
686
- async register(app) {
687
- const appConfig = await loadAppConfig({
688
- moduleUrl: import.meta.url
689
- });
690
- app.instance("appConfig", appConfig);
691
- }
692
-
693
- boot() {}
694
- }
695
-
696
- export { MainServiceProvider };
697
- ```
698
-
699
- It is deliberately small, but it already shows the pattern: register things with the app container first, then grow real backend behavior from there. The client side uses the same provider lifecycle; you already saw the matching pattern earlier in the client boot path.
700
-
701
- The `.jskit/lock.json` file is also important. Treat it like JSKIT's own lock and state file. It records which runtime packages JSKIT believes are installed and which managed changes they introduced. When you use `jskit add`, `jskit update`, or generators that depend on installed package state, this file is part of the source of truth. It belongs in version control, and you should not hand-edit it.
702
-
703
- This file is narrower than `package.json`. `package.json` lists every npm dependency the app needs, including plain libraries such as Vue, Fastify, and Vuetify. `.jskit/lock.json` only tracks JSKIT package-install state: which JSKIT runtime packages were installed into the app and which files, text mutations, and dependency entries JSKIT is managing on their behalf.
704
-
705
- On a brand-new app, the lock file is telling you that only the local app package is installed so far:
706
-
707
- ```json
708
- {
709
- "lockVersion": 1,
710
- "installedPackages": {
711
- "@local/main": {
712
- "packageId": "@local/main",
713
- "version": "0.1.0",
714
- "source": {
715
- "type": "local-package",
716
- "packagePath": "packages/main",
717
- "descriptorPath": "packages/main/package.descriptor.mjs"
718
- },
719
- "managed": {
720
- "packageJson": {
721
- "dependencies": {
722
- "@local/main": {
723
- "value": "file:packages/main"
724
- }
725
- }
726
- }
727
- }
728
- }
729
- }
730
- }
731
- ```
732
-
733
- That is a useful anchor point. Before you add anything else, JSKIT already knows about exactly one runtime package: the one that belongs to your app.
734
-
735
- That is why you saw `@jskit-ai/kernel` and `@jskit-ai/http-runtime` earlier in `package.json`, but you do not see them here. They are npm dependencies of the scaffold, but they are not separate JSKIT-installed app packages in this initial state.
736
-
737
- ### Other files and options
738
-
739
- The remaining files are easier to understand once you know the core pieces above. `vite.config.mjs` configures the frontend build and the `/api` proxy used during development. `index.html` is the HTML shell Vite uses to mount Vue. `tests/` contains basic smoke tests so the app has a verification path from day one. The `scripts/` directory is now much smaller than it used to be, because JSKIT maintenance helpers such as `verify`, `jskit:update`, `devlinks`, and `release` are package-owned CLI commands rather than copied app scripts.
740
-
741
- The `create-app` command also accepts a few other flags that are useful without changing the basic meaning of this chapter's setup. `--title <text>` lets you replace the browser title and other template text with a friendlier app name. `--target <path>` lets you choose a different output directory instead of the default `./exampleapp`. `--tenancy-mode <mode>` can seed `none`, `personal`, or `workspaces`; for this chapter we intentionally use `none` so the first scaffold stays small and non-workspace. `--force` allows writing into a non-empty target directory when you know that is what you want. `--dry-run` prints the planned file writes without touching the filesystem, which is useful when you want to inspect what the generator would do. `-h` or `--help` prints the command help.
742
-
743
- <DocsTerminalTip label="Next step" title="Install shell-web before editing the scaffold">
744
-
745
- If the next thing you plan to do is install `shell-web`, do that **before** you start personalizing the starter shell files.
746
-
747
- `shell-web` upgrades the plain scaffold by claiming a few app-owned files such as `src/App.vue`, `src/pages/home.vue`, and `src/pages/home/index.vue`. It only does that when those files still match the untouched base scaffold exactly. If you edit them first, `shell-web` will refuse to overwrite your changes.
748
-
749
- </DocsTerminalTip>
750
-
751
- ## Summary
752
-
753
- At the end of this first step, you should have more than a generated folder. You should have a mental map. `src/` is the web app, `server.js` is the runtime server, `config/` defines surfaces and shared behavior, `packages/main/` is your app's own local JSKIT package, and `.jskit/lock.json` records what JSKIT has done to the project. That is the foundation the next chapters will build on.