@jskit-ai/agent-docs 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/DISTR_AGENT.md +1 -0
  2. package/guide/agent/app-extras/assistant.md +9 -20
  3. package/guide/agent/app-extras/mobile-capacitor.md +381 -0
  4. package/guide/agent/app-extras/realtime.md +13 -12
  5. package/guide/agent/app-setup/a-more-interesting-shell.md +102 -87
  6. package/guide/agent/app-setup/authentication.md +50 -31
  7. package/guide/agent/app-setup/console.md +8 -8
  8. package/guide/agent/app-setup/database-layer.md +15 -15
  9. package/guide/agent/app-setup/initial-scaffolding.md +37 -5
  10. package/guide/agent/app-setup/multi-homing.md +50 -47
  11. package/guide/agent/app-setup/quickstart.md +11 -14
  12. package/guide/agent/app-setup/users.md +24 -24
  13. package/guide/agent/app-setup/working-with-the-jskit-cli.md +18 -10
  14. package/guide/agent/generators/advanced-cruds.md +111 -68
  15. package/guide/agent/generators/crud-generators.md +5 -5
  16. package/guide/agent/generators/ui-generators.md +44 -36
  17. package/guide/agent/index.md +3 -2
  18. package/package.json +1 -1
  19. package/patterns/INDEX.md +4 -1
  20. package/patterns/client-requests.md +23 -6
  21. package/patterns/crud-repository-mapping.md +1 -1
  22. package/patterns/crud-scaffolding.md +10 -1
  23. package/patterns/filters.md +28 -10
  24. package/patterns/generated-ui-contract-tracking.md +62 -0
  25. package/patterns/page-scaffolding.md +20 -2
  26. package/patterns/placements.md +67 -1
  27. package/patterns/surfaces.md +5 -1
  28. package/patterns/ui-testing.md +3 -0
  29. package/reference/autogen/KERNEL_MAP.md +46 -1
  30. package/reference/autogen/README.md +3 -0
  31. package/reference/autogen/packages/assistant.md +2 -1
  32. package/reference/autogen/packages/auth-web.md +20 -1
  33. package/reference/autogen/packages/crud-ui-generator.md +20 -8
  34. package/reference/autogen/packages/google-rewarded-core.md +222 -0
  35. package/reference/autogen/packages/google-rewarded-web.md +71 -0
  36. package/reference/autogen/packages/kernel.md +94 -10
  37. package/reference/autogen/packages/mobile-capacitor.md +76 -0
  38. package/reference/autogen/packages/realtime.md +1 -0
  39. package/reference/autogen/packages/shell-web.md +48 -3
  40. package/reference/autogen/packages/ui-generator.md +34 -2
  41. package/reference/autogen/packages/users-core.md +2 -0
  42. package/reference/autogen/packages/users-web.md +92 -4
  43. package/reference/autogen/packages/workspaces-web.md +3 -2
  44. package/reference/autogen/tooling/jskit-cli.md +114 -9
  45. package/templates/app/AGENTS.md +1 -0
  46. package/workflow/bootstrap.md +1 -0
@@ -83,7 +83,7 @@ npm run server
83
83
 
84
84
  Then open `http://localhost:5173/auth/login` in the browser.
85
85
 
86
- The login page is now real, and it already contains several different auth modes behind the same card.
86
+ The login page is real, and it already contains several different auth modes behind the same card.
87
87
 
88
88
  - **Sign in** is the normal email-and-password flow.
89
89
  - **Register** creates a new Supabase auth user.
@@ -91,7 +91,7 @@ The login page is now real, and it already contains several different auth modes
91
91
  - **Use one-time code** switches to an email OTP login flow.
92
92
  - **Remember this account on this device** stores a small local hint in browser storage so the next visit can greet the last-used account and let the user keep that email preselected.
93
93
 
94
- If you go back to `http://localhost:5173/home`, the shell also has a small auth widget in the top-right corner. When you are signed out it shows a guest state and a menu entry that leads to `/auth/login`. After you sign in, the same placement changes to a sign-out menu.
94
+ If you go back to `http://localhost:5173/home`, the shell also has a small auth widget in the status area. When you are signed out it shows a guest state and a menu entry that leads to `/auth/login`. After you sign in, the same placement changes to a sign-out menu.
95
95
 
