@jskit-ai/agent-docs 0.1.278 → 0.1.280

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 (114) hide show
  1. package/guide/agent/app-extras/assistant.md +123 -0
  2. package/guide/agent/app-setup/authentication.md +46 -4
  3. package/guide/agent/app-setup/database-layer.md +35 -6
  4. package/guide/agent/app-setup/material-3-migration.md +53 -10
  5. package/guide/agent/app-setup/multi-homing.md +8 -2
  6. package/guide/agent/app-setup/upgrading-jskit.md +30 -0
  7. package/guide/agent/app-setup/users.md +9 -1
  8. package/guide/agent/framework/material-3.md +153 -71
  9. package/guide/agent/framework/sanity-checks.md +180 -0
  10. package/guide/agent/framework/translation.md +4 -0
  11. package/guide/agent/framework/typed-features.md +158 -105
  12. package/guide/agent/framework/ui-operations.md +3 -1
  13. package/guide/agent/index.md +3 -0
  14. package/package.json +2 -2
  15. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  16. package/patterns/minimal-foundation/PATTERN.md +6 -2
  17. package/patterns/minimal-foundation/example/AGENTS.md +10 -0
  18. package/patterns/minimal-foundation/example/package.json +6 -6
  19. package/patterns/minimal-foundation/example/playwright.config.mjs +3 -0
  20. package/patterns/minimal-foundation/example/src/main.js +3 -2
  21. package/patterns/minimal-foundation/example/vite.config.mjs +3 -1
  22. package/patterns/shell-foundation/PATTERN.md +4 -1
  23. package/patterns/shell-foundation/example/AGENTS.md +10 -0
  24. package/patterns/shell-foundation/example/package.json +7 -7
  25. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  26. package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  27. package/patterns/shell-foundation/example/playwright.config.mjs +3 -0
  28. package/patterns/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  29. package/patterns/shell-foundation/example/src/main.js +3 -2
  30. package/patterns/shell-foundation/example/src/pages/home.vue +1 -1
  31. package/patterns/shell-foundation/example/src/views/NotFound.vue +1 -1
  32. package/patterns/shell-foundation/example/vite.config.mjs +3 -1
  33. package/reference/autogen/PATTERN_INDEX.md +37 -37
  34. package/reference/autogen/packages/agent-docs.md +4 -0
  35. package/reference/autogen/packages/assistant-runtime.md +27 -2
  36. package/reference/autogen/packages/auth-provider-local-core.md +1 -1
  37. package/reference/autogen/packages/auth-web.md +10 -0
  38. package/reference/autogen/packages/crud-core.md +3 -0
  39. package/reference/autogen/packages/http-web.md +0 -6
  40. package/reference/autogen/packages/json-rest-api-core.md +17 -4
  41. package/reference/autogen/packages/kernel.md +10 -1
  42. package/reference/autogen/packages/material-web.md +2 -1
  43. package/reference/autogen/packages/shell-web.md +95 -4
  44. package/reference/autogen/packages/users-web.md +9 -2
  45. package/reference/autogen/packages/workspaces-core.md +2 -0
  46. package/reference/autogen/tooling/jskit-catalog.md +55 -6
  47. package/reference/autogen/tooling/testUtils.md +15 -1
  48. package/skills/jskit/SKILL.md +15 -5
  49. package/skills/jskit/references/heuristics/INDEX.md +3 -1
  50. package/skills/jskit/references/heuristics/row-policies.md +3 -3
  51. package/skills/jskit/references/heuristics/typed-features.md +7 -7
  52. package/skills/jskit/references/heuristics/ui-testing.md +8 -7
  53. package/skills/jskit/references/material-3.md +153 -71
  54. package/skills/jskit/references/multi-homing.md +8 -2
  55. package/skills/jskit/references/pattern-index.md +37 -37
  56. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +6 -2
  57. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +10 -0
  58. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +6 -6
  59. package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +3 -0
  60. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +3 -2
  61. package/skills/jskit/references/patterns/app/minimal-foundation/example/vite.config.mjs +3 -1
  62. package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +4 -1
  63. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +10 -0
  64. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +7 -7
  65. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  66. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  67. package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +3 -0
  68. package/skills/jskit/references/patterns/app/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  69. package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +3 -2
  70. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home.vue +1 -1
  71. package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +1 -1
  72. package/skills/jskit/references/patterns/app/shell-foundation/example/vite.config.mjs +3 -1
  73. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  74. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +18 -5
  75. package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue +17 -0
  76. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/login.vue +2 -2
  77. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue +2 -2
  78. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue +2 -2
  79. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue +6 -1
  80. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/ResetPasswordView.vue +6 -1
  81. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue +6 -1
  82. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  83. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  84. package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +1 -1
  85. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  86. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console.vue +1 -1
  87. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  88. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  89. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  90. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  91. package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -2
  92. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  93. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +11 -6
  94. package/skills/jskit/references/patterns/shell/application-shell/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  95. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home.vue +1 -1
  96. package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +1 -1
  97. package/skills/jskit/references/patterns/ui/page-and-placement/PATTERN.md +6 -0
  98. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +9 -4
  99. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placementTopology.js +5 -3
  100. package/skills/jskit/references/patterns/users/account-settings/PATTERN.md +28 -2
  101. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +1 -1
  102. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -1
  103. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +3 -2
  104. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/[section].vue +23 -0
  105. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account.vue +23 -0
  106. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  107. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  108. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +3 -3
  109. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/root.vue +1 -1
  110. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/root.vue +1 -1
  111. package/skills/jskit/references/sanity-checks.md +180 -0
  112. package/skills/jskit/references/translation.md +4 -0
  113. package/skills/jskit/references/typed-features.md +158 -105
  114. package/skills/jskit/references/ui-operations.md +3 -1
