@jskit-ai/agent-docs 0.1.131 → 0.1.133

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 (171) hide show
  1. package/guide/agent/app-extras/assistant.md +29 -605
  2. package/guide/agent/app-extras/mobile-capacitor.md +29 -362
  3. package/guide/agent/app-extras/realtime.md +29 -277
  4. package/guide/agent/app-setup/a-more-interesting-shell.md +44 -815
  5. package/guide/agent/app-setup/authentication.md +43 -1073
  6. package/guide/agent/app-setup/console.md +26 -298
  7. package/guide/agent/app-setup/database-layer.md +110 -790
  8. package/guide/agent/app-setup/existing-application-migration.md +273 -0
  9. package/guide/agent/app-setup/initial-scaffolding.md +50 -784
  10. package/guide/agent/app-setup/multi-homing.md +39 -712
  11. package/guide/agent/app-setup/quickstart.md +43 -179
  12. package/guide/agent/app-setup/users.md +34 -353
  13. package/guide/agent/index.md +17 -23
  14. package/package.json +2 -2
  15. package/patterns/INDEX.md +7 -7
  16. package/patterns/child-cruds.md +3 -3
  17. package/patterns/client-requests.md +6 -6
  18. package/patterns/crud-authoring.md +94 -0
  19. package/patterns/crud-links.md +1 -1
  20. package/patterns/feature-package/PATTERN.md +108 -0
  21. package/patterns/feature-package/example/booking-engine/package.json +48 -0
  22. package/patterns/feature-package/example/booking-engine/src/server/BookingEngineProvider.js +33 -0
  23. package/patterns/feature-package/example/booking-engine/src/server/actions.js +26 -0
  24. package/patterns/feature-package/example/booking-engine/src/server/inputSchemas.js +19 -0
  25. package/patterns/feature-package/example/variations/AvailabilityEngineProvider.js +31 -0
  26. package/patterns/feature-package/example/variations/InvoiceRollupProvider.js +36 -0
  27. package/patterns/feature-package/example/variations/customKnexRepository.js +34 -0
  28. package/patterns/feature-package/example/variations/orchestratorService.js +23 -0
  29. package/patterns/filters.md +8 -8
  30. package/patterns/live-actions.md +5 -18
  31. package/patterns/minimal-foundation/PATTERN.md +98 -0
  32. package/patterns/minimal-foundation/example/.nvmrc +1 -0
  33. package/patterns/minimal-foundation/example/AGENTS.md +17 -0
  34. package/patterns/minimal-foundation/example/Procfile +2 -0
  35. package/patterns/minimal-foundation/example/app.json +14 -0
  36. package/patterns/minimal-foundation/example/bin/develop.js +71 -0
  37. package/patterns/minimal-foundation/example/bin/server.js +8 -0
  38. package/patterns/minimal-foundation/example/config/public.js +40 -0
  39. package/patterns/minimal-foundation/example/config/server.js +1 -0
  40. package/patterns/minimal-foundation/example/config/surfaceAccessPolicies.js +3 -0
  41. package/patterns/minimal-foundation/example/eslint.config.mjs +19 -0
  42. package/patterns/minimal-foundation/example/favicon.svg +7 -0
  43. package/patterns/minimal-foundation/example/gitignore +9 -0
  44. package/patterns/minimal-foundation/example/index.html +13 -0
  45. package/patterns/minimal-foundation/example/jsconfig.json +8 -0
  46. package/patterns/minimal-foundation/example/package.json +57 -0
  47. package/patterns/minimal-foundation/example/packages/main/package.json +42 -0
  48. package/patterns/minimal-foundation/example/packages/main/src/shared/index.js +10 -0
  49. package/patterns/minimal-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
  50. package/patterns/minimal-foundation/example/playwright.config.mjs +31 -0
  51. package/patterns/minimal-foundation/example/server/lib/runtimeEnv.js +45 -0
  52. package/patterns/minimal-foundation/example/server/lib/surfaceRuntime.js +10 -0
  53. package/patterns/minimal-foundation/example/server.js +195 -0
  54. package/patterns/minimal-foundation/example/src/App.vue +13 -0
  55. package/patterns/minimal-foundation/example/src/main.js +85 -0
  56. package/patterns/minimal-foundation/example/src/pages/home/index.vue +48 -0
  57. package/patterns/minimal-foundation/example/src/pages/home.vue +13 -0
  58. package/patterns/minimal-foundation/example/src/views/NotFound.vue +13 -0
  59. package/patterns/minimal-foundation/example/tests/client/smoke.vitest.js +7 -0
  60. package/patterns/minimal-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
  61. package/patterns/minimal-foundation/example/tests/server/smoke.test.js +16 -0
  62. package/patterns/minimal-foundation/example/vite.config.mjs +81 -0
  63. package/patterns/page-scaffolding.md +20 -17
  64. package/patterns/placements.md +17 -15
  65. package/patterns/row-policies.md +4 -5
  66. package/patterns/server-search.md +3 -3
  67. package/patterns/shell-foundation/PATTERN.md +104 -0
  68. package/patterns/shell-foundation/example/.nvmrc +1 -0
  69. package/patterns/shell-foundation/example/AGENTS.md +17 -0
  70. package/patterns/shell-foundation/example/Procfile +2 -0
  71. package/patterns/shell-foundation/example/app.json +14 -0
  72. package/patterns/shell-foundation/example/bin/develop.js +71 -0
  73. package/patterns/shell-foundation/example/bin/server.js +8 -0
  74. package/patterns/shell-foundation/example/config/public.js +40 -0
  75. package/patterns/shell-foundation/example/config/server.js +1 -0
  76. package/patterns/shell-foundation/example/config/surfaceAccessPolicies.js +3 -0
  77. package/patterns/shell-foundation/example/eslint.config.mjs +19 -0
  78. package/patterns/shell-foundation/example/favicon.svg +7 -0
  79. package/patterns/shell-foundation/example/gitignore +9 -0
  80. package/patterns/shell-foundation/example/index.html +13 -0
  81. package/patterns/shell-foundation/example/jsconfig.json +8 -0
  82. package/patterns/shell-foundation/example/package.json +59 -0
  83. package/patterns/shell-foundation/example/packages/main/package.json +56 -0
  84. package/patterns/shell-foundation/example/packages/main/src/client/index.js +9 -0
  85. package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +18 -0
  86. package/patterns/shell-foundation/example/packages/main/src/shared/index.js +10 -0
  87. package/patterns/shell-foundation/example/packages/main/src/shared/schemas/index.js +22 -0
  88. package/patterns/shell-foundation/example/playwright.config.mjs +31 -0
  89. package/patterns/shell-foundation/example/server/lib/runtimeEnv.js +45 -0
  90. package/patterns/shell-foundation/example/server/lib/surfaceRuntime.js +10 -0
  91. package/patterns/shell-foundation/example/server.js +195 -0
  92. package/patterns/shell-foundation/example/src/App.vue +11 -0
  93. package/patterns/shell-foundation/example/src/components/ShellLayout.vue +12 -0
  94. package/patterns/shell-foundation/example/src/components/menus/MenuLinkItem.vue +30 -0
  95. package/patterns/shell-foundation/example/src/components/menus/SurfaceAwareMenuLinkItem.vue +42 -0
  96. package/patterns/shell-foundation/example/src/components/menus/TabLinkItem.vue +42 -0
  97. package/patterns/shell-foundation/example/src/error.js +19 -0
  98. package/patterns/shell-foundation/example/src/main.js +85 -0
  99. package/patterns/shell-foundation/example/src/pages/home/index.vue +116 -0
  100. package/patterns/shell-foundation/example/src/pages/home/settings/general/index.vue +40 -0
  101. package/patterns/shell-foundation/example/src/pages/home/settings/index.vue +7 -0
  102. package/patterns/shell-foundation/example/src/pages/home/settings.vue +109 -0
  103. package/patterns/shell-foundation/example/src/pages/home.vue +20 -0
  104. package/patterns/shell-foundation/example/src/placement.js +56 -0
  105. package/patterns/shell-foundation/example/src/placementTopology.js +149 -0
  106. package/patterns/shell-foundation/example/src/views/NotFound.vue +13 -0
  107. package/patterns/shell-foundation/example/tests/client/smoke.vitest.js +7 -0
  108. package/patterns/shell-foundation/example/tests/e2e/adaptive-shell.spec.ts +10 -0
  109. package/patterns/shell-foundation/example/tests/e2e/base-shell.spec.ts +23 -0
  110. package/patterns/shell-foundation/example/tests/server/smoke.test.js +16 -0
  111. package/patterns/shell-foundation/example/vite.config.mjs +81 -0
  112. package/patterns/ui-contract.md +56 -0
  113. package/patterns/ui-testing.md +10 -12
  114. package/reference/autogen/KERNEL_MAP.md +29 -107
  115. package/reference/autogen/PATTERN_INDEX.md +230 -0
  116. package/reference/autogen/README.md +4 -8
  117. package/reference/autogen/packages/agent-docs.md +259 -0
  118. package/reference/autogen/packages/assistant-core.md +3 -3
  119. package/reference/autogen/packages/assistant-runtime.md +32 -17
  120. package/reference/autogen/packages/auth-core.md +31 -33
  121. package/reference/autogen/packages/auth-provider-local-core.md +4 -12
  122. package/reference/autogen/packages/auth-provider-local-db-core.md +4 -4
  123. package/reference/autogen/packages/auth-provider-supabase-core.md +14 -18
  124. package/reference/autogen/packages/auth-web.md +42 -22
  125. package/reference/autogen/packages/console-core.md +8 -25
  126. package/reference/autogen/packages/console-web.md +5 -5
  127. package/reference/autogen/packages/crud-core.md +61 -17
  128. package/reference/autogen/packages/database-runtime-mysql.md +12 -2
  129. package/reference/autogen/packages/database-runtime-postgres.md +12 -2
  130. package/reference/autogen/packages/database-runtime.md +26 -25
  131. package/reference/autogen/packages/google-rewarded-core.md +19 -104
  132. package/reference/autogen/packages/http-runtime.md +4 -8
  133. package/reference/autogen/packages/http-web.md +32 -0
  134. package/reference/autogen/packages/json-rest-api-core.md +4 -6
  135. package/reference/autogen/packages/kernel.md +109 -390
  136. package/reference/autogen/packages/mobile-capacitor.md +2 -13
  137. package/reference/autogen/packages/realtime.md +29 -26
  138. package/reference/autogen/packages/resource-crud-core.md +6 -0
  139. package/reference/autogen/packages/shell-web.md +69 -54
  140. package/reference/autogen/packages/storage-runtime.md +3 -3
  141. package/reference/autogen/packages/uploads-image-web.md +0 -1
  142. package/reference/autogen/packages/uploads-runtime.md +3 -3
  143. package/reference/autogen/packages/users-core.md +45 -90
  144. package/reference/autogen/packages/users-web.md +5 -7
  145. package/reference/autogen/packages/workspaces-core.md +53 -74
  146. package/reference/autogen/packages/workspaces-web.md +15 -16
  147. package/reference/autogen/tooling/jskit-catalog.md +34 -0
  148. package/reference/autogen/tooling/testUtils.md +4 -4
  149. package/skills/jskit/SKILL.md +38 -31
  150. package/skills/jskit/references/app-operations.md +68 -53
  151. package/skills/jskit/references/crud-operations.md +58 -106
  152. package/skills/jskit/references/material-3.md +1 -1
  153. package/skills/jskit/references/ui-operations.md +41 -44
  154. package/templates/app/AGENTS.md +7 -3
  155. package/guide/agent/app-setup/upgrade-beta-1-to-final.md +0 -252
  156. package/guide/agent/app-setup/working-with-the-jskit-cli.md +0 -325
  157. package/guide/agent/generators/advanced-cruds.md +0 -1935
  158. package/guide/agent/generators/crud-generators.md +0 -948
  159. package/guide/agent/generators/intro.md +0 -65
  160. package/guide/agent/generators/row-policies.md +0 -537
  161. package/guide/agent/generators/ui-generators.md +0 -690
  162. package/patterns/crud-scaffolding.md +0 -198
  163. package/patterns/generated-ui-contract-tracking.md +0 -66
  164. package/reference/autogen/packages/assistant.md +0 -68
  165. package/reference/autogen/packages/crud-server-generator.md +0 -215
  166. package/reference/autogen/packages/crud-ui-generator.md +0 -192
  167. package/reference/autogen/packages/feature-server-generator.md +0 -65
  168. package/reference/autogen/packages/ui-generator.md +0 -127
  169. package/reference/autogen/tooling/create-app.md +0 -317
  170. package/reference/autogen/tooling/jskit-cli.md +0 -933
  171. package/reference/autogen/tooling/test-support.md +0 -27
