@jskit-ai/agent-docs 0.1.279 → 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 (113) hide show
  1. package/guide/agent/app-extras/assistant.md +123 -0
  2. package/guide/agent/app-setup/authentication.md +34 -3
  3. package/guide/agent/app-setup/database-layer.md +35 -6
  4. package/guide/agent/app-setup/material-3-migration.md +38 -9
  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 +111 -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-web.md +10 -0
  37. package/reference/autogen/packages/crud-core.md +3 -0
  38. package/reference/autogen/packages/http-web.md +0 -4
  39. package/reference/autogen/packages/json-rest-api-core.md +17 -4
  40. package/reference/autogen/packages/kernel.md +10 -1
  41. package/reference/autogen/packages/material-web.md +2 -1
  42. package/reference/autogen/packages/shell-web.md +84 -0
  43. package/reference/autogen/packages/users-web.md +5 -2
  44. package/reference/autogen/packages/workspaces-core.md +2 -0
  45. package/reference/autogen/tooling/jskit-catalog.md +52 -5
  46. package/reference/autogen/tooling/testUtils.md +15 -1
  47. package/skills/jskit/SKILL.md +15 -5
  48. package/skills/jskit/references/heuristics/INDEX.md +3 -1
  49. package/skills/jskit/references/heuristics/row-policies.md +3 -3
  50. package/skills/jskit/references/heuristics/typed-features.md +7 -7
  51. package/skills/jskit/references/heuristics/ui-testing.md +8 -7
  52. package/skills/jskit/references/material-3.md +111 -71
  53. package/skills/jskit/references/multi-homing.md +8 -2
  54. package/skills/jskit/references/pattern-index.md +37 -37
  55. package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +6 -2
  56. package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +10 -0
  57. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +6 -6
  58. package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +3 -0
  59. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +3 -2
  60. package/skills/jskit/references/patterns/app/minimal-foundation/example/vite.config.mjs +3 -1
  61. package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +4 -1
  62. package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +10 -0
  63. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +7 -7
  64. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  65. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
  66. package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +3 -0
  67. package/skills/jskit/references/patterns/app/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  68. package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +3 -2
  69. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home.vue +1 -1
  70. package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +1 -1
  71. package/skills/jskit/references/patterns/app/shell-foundation/example/vite.config.mjs +3 -1
  72. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  73. package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +18 -5
  74. package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue +17 -0
  75. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/login.vue +2 -2
  76. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue +2 -2
  77. package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue +2 -2
  78. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue +6 -1
  79. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/ResetPasswordView.vue +6 -1
  80. package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue +6 -1
  81. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  82. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  83. package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +1 -1
  84. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  85. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console.vue +1 -1
  86. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  87. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  88. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  89. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  90. package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -2
  91. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  92. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +11 -6
  93. package/skills/jskit/references/patterns/shell/application-shell/example/src/components/menus/SectionTabLinkItem.vue +42 -0
  94. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home.vue +1 -1
  95. package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +1 -1
  96. package/skills/jskit/references/patterns/ui/page-and-placement/PATTERN.md +6 -0
  97. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +9 -4
  98. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placementTopology.js +5 -3
  99. package/skills/jskit/references/patterns/users/account-settings/PATTERN.md +24 -3
  100. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +1 -1
  101. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -1
  102. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +3 -2
  103. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/[section].vue +23 -0
  104. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account.vue +4 -3
  105. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  106. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  107. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +3 -3
  108. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/root.vue +1 -1
  109. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/root.vue +1 -1
  110. package/skills/jskit/references/sanity-checks.md +180 -0
  111. package/skills/jskit/references/translation.md +4 -0
  112. package/skills/jskit/references/typed-features.md +158 -105
  113. 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,11 +75,17 @@ 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
@@ -90,14 +97,20 @@ Exactly this, and nothing more:
90
97
  `:to="paths.page('/suppliers')"` and `router.push(paths.page(...))` with
91
98
  `usePaths()`, the CRUD runtime's `resolveViewUrl()`, `resolveEditUrl()` and
