@jskit-ai/agent-docs 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/DISTR_AGENT.md +25 -0
  2. package/guide/agent/app-extras/assistant.md +636 -0
  3. package/guide/agent/app-extras/realtime.md +223 -0
  4. package/guide/agent/app-setup/a-more-interesting-shell.md +643 -0
  5. package/guide/agent/app-setup/authentication.md +948 -0
  6. package/guide/agent/app-setup/console.md +316 -0
  7. package/guide/agent/app-setup/database-layer.md +775 -0
  8. package/guide/agent/app-setup/initial-scaffolding.md +714 -0
  9. package/guide/agent/app-setup/multi-homing.md +655 -0
  10. package/guide/agent/app-setup/users.md +355 -0
  11. package/guide/agent/app-setup/working-with-the-jskit-cli.md +983 -0
  12. package/guide/agent/generators/advanced-cruds.md +923 -0
  13. package/guide/agent/generators/crud-generators.md +556 -0
  14. package/guide/agent/generators/intro.md +63 -0
  15. package/guide/agent/generators/ui-generators.md +648 -0
  16. package/guide/agent/index.md +39 -0
  17. package/guide/human/app-extras/assistant.md +695 -0
  18. package/guide/human/app-extras/realtime.md +270 -0
  19. package/guide/human/app-setup/a-more-interesting-shell.md +734 -0
  20. package/guide/human/app-setup/authentication.md +963 -0
  21. package/guide/human/app-setup/console.md +352 -0
  22. package/guide/human/app-setup/database-layer.md +822 -0
  23. package/guide/human/app-setup/initial-scaffolding.md +738 -0
  24. package/guide/human/app-setup/multi-homing.md +795 -0
  25. package/guide/human/app-setup/users.md +404 -0
  26. package/guide/human/app-setup/working-with-the-jskit-cli.md +997 -0
  27. package/guide/human/generators/advanced-cruds.md +923 -0
  28. package/guide/human/generators/crud-generators.md +556 -0
  29. package/guide/human/generators/intro.md +109 -0
  30. package/guide/human/generators/ui-generators.md +665 -0
  31. package/guide/human/index.md +39 -0
  32. package/package.json +28 -0
  33. package/reference/autogen/KERNEL_MAP.md +536 -0
  34. package/reference/autogen/README.md +44 -0
  35. package/reference/autogen/packages/agent-docs.md +13 -0
  36. package/reference/autogen/packages/assistant-core.md +310 -0
  37. package/reference/autogen/packages/assistant-runtime.md +219 -0
  38. package/reference/autogen/packages/assistant.md +73 -0
  39. package/reference/autogen/packages/auth-core.md +352 -0
  40. package/reference/autogen/packages/auth-provider-supabase-core.md +223 -0
  41. package/reference/autogen/packages/auth-web.md +267 -0
  42. package/reference/autogen/packages/console-core.md +116 -0
  43. package/reference/autogen/packages/console-web.md +37 -0
  44. package/reference/autogen/packages/crud-core.md +283 -0
  45. package/reference/autogen/packages/crud-server-generator.md +220 -0
  46. package/reference/autogen/packages/crud-ui-generator.md +154 -0
  47. package/reference/autogen/packages/database-runtime-mysql.md +61 -0
  48. package/reference/autogen/packages/database-runtime-postgres.md +39 -0
  49. package/reference/autogen/packages/database-runtime.md +216 -0
  50. package/reference/autogen/packages/http-runtime.md +213 -0
  51. package/reference/autogen/packages/kernel.md +1350 -0
  52. package/reference/autogen/packages/realtime.md +95 -0
  53. package/reference/autogen/packages/shell-web.md +349 -0
  54. package/reference/autogen/packages/storage-runtime.md +39 -0
  55. package/reference/autogen/packages/ui-generator.md +101 -0
  56. package/reference/autogen/packages/uploads-image-web.md +76 -0
  57. package/reference/autogen/packages/uploads-runtime.md +85 -0
  58. package/reference/autogen/packages/users-core.md +307 -0
  59. package/reference/autogen/packages/users-web.md +473 -0
  60. package/reference/autogen/packages/workspaces-core.md +415 -0
  61. package/reference/autogen/packages/workspaces-web.md +372 -0
  62. package/reference/autogen/tooling/config-eslint.md +52 -0
  63. package/reference/autogen/tooling/create-app.md +194 -0
  64. package/reference/autogen/tooling/jskit-catalog.md +27 -0
  65. package/reference/autogen/tooling/jskit-cli.md +624 -0
  66. package/reference/autogen/tooling/test-support.md +27 -0
  67. package/reference/autogen/tooling/testUtils.md +31 -0
  68. package/templates/APP_BLUEPRINT.md +57 -0
  69. package/workflow/app-state.md +33 -0
  70. package/workflow/bootstrap.md +24 -0
  71. package/workflow/feature-delivery.md +21 -0
  72. package/workflow/review.md +22 -0
  73. package/workflow/scoping.md +26 -0