@@ -1,735 +1,62 @@
1
1
  <!-- Generated by `npm run agent-docs:build` from `packages/agent-docs/site/guide/app-setup/multi-homing.md`. Do not edit manually. -->
2
2
 
3
- # Multi-homing
3
+ # Workspace tenancy
4
4
 
5
- Up to this point, the app has had several surfaces, but none of them were workspace-dependent. `home`, `auth`, `account`, and `console` all live outside any workspace slug.
6
-
7
- This chapter changes that. We turn the app into a workspace-aware application and install the packages that add the first real workspace surfaces.
8
-
9
- If you are rebuilding this chapter from scratch, the cleanest setup is to start with a workspace-capable tenancy mode from day 0. If you are literally continuing from the previous chapter, you can still move the existing app from `none` to `personal`, but that retrofit has to be done in the right order.
10
-
11
- ## Tenancy modes
12
-
13
- JSKIT currently accepts three tenancy modes:
14
-
15
- - `none`
16
- - no workspace routing
17
- - no `/w/[workspaceSlug]` surfaces
18
- - useful for a purely global app with no workspace concept
19
- - `personal`
20
- - workspace routing is enabled
21
- - each user gets one auto-provisioned personal workspace
22
- - the workspace slug is derived from the user's identity and treated as immutable
23
- - creating additional workspaces is off by default
24
- - `workspaces`
25
- - workspace routing is enabled
26
- - users can belong to multiple named workspaces
27
- - workspace slugs are user-selected rather than derived from the username
28
- - auto-provisioning is off by default, and self-creation is a separate policy choice
29
-
30
- Both `personal` and `workspaces` are workspace-capable modes, so they allow the workspace package metadata to install the full workspace scaffold.
31
-
32
- This chapter teaches `personal`, not `workspaces`.
33
-
34
- That is deliberate. `personal` gives the guide a much better first-run experience because the first workspace is auto-provisioned for the user. The app still becomes multi-homing-capable, because invitations and memberships can still put one user in several workspaces at once. The `personal` part only changes how the first workspace is provisioned and how its slug policy works.
35
-
36
- Once `workspaces-core` and `workspaces-web` are installed, the baseline workspace invitation flow is already part of the package stack. Treat that as the default behavior unless the app explicitly needs custom invite rules or custom UI beyond what the packages already provide.
37
-
38
- ## Installing the workspace packages
39
-
40
- If your app is already on `tenancyMode = "personal"`, run:
5
+ Use JSKIT workspaces when routes, records, or permissions belong to a selected
6
+ workspace and membership or role changes access.
41
7
 