92
99
  `resolveParams()`, and link placements (`surface` with `scopedSuffix` and
93
- `unscopedSuffix`; `to` only for a global route such as `/auth/login`). The
94
- 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.
95
105
  4. **A custom new or edit page finishes through the shell:**
96
106
  `useShellNavigation()` (`@jskit-ai/shell-web/client/navigation/useShellNavigation`)
97
107
  gives `finish(target)` for Save (the target replaces the form in the trail)
98
108
  and `finish()` for Cancel (back to where the form was opened, else its
99
- parent). A page whose record turns out deleted or forbidden calls
100
- `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.
101
114
  5. **A full page load goes through the shell too:** `reloadAppPage(path)`
102
115
  (after sign-in or a user switch, to rebuild client state) and
103
116
  `leaveApp(url)` (an external site in the same tab, such as a payment page),
@@ -113,11 +126,12 @@ Exactly this, and nothing more:
113
126
  | `nav/history` | `history.back()`, `history.go()`, `history.pushState()`/`replaceState()`, `router.back()`, `router.go()` |
114
127
  | `nav/window-location` | `window.location` assignments, `.href =`, `location.assign()`, `location.replace()` |
115
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 |
116
- | `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 |
117
- | `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) |
118
131
  | `nav/top-level-parent` | a main-navigation destination that declares a parent |
119
132
  | `nav/placement-data` | a main-navigation placement that isn't literal data (no page is judged for a missing parent until it is) |
120
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 |
121
135
  | `nav/allow` | any `m3-check: allow nav/...` comment: nothing excuses a navigation rule |
122
136
 
123
137
  ### Each rule, right and wrong
@@ -175,35 +189,43 @@ Exactly this, and nothing more:
175
189
  - **Type:** Vuetify 4 Material 3 roles (`text-headline-small`,
176
190
  `text-title-large`, `text-body-medium`, `text-label-large`). Never the
177
191
  Material 2 scale (`text-h5`, `text-body-2`) or a page-local type system.
178
- 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.
179
198
  - **Icons:** Material Symbols Rounded through Vuetify `$` aliases: Vuetify's
180
199
  standard ones (`$close`, `$menu`, `$delete`, `$prev`, `$next`) and JSKIT's
181
200
  camelCase names (`$add`, `$edit`, `$search`, `$moreVert`, `$filterList`,
182
201
  `$arrowBack`); `$homeFilled`-style `Filled` variants mark active navigation.
183
202
  An app adds an icon under `theme.icons` in `src/theme/index.js` as
184
203
  `{ name: "<24x24 path data>" }` from Material Symbols Rounded, then uses
185
- `$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.
186
208
 
187
209
  ## Exact sizes
188
210
 
189
- JSKIT ships these exact sizes; an app never re-implements or overrides them,
190
- and needs no Sass and no `!important` (Vuetify 4 keeps its CSS in cascade
191
- layers):
211
+ JSKIT ships these exact sizes; an app never re-implements or overrides them:
192
212
 
193
213
  - 40dp buttons with a 48dp touch target, icon buttons included; a smaller
194
214
  button is `size="small"` or `size="x-small"`, which keeps its smaller look
195
- and the 48dp target;
196
- - one-line button labels: a label longer than its button ends in an ellipsis.
197
- Keep labels short, and give a button whose label can run long, such as a
198
- name, the full label as its `title`;
199
- - 12dp cards: elevated (the default) at level 1, flat and tonal at level 0;
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;
200
222
  - 28dp dialogs and bottom-sheet top corners, square full-screen dialogs;
201
223
  - 8dp chips, and FABs of 56dp (small 40dp, large 96dp; extended 56dp tall)
202
224
  with 16dp corners;
203
225
  - emphasized easing, with reduced motion honoured.
204
226
 
205
227
  So never set `height` or `min-height` on a button, or a radius on a card,
206
- dialog, chip or FAB. A missing size is a JSKIT defect.
228
+ dialog, chip or FAB.
207
229
 
208
230
  ## The top app bar carries the page
209
231
 
@@ -211,35 +233,46 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
211
233
  `useShellPageTitle(title)` (`@jskit-ai/shell-web/client/composables/useShellPageTitle`)