@@ -42,7 +42,8 @@ app and never leaves it.
42
42
  - **What the person sees:** one Up arrow on every inner page, also when opened
43
43
  directly: icon only (`$arrowBack`), accessible name "Back" (localized), a
44
44
  tooltip on pointer devices. None on top-level destinations, and no other
45
- back control anywhere; the bar title says where the person is. A list-detail
45
+ back control anywhere; the bar title says where the person is. A new or edit
46
+ form shows a close (X) in the arrow's place, which cancels. A list-detail
46
47
  layout showing the parent beside the page needs no arrow for that step;
47
48
  collapsed to one pane, the record pane gets it back.
48
49
  - **Where Up goes:** to the previous trail entry while it is valid, so inside
@@ -74,31 +75,49 @@ Exactly this, and nothing more:
74
75
  </script>
75
76
  ```
76
77
 
77
- The same key works in a `<route lang="json">` block. A record names its
78
- list, an edit page its record, a sub-page its page. Tabs under a layout
78
+ The same key works in a `<route lang="json">` block. Tabs under a layout
79
79
  (`settings.vue` rendering `<RouterView>` for `settings/`) declare nothing:
80
80
  they inherit the nearest layout's declaration, and moving between them keeps
81
- the trail.
81
+ the trail and the scroll. A folder without a layout (`bookings/` with no
82
+ `bookings.vue`) only groups routes: its pages are different pages, each
83
+ opening at the top. The parent is read from the page up to its surface root, the
84
+ record that declares `jskit.surface`, and stops there. So the surface goes
85
+ only on the surface's layout page (`account.vue`: `ShellLayout` around
86
+ `<RouterView />`) and the parent on the page it renders
87
+ (`account/index.vue`). A package route declares its parent in its
88
+ `package.json` `meta`; under no layout, its component draws `ShellLayout`.
82
89
  2. **Each top-level destination is a main-navigation item** (a `kind: "link"`
83
90
  placement with `target: "shell.primary-nav"` or `"shell.secondary-nav"`) and
84
91
  declares no parent; nor do its tabs. A shell page with no parent is
85
- top-level: no arrow, a fresh trail.
92
+ top-level: no arrow, a fresh trail. Placements are literal declarative data,
93
+ one `addPlacement({ ... })` per destination with plain string `surface`,
94
+ `scopedSuffix` and `unscopedSuffix`, never built by a helper, a spread or a
95
+ template, so a reader and `jskit check` can map every destination.
86
96
  3. **Every in-app link and navigation goes through JSKIT navigation:**
87
97
  `:to="paths.page('/suppliers')"` and `router.push(paths.page(...))` with
88
98
  `usePaths()`, the CRUD runtime's `resolveViewUrl()`, `resolveEditUrl()` and
89
99
  `resolveParams()`, and link placements (`surface` with `scopedSuffix` and
90
- `unscopedSuffix`; `to` only for a global route such as `/auth/login`). The
91
- trail follows the router, so there is no special link component.
100
+ `unscopedSuffix`; `to` only for a global route). A global route
101
+ (`scope: "global"`, on no surface) is
102
+ `paths.page("/auth/login", { scope: "global" })`; `explicitTo` only passes
103
+ through a finished target that arrives as data, never a hand-written path.
104
+ The trail follows the router, so there is no special link component.
92
105
  4. **A custom new or edit page finishes through the shell:**
93
106
  `useShellNavigation()` (`@jskit-ai/shell-web/client/navigation/useShellNavigation`)
94
107
  gives `finish(target)` for Save (the target replaces the form in the trail)
95
108
  and `finish()` for Cancel (back to where the form was opened, else its
96
- parent). A page whose record turns out deleted or forbidden calls
97
- `pageUnavailable()`. JSKIT's CRUD screens already do both.
109
+ parent); `useShellPageClose({ label, close })`
110
+ (`@jskit-ai/shell-web/client/composables/useShellPageClose`) makes the
111
+ arrow its Cancel, never an X beside it. A page whose
112
+ record turns out deleted or forbidden calls `pageUnavailable()`. JSKIT's
113
+ CRUD screens already do all three.
98
114
  5. **A full page load goes through the shell too:** `reloadAppPage(path)`
99
115
  (after sign-in or a user switch, to rebuild client state) and
100
116
  `leaveApp(url)` (an external site in the same tab, such as a payment page),
101
- from `@jskit-ai/shell-web/client/navigation/documentNavigation`.
117
+ from `@jskit-ai/shell-web/client/navigation/documentNavigation`. `leaveApp`
118
+ opens the site with a push, so Back returns to the page; a hand-off page
119
+ that only forwards (to an OAuth consent screen) uses
120
+ `leaveApp(url, { replace: true })`, so Back from the site doesn't rerun it.
102
121
 
103
122
  ### Forbidden in app code
104
123
 
@@ -107,9 +126,12 @@ Exactly this, and nothing more:
107
126
  | `nav/history` | `history.back()`, `history.go()`, `history.pushState()`/`replaceState()`, `router.back()`, `router.go()` |
108
127
  | `nav/window-location` | `window.location` assignments, `.href =`, `location.assign()`, `location.replace()` |
109
128
  | `nav/raw-link` | a hand-built in-app path in `to`, `href`, `router.push()`, `router.replace()` or a `to:`/`href:` object key outside a placement |
110
- | `nav/back-control` | any back control but the shell's Up that navigates the app: "Back to X", "← Suppliers", back arrows and navigating chevrons, breadcrumbs, a custom app-bar back button, a back component, a `goBack()`-style handler |
111
- | `nav/missing-parent` | an inner page of a shell surface without a declared parent |
129
+ | `nav/back-control` | any back control but the shell's Up that navigates the app: "Back to X", "← Suppliers", back arrows and navigating chevrons, breadcrumbs, a custom app-bar back button, a back component (`<ContextBackButton />` or `<page-back-link>`, and its import), a `goBack()`-style handler |
130
+ | `nav/missing-parent` | an inner page of a shell surface without a declared parent, when it always renders under the shell's top app bar (a layout that also renders its pages without `ShellLayout`, such as a shop device's sign-in in a `v-if`, leaves its pages unjudged) |
112
131
  | `nav/top-level-parent` | a main-navigation destination that declares a parent |
132
+ | `nav/placement-data` | a main-navigation placement that isn't literal data (no page is judged for a missing parent until it is) |
133
+ | `nav/missing-shell-layout` | a page that declares a parent with no shell layout rendering it, so its title, Up and actions never show |
134
+ | `nav/surface-parent` | a parent declared beside `jskit.surface`, which the shell never reads |
113
135
  | `nav/allow` | any `m3-check: allow nav/...` comment: nothing excuses a navigation rule |
114
136
 
115
137
  ### Each rule, right and wrong
@@ -167,29 +189,43 @@ Exactly this, and nothing more:
167
189
  - **Type:** Vuetify 4 Material 3 roles (`text-headline-small`,
168
190
  `text-title-large`, `text-body-medium`, `text-label-large`). Never the
169
191
  Material 2 scale (`text-h5`, `text-body-2`) or a page-local type system.
170
- Roboto Flex is the font.
192
+ Material 3 has two typefaces, both Roboto Flex unless the app names its own
193
+ in `src/theme/index.js`:
194
+ `typography: { brand: '"Fraunces", serif', plain: '"Inter", sans-serif' }`.
195
+ `brand` sets display, headline and title-large, `plain` the rest (title-medium
196
+ and -small, body, label); each is a CSS font-family value, and the app loads
197
+ the font. Never set `font-family` or `--v-font-body` in app CSS.
171
198
  - **Icons:** Material Symbols Rounded through Vuetify `$` aliases: Vuetify's
172
199
  standard ones (`$close`, `$menu`, `$delete`, `$prev`, `$next`) and JSKIT's
173
200
  camelCase names (`$add`, `$edit`, `$search`, `$moreVert`, `$filterList`,
174
201
  `$arrowBack`); `$homeFilled`-style `Filled` variants mark active navigation.
175
202
  An app adds an icon under `theme.icons` in `src/theme/index.js` as
176
203
  `{ name: "<24x24 path data>" }` from Material Symbols Rounded, then uses
177
- `$name`. No `mdi-*` strings and no `@mdi/js` imports in app code.
204
+ `$name`. No `mdi-*` strings and no `@mdi/js` imports in app code. Material
205
+ Symbols has no brand marks (Google, GitHub, a payment network): a brand's
206
+ logo is the brand's own artwork, a content image or SVG used as its owner
207
+ publishes it, not an icon alias.
178
208
 
179
209
  ## Exact sizes
180
210
 
181
- JSKIT ships these exact sizes; an app never re-implements or overrides them,
182
- and needs no Sass and no `!important` (Vuetify 4 keeps its CSS in cascade
183
- layers):
184
-
185
- - 40dp buttons with a 48dp touch target, icon buttons included;
186
- - 12dp cards;
211
+ JSKIT ships these exact sizes; an app never re-implements or overrides them:
212
+
213
+ - 40dp buttons with a 48dp touch target, icon buttons included; a smaller
214
+ button is `size="small"` or `size="x-small"`, which keeps its smaller look
215
+ and the 48dp target. No icon button is under 32dp, and a target stops at
216
+ the edge of a button beside it, so each of a row's touching icon buttons
217
+ answers its own press;
218
+ - one-line button labels, ending in an ellipsis: keep labels short, and give
219
+ one that can run long, such as a name, its full label as `title`;
220
+ - 12dp cards: flat (the default) and tonal at level 0, `variant="elevated"`
221
+ at level 1; flat expansion panels;
187
222
  - 28dp dialogs and bottom-sheet top corners, square full-screen dialogs;
188
- - 8dp chips and 16dp FABs;
223
+ - 8dp chips, and FABs of 56dp (small 40dp, large 96dp; extended 56dp tall)
224
+ with 16dp corners;
189
225
  - emphasized easing, with reduced motion honoured.
190
226
 
191
227
  So never set `height` or `min-height` on a button, or a radius on a card,
192
- dialog, chip or FAB. A missing size is a JSKIT defect.
228
+ dialog, chip or FAB.
193
229
 
194
230
  ## The top app bar carries the page
195
231
 
@@ -197,29 +233,46 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
197
233
  `useShellPageTitle(title)` (`@jskit-ai/shell-web/client/composables/useShellPageTitle`)
198
234
  for a runtime title such as the record's name. The shell renders it as the
199
235
  app bar's only `h1`; pages add no `h1`, page header, title block or
200
- standalone heading. A section inside a page uses a title role.
201
- - **Up** sits in the bar on every inner page (see Navigation and Up). On phones
202
- it replaces the menu icon.
236
+ standalone heading. Route pages own the title: a section or a nested
237
+ component uses a title role (a component's claim gives the title back when
238
+ it goes). A page with no top app bar (a hero front page, an intro, an invite
239
+ or hand-off page) has its own one `h1`.
240
+ - **Up** leads the bar on every inner page; on phones it replaces the menu
241
+ icon.
203
242
  - **A layout that draws its own top app bar** (a workspace bar with the
204
243
  app's own controls, instead of `ShellLayout`) builds it from `ShellLayout`'s
205
244
  own pieces, from `@jskit-ai/shell-web/client`. It calls `useShellAppBar()`
206
245
  once, so its pages enter the trail and their `ShellPageActions` show in its
207
246
  bar, and renders `ShellUpButton` first, then `ShellPageTitle` (the page's one
208
- `h1`: `useShellPageTitle()`, else the route's `jskit.title`, else its `title`
209
- prop) and `ShellPageActionsTarget`. `jskit check` counts it as a shell
210
- layout, so its inner pages need parents; an Up the app draws itself is a
211
- `nav/back-control` finding.
247
+ `h1`) and `ShellPageActionsTarget`. A title that is also a switcher, such as a
248
+ project menu, goes in `ShellPageTitle`'s default slot (given `{ title }`) as
249
+ the menu's activator, so it stays the one `h1`. `jskit check` counts it as a
250
+ shell layout, so its inner pages need parents.
212
251
  - **Actions:** a screen's actions go in the bar inside `ShellPageActions`
213
252
  (`@jskit-ai/shell-web/client/components/ShellPageActions`), each a
214
253
  `ShellPageAction` with `icon`, `label` (also its accessible name and compact
215
- tooltip), and `primary`, `to`, `replace`, `disabled`, `loading` or `color`.
216
- On compact widths every action shows only its icon except the single
217
- `primary` one.
254
+ tooltip), and `primary`, `to`, `replace`, `href` (with `download` for a
255
+ file, such as a CSV export), `disabled`, `loading` or `color` (`error` for a
256
+ destructive one); attributes such as `target` reach its button. On compact
257
+ widths the bar keeps one trailing item after the title: the single
258
+ `primary` action when the page has one (a form's Save), otherwise its own
259
+ item nearest the corner (a top-right placement such as the account avatar).
260
+ Every other action and the bar's other items (a notification bell, tools)
261
+ go to its "More actions" menu, where `overflow` actions always are. Medium
262
+ and wider bars show them all.
218
263
  - **Window size classes:** compact under 600 (navigation bar), medium 600–839
219
- (navigation rail), expanded 840–1199 and large from 1200 (drawer). Read them
220
- from `useShellWindowSizeClass()`
264
+ (navigation rail), expanded 840–1199 and large from 1200 (drawer), read from
265
+ `useShellWindowSizeClass()`
221
266
  (`@jskit-ai/shell-web/client/composables/useShellWindowSizeClass`), never from
222
- copied breakpoint numbers.
267
+ copied breakpoint numbers. A surface with fewer than two navigation
268
+ destinations (none, as on an account surface, or Home alone) draws no
269
+ navigation bar, rail or drawer; its pages keep the app bar.
270
+ - **Section tabs:** a page's own sections (`page.section-nav`) are Material 3
271
+ primary tabs: the topology names `ShellSectionTabLinkItem`'s token (the
272
+ starter registers it as `local.main.ui.section-tab-link-item`), and the page
273
+ draws one tab row, `display: flex; overflow-x: auto` over a 1dp
274
+ `outline-variant` bottom border. `ShellTabLinkItem` is the navigation bar's
275
+ destination; placed in a page it draws a section tab.
223
276
 
224
277
  ## Colour
225
278
 
@@ -242,35 +295,41 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
242
295
  control; never a permanent panel of outlined fields. A card opens its record
243
296
  when tapped (a stretched title link); a table row opens on click. Row actions
244
297
  are trailing icon buttons (edit, a `$moreVert` overflow menu, a chevron to
245
- open), never "View" or "Edit" text buttons on every row. "New" is an extended
246
- FAB on compact and medium widths and a filled bar button on expanded. Empty
247
- and error states carry an icon.
298
+ open), never "View" or "Edit" text buttons on every row. "New" is
299
+ `ShellPrimaryAction` (`label`, and `to` or a click handler): an extended FAB
300
+ on compact and medium widths, a filled bar button on expanded. On/off filters
301
+ are `aria-pressed` chips, or chips in a `v-chip-group`; JSKIT draws them as
302
+ filter chips. Empty and error states carry an icon.
248
303
  - **A list never scrolls sideways, at any width.** The list's own width (a
249
304
  container query, not the window) chooses cards or table. Cards flow into a
250
305
  grid; tables use a fixed layout with an ellipsis on long values, and fold
251
306
  low-priority columns into a stronger column's second line. Lists show figures
252
307
  ("120 kg", tabular and right-aligned) and names, never ids.
253
308
  - **Record:** a header (avatar, title, status chips) and value-first detail
254
- lists, never a grid of label/value text. Edit sits in the bar; the record's
309
+ lists, never a grid of label/value text. Edit sits in the bar, its secondary
310
+ actions in the bar's overflow menu, never in the content; the record's
255
311
  next workflow step is the one filled button, shown in context rather than a
256
312
  row of mostly disabled actions.
257
- - **Form:** Cancel and Save sit in the bar. Fields use `hideDetails: "auto"`.
258
- Nothing moves under a finger: a multi-select chip field keeps its input width
259
- when it loses focus. A pick-one-of-N choice is a segmented button with
260
- `role="radiogroup"` and `role="radio"`/`aria-checked` buttons.
313
+ - **Form:** the bar's close (X) cancels and Save is its filled action. Fields
314
+ use `hideDetails: "auto"`. Nothing moves under a finger: a multi-select chip
315
+ field keeps its input width when it loses focus. A pick-one-of-N choice is a
316
+ segmented button (`v-btn-toggle`, drawn by JSKIT, with 12dp segment sides as
317
+ in any `v-btn-group`) with `role="radiogroup"` and
318
+ `role="radio"`/`aria-checked` buttons.
261
319
  - **Dialogs:** plain `v-dialog` and `v-bottom-sheet`; JSKIT gives them their
262
320
  Material 3 shapes. A full-screen dialog (compact and medium) puts close, title
263
321
  and its confirm action in a top bar, never at the bottom where a snackbar
264
322
  covers them.
265
323
  - **Menus and badges:** a menu's list has `role="menu"` and its items
266
324
  `role="menuitem"`, with `aria-disabled` on unavailable ones; a badge has a
267
- `label` such as "3 batches".
325
+ `label` such as "3 batches". JSKIT draws a menu's list and a dialog's card;
326
+ an app sets no `VMenu` or `VDialog` defaults, under which
327
+ Vuetify 4.2.1 to 4.3.2 loses a `v-select`'s typeahead match, or a dialog's
328
+ virtual list, when an ancestor re-renders.
268
329
 
269
330
  ## Components, state, and feedback
270
331
 
271
- - Choose components by purpose. Prefer standard buttons, fields, lists,
272
- tables, sheets, dialogs, menus, navigation, progress, alerts, snackbars, and
273
- tooltips over custom imitations.
332
+ - Choose standard components by purpose over custom imitations.
274
333
  - Give one primary action emphasis. Distinguish destructive, secondary, and
275
334
  icon-only actions without relying on color; give icon controls accessible
276
335
  names and fields meaningful labels, errors, and autocomplete behavior.
@@ -287,6 +346,11 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
287
346
  field. A user-triggered command uses JSKIT's shared action
288
347
  feedback/snackbar path. Never insert a transient command-error alert above
289
348
  page content where it shifts the working layout.
349
+ - A snackbar never covers the FAB or the actions it reports on. `ShellErrorHost`
350
+ centres it above the navigation bar on compact windows and starts it at the
351
+ content's start edge, at most half its width, on wider ones. A FAB takes
352
+ layout room (`<v-fab app layout>`, as `ShellPrimaryAction`'s does), so the
353
+ snackbar shows above it.
290
354
 
291
355
  ## Adaptive layout
292
356
 
@@ -308,16 +372,15 @@ npx jskit check m3 src/pages/home/books # a section
308
372
  npx jskit check m3 $(git diff --name-only --diff-filter=d main) # changed files
309
373
  ```