42
8
  ```bash
43
- npx jskit add package workspaces-core
44
- npx jskit add package workspaces-web
9
+ npm install @jskit-ai/workspaces-web
45
10
  npm run db:migrate
46
11
  ```
47
12
 
48
- `workspaces-core` adds the server-side workspace runtime and schema migrations. `workspaces-web` adds the workspace-facing client surfaces, shell placements, and app-owned route files.
49
-
50
- If you want to inspect that package before installing it, this is a very good moment to use the CLI chapter's inspection command:
51
-
52
- ```bash
53
- npx jskit show package @jskit-ai/workspaces-web
54
- ```
55
-
56
- That output makes the package feel much less mysterious, because it shows the exact workspace shell contributions, settings outlets, client tokens, app-owned file writes, and capability requirements before you mutate the app.
57
-
58
- ## What workspaces add
59
-
60
- This chapter is where the app stops being a collection of global surfaces and starts supporting workspace-dependent ones.
61
-
62
- ### Two new workspace surfaces appear
63
-
64
- After the install, the app gains:
65
-
66
- - an `app` surface rooted at `/w/[workspaceSlug]`
67
- - an `admin` surface rooted at `/w/[workspaceSlug]/admin`
68
-
69
- These are real surfaces, not only pages.
70
-
71
- - `app` is the generic workspace surface that normal authenticated workspace members can use
72
- - `admin` is the richer workspace administration surface
73
-
74
- That distinction matters.
75
-
76
- The `app` surface is the "normal" workspace area. It is where you would usually put the main member-facing experience for that workspace: dashboards, documents, tasks, project views, customer-facing content, or whatever the ordinary in-workspace product actually is.
77
-
78
- The `admin` surface is where the workspace is managed. It is the natural place for things like:
79
-
80
- - inviting other users into the workspace
81
- - managing members and roles
82
- - editing workspace settings
83
- - running workspace-specific admin tools
84
-
85
- So `admin` is not just "another page." It is the surface where the workspace itself is configured and operated.
86
-
87
- There is also no rule that says every app must use these two surfaces in the same way.
88
-
89
- - In one app, `app` could be the real product and `admin` could be the backend for managing it.
90
- - In another app, `admin` might be where most of the real work happens, while `app` stays minimal or even almost empty.
91
- - In a storefront-style app, `app` could be the workspace's visible shop area and `admin` could be the merchant backend.
92
-
93
- The important point is not the label. The important point is the split:
94
-
95
- - `app` is the general workspace-facing surface
96
- - `admin` is the management and control surface for that workspace
97
-
98
- That is why this chapter feels larger than the previous ones. It is not just adding another route. It is adding a new routing topology.
99
-
100
- ### The shell and account surface gain workspace-aware controls
101
-
102
- The placement registry gets a workspace selector in `shell.identity`, pending-invites and workspace tools in `shell.status`, and an `Invites` section in the existing `/account` settings screen through the account-settings extension seam that `users-web` exposes. The default shell topology maps identity/status placements to the visible shell chrome.
103
-
104
- That means the shell itself starts adapting to workspace context.
105
-
106
- - on any authenticated surface, the shell can expose a workspace selector
107
- - signed-in users can see a pending-invites cue without `users-web` owning that workspace feature
108
- - on admin workspace surfaces, the shell can expose workspace-specific tools and settings
109
-
110
- This is the first time the guide shows the shell reacting not just to authentication state, but to workspace state as well.
111
-
112
- ### The database schema grows real multi-workspace tables
113
-
114
- `workspaces-core` adds the schema needed for:
115
-
116
- - workspaces
117
- - workspace memberships
118
- - workspace settings
119
- - workspace invites
120
-
121
- That is why `npm run db:migrate` is required again in this chapter. The workspace runtime is not only client-side routing. It is persistent tenancy data.
122
-
123
- ### Existing surfaces do not disappear
124
-
125
- This is also important to notice:
126
-
127
- - `home` still exists
128
- - `auth` still exists
129
- - `account` still exists
130
- - `console` still exists
131
-
132
- The new workspace surfaces are added on top of the existing app, not instead of it.
133
-
134
- That is exactly what multi-homing should feel like. The app has both:
135
-
136
- - global surfaces
137
- - workspace-scoped surfaces
138
-
139
- ## What to look at in the browser
140
-
141
- Start both processes again:
142
-
143
- ```bash
144
- npm run dev
145
- npm run server
146
- ```
147
-
148
- After you sign in, the app has the routing structure needed for workspace-aware paths such as:
149
-
150
- ```text
151
- /w/your-personal-slug
152
- /w/your-personal-slug/admin
153
- ```
154
-
155
- In `personal` mode, the first workspace is auto-provisioned for the signed-in user, so you should have a real workspace route to open immediately after login. Later, if the same user is invited into another workspace, routes such as `/w/acme` can exist alongside that personal workspace too.
156
-
157
- At this stage of the guide, the starter workspace pages are still intentionally simple. That is helpful. It lets you see the new routing and shell topology clearly before later chapters add real modules inside those surfaces.
158
-
159
- The most important new visible ideas are:
160
-
161
- - a workspace slug appears in the route
162
- - the shell can expose workspace selection and workspace tools
163
- - workspace-dependent surfaces can show a dedicated unavailable-state card when the requested workspace cannot be resolved
164
-
165
- First, even the global `/home` surface reacts to workspace state. The selector and invites cue come from semantic placement entries and workspace bootstrap context, not from the `home` page itself.
166
-
167
- Then open your personal workspace route. The page itself is deliberately light. `src/pages/w/[workspaceSlug]/index.vue` mostly exists to prove that the `app` surface is real and ready to host later modules.
168
-
169
- Next open the admin surface for the same workspace. This is the matching pattern on the admin side: `src/pages/w/[workspaceSlug]/admin.vue` is the shell wrapper, `src/pages/w/[workspaceSlug]/admin/index.vue` is the starter page, and the workspace tools button is coming from placements rather than being hand-built into that page file.
170
-
171
- Finally open the nested workspace settings route. This is useful to inspect because it shows the container pattern most clearly. `src/pages/w/[workspaceSlug]/admin/workspace/settings.vue` owns the section frame and the child outlet, while the actual settings sub-pages can keep arriving later under that shell.
172
-
173
- ## What the workspace packages add to the app
174
-
175
- This chapter changes the app in four main places:
176
-
177
- - public config
178
- - surface access policies
179
- - migrations
180
- - workspace surface route files and placements
181
-
182
- ### `config/public.js` changes in a big way
183
-
184
- The first change is the explicit tenancy mode:
185
-
186
- ```js
187
- config.tenancyMode = "personal";
188
- ```
189
-
190
- Then the app gets two new surface definitions:
191
-
192
- ```js
193
- config.surfaceDefinitions.app = {
194
- id: "app",
195
- label: "App",
196
- pagesRoot: "w/[workspaceSlug]",
197
- enabled: true,
198
- requiresAuth: true,
199
- requiresWorkspace: true,
200
- accessPolicyId: "workspace_member",
201
- origin: ""
202
- };
203
-
204
- config.surfaceDefinitions.admin = {
205
- id: "admin",
206
- label: "Admin",
207
- pagesRoot: "w/[workspaceSlug]/admin",
208
- enabled: true,
209
- requiresAuth: true,
210
- requiresWorkspace: true,
211
- accessPolicyId: "workspace_member",
212
- origin: ""
213
- };
214
- ```
215
-
216
- And the app also gains workspace-level feature config:
217
-
218
- ```js
219
- config.workspaceSwitching = true;
220
- config.workspaceInvitations = {
221
- enabled: true,
222
- allowInPersonalMode: true
223
- };
224
- ```
225
-
226
- Those lines are the public contract that tells both client and server that this app is workspace-aware.
227
-
228
- ### `config/surfaceAccessPolicies.js` gains `workspace_member`
229
-
230
- The new workspace surfaces use a workspace membership rule:
231
-
232
- ```js
233
- surfaceAccessPolicies.workspace_member = {
234
- requireAuth: true,
235
- requireWorkspaceMembership: true
236
- };
237
- ```
238
-
239
- This is the first time the guide shows a surface guarded not just by auth or a simple flag, but by real workspace membership.
240
-
241
- That is the core idea of multi-homing in JSKIT:
242
-
243
- - the route contains a workspace slug
244
- - the server resolves that workspace
245
- - access depends on whether the current user belongs to it
246
-
247
- ### The migrations include workspace schema
248
-
249
- After `workspaces-core`, the migration directory grows again with files such as:
250
-
251
- ```text
252
- migrations/
253
- 2026..._workspaces-core-initial-schema.cjs
254
- 2026..._users-core-workspace-settings-single-name-source.cjs
255
- 2026..._users-core-workspaces-drop-color.cjs
256
- ```
257
-
258
- These are the tables and schema changes that make workspace tenancy real in the database.
259
-
260
- That is why the chapter needs another `npm run db:migrate`. Without those tables, the workspace runtime would have routes and UI, but nowhere to persist workspace membership and settings.
261
-
262
- ### The route tree gains workspace-dependent pages
263
-
264
- The app gets:
265
-
266
- ```text
267
- src/pages/w/[workspaceSlug].vue
268
- src/pages/w/[workspaceSlug]/index.vue
269
- src/pages/w/[workspaceSlug]/admin.vue
270
- src/pages/w/[workspaceSlug]/admin/index.vue
271
- src/pages/w/[workspaceSlug]/admin/members/index.vue
272
- src/pages/w/[workspaceSlug]/admin/workspace/settings.vue
273
- src/pages/w/[workspaceSlug]/admin/workspace/settings/index.vue
274
- ```
275
-
276
- That list shows the first real nested workspace topology.
277
-
278
- - `w/[workspaceSlug].vue` is the `app` surface wrapper
279
- - `w/[workspaceSlug]/index.vue` is the starter landing page for the `app` surface
280
- - `w/[workspaceSlug]/admin.vue` is the `admin` surface wrapper
281
- - `w/[workspaceSlug]/admin/index.vue` is the starter landing page for the `admin` surface
282
- - `w/[workspaceSlug]/admin/members/index.vue` mounts the first real workspace admin client element
283
- - `w/[workspaceSlug]/admin/workspace/settings.vue` is a local section shell for nested workspace settings routes
284
- - `w/[workspaceSlug]/admin/workspace/settings/index.vue` is the default child route for that settings shell
285
-
286
- So the workspace surface model is not only a config concept. It becomes a real file-based routing tree in `src/pages/`.
287
-
288
- These files are intentionally thinner than they look.
289
-
290
- Most of the machinery happens up-hill from them:
291
-
292
- - `config/public.js` defines which surfaces exist and which path roots they own
293
- - `config/surfaceAccessPolicies.js` defines the membership rule that guards them
294
- - `src/placement.js` wires the selector, invites cue, and admin tools into the shell
295
- - `workspaces-core` and `workspaces-web` provide the bootstrap, workspace resolution, permissions, settings, and reusable client elements underneath those routes
296
-
297
- So the `src/pages` files are mostly containers and composition points, not the place where workspace tenancy is implemented.
298
-
299
- Concretely, that route tree works like this:
300
-
301
- - `w/[workspaceSlug].vue` and `w/[workspaceSlug]/admin.vue` are almost pure wrappers. They tag the route with the correct surface id and mount `ShellLayout` plus a child `<RouterView />`.
302
- - `w/[workspaceSlug]/index.vue` and `w/[workspaceSlug]/admin/index.vue` are intentionally simple starter cards. They prove that the new workspace surfaces are live, but they are meant to be replaced by real product modules later.
303
- - `w/[workspaceSlug]/admin/members/index.vue` is still thin, but in a different way: it mostly hands control to a packaged `WorkspaceMembersClientElement`, so the route file stays small while the reusable member-management behavior lives in `workspaces-web`.
304
- - `w/[workspaceSlug]/admin/workspace/settings.vue` is a section shell. It does not own the actual settings fields. It owns the card frame, the left-side settings menu outlet, and the nested `<RouterView />` where child settings pages render.
305
- - `w/[workspaceSlug]/admin/workspace/settings/index.vue` is intentionally almost empty. Its job is to make `/admin/workspace/settings` a real route today and give you a clean place to redirect or add child settings pages later.
306
-
307
- If you want to add a real workspace settings child page at this point, use the normal page generator under that route tree:
308
-
309
- ```bash
310
- npx jskit generate ui-generator page \
311
- w/[workspaceSlug]/admin/workspace/settings/billing/index.vue \
312
- --name "Billing"
313
- ```
314
-
315
- That command does two things:
316
-
317
- - it creates `src/pages/w/[workspaceSlug]/admin/workspace/settings/billing/index.vue`
318
- - it also appends the matching workspace settings menu entry into `src/placement.js`
319
-
320
- The reason JSKIT can wire that link automatically is that the workspace settings shell already exposes a concrete outlet and topology maps it to semantic section navigation:
321
-
322
- ```vue
323
- <ShellOutlet target="admin-settings:primary-menu" />
324
- ```
325
-
326
- The route host lets the generator infer `page.section-nav` with owner `admin-settings`. `src/placementTopology.js` then maps that semantic placement to `admin-settings:primary-menu` and supplies the link renderer for compact, medium, and expanded layouts. So a page generated under `w/[workspaceSlug]/admin/workspace/settings/...` automatically lands in the left-side workspace settings menu without you hand-writing the placement entry.
327
-
328
- ### Workspace pages are prepared for missing-workspace states
329
-
330
- The starter workspace pages already use a dedicated unavailable-state helper:
331
-
332
- ```vue
333
- <WorkspaceNotFoundCard
334
- v-if="workspaceUnavailable"
335
- :message="workspaceUnavailableMessage"
336
- surface-label="App"
337
- />
338
- ```
339
-
340
- That is worth noticing because it shows that workspace routing is not just string matching on `[workspaceSlug]`. The runtime is expected to decide whether the requested workspace is actually valid and accessible.
341
-
342
- This is another example of the "mostly containers" pattern. The page file does not resolve the workspace itself. It asks the shared `useWorkspaceNotFoundState()` helper for the current workspace-bootstrap status and then swaps between:
343
-
344
- - a shared unavailable card when the workspace is missing or inaccessible
345
- - the local starter content when the workspace context is valid
346
-
347
- So the starter pages already distinguish:
348
-
349
- - valid workspace context
350
- - invalid or inaccessible workspace context
351
-
352
- ### The public workspace client API for custom pages
353
-
354
- At this point in the chapter, it is worth separating two different kinds of client-side helpers:
355
-
356
- - app-owned scaffold helpers written into `src/`
357
- - public package helpers exported by `@jskit-ai/workspaces-web`
358
-
359
- `useWorkspaceNotFoundState()` belongs to the first group. It is an app-owned helper scaffolded into:
360
-
361
- ```text
362
- src/composables/useWorkspaceNotFoundState.js
363
- ```
364
-
365
- That makes it easy to customize locally.
366
-
367
- The main app-author-facing helper exported by `workspaces-web` itself is:
368
-
369
- ```js
370
- import { useWorkspaceRouteContext } from "@jskit-ai/workspaces-web/client/composables/useWorkspaceRouteContext";
371
- ```
372
-
373
- That is the public composable most custom workspace pages should reach for first.
374
-
375
- #### What `workspaces-web` actually exposes publicly on the client side
376
-
377
- Today, the public client surface is intentionally small.
378
-
379
- - `@jskit-ai/workspaces-web/client`
380
- - the package's client runtime registration surface
381
- - exports `clientProviders`, `WorkspacesWebClientProvider`, and `WorkspaceMembersClientElement`
382
- - `@jskit-ai/workspaces-web/client/composables/useWorkspaceRouteContext`
383
- - the main public page-level composable for workspace-aware route context
384
-
385
- That is a useful distinction.
386
-
387
- - `WorkspacesWebClientProvider` and `clientProviders` are runtime wiring, not something a normal page imports.
388
- - `WorkspaceMembersClientElement` is a packaged feature element that a route can render directly.
389
- - `useWorkspaceRouteContext()` is the public helper most app-authored workspace pages will actually use.
390
-
391
- There are other helpers inside the package source, but if they are not exported by the package, treat them as internal implementation details rather than app code API.
392
-
393
- #### What `useWorkspaceRouteContext()` gives you
394
-
395
- The composable returns:
396
-
397
- - `route`
398
- - the live Vue Router route object
399
- - `routePath`
400
- - the normalized runtime pathname
401
- - `currentSurfaceId`
402
- - the resolved current surface id, such as `app` or `admin`
403
- - `workspaceSlugFromRoute`
404
- - the current workspace slug, but only when the current route really belongs to a workspace-dependent surface
405
- - `placementContext`
406
- - the current shell placement/bootstrap context
407
- - `mergePlacementContext`
408
- - the function used to merge new context back into the shell runtime
409
-
410
- For most custom pages, the two values you care about most are:
411
-
412
- - `workspaceSlugFromRoute`
413
- - `currentSurfaceId`
414
-
415
- #### Why use it instead of reading `$route.params.workspaceSlug` directly
416
-
417
- For a very simple page, reading the raw route param can work.
418
-
419
- But `useWorkspaceRouteContext()` is the better default for workspace-aware app code because it does more than "read a param":
420
-
421
- - it resolves the current surface through the shell placement/runtime context
422
- - it normalizes the current route path before extracting anything
423
- - it only returns a workspace slug when the current surface is actually workspace-scoped
424
- - it works the same way on both workspace surfaces, `app` and `admin`
425
-
426
- That means your page logic stays aligned with the same route model that the packaged workspace components use internally.
427
-
428
- In other words:
429
-
430
- - `$route.params.workspaceSlug` is a raw route detail
431
- - `useWorkspaceRouteContext()` is a workspace-aware view of the current route
432
-
433
- #### A concrete custom page example
434
-
435
- Suppose you create a real admin page at:
436
-
437
- ```text
438
- src/pages/w/[workspaceSlug]/admin/reports/index.vue
439
- ```
440
-
441
- and that page needs to:
442
-
443
- - know which workspace it is looking at
444
- - know that it is running on the `admin` surface
445
- - build a workspace-scoped API request or query key
446
-
447
- That is exactly the kind of page `useWorkspaceRouteContext()` is for:
448
-
449
- ```vue
450
- <script setup>
451
- import { computed } from "vue";
452
- import { usePaths } from "@jskit-ai/shell-web/client/navigation/usePaths";
453
- import { useWorkspaceRouteContext } from "@jskit-ai/workspaces-web/client/composables/useWorkspaceRouteContext";
454
-
455
- const { workspaceSlugFromRoute, currentSurfaceId } = useWorkspaceRouteContext();
456
- const paths = usePaths();
457
-
458
- const reportsApiPath = computed(() => {
459
- if (!workspaceSlugFromRoute.value) {
460
- return "";
461
- }
462
-
463
- return paths.api("/reports", {
464
- params: {
465
- workspaceSlug: workspaceSlugFromRoute.value
466
- }
467
- });
468
- });
469
-
470
- const queryKey = computed(() => ([
471
- "workspace-reports",
472
- currentSurfaceId.value,
473
- workspaceSlugFromRoute.value
474
- ]));
475
- </script>
476
- ```
477
-
478
- That pattern is intentionally boring in the right way.
479
-
480
- - the page does not hard-code path parsing rules itself
481
- - the page gets a normalized workspace slug from the public workspace helper
482
- - the query key can still distinguish between `app` and `admin` when needed
483
-
484
- The same composable works just as well in a custom workspace app page under:
485
-
486
- ```text
487
- src/pages/w/[workspaceSlug]/index.vue
488
- ```
489
-
490
- or any later child page below that surface.
491
-
492
- #### When you need the rest of the returned context
493
-
494
- Most custom pages only need:
495
-
496
- - `workspaceSlugFromRoute`
497
- - `currentSurfaceId`
498
- - maybe `route`
499
-
500
- The other returned values are there for more advanced cases.
501
-
502
- - `placementContext` matters when a page needs to read the current shell/bootstrap context directly, for example available workspaces or current permissions already in the shell state.
503
- - `mergePlacementContext` is for pages that need to push refreshed workspace data back into the shell runtime after a fetch or save.
504
-
505
- That second case is real, but it is more advanced. It is available for app-owned workspace pages that intentionally need to push refreshed workspace state back into shell-visible context after a save.
506
-
507
- For normal custom page code, you usually do **not** start there. Start with `workspaceSlugFromRoute`.
508
-
509
- ### `src/placement.js` becomes workspace-aware
510
-
511
- The workspace packages append a new block of placements:
512
-
513
- ```js
514
- addPlacement({
515
- id: "workspaces.profile.menu.surface-switch",
516
- target: "auth.profile-menu",
517
- kind: "component",
518
- surfaces: ["*"],
519
- order: 100,
520
- componentToken: "workspaces.web.profile.menu.surface-switch-item",
521
- when: ({ auth }) => Boolean(auth?.authenticated)
522
- });
523
-
524
- addPlacement({
525
- id: "workspaces.workspace.selector",
526
- target: "shell.identity",
527
- kind: "component",
528
- surfaces: ["*"],
529
- order: 200,
530
- componentToken: "workspaces.web.workspace.selector",
531
- props: {
532
- allowOnNonWorkspaceSurface: true,
533
- targetSurfaceId: "app"
534
- },
535
- when: ({ auth }) => {
536
- return Boolean(auth?.authenticated);
537
- }
538
- });
539
-
540
- addPlacement({
541
- id: "workspaces.account.invites.cue",
542
- target: "shell.status",
543
- kind: "component",
544
- surfaces: ["*"],
545
- order: 850,
546
- componentToken: "local.main.account.pending-invites.cue",
547
- when: ({ auth }) => Boolean(auth?.authenticated)
548
- });
549
-
550
- addPlacement({
551
- id: "workspaces.workspace.tools.widget",
552
- target: "shell.status",
553
- kind: "component",
554
- surfaces: ["admin"],
555
- order: 900,
556
- componentToken: "workspaces.web.workspace.tools.widget"
557
- });
558
-
559
- addPlacement({
560
- id: "workspaces.workspace.menu.workspace-settings",
561
- target: "admin.tools-menu",
562
- kind: "component",
563
- surfaces: ["admin"],
564
- order: 100,
565
- componentToken: "workspaces.web.workspace-settings.menu-item"
566
- });
567
-
568
- addPlacement({
569
- id: "workspaces.workspace.menu.members",
570
- target: "admin.tools-menu",
571
- kind: "component",
572
- surfaces: ["admin"],
573
- order: 200,
574
- componentToken: "workspaces.web.workspace-members.menu-item"
575
- });
576
- ```
577
-
578
- That one block explains a lot of the workspace shell behavior.
579
-
580
- - the authenticated profile menu can switch into workspace surfaces
581
- - `shell.identity` carries the workspace selector
582
- - `shell.status` can show a pending-invites cue
583
- - the admin surface gets workspace tools through `shell.status`
584
- - the admin surface gets a workspace tools menu with `Settings` and `Members`
585
- - the workspace settings shell exposes its own nested menu host for app-owned settings child pages
586
-
587
- If you want to add your own app-owned page into that top cog menu, first ask JSKIT which semantic placements exist:
588
-
589
- ```bash
590
- npx jskit list-placements
591
- ```
592
-
593
- In a workspace-enabled app, that list includes:
594
-
595
- ```text
596
- - admin.tools-menu: Admin surface tools menu actions.
597
- ```
598
-
599
- If you want the underlying outlet inventory, use `npx jskit list-placements --concrete`. If you want more package context, `npx jskit show @jskit-ai/workspaces-web --details` also shows the topology plus the default `Settings` and `Members` entries already targeting it.
600
-
601
- Once you know the semantic placement id, generate the page like this:
602
-
603
- ```bash
604
- npx jskit generate ui-generator page \
605
- w/[workspaceSlug]/admin/catalogue/index.vue \
606
- --name "Catalogue" \
607
- --link-placement admin.tools-menu
608
- ```
609
-
610
- That command creates `src/pages/w/[workspaceSlug]/admin/catalogue/index.vue` and appends the matching link entry into `src/placement.js`.
611
-
612
- `--link-placement` is necessary here because this route is just a normal `admin` page. It is **not** a child page under a local host like `w/[workspaceSlug]/admin/workspace/settings.vue`, so the generator has no nested settings placement to infer automatically. If you omit `--link-placement`, the new page link falls back to the app's default `shell.primary-nav` placement instead of the cog menu.
613
-
614
- You also do **not** need a renderer flag here. `admin.tools-menu` defines its link renderer in topology, so JSKIT resolves that when it renders the placement entry.
615
-
616
- So the placement system from the shell chapter is doing the same job with a richer routing and tenancy context.
617
-
618
- ### The local client provider gets one more app-owned token
619
-
620
- `workspaces-web` also appends an app-owned component registration:
621
-
622
- ```js
623
- registerMainClientComponent("local.main.account.pending-invites.cue", () => AccountPendingInvitesCue);
624
- ```
625
-
626
- That is the pending-invites cue used in the shell when workspace invitations exist.
627
-
628
- This is worth noticing because it follows the same app-owned token pattern the guide has shown before:
629
-
630
- - the package installs an app-owned component file
631
- - the app-local provider publishes it under a stable token
632
- - placements can then render it through the shell
633
-
634
- ## Under the hood
635
-
636
- ### `workspaces-core` plugs into the users profile-sync registry
637
-
638
- There is also an important server-side integration point that is easy to miss if you only look at routes and pages.
639
-
640
- In the previous chapter, `users-core` introduced the tagged profile-sync lifecycle registry that runs after a JSKIT user record has been synchronized from auth. `workspaces-core` uses that seam by registering a contributor:
641
-
642
- ```js
643
- registerProfileSyncLifecycleContributor(app, "workspaces.core.profileSyncLifecycleContributor", (scope) => {
644
- const workspaceService = scope.make("workspaces.service");
645
-
646
- return Object.freeze({
647
- contributorId: "workspaces.core.profileSync",
648
- order: 100,
649
- async afterIdentityProfileSynced({ profile, options } = {}) {
650
- if (!profile || typeof workspaceService?.ensureProvisionedWorkspaceForAuthenticatedUser !== "function") {
651
- return;
652
- }
653
-
654
- await workspaceService.ensureProvisionedWorkspaceForAuthenticatedUser(profile, options);
655
- }
656
- });
657
- });
658
- ```
659
-
660
- That means the workspace package does not need to patch auth directly to learn that a user was added. It listens through the `users-core` lifecycle registry instead.
661
-
662
- For this chapter's `tenancyMode = "personal"` setup, that contributor does real work. When an authenticated JSKIT user is synchronized from auth, `workspaces-core` ensures that user's personal workspace exists.
663
-
664
- That detail matters for the retrofit path described earlier in this chapter. If the app started on `none`, then later switched to `personal`, the first sign-in after that switch still needs to backfill the personal workspace for the already-existing user record. The lifecycle contributor handles that because the workspace provision step is idempotent.
665
-
666
- - `users-core` owns the "user was synchronized" lifecycle
667
- - `workspaces-core` subscribes to that lifecycle through the registry
668
- - tenancy policy decides whether the workspace layer provisions a personal workspace automatically
669
-
670
- So even in this chapter, the package boundary is already correct: users owns user creation/sync, and workspaces reacts through an extension point.
671
-
672
- ### Workspace auth context comes from the auth policy resolver registry
673
-
674
- There is a second registry seam in this chapter that matters just as much for the runtime model.
675
-
676
- Workspace-aware routes do not hard-code workspace lookup inside the auth plugin itself. Instead, `workspaces-core` registers an auth policy context resolver contribution, and `auth-core` composes the registered resolvers at request time.
677
-
678
- The important consequence is:
679
-
680
- - `auth-core` stays generic
681
- - `workspaces-core` contributes workspace-specific context
682
- - the request only resolves workspace membership and permissions when a route actually asks for auth context or permissions
683
-
684
- So the flow is:
685
-
686
- 1. a workspace-aware route declares auth and, when needed, permission requirements
687
- 2. the auth policy runtime authenticates the actor
688
- 3. the composed auth policy context resolver asks the workspace layer for the current workspace context
689
- 4. the request gains normalized `workspace`, `membership`, and `permissions` values
690
-
691
- That is why the workspace package does not need to patch the auth plugin directly, and it is also why this setup scales better than a one-off global hook. It uses the same tagged-registry pattern as other JSKIT extension seams, but for request-time auth context instead of bootstrap payloads or lifecycle events.
692
-
693
- ### Why this chapter is the real routing pivot
694
-
695
- Earlier chapters added features inside a flat top-level app.
13
+ The dependency graph supplies `workspaces-core` and the required users runtime.
14
+ Package migrations run from their installed locations through the application's
15
+ normal Knex command.
696
16
 