212
234
  for a runtime title such as the record's name. The shell renders it as the
213
235
  app bar's only `h1`; pages add no `h1`, page header, title block or
214
- standalone heading. A section inside a page uses a title role. A page with
215
- no top app bar (a hero front page, an intro, an invite or hand-off page)
216
- has its own one `h1`.
217
- - **Up** sits in the bar on every inner page (see Navigation and Up). On phones
218
- 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.
219
242
  - **A layout that draws its own top app bar** (a workspace bar with the
220
243
  app's own controls, instead of `ShellLayout`) builds it from `ShellLayout`'s
221
244
  own pieces, from `@jskit-ai/shell-web/client`. It calls `useShellAppBar()`
222
245
  once, so its pages enter the trail and their `ShellPageActions` show in its
223
246
  bar, and renders `ShellUpButton` first, then `ShellPageTitle` (the page's one
224
- `h1`: `useShellPageTitle()`, else the route's `jskit.title`, else its `title`
225
- prop) and `ShellPageActionsTarget`. A title that is also a switcher, such as a
247
+ `h1`) and `ShellPageActionsTarget`. A title that is also a switcher, such as a
226
248
  project menu, goes in `ShellPageTitle`'s default slot (given `{ title }`) as
227
- the menu's activator, so it stays the one `h1`. `jskit check` counts it as a shell
228
- layout, so its inner pages need parents; an Up the app draws itself is a
229
- `nav/back-control` finding.
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.
230
251
  - **Actions:** a screen's actions go in the bar inside `ShellPageActions`
231
252
  (`@jskit-ai/shell-web/client/components/ShellPageActions`), each a
232
253
  `ShellPageAction` with `icon`, `label` (also its accessible name and compact
233
- tooltip), and `primary`, `to`, `replace`, `disabled`, `loading` or `color`.
234
- On compact widths every action shows only its icon except the single
235
- `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.
236
263
  - **Window size classes:** compact under 600 (navigation bar), medium 600–839
237
- (navigation rail), expanded 840–1199 and large from 1200 (drawer). A surface
238
- with no navigation destinations, such as an account surface, draws no
239
- navigation bar, rail or drawer; its pages keep the app bar. Read them
240
- from `useShellWindowSizeClass()`
264
+ (navigation rail), expanded 840–1199 and large from 1200 (drawer), read from
265
+ `useShellWindowSizeClass()`
241
266
  (`@jskit-ai/shell-web/client/composables/useShellWindowSizeClass`), never from
242
- 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.
243
276
 
244
277
  ## Colour
245
278
 
@@ -262,35 +295,41 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
262
295
  control; never a permanent panel of outlined fields. A card opens its record
263
296
  when tapped (a stretched title link); a table row opens on click. Row actions
264
297
  are trailing icon buttons (edit, a `$moreVert` overflow menu, a chevron to
265
- open), never "View" or "Edit" text buttons on every row. "New" is an extended
266
- FAB on compact and medium widths and a filled bar button on expanded. Empty
267
- 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.
268
303
  - **A list never scrolls sideways, at any width.** The list's own width (a
269
304
  container query, not the window) chooses cards or table. Cards flow into a
270
305
  grid; tables use a fixed layout with an ellipsis on long values, and fold
271
306
  low-priority columns into a stronger column's second line. Lists show figures
272
307
  ("120 kg", tabular and right-aligned) and names, never ids.
273
308
  - **Record:** a header (avatar, title, status chips) and value-first detail
274
- 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
275
311
  next workflow step is the one filled button, shown in context rather than a
276
312
  row of mostly disabled actions.
277
- - **Form:** Cancel and Save sit in the bar. Fields use `hideDetails: "auto"`.
278
- Nothing moves under a finger: a multi-select chip field keeps its input width
279
- when it loses focus. A pick-one-of-N choice is a segmented button with
280
- `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.
281
319
  - **Dialogs:** plain `v-dialog` and `v-bottom-sheet`; JSKIT gives them their
282
320
  Material 3 shapes. A full-screen dialog (compact and medium) puts close, title