@@ -0,0 +1,714 @@
1
+ <!-- Generated by `npm run agent-docs:build` from `docs/guide/app-setup/initial-scaffolding.md`. Do not edit manually. -->
2
+
3
+ # Initial scaffolding
4
+
5
+ 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.
6
+
7
+ Start in a working directory and run:
8
+
9
+ ```bash
10
+ npx @jskit-ai/create-app exampleapp --tenancy-mode none
11
+ cd exampleapp
12
+ npm install
13
+ ```
14
+
15
+ 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.
16
+
17
+ After creating the scaffolding (which comes with a package.json file), you will need to run `npm install` to install dependencies.
18
+
19
+ **Try Bash Completion!**
20
+
21
+ 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:
22
+
23
+ ```bash
24
+ source <(npx jskit completion bash)
25
+ ```
26
+
27
+ If you want JSKIT completion to keep working in future Bash sessions as well, run:
28
+
29
+ ```bash
30
+ npx jskit completion bash --install
31
+ ```
32
+
33
+ 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`.
34
+
35
+ Once completion is loaded, you can test it immediately.
36
+
37
+ If you type:
38
+
39
+ ```bash
40
+ npx jskit li
41
+ ```
42
+
43
+ and press Tab twice, Bash will show completions such as `list`, `list-placements`, and `list-component-tokens`.
44
+
45
+ If you type:
46
+
47
+ ```bash
48
+ npx jskit add p
49
+ ```
50
+
51
+ and press Tab, JSKIT will complete that subcommand argument to `package`.
52
+
53
+ The `jskit` command is central in the use of JSKIT. The autocompletion will help speeding things up.
54
+
55
+ To see the app in the browser, the quickest path is:
56
+
57
+ ```bash
58
+ npm run dev
59
+ ```
60
+
61
+ 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.
62
+
63
+ 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:
64
+
65
+ ```bash
66
+ npm run server
67
+ ```
68
+
69
+ 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.
70
+
71
+ If you want a fast sanity check that the backend is alive, open `http://localhost:3000/api/health` or request it from the terminal:
72
+
73
+ ```bash
74
+ curl http://localhost:3000/api/health
75
+ ```
76
+
77
+ You should get a small JSON response with `ok: true`.
78
+
79
+ ## Reading the scaffold
80
+
81
+ 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:
82
+
83
+ ```text
84
+ exampleapp/
85
+ .jskit/
86
+ config/
87
+ packages/main/
88
+ server/
89
+ src/
90
+ tests/
91
+ package.json
92
+ server.js
93
+ vite.config.mjs
94
+ ```
95
+
96
+ 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.
97
+
98
+ The most important parts look like this:
99
+
100
+ ```json
101
+ {
102
+ "scripts": {
103
+ "server": "node ./bin/server.js",
104
+ "server:all": "node ./bin/server.js",
105
+ "server:home": "SERVER_SURFACE=home node ./bin/server.js",
106
+ "dev": "vite",
107
+ "dev:all": "vite",
108
+ "dev:home": "VITE_SURFACE=home vite",
109
+ "build": "vite build",
110
+ "build:all": "vite build",
111
+ "build:home": "VITE_SURFACE=home vite build",
112
+ "test": "node --test",
113
+ "test:client": "vitest run tests/client",
114
+ "verify": "npm run lint && npm run test && npm run test:client && npm run build && npx jskit doctor"
115
+ },
116
+ "dependencies": {
117
+ "@local/main": "file:packages/main",
118
+ "@jskit-ai/kernel": "0.x",
119
+ "@jskit-ai/http-runtime": "0.x",
120
+ "fastify": "^5.7.4",
121
+ "pinia": "^3.0.4",
122
+ "vue": "^3.5.13",
123
+ "vuetify": "^4.0.0"
124
+ },
125
+ "devDependencies": {
126
+ "@jskit-ai/jskit-cli": "0.x",
127
+ "vite": "^6.1.0",
128
+ "vitest": "^4.0.18"
129
+ }
130
+ }
131
+ ```
132
+
133
+ 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 `verify` script is also useful to notice early, because it shows the default quality gate the scaffold expects you to run later.
134
+
135
+ The last command in that script, `npx jskit doctor`, is worth noticing specifically. Linting, tests, and builds check your source code and runtime behavior. `doctor` 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.
136
+
137
+ 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.
138
+
139
+ ### App surfaces in JSKIT
140
+
141
+ 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.
142
+
143
+ 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
144
+
145
+ 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.
146
+
147
+ Here is the part of `config/public.js` that sets that up:
148
+
149
+ ```js
150
+ import { surfaceAccessPolicies } from "./surfaceAccessPolicies.js";
151
+
152
+ export const config = {};
153
+ config.tenancyMode = "none";
154
+
155
+ config.surfaceModeAll = "all";
156
+ config.surfaceDefaultId = "home";
157
+ config.webRootAllowed = "no";
158
+ config.surfaceAccessPolicies = surfaceAccessPolicies;
159
+ config.surfaceDefinitions = {};
160
+ config.surfaceDefinitions.home = {
161
+ id: "home",
162
+ label: "Home",
163
+ pagesRoot: "home",
164
+ enabled: true,
165
+ requiresAuth: false,
166
+ requiresWorkspace: false,
167
+ accessPolicyId: "public",
168
+ origin: ""
169
+ };
170
+ ```
171
+
172
+ 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.
173
+
174
+ The starter policies are small enough to read in one glance:
175
+
176
+ ```js
177
+ export const surfaceAccessPolicies = {};
178
+
179
+ surfaceAccessPolicies.public = {};
180
+ ```
181
+
182
+ That tells you one thing immediately: `home` is open. More specific policies only appear when later packages add them.
183
+
184
+ ### The client side
185
+
186
+ #### Client bootstrap
187
+
188
+ 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.
189
+
190
+ The important part looks like this:
191
+
192
+ ```js
193
+ import { createApp } from "vue";
194
+ import { createPinia } from "pinia";
195
+ import { createRouter, createWebHistory } from "vue-router/auto";
196
+ import { routes } from "vue-router/auto-routes";
197
+ import "vuetify/styles";
198
+ import { createVuetify } from "vuetify";
199
+ import * as components from "vuetify/components";
200
+ import * as directives from "vuetify/directives";
201
+ import { aliases as mdiAliases, mdi } from "vuetify/iconsets/mdi-svg";
202
+ import { createSurfaceRuntime } from "@jskit-ai/kernel/shared/surface/runtime";
203
+ import {
204
+ bootstrapClientShellApp,
205
+ createShellRouter
206
+ } from "@jskit-ai/kernel/client";
207
+ import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
208
+ import App from "./App.vue";
209
+ import NotFoundView from "./views/NotFound.vue";
210
+ import { config } from "../config/public.js";
211
+
212
+ const surfaceRuntime = createSurfaceRuntime({
213
+ allMode: config.surfaceModeAll,
214
+ surfaces: config.surfaceDefinitions,
215
+ defaultSurfaceId: config.surfaceDefaultId
216
+ });
217
+
218
+ const surfaceMode = surfaceRuntime.normalizeSurfaceMode(import.meta.env.VITE_SURFACE);
219
+ const { router, fallbackRoute } = createShellRouter({
220
+ createRouter,
221
+ history: createWebHistory(),
222
+ routes,
223
+ surfaceRuntime,
224
+ surfaceMode,
225
+ notFoundComponent: NotFoundView,
226
+ guard: {
227
+ surfaceDefinitions: config.surfaceDefinitions,
228
+ defaultSurfaceId: config.surfaceDefaultId,
229
+ webRootAllowed: config.webRootAllowed
230
+ }
231
+ });
232
+
233
+ const vuetify = createVuetify({
234
+ components,
235
+ directives,
236
+ theme: {
237
+ defaultTheme: "light"
238
+ },
239
+ icons: {
240
+ defaultSet: "mdi",
241
+ aliases: mdiAliases,
242
+ sets: { mdi }
243
+ }
244
+ });
245
+ const pinia = createPinia();
246
+
247
+ void bootstrapClientShellApp({
248
+ createApp,
249
+ rootComponent: App,
250
+ appConfig: config,
251
+ appPlugins: [pinia, vuetify],
252
+ pinia,
253
+ router,
254
+ bootClientModules: bootInstalledClientModules,
255
+ surfaceRuntime,
256
+ surfaceMode,
257
+ env: import.meta.env,
258
+ fallbackRoute
259
+ }).catch((error) => {
260
+ console.error("Failed to bootstrap client app.", error);
261
+ });
262
+ ```
263
+
264
+ 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.
265
+
266
+ <DocsInDepth title="In depth" preview-height="15rem">
267
+
268
+ `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:
269
+
270
+ ```js
271
+ config.surfaceDefinitions = {
272
+ home: {
273
+ pagesRoot: "home", // this surface lives under src/pages/home
274
+ requiresAuth: false,
275
+ accessPolicyId: "public"
276
+ }
277
+ };
278
+
279
+ config.surfaceDefaultId = "home"; // default surface
280
+ config.surfaceModeAll = "all"; // unrestricted surface mode
281
+ ```
282
+
283
+ 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`.
284
+
285
+ That gives the client a normalized surface runtime with answers to questions such as:
286
+
287
+ ```js
288
+ surfaceRuntime.DEFAULT_SURFACE_ID; // "home"
289
+ surfaceRuntime.listEnabledSurfaceIds(); // ["home"]
290
+ surfaceRuntime.resolveSurfaceFromPathname("/home"); // "home"
291
+ ```
292
+
293
+ `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.
294
+
295
+ `createShellRouter(...)` uses that `surfaceRuntime` object to assemble the actual router. Concretely, it does this:
296
+
297
+ 1. Takes the generated Vue routes.
298
+ 2. Adds a fallback not-found route.
299
+ 3. Filters those routes down to the ones that belong to the current surface mode.
300
+ 4. Calls Vue Router's real `createRouter(...)` with that filtered route list.
301
+ 5. Installs a `beforeEach` guard for surface-aware redirects and access behavior.
302
+
303
+ 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.
304
+
305
+ In the starter app, with only `home`, this is almost boring. It mostly means:
306
+
307
+ - build the router
308
+ - add not-found
309
+ - enforce the `/` to `/home` redirect behavior
310
+
311
+ `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.
312
+
313
+ 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()`.
314
+
315
+ `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.
316
+
317
+ `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:
318
+
319
+ ```js
320
+ import { bootInstalledClientModules } from "virtual:jskit-client-bootstrap";
321
+ ```
322
+
323
+ 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.
324
+
325
+ The startup path looks like this:
326
+
327
+ 1. `src/main.js` imports `virtual:jskit-client-bootstrap`.
328
+ 2. The JSKIT Vite plugin sees that import and generates a module on the fly.
329
+ 3. That generated module imports the `.../client` entrypoint of each installed JSKIT package that has client-side behavior.
330
+ 4. It wraps those imported modules into one function called `bootInstalledClientModules(...)`.
331
+ 5. `bootstrapClientShellApp(...)` calls that function before the router is installed and before the app is mounted.
332
+ 6. `bootClientModules(...)` then starts client providers and registers any client routes contributed by those installed packages.
333
+
334
+ The generated module is roughly like this:
335
+
336
+ ```js
337
+ import * as authClient from "@jskit-ai/auth-web/client";
338
+ import * as usersClient from "@jskit-ai/users-web/client";
339
+ import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
340
+
341
+ const installedClientModules = [
342
+ { packageId: "@jskit-ai/auth-web", module: authClient },
343
+ { packageId: "@jskit-ai/users-web", module: usersClient }
344
+ ];
345
+
346
+ async function bootInstalledClientModules(context = {}) {
347
+ return bootClientModules({
348
+ ...context,
349
+ clientModules: installedClientModules
350
+ });
351
+ }
352
+ ```
353
+
354
+ 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.
355
+
356
+ If this were plain Vue without that plugin, you would have to maintain the list yourself:
357
+
358
+ ```js
359
+ import { bootClientModules } from "@jskit-ai/kernel/client/moduleBootstrap";
360
+ import * as authClient from "@jskit-ai/auth-web/client";
361
+ import * as usersClient from "@jskit-ai/users-web/client";
362
+
363
+ await bootClientModules({
364
+ app,
365
+ router,
366
+ surfaceRuntime,
367
+ surfaceMode,
368
+ clientModules: [
369
+ { packageId: "@jskit-ai/auth-web", module: authClient },
370
+ { packageId: "@jskit-ai/users-web", module: usersClient }
371
+ ]
372
+ });
373
+ ```
374
+
375
+ JSKIT automates that step so adding or removing packages does not require hand-editing `src/main.js`.
376
+
377
+ 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.
378
+
379
+ `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.
380
+
381
+ </DocsInDepth>
382
+
383
+ #### The main package (client side)
384
+
385
+ 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:
386
+
387
+ ```js
388
+ client: {
389
+ providers: [
390
+ {
391
+ entrypoint: "src/client/providers/MainClientProvider.js",
392
+ export: "MainClientProvider"
393
+ }
394
+ ]
395
+ }
396
+ ```
397
+
398
+ That declaration is one of the things `bootClientModules(...)` uses. On the client, the lifecycle is:
399
+
400
+ 1. collect the installed client modules
401
+ 2. resolve the provider classes they declare
402
+ 3. create the client runtime application container
403
+ 4. run each provider's `register()` method
404
+ 5. run each provider's `boot()` method, if it has one
405
+
406
+ 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:
407
+
408
+ ```js
409
+ const mainClientComponents = [];
410
+
411
+ function registerMainClientComponent(token, resolveComponent) {
412
+ mainClientComponents.push({ token, resolveComponent });
413
+ }
414
+
415
+ class MainClientProvider {
416
+ static id = "local.main.client";
417
+
418
+ register(app) {
419
+ for (const { token, resolveComponent } of mainClientComponents) {
420
+ app.singleton(token, resolveComponent);
421
+ }
422
+ }
423
+ }
424
+
425
+ export {
426
+ MainClientProvider,
427
+ registerMainClientComponent
428
+ };
429
+ ```
430
+
431
+ 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.
432
+
433
+ ```js
434
+ import MenuLinkItem from "/src/components/menus/MenuLinkItem.vue";
435
+
436
+ registerMainClientComponent("local.main.ui.menu-link-item", () => MenuLinkItem);
437
+ ```
438
+
439
+ 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.
440
+
441
+ 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.
442
+
443
+ `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()`.
444
+
445
+ 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.
446
+
447
+ The wrapper file is tiny, but it is doing an important job:
448
+
449
+ ```vue
450
+ <route lang="json">
451
+ {
452
+ "meta": {
453
+ "jskit": {
454
+ "surface": "home"
455
+ }
456
+ }
457
+ }
458
+ </route>
459
+
460
+ <template>
461
+ <RouterView />
462
+ </template>
463
+ ```
464
+
465
+ This is why `src/pages/home/index.vue` becomes part of the `home` surface instead of just being "some route".
466
+
467
+ **Routing: How JSKIT Reads Route Metadata**
468
+
469
+ The `<route lang="json">` block is not something `shell-web` or `auth-web` parses directly.
470
+
471
+ 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.
472
+
473
+ That is why a block like this can affect behavior without any extra code in the page component itself:
474
+
475
+ - `meta.jskit.surface` tells JSKIT which surface the page tree belongs to
476
+ - `meta.guard.policy` can later tell JSKIT whether the route should be treated as `public` or `authenticated`
477
+
478
+ So the order is:
479
+
480
+ 1. the router plugin reads the `<route>` block
481
+ 2. the generated route record gets a normal `meta` object
482
+ 3. JSKIT reads that `meta` during routing
483
+
484
+ `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.
485
+
486
+ **Container: App Methods**
487
+
488
+ The `app` object in `register(app)` and `boot(app)` is JSKIT's application container.
489
+
490
+ Defining values:
491
+
492
+ - `app.singleton(token, factory)`: register one lazily created shared value for the whole app.
493
+ - `app.bind(token, factory)`: register a factory that creates a fresh value every time the token is resolved.
494
+ - `app.scoped(token, factory)`: register one value per child scope.
495
+ - `app.instance(token, value)`: register an already-created value directly.
496
+ - `app.tag(token, tagName)`: add a token to a named group.
497
+
498
+ Typical examples look like this:
499
+
500
+ ```js
501
+ app.instance("appConfig", appConfig);
502
+ app.singleton("local.main.ui.menu-link-item", () => TabLinkItem);
503
+ app.bind("feature.clock", () => new Clock());
504
+ app.tag("auth.login.component", "auth.ui");
505
+ ```
506
+
507
+ That means:
508
+
509
+ - `instance(...)` is good for something you already created, such as loaded config.
510
+ - `singleton(...)` is good for one shared runtime object or component resolver.
511
+ - `bind(...)` is for "make a fresh one each time someone asks".
512
+ - `tag(...)` lets you group related tokens so another part of the app can ask for the whole set later.
513
+
514
+ Using values:
515
+
516
+ - `app.make(token)`: resolve a token from the container.
517
+ - `app.has(token)`: check whether a token is registered.
518
+ - `app.createScope(scopeId)`: create a child scope.
519
+ - `app.resolveTag(tagName)`: resolve all tokens in that group.
520
+
521
+ Then later code can consume those registrations like this:
522
+
523
+ ```js
524
+ const authGuardRuntime = app.make("runtime.auth-guard.client");
525
+
526
+ if (app.has("jskit.client.vue.app")) {
527
+ const vueApp = app.make("jskit.client.vue.app");
528
+ }
529
+
530
+ const authUi = app.resolveTag("auth.ui");
531
+ const requestScope = app.createScope("request:123");
532
+ ```
533
+
534
+ 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:
535
+
536
+ ```js
537
+ app.singleton("logger", () => createLogger());
538
+
539
+ const requestScope = app.createScope("request:123");
540
+ requestScope.instance("request.id", "request:123");
541
+
542
+ const logger = requestScope.make("logger"); // inherited from the parent app container
543
+ const requestId = requestScope.make("request.id"); // local to this scope
544
+ ```
545
+
546
+ Here:
547
+
548
+ - `make(...)` gets the thing behind a token.
549
+ - `has(...)` lets you check before assuming a token exists.
550
+ - `resolveTag(...)` gets every token in a named group.
551
+ - `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.
552
+
553
+ ### The server side
554
+
555
+ 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.
556
+
557
+ The core of that startup path looks like this:
558
+
559
+ ```js
560
+ async function createServer() {
561
+ const app = Fastify({ logger: true });
562
+ registerTypeBoxFormats();
563
+ app.setValidatorCompiler(TypeBoxValidatorCompiler);
564
+
565
+ app.get("/api/health", async () => {
566
+ return {
567
+ ok: true,
568
+ app: "exampleapp"
569
+ };
570
+ });
571
+
572
+ const runtimeEnv = resolveRuntimeEnv();
573
+ const appRoot = path.resolve(process.cwd());
574
+ const runtime = await tryCreateProviderRuntimeFromApp({
575
+ appRoot,
576
+ profile: resolveRuntimeProfileFromSurface({
577
+ surfaceRuntime,
578
+ serverSurface: runtimeEnv.SERVER_SURFACE
579
+ }),
580
+ env: runtimeEnv,
581
+ logger: app.log,
582
+ fastify: app
583
+ });
584
+
585
+ registerSurfaceRequestConstraint({
586
+ fastify: app,
587
+ surfaceRuntime,
588
+ serverSurface: runtimeEnv.SERVER_SURFACE,
589
+ globalUiPaths: resolveGlobalUiPaths(runtime?.globalUiPaths || [])
590
+ });
591
+
592
+ return app;
593
+ }
594
+ ```
595
+
596
+ 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.
597
+
598
+ 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.
599
+
600
+ 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.
601
+
602
+ #### The main package (server side)
603
+
604
+ 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.
605
+
606
+ 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()`.
607
+
608
+ The server part of that descriptor looks like this:
609
+
610
+ ```js
611
+ export default Object.freeze({
612
+ packageVersion: 1,
613
+ packageId: "@local/main",
614
+ version: "0.1.0",
615
+ kind: "runtime",
616
+ runtime: {
617
+ server: {
618
+ providerEntrypoint: "src/server/index.js",
619
+ providers: [
620
+ {
621
+ discover: {
622
+ dir: "src/server/providers",
623
+ pattern: "*Provider.js"
624
+ }
625
+ }
626
+ ]
627
+ }
628
+ },
629
+ metadata: {
630
+ server: {
631
+ routes: []
632
+ }
633
+ }
634
+ });
635
+ ```
636
+
637
+ 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.
638
+
639
+ 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.
640
+
641
+ The server-side provider starts like this:
642
+
643
+ ```js
644
+ import { loadAppConfig } from "../support/loadAppConfig.js";
645
+
646
+ class MainServiceProvider {
647
+ static id = "local.main";
648
+
649
+ async register(app) {
650
+ const appConfig = await loadAppConfig({
651
+ moduleUrl: import.meta.url
652
+ });
653
+ app.instance("appConfig", appConfig);
654
+ }
655
+
656
+ boot() {}
657
+ }
658
+
659
+ export { MainServiceProvider };
660
+ ```
661
+
662
+ 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.
663
+
664
+ 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.
665
+
666
+ 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.
667
+
668
+ On a brand-new app, the lock file is telling you that only the local app package is installed so far:
669
+
670
+ ```json
671
+ {
672
+ "lockVersion": 1,
673
+ "installedPackages": {
674
+ "@local/main": {
675
+ "packageId": "@local/main",
676
+ "version": "0.1.0",
677
+ "source": {
678
+ "type": "local-package",
679
+ "packagePath": "packages/main",
680
+ "descriptorPath": "packages/main/package.descriptor.mjs"
681
+ },
682
+ "managed": {
683
+ "packageJson": {
684
+ "dependencies": {
685
+ "@local/main": {
686
+ "value": "file:packages/main"
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ }
693
+ }
694
+ ```
695
+
696
+ 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.
697
+
698
+ 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.
699
+
700
+ ### Other files and options
701
+
702
+ 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 collects helper scripts for release, updating JSKIT packages, and linking a local JSKIT checkout during framework development.
703
+
704
+ 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.
705
+
706
+ **Next step: Install shell-web before editing the scaffold**
707
+
708
+ If the next thing you plan to do is install `shell-web`, do that **before** you start personalizing the starter shell files.
709
+
710
+ `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.
711
+
712
+ ## Summary
713
+
714
+ 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.