697
- This chapter is different. It changes the shape of the app itself.
17
+ ## Decide the tenancy model first
698
18
 
699
- Before:
19
+ Choose whether the product is:
700
20
 
701
- - global surfaces only
702
- - no workspace slug in routes
703
- - no workspace membership checks
21
+ - global, with no workspace concept;
22
+ - personal, with one automatically provisioned personal workspace; or
23
+ - multi-workspace, with named workspaces, membership, invitations, and
24
+ switching.
704
25
 
705
- After:
26
+ Do not add workspace machinery to a product that only needs an owner id on one
27
+ domain record.
706
28
 
707
- - global surfaces still exist
708
- - workspace-scoped surfaces exist too
709
- - the shell can navigate between workspaces
710
- - access to some surfaces depends on workspace membership
29
+ ## Server composition
711
30
 
712
- That is why multi-homing deserves its own chapter. It is not just another feature package. It is the moment the app becomes tenancy-aware.
31
+ Use `workspaces/workspace-server` for role vocabulary, creation policy,
32
+ invitations, settings, and app-owned invitation email rendering.
713
33
 
714
- ## Summary
34
+ Workspace scope must reach repository queries. Parsing a workspace slug or
35
+ hiding an admin link is not authorization. Membership and role checks happen
36
+ before an operation discloses records or record existence.
715
37
 