96
96
  ## Reading the screen carefully
97
97
 
@@ -165,7 +165,7 @@ JSKIT asks Supabase to send OTP login emails only for existing users. In other w
165
165
 
166
166
  That endpoint asks Supabase to send a password reset email. In this chapter's scaffold, this mode is only the **request** step.
167
167
 
168
- The backend already supports the later recovery endpoints too, but the chapter's simple app does **not** yet scaffold a dedicated app-owned `reset-password` page. So at this stage, the guide should be read as: the screen can request recovery emails now, while the full browser-side password-reset completion UI is still something you would add explicitly in the app.
168
+ The backend already supports the later recovery endpoints too, but the chapter's simple app does **not** yet scaffold a dedicated app-owned `reset-password` page. So at this stage, the guide should be read as: the screen can request recovery emails, while the full browser-side password-reset completion UI is still something you would add explicitly in the app.
169
169
 
170
170
  ### Remembered account behavior
171
171
 
@@ -184,7 +184,7 @@ On the next visit, the card can show a `Welcome back, ...` panel and a `Use anot
184
184
 
185
185
  The screen is also ready to show OAuth provider buttons, but only if providers are configured.
186
186
 
187
- Right now the chapter's `config.server.js` keeps this empty:
187
+ For this chapter, `config.server.js` keeps this empty:
188
188
 
189
189
  ```js