310
374
 
311
- Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code);
312
- fix every one, because nothing excuses them. `jskit check m3` lists those and
313
- the visual rules below. The visual rules are a report until every JSKIT app has
314
- been converted to them; the JSKIT release after that makes plain `jskit check`,
315
- and so every app's verify, fail on them too:
375
+ Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code).
376
+ `jskit check m3` lists those and the visual rules below. The visual rules are a
377
+ report until every JSKIT app has been converted to them; the JSKIT release after
378
+ that makes plain `jskit check`, and so every app's verify, fail on them too:
316
379
 
317
380
  | Rule | Finds |
318
381
  |---|---|
319
- | `m3/duplicate-h1` | a second `h1`, or an in-page `h1` in an app that uses the JSKIT shell |
320
- | `m3/raw-hex` | a hex colour outside `src/theme/` |
382
+ | `m3/duplicate-h1` | a second `h1` that can show beside the first (one per `v-if` branch is one), or an in-page `h1` under a layout that always shows the shell's page title |
383
+ | `m3/raw-hex` | a hex colour, or one written as channels (`rgba(0, 0, 0, 0.15)`, as in a hand-made shadow), outside `src/theme/` (`palettes.js` holds content colours); `rgba(var(--v-theme-shadow), 0.2)` reads a role and passes |
321
384
  | `m3/dark-scheme` | no dark scheme, or the theme pinned to light |