716
- This chapter is the real routing and tenancy pivot in the guide.
38
+ `workspace_authenticated` and similar policies must implement their documented
39
+ membership semantics exactly; do not synthesize memberships to compensate for
40
+ a policy/runtime mismatch.
717
41
 
718
- - the app uses `tenancyMode = "personal"`
719
- - `workspaces-core` added the persistent schema and server runtime for workspaces
720
- - `workspaces-web` added the first workspace-scoped surfaces, shell controls, and the workspace-owned account invites extension
42
+ ## Browser composition
721
43
 
722
- At the end of this chapter, the app has both:
44
+ Use `workspaces/workspace-surfaces` for workspace selection, invitation landing,
45
+ member administration, settings, not-found states, and semantic shell
46
+ placements.
723
47
 
724
- - global surfaces such as `home`, `auth`, `account`, and `console`
725
- - workspace-scoped surfaces such as `/w/[workspaceSlug]` and `/w/[workspaceSlug]/admin`
48
+ The route slug, selected workspace, query keys, and server scope stay aligned.
49
+ Invalid workspace URLs show a deliberate not-found/forbidden state instead of
50
+ silently selecting another workspace.
726
51
 
727
- That is the most important mental shift to keep:
52
+ Loading uses skeletons; mutation errors use toasts. Warm-cache and browser
53
+ back/forward navigation must restore the correct workspace context.
728
54
 
729
- - earlier chapters added features inside one global app shell
730
- - this chapter changed the topology of the app itself
55
+ ## Verification
731
56
 
732
- From here on, later modules can add features inside either:
57
+ Test create/list/select, each role boundary, invitations and expiry,
58
+ cross-workspace isolation, invalid routes, direct URLs, switching, browser
59
+ back/forward, and responsive navigation.
733
60
 
734
- - the global surfaces
735
- - the workspace-specific surfaces
61
+ Do not authorize from client state, add unscoped queries, or preserve old
62
+ tenancy setup through compatibility shims.