190
190
  config.auth = {
@@ -319,10 +319,10 @@ The placement entry is just a normal shell link:
319
319
  ```js
320
320
  addPlacement({
321
321
  id: "ui-generator.page.home.reports.link",
322
- target: "shell-layout:primary-menu",
322
+ target: "shell.primary-nav",
323
+ kind: "link",
323
324
  surfaces: ["home"],
324
325
  order: 155,
325
- componentToken: "local.main.ui.surface-aware-menu-link-item",
326
326
  props: {
327
327
  label: "Reports",
328
328
  surface: "home",
@@ -332,13 +332,22 @@ addPlacement({
332
332
  });
333
333
  ```
334
334
 
335
- And the page file itself is a normal page scaffold:
335
+ And the page file itself is a normal generated empty-state screen:
336
336
 
337
337
  ```vue
338
338
  <template>
339
- <section class="pa-4">
340
- <h1 class="text-h5 mb-2">Reports</h1>
341
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
339
+ <section class="generated-page-screen d-flex flex-column ga-4">
340
+ <header>
341
+ <p class="text-overline text-medium-emphasis mb-1">Screen</p>
342
+ <h1 class="generated-page-screen__title">Reports</h1>
343
+ </header>
344
+
345
+ <v-sheet rounded="lg" border class="generated-page-screen__empty-state">
346
+ <h2 class="text-h6 mb-2">No Reports activity yet</h2>
347
+ <p class="text-body-2 text-medium-emphasis mb-0">
348
+ Activity and actions for this screen will appear here.
349
+ </p>
350
+ </v-sheet>
342
351
  </section>
343
352
  </template>
344
353
  ```
@@ -364,9 +373,18 @@ To make the route require login, add a route guard block to `src/pages/home/repo
364
373
  </route>
365
374
 
366
375
  <template>
367
- <section class="pa-4">
368
- <h1 class="text-h5 mb-2">Reports</h1>
369
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
376
+ <section class="generated-page-screen d-flex flex-column ga-4">
377
+ <header>
378
+ <p class="text-overline text-medium-emphasis mb-1">Screen</p>
379
+ <h1 class="generated-page-screen__title">Reports</h1>
380
+ </header>
381
+
382
+ <v-sheet rounded="lg" border class="generated-page-screen__empty-state">
383
+ <h2 class="text-h6 mb-2">No Reports activity yet</h2>
384
+ <p class="text-body-2 text-medium-emphasis mb-0">
385
+ Activity and actions for this screen will appear here.
386
+ </p>
387
+ </v-sheet>
370
388
  </section>
371
389
  </template>
372
390
  ```
@@ -383,17 +401,17 @@ So after login, JSKIT can send the user back to the page they originally asked f
383
401
 
384
402
  ### Hide the menu entry when signed out
385
403
 
386
- The route is now protected, but the drawer link is still visible. That is expected. Route protection and shell visibility are separate concerns.
404
+ The route is protected, but the drawer link is still visible. That is expected. Route protection and shell visibility are separate concerns.
387
405
 
388
406
  To hide the `Reports` menu entry until the user is logged in, update the placement entry in `src/placement.js`:
389
407
 
390
408
  ```js
391
409
  addPlacement({
392
410
  id: "ui-generator.page.home.reports.link",
393
- target: "shell-layout:primary-menu",
411
+ target: "shell.primary-nav",
412
+ kind: "link",
394
413
  surfaces: ["home"],
395
414
  order: 155,
396
- componentToken: "local.main.ui.surface-aware-menu-link-item",
397
415
  props: {
398
416
  label: "Reports",
399
417
  surface: "home",
@@ -407,7 +425,7 @@ addPlacement({
407
425
 
408
426
  The only new part is the `when(...)` line. That predicate is evaluated by the shell placement runtime using the auth context that `auth-web` injects from `/api/session`.
409
427
 
410
- So the behavior now becomes:
428
+ So the behavior becomes:
411
429
 
412
430
  - signed out:
413
431
  - the `Reports` drawer entry disappears
@@ -440,7 +458,7 @@ const auth = useAuthStore();
440
458
  </v-alert>
441
459
 
442
460
  <h1 class="text-h5 mb-2">Home</h1>
443
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
461
+ <p class="text-body-2 text-medium-emphasis">Your account session is active on this device.</p>
444
462
  </section>
445
463
  </template>
446
464
  ```
@@ -501,7 +519,7 @@ At this point the guide has shown three distinct layers of client state:
501
519
  - `shell-web` adds shell-facing stores such as `useShellLayoutStore()`
502
520
  - `auth-web` adds `useAuthStore()` for authentication state
503
521
 
504
- That progression is intentional. Packages keep their operational runtimes internally, but the app-facing shared state they surface to Vue code is now store-based.
522
+ That progression is intentional. Packages keep their operational runtimes internally, but the app-facing shared state they surface to Vue code is store-based.
505
523
 
506
524
  ## What `auth-base` adds to the app
507
525
 
@@ -528,7 +546,7 @@ Three things are worth noticing immediately.
528
546
 
529
547
  - `auth-provider-supabase-core` is the provider-specific runtime.
530
548
  - `auth-web` is the part that adds the web routes and the default auth UI.
531
- - there is now an `auth` surface-specific dev/build script family, just as `home` already had.
549
+ - there is an `auth` surface-specific dev/build script family, just as `home` already had.
532
550
 
533
551
  The provider command also writes a new `.env` file:
534
552
 
@@ -541,7 +559,7 @@ APP_PUBLIC_URL=http://localhost:5173
541
559
 
542
560
  This is the bridge between the scaffold and your real Supabase project. `APP_PUBLIC_URL` matters because auth emails and callback flows need to know which browser URL they should return to.
543
561
 
544
- Public routing config changes too. `config/public.js` now has a second surface:
562
+ Public routing config changes too. `config/public.js` has a second surface:
545
563
 
546
564
  ```js
547
565
  config.surfaceDefinitions.auth = {
@@ -568,7 +586,7 @@ config.auth = {
568
586
  };
569
587
  ```
570
588
 
571
- That small block explains a lot of the default login screen. The stock UI is ready for OAuth providers such as Google, but right now the provider list is empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
589
+ That small block explains a lot of the default login screen. The stock UI is ready for OAuth providers such as Google, but this chapter keeps the provider list empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
572
590
 
573
591
  The auth routes themselves are app-owned wrappers around the module-supplied default views. `src/pages/auth/login.vue` looks like this:
574
592
 
@@ -606,7 +624,8 @@ Authentication also becomes visible in the shell through `src/placement.js`:
606
624
  ```js
607
625
  addPlacement({
608
626
  id: "auth.profile.widget",
609
- target: "shell-layout:top-right",
627
+ target: "shell.status",
628
+ kind: "component",
610
629
  surfaces: ["*"],
611
630
  order: 1000,
612
631
  componentToken: "auth.web.profile.widget"
@@ -614,10 +633,10 @@ addPlacement({
614
633
 
615
634
  addPlacement({
616
635
  id: "auth.profile.menu.sign-in",
617
- target: "auth-profile-menu:primary-menu",
636
+ target: "auth.profile-menu",
637
+ kind: "link",
618
638
  surfaces: ["*"],
619
639
  order: 200,
620
- componentToken: "auth.web.profile.menu.link-item",
621
640
  props: {
622
641
  label: "Sign in",
623
642
  to: "/auth/login"
@@ -627,10 +646,10 @@ addPlacement({
627
646
 
628
647
  addPlacement({
629
648
  id: "auth.profile.menu.sign-out",
630
- target: "auth-profile-menu:primary-menu",
649
+ target: "auth.profile-menu",
650
+ kind: "link",
631
651
  surfaces: ["*"],
632
652
  order: 1000,
633
- componentToken: "auth.web.profile.menu.link-item",
634
653
  props: {
635
654
  label: "Sign out",
636
655
  to: "/auth/signout"
@@ -639,7 +658,7 @@ addPlacement({
639
658
  });
640
659
  ```
641
660
 
642
- This is the shell placement system from the previous chapter doing real work again. `auth-web` does not hard-code a permanent top-right button into `ShellLayout.vue`. Instead, it contributes a widget and two menu entries into named outlets, and those entries react to the current auth state.
661
+ This is the shell placement system from the previous chapter doing real work again. `auth-web` does not hard-code a permanent auth button into `ShellLayout.vue`. Instead, it contributes the profile widget into the semantic `shell.status` placement and contributes sign-in/sign-out links into the semantic `auth.profile-menu` placement. Topology maps those semantic targets to concrete outlets and renderers.
643
662
 
644
663
  So the auth story in this chapter is spread across clear responsibilities:
645
664
 
@@ -701,7 +720,7 @@ onBeforeUnmount(() => {
701
720
  </v-alert>
702
721
 
703
722
  <h1 class="text-h5 mb-2">Home</h1>
704
- <p class="text-body-2 text-medium-emphasis">Replace this scaffold with your page implementation.</p>
723
+ <p class="text-body-2 text-medium-emphasis">Your account session is active on this device.</p>
705
724
  </section>
706
725
  </template>
707
726
  ```
@@ -916,7 +935,7 @@ It is also why the shell widget can react cleanly to auth state without storing
916
935
 
917
936
  ### Authenticated Playwright testing with the dev auth bypass
918
937
 
919
- JSKIT now ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through Supabase.
938
+ JSKIT ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through Supabase.
920
939
 
921
940
  This is the standard path the agent should use for authenticated browser tests:
922
941
 
@@ -1082,6 +1101,6 @@ That is why the confirmation screen in the app should be understood as a **sessi
1082
1101
 
1083
1102
  ## Summary
1084
1103
 
1085
- After this chapter, the app can really authenticate against Supabase. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The provider-specific values now live in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1104
+ After this chapter, the app can really authenticate against Supabase. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The provider-specific values live in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1086
1105
 
1087
1106
  Just as importantly, the app is still deliberately incomplete. Authentication exists, but the database-backed user model does not. That separation is useful, because the next layer of the guide can explain users and persistence without having to also explain the first auth setup at the same time.
@@ -25,7 +25,7 @@ npm run db:migrate
25
25
  - `console-core` owns the console schema, owner check, bootstrap flag, and API routes
26
26
  - `console-web` owns the console surface scaffold, settings shell, and menu placement
27
27
 
28
- So the console is a real vertical slice now. The users packages no longer know about it.
28
+ The console is a real vertical slice. The users packages do not own it.
29
29
 
30
30
  ## What the console is for
31
31
 
@@ -70,7 +70,7 @@ Then sign in.
70
70
 
71
71
  Once you are authenticated, two things matter:
72
72
 
73
- - the app now knows who you are as a persistent JSKIT user
73
+ - the app knows who you are as a persistent JSKIT user
74
74
  - the console bootstrap logic can determine whether you are the console owner
75
75
 
76
76
  Open:
@@ -92,7 +92,7 @@ This is the most important idea in the chapter.
92
92
 
93
93
  The account surface only requires authentication. The console surface requires a specific access flag: `console_owner`.
94
94
 
95
- That means the guide now has two different kinds of authenticated routes:
95
+ That means the guide has two different kinds of authenticated routes:
96
96
 
97
97
  - normal authenticated routes such as `/account`
98
98
  - privileged authenticated routes such as `/console`
@@ -163,7 +163,7 @@ This is the first clear example in the guide of a surface guarded by a named fla
163
163
 
164
164
  ### The starter console routes are app-owned
165
165
 
166
- After the previous chapter, the app now has:
166
+ After the previous chapter, the app has:
167
167
 
168
168
  ```text
169
169
  src/pages/console.vue
@@ -179,7 +179,7 @@ This follows the same route-owner pattern the guide has already shown on `home`
179
179
  - `src/pages/console/settings.vue` is the nested settings shell
180
180
  - `src/pages/console/settings/index.vue` is the initial developer-owned stub
181
181
 
182
- That last file is intentionally empty, because later modules are expected to add real console settings sections into the `console-settings:primary-menu` outlet.
182
+ That last file is intentionally empty, because later modules are expected to add real console settings sections through `page.section-nav` with owner `console-settings`.
183
183
 
184
184
  ### `src/placement.js` wires the first console menu entry
185
185
 
@@ -188,10 +188,10 @@ The starter placement block is:
188
188
  ```js
189
189
  addPlacement({
190
190
  id: "console.web.menu.settings",
191
- target: "shell-layout:primary-menu",
191
+ target: "shell.primary-nav",
192
+ kind: "link",
192
193
  surfaces: ["console"],
193
194
  order: 100,
194
- componentToken: "local.main.ui.menu-link-item",
195
195
  props: {
196
196
  label: "Settings",
197
197
  to: "/console/settings",
@@ -308,7 +308,7 @@ That stricter access model is the key idea to keep:
308
308
  - `/account` is for any signed-in user
309
309
  - `/console` is only for the console owner
310
310
 
311
- So the app now has both:
311
+ So the app has both:
312
312
 
313
313
  - a normal authenticated user area
314
314
  - a privileged operator area with its own server-side ownership check
@@ -49,11 +49,11 @@ Installing `database-runtime-mysql` gives the app three important things right a
49
49
 
50
50
  ### A real database runtime
51
51
 
52
- The server can now build a real Knex client from environment variables. That gives later packages a standard way to ask for a database connection instead of each package inventing its own wiring.
52
+ The server can build a real Knex client from environment variables. That gives later packages a standard way to ask for a database connection instead of each package inventing its own wiring.
53
53
 
54
54
  ### A migration workflow
55
55
 
56
- The app now gets three new scripts in `package.json`:
56
+ The app gets three database scripts in `package.json`:
57
57
 
58
58
  ```json
59
59
  {
@@ -73,7 +73,7 @@ If you run the status command immediately after this chapter:
73
73
  npm run db:migrate:status
74
74
  ```
75
75
 
76
- you should still see that there are no completed migrations and no pending migration files yet. The runtime and the Knex wiring exist now, but no package has added real schema files until the next chapter.
76
+ you should still see that there are no completed migrations and no pending migration files yet. The runtime and the Knex wiring exist at this point, but no package has added real schema files until the next chapter.
77
77
 
78
78
  ### A place for future schema files
79
79
 
@@ -88,7 +88,7 @@ That is the key idea of this chapter:
88
88
 
89
89
  This chapter is the right place to make one distinction very explicit.
90
90
 
91
- There are now two different migration-related layers in the app:
91
+ The app has two different migration-related layers:
92
92
 
93
93
  - JSKIT-managed migration files on disk
94
94
  - Knex actually applying those files to the database
@@ -171,7 +171,7 @@ import {
171
171
  } from "@jskit-ai/database-runtime/shared";
172
172
  ```
173
173
 
174
- This is worth calling out now because the database layer is not only "Knex plus migrations".
174
+ This is worth calling out here because the database layer is not only "Knex plus migrations".
175
175
 
176
176
  It also gives your later repositories and services a standard persistence toolbox so every package does not have to solve the same problems differently. The main point is consistency:
177
177
 
@@ -561,7 +561,7 @@ The three new scripts are also worth reading carefully:
561
561
 
562
562
  They are not special JSKIT commands. They are ordinary project scripts, which makes them easy to run in any environment.
563
563
 
564
- ### `.env` now owns the database connection settings
564
+ ### `.env` owns the database connection settings
565
565
 
566
566
  The package install also writes the database settings into `.env`:
567
567
 
@@ -583,7 +583,7 @@ This matters because the generic runtime is written to support more than one dri
583
583
 
584
584
  ### `knexfile.js` is for migration commands, not normal page code
585
585
 
586
- The migration scripts in `package.json` work because the app now has a top-level `knexfile.js`:
586
+ The migration scripts in `package.json` work because the app has a top-level `knexfile.js`:
587
587
 
588
588
  ```js
589
589
  import path from "node:path";
@@ -628,7 +628,7 @@ It is not the main runtime API that your app code imports during a request. It i
628
628
  npm run db:migrate
629
629
  ```
630
630
 
631
- So there are really two separate database entry points now:
631
+ So there are really two separate database entry points:
632
632
 
633
633
  - `knexfile.js` for migration commands
634
634
  - the JSKIT server provider runtime for application code
@@ -695,7 +695,7 @@ At first glance it can feel strange that the database layer is installed but the
695
695
 
696
696
  The reason is simple:
697
697
 
698
- - the runtime is now available
698
+ - the runtime is available
699
699
  - but almost no installed package is using it yet
700
700
 
701
701
  Right now:
@@ -739,7 +739,7 @@ So the auth layer keeps behaving the same way it did before:
739
739
  - JSKIT still mirrors just enough profile data locally
740
740
  - that JSKIT-side mirror is still not persistent yet
741
741
 
742
- The database runtime is now ready, but the users layer that will actually use it has not been installed yet.
742
+ The database runtime is ready, but the users layer that will actually use it has not been installed yet.
743
743
 
744
744
  ### Why the empty `migrations/` directory is important
745
745
 
@@ -747,7 +747,7 @@ The new `migrations/` directory can look almost silly at first because it only c
747
747
 
748
748
  It means:
749
749
 
750
- - the app now has a migration system
750
+ - the app has a migration system
751
751
  - the app does **not** yet have a schema of its own
752
752
 
753
753
  That is exactly the right state at this stage of the guide.
@@ -758,9 +758,9 @@ The database runtime chapter should give the app a database foundation first. Th
758
758
 
759
759
  This chapter did not make the app feel dramatically different in the browser, but it changed the server foundation in an important way.
760
760
 
761
- - the app now has a real JSKIT database runtime
762
- - the app now has a standard Knex migration workflow
763
- - the app now has a place for future schema files
761
+ - the app has a real JSKIT database runtime
762
+ - the app has a standard Knex migration workflow
763
+ - the app has a place for future schema files
764
764
 
765
765
  But just as importantly, this chapter also defined what has **not** changed yet:
766
766
 
@@ -771,5 +771,5 @@ But just as importantly, this chapter also defined what has **not** changed yet:
771
771
  So the right mental model at the end of this chapter is:
772
772
 
773
773
  - Supabase already handles real authentication
774
- - MySQL is now wired up and ready
774
+ - MySQL is wired up and ready
775
775
  - the persistent JSKIT-side user model arrives in the next chapter
@@ -14,7 +14,7 @@ npm install
14
14
 
15
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
16
 
17
- If you are working with an AI agent and want the agent to drive the initial JSKIT setup conversation, there is now a dedicated seed path:
17
+ If you are working with an AI agent and want the agent to drive the initial JSKIT setup conversation, use the dedicated seed path:
18
18
 
19
19
  ```bash
20
20
  npx @jskit-ai/create-app exampleapp --template ai-seed
@@ -166,23 +166,35 @@ The most important parts look like this:
166
166
  "build": "vite build",
167
167
  "build:all": "vite build",
168
168
  "build:home": "VITE_SURFACE=home vite build",
169
+ "preview": "vite preview",
170
+ "lint": "eslint .",
169
171
  "test": "node --test",
170
172
  "test:client": "vitest run tests/client",
173
+ "test:e2e": "playwright test tests/e2e",
171
174
  "verify": "jskit app verify && npm run --if-present verify:app",
172
175
  "release": "jskit app release",
173
176
  "jskit:update": "jskit app update-packages"
174
177
  },
175
178
  "dependencies": {
176
179
  "@local/main": "file:packages/main",
180
+ "@fastify/static": "^9.1.1",
177
181
  "@jskit-ai/kernel": "0.x",
182
+ "@tanstack/vue-query": "^5.90.5",
178
183
  "@jskit-ai/http-runtime": "0.x",
179
184
  "fastify": "^5.7.4",
185
+ "json-rest-schema": "^1.0.13",
180
186
  "pinia": "^3.0.4",
181
187
  "vue": "^3.5.13",
188
+ "vue-router": "^5.0.4",
182
189
  "vuetify": "^4.0.0"
183
190
  },
184
191
  "devDependencies": {
192
+ "@jskit-ai/agent-docs": "0.x",
193
+ "@jskit-ai/config-eslint": "0.x",
185
194
  "@jskit-ai/jskit-cli": "0.x",
195
+ "@playwright/test": "^1.57.0",
196
+ "@vitejs/plugin-vue": "^5.2.1",
197
+ "eslint": "^9.39.1",
186
198
  "vite": "^6.1.0",
187
199
  "vitest": "^4.0.18"
188
200
  }
@@ -191,7 +203,7 @@ The most important parts look like this:
191
203
 
192
204
  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.
193
205
 
194
- `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.
206
+ `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 lives in `jskit app ...`, not in copied scaffold scripts.
195
207
 
196
208
  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.
197
209
 
@@ -257,6 +269,7 @@ The important part looks like this:
257
269
  ```js
258
270
  import { createApp } from "vue";
259
271
  import { createPinia } from "pinia";
272
+ import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
260
273
  import { createRouter, createWebHistory } from "vue-router/auto";
261
274
  import { routes } from "vue-router/auto-routes";
262
275
  import "vuetify/styles";
@@ -265,6 +278,10 @@ import * as components from "vuetify/components";
265
278
  import * as directives from "vuetify/directives";
266
279
  import { aliases as mdiAliases, mdi } from "vuetify/iconsets/mdi-svg";
267
280
  import { createSurfaceRuntime } from "@jskit-ai/kernel/shared/surface/runtime";
281
+ import {
282
+ shouldRetryTransientQueryFailure,
283
+ transientQueryRetryDelay
284
+ } from "@jskit-ai/kernel/shared/support";
268
285
  import {
269
286
  bootstrapClientShellApp,
270
287
  createShellRouter
@@ -308,13 +325,28 @@ const vuetify = createVuetify({
308
325
  }
309
326
  });
310
327
  const pinia = createPinia();
328
+ const queryClient = new QueryClient({
329
+ defaultOptions: {
330
+ queries: {
331
+ refetchOnWindowFocus: false,
332
+ refetchOnReconnect: true,
333
+ retry: shouldRetryTransientQueryFailure,
334
+ retryDelay: transientQueryRetryDelay
335
+ }
336
+ }
337
+ });
311
338
 
312
339
  void bootstrapClientShellApp({
313
340
  createApp,
314
341
  rootComponent: App,
315
342
  appConfig: config,
316
- appPlugins: [pinia, vuetify],
343
+ appPlugins: [
344
+ pinia,
345
+ [VueQueryPlugin, { queryClient }],
346
+ vuetify
347
+ ],
317
348
  pinia,
349
+ queryClient,
318
350
  router,
319
351
  bootClientModules: bootInstalledClientModules,
320
352
  surfaceRuntime,
@@ -326,7 +358,7 @@ void bootstrapClientShellApp({
326
358
  });
327
359
  ```
328
360
 
329
- 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.
361
+ The flow is simple once you read it in order: config in, runtime in memory, router built from that runtime, app-owned Vue plugins created once, app bootstrapped. Pinia, Vue Query, the router, and Vuetify are owned by the base app bootstrap so runtime packages share the same instances instead of carrying independent copies.
330
362
 
331
363
  <DocsInDepth title="In depth" preview-height="15rem">
332
364
 
@@ -766,7 +798,7 @@ That is why you saw `@jskit-ai/kernel` and `@jskit-ai/http-runtime` earlier in `
766
798
 
767
799
  ### Other files and options
768
800
 
769
- 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.
801
+ 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 intentionally small because JSKIT maintenance helpers such as `verify`, `jskit:update`, `devlinks`, and `release` are package-owned CLI commands rather than copied app scripts.
770
802
 
771
803
  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.
772
804