322
385
  | `m3/outlined-card` | `variant="outlined"` or `border` on a card or sheet |
323
386
  | `m3/mdi-icon` | `mdi-*` icon strings and `@mdi/js` imports |
@@ -338,19 +401,41 @@ Take the approved route first; none needs an exception:
338
401
  `<v-chip color="partner">`. A hex string is harmonized toward the seed; a
339
402
  colour that must keep its hue is `{ value: "#F5C518", blend: false }`
340
403
  (Material Theme Builder's Blend switched off), with the same four roles.
341
- - **Chart and data colours** come from the theme: `const theme = useTheme()`,
342
- then `["primary", "tertiary"].map((role) => theme.current.value.colors[role])`.
404
+ - **Content colours**, colours that are content rather than UI, keep one
405
+ value in both schemes: an identity palette a person picks and the server
406
+ stores, avatar and illustration artwork, brand marks (a language's
407
+ file-type tint), fixed chart series, QR codes, colours written into
408
+ exported files. They are named exports of plain hex values in
409
+ `src/theme/palettes.js`, used as they are, never for UI chrome (a theme
410
+ colour is toned per scheme, so it can't keep an identity the same).
411
+ Components read them through bindings:
412
+
413
+ ```vue
414
+ <script setup>
415
+ // src/theme/palettes.js: export const botColours = ["#6750A4", "#B3261E"];
416
+ import { botColours } from "@/theme/palettes.js";
417
+ const props = defineProps({ bot: Object });
418
+ const ring = botColours[props.bot.colour];
419
+ </script>
420
+ <template>
421
+ <v-avatar :style="{ backgroundColor: ring }"><svg><path :fill="ring" /></svg></v-avatar>
422
+ </template>
423
+ <style scoped>.v-avatar { outline: 2px solid v-bind(ring); }</style>
424
+ ```
425
+ - **Chart and data colours** that follow the scheme come from the theme:
426
+ `const theme = useTheme()`, then
427
+ `["primary", "tertiary"].map((role) => theme.current.value.colors[role])`.
343
428
  - **A third-party widget** lives in one wrapper component, such as
344
429
  `src/components/RouteMap.vue`, that hands it theme colours; pages use only
345
430
  the wrapper.
346
431
 
347
- What is left may be excused one line at a time, on its line or the line
348
- before: `<!-- m3-check: allow m3/raw-hex - the partner's contractual logo colour -->`
432
+ What is left, such as a colour inside static SVG markup that can't import,
433
+ may be excused one line at a time, on its line or the line before: `<!-- m3-check: allow m3/raw-hex - the partner's contractual logo colour -->`
349
434
  (`// m3-check: ...` in scripts, `/* ... */` in CSS). **Never add one on your
350
435
  own:** ask the person first, naming the rule and the reason. A comment with no
351
- reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`), and any
352
- `m3-check: allow nav/...` (`nav/allow`), is itself a finding; nothing turns the
353
- check off for a file, a folder or the app. `jskit check m3` lists every
436
+ reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`) is itself a
437
+ finding, as any `nav/allow` is; nothing turns the check off for a file, a
438
+ folder or the app. `jskit check m3` lists every
354
439
  exception with its file, line and reason, and their total.
355
440
 
356
441
  ## Material 3 audit
@@ -359,19 +444,16 @@ Run this behavior-preserving audit over every affected screen:
359
444
 
360
445
  1. Identify the owning JSKIT surface, shared screen, shell, placement, theme,
361
446
  and Vuetify components before judging local markup.
362
- 2. Check navigation and Up first, as must-fix: plain `jskit check` passes;
363
- every inner page has a parent and one Up arrow in the bar; main-menu
364
- destinations have none; no inner link is hand-built; nothing writes back
365
- logic.
447
+ 2. Check navigation and Up first, as must-fix: plain `jskit check` passes and
448
+ every rule in Navigation and Up holds.
366
449
  3. Find parallel components, duplicated state chrome, page-local themes, raw
367
450
  visual constants, CSS against Vuetify internals, nested generic cards,
368
- borders instead of tones, Material 2 typography, and desktop-only layout.
369
- 4. Check hierarchy, token use, component purpose, responsive presentation,
370
- interaction states, keyboard/focus behavior, labels, targets, contrast, and
371
- reduced motion against the rules above.
372
- 5. Correct issues at the narrowest established owner. Consolidate only truly
451
+ borders instead of tones, Material 2 typography, and desktop-only layout;
452
+ check states, focus, labels, targets, contrast and reduced motion against
453
+ the rules above.
454
+ 4. Correct issues at the narrowest established owner. Consolidate only truly
373
455
  repeated policy; avoid pass-through wrappers and speculative abstractions.
374
- 6. Run `jskit check` and `jskit check m3` on the affected files, then focused
456
+ 5. Run `jskit check` and `jskit check m3` on the affected files, then focused
375
457
  tests and Playwright at compact, medium, and expanded widths in light and
376
458
  dark (`shellBackButton(page)` and `pressShellBack(page)` from
377
459
  `@jskit-ai/shell-web/test/shellNavigation` drive Up).
@@ -174,6 +174,12 @@ capability provides:
174
174
  | `workspaces.repositories.workspaces`, `.workspaceMemberships`, `.workspaceSettings`, `.workspaceInvites` | Repository access inside the application's own transactions |
175
175
  | `workspaces.tenancyProfile` | The resolved tenancy mode and workspace policy |
176
176
 
177
+ To check one person's membership, for example a notification recipient, call
178
+ `workspaces.services.members.getMember(workspace, userId, options)`. It answers
179
+ that member's `listMembers` entry, or `null` for a removed or unknown member,
180
+ and reads only that membership. Do not list every member to find one: the list
181
+ grows with the workspace, and a lookup per row multiplies it.
182
+
177
183
  ### Invitation email
178
184
 
179
185
  To send invitation email, provide the optional `workspaces.invite-mailer`
@@ -452,9 +458,9 @@ Use only a verified empty disposable database named
452
458
  reports its engine version, applies package migrations and drops fixture tables.
453
459
 
454
460
  ```sh
455
- node --test packages/workspaces-core/test/*.test.js
461
+ npx jskit test packages/workspaces-core/test/*.test.js
456
462
  # Set JSKIT_REPOSITORY_TEST_DATABASE_URL to the verified empty disposable target.
457
- node --test packages/workspaces-core/test/repositories.integration.test.js
463
+ npx jskit test packages/workspaces-core/test/repositories.integration.test.js
458
464
  npm run verify
459
465
  ```
460
466