283
321
  and its confirm action in a top bar, never at the bottom where a snackbar
284
322
  covers them.
285
323
  - **Menus and badges:** a menu's list has `role="menu"` and its items
286
324
  `role="menuitem"`, with `aria-disabled` on unavailable ones; a badge has a
287
- `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.
288
329
 
289
330
  ## Components, state, and feedback
290
331
 
291
- - Choose components by purpose. Prefer standard buttons, fields, lists,
292
- tables, sheets, dialogs, menus, navigation, progress, alerts, snackbars, and
293
- tooltips over custom imitations.
332
+ - Choose standard components by purpose over custom imitations.
294
333
  - Give one primary action emphasis. Distinguish destructive, secondary, and
295
334
  icon-only actions without relying on color; give icon controls accessible
296
335
  names and fields meaningful labels, errors, and autocomplete behavior.
@@ -307,6 +346,11 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
307
346
  field. A user-triggered command uses JSKIT's shared action
308
347
  feedback/snackbar path. Never insert a transient command-error alert above
309
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.
310
354
 
311
355
  ## Adaptive layout
312
356
 
@@ -328,16 +372,15 @@ npx jskit check m3 src/pages/home/books # a section
328
372
  npx jskit check m3 $(git diff --name-only --diff-filter=d main) # changed files
329
373
  ```
330
374
 
331
- Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code);
332
- fix every one, because nothing excuses them. `jskit check m3` lists those and
333
- the visual rules below. The visual rules are a report until every JSKIT app has
334
- been converted to them; the JSKIT release after that makes plain `jskit check`,
335
- 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:
336
379
 
337
380
  | Rule | Finds |
338
381
  |---|---|
339
- | `m3/duplicate-h1` | a second `h1`, or an in-page `h1` under the shell's page title |
340
- | `m3/raw-hex` | a hex colour outside `src/theme/` (`palettes.js` holds content colours) |
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 |
341
384
  | `m3/dark-scheme` | no dark scheme, or the theme pinned to light |
342
385
  | `m3/outlined-card` | `variant="outlined"` or `border` on a card or sheet |
343
386
  | `m3/mdi-icon` | `mdi-*` icon strings and `@mdi/js` imports |
@@ -390,9 +433,9 @@ What is left, such as a colour inside static SVG markup that can't import,
390
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 -->`
391
434
  (`// m3-check: ...` in scripts, `/* ... */` in CSS). **Never add one on your
392
435
  own:** ask the person first, naming the rule and the reason. A comment with no
393
- reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`), and any
394
- `m3-check: allow nav/...` (`nav/allow`), is itself a finding; nothing turns the
395
- 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
396
439
  exception with its file, line and reason, and their total.
397
440
 
398
441
  ## Material 3 audit
@@ -401,19 +444,16 @@ Run this behavior-preserving audit over every affected screen:
401
444
 
402
445
  1. Identify the owning JSKIT surface, shared screen, shell, placement, theme,
403
446
  and Vuetify components before judging local markup.
404
- 2. Check navigation and Up first, as must-fix: plain `jskit check` passes;
405
- every inner page has a parent and one Up arrow in the bar; main-menu
406
- destinations have none; no inner link is hand-built; nothing writes back
407
- logic.
447
+ 2. Check navigation and Up first, as must-fix: plain `jskit check` passes and
448
+ every rule in Navigation and Up holds.
408
449
  3. Find parallel components, duplicated state chrome, page-local themes, raw
409
450
  visual constants, CSS against Vuetify internals, nested generic cards,
410
- borders instead of tones, Material 2 typography, and desktop-only layout.
411
- 4. Check hierarchy, token use, component purpose, responsive presentation,
412
- interaction states, keyboard/focus behavior, labels, targets, contrast, and
413
- reduced motion against the rules above.
414
- 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
415
455
  repeated policy; avoid pass-through wrappers and speculative abstractions.
416
- 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
417
457
  tests and Playwright at compact, medium, and expanded widths in light and
418
458
  dark (`shellBackButton(page)` and `pressShellBack(page)` from
419
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