@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
@@ -47,7 +47,8 @@ app and never leaves it.
47
47
  - **What the person sees:** one Up arrow on every inner page, also when opened
48
48
  directly: icon only (`$arrowBack`), accessible name "Back" (localized), a
49
49
  tooltip on pointer devices. None on top-level destinations, and no other
50
- back control anywhere; the bar title says where the person is. A list-detail
50
+ back control anywhere; the bar title says where the person is. A new or edit
51
+ form shows a close (X) in the arrow's place, which cancels. A list-detail
51
52
  layout showing the parent beside the page needs no arrow for that step;
52
53
  collapsed to one pane, the record pane gets it back.
53
54
  - **Where Up goes:** to the previous trail entry while it is valid, so inside
@@ -79,11 +80,17 @@ Exactly this, and nothing more:
79
80
  </script>
80
81
  ```
81
82
 
82
- The same key works in a `<route lang="json">` block. A record names its
83
- list, an edit page its record, a sub-page its page. Tabs under a layout
83
+ The same key works in a `<route lang="json">` block. Tabs under a layout
84
84
  (`settings.vue` rendering `<RouterView>` for `settings/`) declare nothing:
85
85
  they inherit the nearest layout's declaration, and moving between them keeps
86
- the trail.
86
+ the trail and the scroll. A folder without a layout (`bookings/` with no
87
+ `bookings.vue`) only groups routes: its pages are different pages, each
88
+ opening at the top. The parent is read from the page up to its surface root, the
89
+ record that declares `jskit.surface`, and stops there. So the surface goes
90
+ only on the surface's layout page (`account.vue`: `ShellLayout` around
91
+ `<RouterView />`) and the parent on the page it renders
92
+ (`account/index.vue`). A package route declares its parent in its
93
+ `package.json` `meta`; under no layout, its component draws `ShellLayout`.
87
94
  2. **Each top-level destination is a main-navigation item** (a `kind: "link"`
88
95
  placement with `target: "shell.primary-nav"` or `"shell.secondary-nav"`) and
89
96
  declares no parent; nor do its tabs. A shell page with no parent is
@@ -95,14 +102,20 @@ Exactly this, and nothing more:
95
102
  `:to="paths.page('/suppliers')"` and `router.push(paths.page(...))` with
96
103
  `usePaths()`, the CRUD runtime's `resolveViewUrl()`, `resolveEditUrl()` and
97
104
  `resolveParams()`, and link placements (`surface` with `scopedSuffix` and
98
- `unscopedSuffix`; `to` only for a global route such as `/auth/login`). The
99
- trail follows the router, so there is no special link component.
105
+ `unscopedSuffix`; `to` only for a global route). A global route
106
+ (`scope: "global"`, on no surface) is
107
+ `paths.page("/auth/login", { scope: "global" })`; `explicitTo` only passes
108
+ through a finished target that arrives as data, never a hand-written path.
109
+ The trail follows the router, so there is no special link component.
100
110
  4. **A custom new or edit page finishes through the shell:**
101
111
  `useShellNavigation()` (`@jskit-ai/shell-web/client/navigation/useShellNavigation`)
102
112
  gives `finish(target)` for Save (the target replaces the form in the trail)
103
113
  and `finish()` for Cancel (back to where the form was opened, else its
104
- parent). A page whose record turns out deleted or forbidden calls
105
- `pageUnavailable()`. JSKIT's CRUD screens already do both.
114
+ parent); `useShellPageClose({ label, close })`
115
+ (`@jskit-ai/shell-web/client/composables/useShellPageClose`) makes the
116
+ arrow its Cancel, never an X beside it. A page whose
117
+ record turns out deleted or forbidden calls `pageUnavailable()`. JSKIT's
118
+ CRUD screens already do all three.
106
119
  5. **A full page load goes through the shell too:** `reloadAppPage(path)`
107
120
  (after sign-in or a user switch, to rebuild client state) and
108
121
  `leaveApp(url)` (an external site in the same tab, such as a payment page),
@@ -118,11 +131,12 @@ Exactly this, and nothing more:
118
131
  | `nav/history` | `history.back()`, `history.go()`, `history.pushState()`/`replaceState()`, `router.back()`, `router.go()` |
119
132
  | `nav/window-location` | `window.location` assignments, `.href =`, `location.assign()`, `location.replace()` |
120
133
  | `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 |
121
- | `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 |
122
- | `nav/missing-parent` | an inner page of a shell surface without a declared parent |
134
+ | `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 |
135
+ | `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) |
123
136
  | `nav/top-level-parent` | a main-navigation destination that declares a parent |
124
137
  | `nav/placement-data` | a main-navigation placement that isn't literal data (no page is judged for a missing parent until it is) |
125
138
  | `nav/missing-shell-layout` | a page that declares a parent with no shell layout rendering it, so its title, Up and actions never show |
139
+ | `nav/surface-parent` | a parent declared beside `jskit.surface`, which the shell never reads |
126
140
  | `nav/allow` | any `m3-check: allow nav/...` comment: nothing excuses a navigation rule |
127
141
 
128
142
  ### Each rule, right and wrong
@@ -180,35 +194,43 @@ Exactly this, and nothing more:
180
194
  - **Type:** Vuetify 4 Material 3 roles (`text-headline-small`,
181
195
  `text-title-large`, `text-body-medium`, `text-label-large`). Never the
182
196
  Material 2 scale (`text-h5`, `text-body-2`) or a page-local type system.
183
- Roboto Flex is the font.
197
+ Material 3 has two typefaces, both Roboto Flex unless the app names its own
198
+ in `src/theme/index.js`:
199
+ `typography: { brand: '"Fraunces", serif', plain: '"Inter", sans-serif' }`.
200
+ `brand` sets display, headline and title-large, `plain` the rest (title-medium
201
+ and -small, body, label); each is a CSS font-family value, and the app loads
202
+ the font. Never set `font-family` or `--v-font-body` in app CSS.
184
203
  - **Icons:** Material Symbols Rounded through Vuetify `$` aliases: Vuetify's
185
204
  standard ones (`$close`, `$menu`, `$delete`, `$prev`, `$next`) and JSKIT's
186
205
  camelCase names (`$add`, `$edit`, `$search`, `$moreVert`, `$filterList`,
187
206
  `$arrowBack`); `$homeFilled`-style `Filled` variants mark active navigation.
188
207
  An app adds an icon under `theme.icons` in `src/theme/index.js` as
189
208
  `{ name: "<24x24 path data>" }` from Material Symbols Rounded, then uses
190
- `$name`. No `mdi-*` strings and no `@mdi/js` imports in app code.
209
+ `$name`. No `mdi-*` strings and no `@mdi/js` imports in app code. Material
210
+ Symbols has no brand marks (Google, GitHub, a payment network): a brand's
211
+ logo is the brand's own artwork, a content image or SVG used as its owner
212
+ publishes it, not an icon alias.
191
213
 
192
214
  ## Exact sizes
193
215
 
194
- JSKIT ships these exact sizes; an app never re-implements or overrides them,
195
- and needs no Sass and no `!important` (Vuetify 4 keeps its CSS in cascade
196
- layers):
216
+ JSKIT ships these exact sizes; an app never re-implements or overrides them:
197
217
 
198
218
  - 40dp buttons with a 48dp touch target, icon buttons included; a smaller
199
219
  button is `size="small"` or `size="x-small"`, which keeps its smaller look
200
- and the 48dp target;
201
- - one-line button labels: a label longer than its button ends in an ellipsis.
202
- Keep labels short, and give a button whose label can run long, such as a
203
- name, the full label as its `title`;
204
- - 12dp cards: elevated (the default) at level 1, flat and tonal at level 0;
220
+ and the 48dp target. No icon button is under 32dp, and a target stops at
221
+ the edge of a button beside it, so each of a row's touching icon buttons
222
+ answers its own press;
223
+ - one-line button labels, ending in an ellipsis: keep labels short, and give
224
+ one that can run long, such as a name, its full label as `title`;
225
+ - 12dp cards: flat (the default) and tonal at level 0, `variant="elevated"`
226
+ at level 1; flat expansion panels;
205
227
  - 28dp dialogs and bottom-sheet top corners, square full-screen dialogs;
206
228
  - 8dp chips, and FABs of 56dp (small 40dp, large 96dp; extended 56dp tall)
207
229
  with 16dp corners;
208
230
  - emphasized easing, with reduced motion honoured.
209
231
 
210
232
  So never set `height` or `min-height` on a button, or a radius on a card,
211
- dialog, chip or FAB. A missing size is a JSKIT defect.
233
+ dialog, chip or FAB.
212
234
 
213
235
  ## The top app bar carries the page
214
236
 
@@ -216,35 +238,46 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
216
238
  `useShellPageTitle(title)` (`@jskit-ai/shell-web/client/composables/useShellPageTitle`)
217
239
  for a runtime title such as the record's name. The shell renders it as the
218
240
  app bar's only `h1`; pages add no `h1`, page header, title block or
219
- standalone heading. A section inside a page uses a title role. A page with
220
- no top app bar (a hero front page, an intro, an invite or hand-off page)
221
- has its own one `h1`.
222
- - **Up** sits in the bar on every inner page (see Navigation and Up). On phones
223
- it replaces the menu icon.
241
+ standalone heading. Route pages own the title: a section or a nested
242
+ component uses a title role (a component's claim gives the title back when
243
+ it goes). A page with no top app bar (a hero front page, an intro, an invite
244
+ or hand-off page) has its own one `h1`.
245
+ - **Up** leads the bar on every inner page; on phones it replaces the menu
246
+ icon.
224
247
  - **A layout that draws its own top app bar** (a workspace bar with the
225
248
  app's own controls, instead of `ShellLayout`) builds it from `ShellLayout`'s
226
249
  own pieces, from `@jskit-ai/shell-web/client`. It calls `useShellAppBar()`
227
250
  once, so its pages enter the trail and their `ShellPageActions` show in its
228
251
  bar, and renders `ShellUpButton` first, then `ShellPageTitle` (the page's one
229
- `h1`: `useShellPageTitle()`, else the route's `jskit.title`, else its `title`
230
- prop) and `ShellPageActionsTarget`. A title that is also a switcher, such as a
252
+ `h1`) and `ShellPageActionsTarget`. A title that is also a switcher, such as a
231
253
  project menu, goes in `ShellPageTitle`'s default slot (given `{ title }`) as
232
- the menu's activator, so it stays the one `h1`. `jskit check` counts it as a shell
233
- layout, so its inner pages need parents; an Up the app draws itself is a
234
- `nav/back-control` finding.
254
+ the menu's activator, so it stays the one `h1`. `jskit check` counts it as a
255
+ shell layout, so its inner pages need parents.
235
256
  - **Actions:** a screen's actions go in the bar inside `ShellPageActions`
236
257
  (`@jskit-ai/shell-web/client/components/ShellPageActions`), each a
237
258
  `ShellPageAction` with `icon`, `label` (also its accessible name and compact
238
- tooltip), and `primary`, `to`, `replace`, `disabled`, `loading` or `color`.
239
- On compact widths every action shows only its icon except the single
240
- `primary` one.
259
+ tooltip), and `primary`, `to`, `replace`, `href` (with `download` for a
260
+ file, such as a CSV export), `disabled`, `loading` or `color` (`error` for a
261
+ destructive one); attributes such as `target` reach its button. On compact
262
+ widths the bar keeps one trailing item after the title: the single
263
+ `primary` action when the page has one (a form's Save), otherwise its own
264
+ item nearest the corner (a top-right placement such as the account avatar).
265
+ Every other action and the bar's other items (a notification bell, tools)
266
+ go to its "More actions" menu, where `overflow` actions always are. Medium
267
+ and wider bars show them all.
241
268
  - **Window size classes:** compact under 600 (navigation bar), medium 600–839
242
- (navigation rail), expanded 840–1199 and large from 1200 (drawer). A surface
243
- with no navigation destinations, such as an account surface, draws no
244
- navigation bar, rail or drawer; its pages keep the app bar. Read them
245
- from `useShellWindowSizeClass()`
269
+ (navigation rail), expanded 840–1199 and large from 1200 (drawer), read from
270
+ `useShellWindowSizeClass()`
246
271
  (`@jskit-ai/shell-web/client/composables/useShellWindowSizeClass`), never from
247
- copied breakpoint numbers.
272
+ copied breakpoint numbers. A surface with fewer than two navigation
273
+ destinations (none, as on an account surface, or Home alone) draws no
274
+ navigation bar, rail or drawer; its pages keep the app bar.
275
+ - **Section tabs:** a page's own sections (`page.section-nav`) are Material 3
276
+ primary tabs: the topology names `ShellSectionTabLinkItem`'s token (the
277
+ starter registers it as `local.main.ui.section-tab-link-item`), and the page
278
+ draws one tab row, `display: flex; overflow-x: auto` over a 1dp
279
+ `outline-variant` bottom border. `ShellTabLinkItem` is the navigation bar's
280
+ destination; placed in a page it draws a section tab.
248
281
 
249
282
  ## Colour
250
283
 
@@ -267,35 +300,41 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
267
300
  control; never a permanent panel of outlined fields. A card opens its record
268
301
  when tapped (a stretched title link); a table row opens on click. Row actions
269
302
  are trailing icon buttons (edit, a `$moreVert` overflow menu, a chevron to
270
- open), never "View" or "Edit" text buttons on every row. "New" is an extended
271
- FAB on compact and medium widths and a filled bar button on expanded. Empty
272
- and error states carry an icon.
303
+ open), never "View" or "Edit" text buttons on every row. "New" is
304
+ `ShellPrimaryAction` (`label`, and `to` or a click handler): an extended FAB
305
+ on compact and medium widths, a filled bar button on expanded. On/off filters
306
+ are `aria-pressed` chips, or chips in a `v-chip-group`; JSKIT draws them as
307
+ filter chips. Empty and error states carry an icon.
273
308
  - **A list never scrolls sideways, at any width.** The list's own width (a
274
309
  container query, not the window) chooses cards or table. Cards flow into a
275
310
  grid; tables use a fixed layout with an ellipsis on long values, and fold
276
311
  low-priority columns into a stronger column's second line. Lists show figures
277
312
  ("120 kg", tabular and right-aligned) and names, never ids.
278
313
  - **Record:** a header (avatar, title, status chips) and value-first detail
279
- lists, never a grid of label/value text. Edit sits in the bar; the record's
314
+ lists, never a grid of label/value text. Edit sits in the bar, its secondary
315
+ actions in the bar's overflow menu, never in the content; the record's
280
316
  next workflow step is the one filled button, shown in context rather than a
281
317
  row of mostly disabled actions.
282
- - **Form:** Cancel and Save sit in the bar. Fields use `hideDetails: "auto"`.
283
- Nothing moves under a finger: a multi-select chip field keeps its input width
284
- when it loses focus. A pick-one-of-N choice is a segmented button with
285
- `role="radiogroup"` and `role="radio"`/`aria-checked` buttons.
318
+ - **Form:** the bar's close (X) cancels and Save is its filled action. Fields
319
+ use `hideDetails: "auto"`. Nothing moves under a finger: a multi-select chip
320
+ field keeps its input width when it loses focus. A pick-one-of-N choice is a
321
+ segmented button (`v-btn-toggle`, drawn by JSKIT, with 12dp segment sides as
322
+ in any `v-btn-group`) with `role="radiogroup"` and
323
+ `role="radio"`/`aria-checked` buttons.
286
324
  - **Dialogs:** plain `v-dialog` and `v-bottom-sheet`; JSKIT gives them their
287
325
  Material 3 shapes. A full-screen dialog (compact and medium) puts close, title
288
326
  and its confirm action in a top bar, never at the bottom where a snackbar
289
327
  covers them.
290
328
  - **Menus and badges:** a menu's list has `role="menu"` and its items
291
329
  `role="menuitem"`, with `aria-disabled` on unavailable ones; a badge has a
292
- `label` such as "3 batches".
330
+ `label` such as "3 batches". JSKIT draws a menu's list and a dialog's card;
331
+ an app sets no `VMenu` or `VDialog` defaults, under which
332
+ Vuetify 4.2.1 to 4.3.2 loses a `v-select`'s typeahead match, or a dialog's
333
+ virtual list, when an ancestor re-renders.
293
334
 
294
335
  ## Components, state, and feedback
295
336
 
296
- - Choose components by purpose. Prefer standard buttons, fields, lists,
297
- tables, sheets, dialogs, menus, navigation, progress, alerts, snackbars, and
298
- tooltips over custom imitations.
337
+ - Choose standard components by purpose over custom imitations.
299
338
  - Give one primary action emphasis. Distinguish destructive, secondary, and
300
339
  icon-only actions without relying on color; give icon controls accessible
301
340
  names and fields meaningful labels, errors, and autocomplete behavior.
@@ -312,6 +351,11 @@ dialog, chip or FAB. A missing size is a JSKIT defect.
312
351
  field. A user-triggered command uses JSKIT's shared action
313
352
  feedback/snackbar path. Never insert a transient command-error alert above
314
353
  page content where it shifts the working layout.
354
+ - A snackbar never covers the FAB or the actions it reports on. `ShellErrorHost`
355
+ centres it above the navigation bar on compact windows and starts it at the
356
+ content's start edge, at most half its width, on wider ones. A FAB takes
357
+ layout room (`<v-fab app layout>`, as `ShellPrimaryAction`'s does), so the
358
+ snackbar shows above it.
315
359
 
316
360
  ## Adaptive layout
317
361
 
@@ -333,16 +377,15 @@ npx jskit check m3 src/pages/home/books # a section
333
377
  npx jskit check m3 $(git diff --name-only --diff-filter=d main) # changed files
334
378
  ```
335
379
 
336
- Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code);
337
- fix every one, because nothing excuses them. `jskit check m3` lists those and
338
- the visual rules below. The visual rules are a report until every JSKIT app has
339
- been converted to them; the JSKIT release after that makes plain `jskit check`,
340
- and so every app's verify, fail on them too:
380
+ Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code).
381
+ `jskit check m3` lists those and the visual rules below. The visual rules are a
382
+ report until every JSKIT app has been converted to them; the JSKIT release after
383
+ that makes plain `jskit check`, and so every app's verify, fail on them too:
341
384
 
342
385
  | Rule | Finds |
343
386
  |---|---|
344
- | `m3/duplicate-h1` | a second `h1`, or an in-page `h1` under the shell's page title |
345
- | `m3/raw-hex` | a hex colour outside `src/theme/` (`palettes.js` holds content colours) |
387
+ | `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 |
388
+ | `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 |
346
389
  | `m3/dark-scheme` | no dark scheme, or the theme pinned to light |
347
390
  | `m3/outlined-card` | `variant="outlined"` or `border` on a card or sheet |
348
391
  | `m3/mdi-icon` | `mdi-*` icon strings and `@mdi/js` imports |
@@ -395,9 +438,9 @@ What is left, such as a colour inside static SVG markup that can't import,
395
438
  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 -->`
396
439
  (`// m3-check: ...` in scripts, `/* ... */` in CSS). **Never add one on your
397
440
  own:** ask the person first, naming the rule and the reason. A comment with no
398
- reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`), and any
399
- `m3-check: allow nav/...` (`nav/allow`), is itself a finding; nothing turns the
400
- check off for a file, a folder or the app. `jskit check m3` lists every
441
+ reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`) is itself a
442
+ finding, as any `nav/allow` is; nothing turns the check off for a file, a
443
+ folder or the app. `jskit check m3` lists every
401
444
  exception with its file, line and reason, and their total.
402
445
 
403
446
  ## Material 3 audit
@@ -406,19 +449,16 @@ Run this behavior-preserving audit over every affected screen:
406
449
 
407
450
  1. Identify the owning JSKIT surface, shared screen, shell, placement, theme,
408
451
  and Vuetify components before judging local markup.
409
- 2. Check navigation and Up first, as must-fix: plain `jskit check` passes;
410
- every inner page has a parent and one Up arrow in the bar; main-menu
411
- destinations have none; no inner link is hand-built; nothing writes back
412
- logic.
452
+ 2. Check navigation and Up first, as must-fix: plain `jskit check` passes and
453
+ every rule in Navigation and Up holds.
413
454
  3. Find parallel components, duplicated state chrome, page-local themes, raw
414
455
  visual constants, CSS against Vuetify internals, nested generic cards,
415
- borders instead of tones, Material 2 typography, and desktop-only layout.
416
- 4. Check hierarchy, token use, component purpose, responsive presentation,
417
- interaction states, keyboard/focus behavior, labels, targets, contrast, and
418
- reduced motion against the rules above.
419
- 5. Correct issues at the narrowest established owner. Consolidate only truly
456
+ borders instead of tones, Material 2 typography, and desktop-only layout;
457
+ check states, focus, labels, targets, contrast and reduced motion against
458
+ the rules above.
459
+ 4. Correct issues at the narrowest established owner. Consolidate only truly
420
460
  repeated policy; avoid pass-through wrappers and speculative abstractions.
421
- 6. Run `jskit check` and `jskit check m3` on the affected files, then focused
461
+ 5. Run `jskit check` and `jskit check m3` on the affected files, then focused
422
462
  tests and Playwright at compact, medium, and expanded widths in light and
423
463
  dark (`shellBackButton(page)` and `pressShellBack(page)` from
424
464
  `@jskit-ai/shell-web/test/shellNavigation` drive Up).
@@ -0,0 +1,180 @@
1
+ <!-- Generated by `npm run agent-docs:build` from `packages/agent-docs/site/guide/framework/sanity-checks.md`. Do not edit manually. -->
2
+
3
+ # Sanity checks
4
+
5
+ Before calling a list, a save, a test, a refactor, a screen or a deployment
6
+ done, measure what it actually costs and does, judge whether that is sane, and
7
+ fix the cause when it is not. This page is the JSKIT part of that: the
8
+ pitfalls JSKIT's own packages cause, then which JSKIT call, helper or setting
9
+ applies each check, with one line on what the check catches and why.
10
+
11
+ ## JSKIT's own pitfalls
12
+
13
+ ### Theme and component defaults
14
+
15
+ **Check:** after changing a theme, a Vuetify default, a material-web default
16
+ or a shared component, use what it affects in a production build: open a
17
+ select and type, press a segmented button at desktop width, tap neighbouring
18
+ icon buttons, and leave a field to see whether the layout shifts under the
19
+ next tap. A default reaches every component that renders the one it names, so
20
+ a change made for one screen can break others that never import it
21
+ ([Material 3](/guide/framework/material-3)).
22
+
23
+ **Examples:**
24
+
25
+ - A `VMenu: { VSheet: {} }` default broke a select's typeahead.
26
+ - A common button padding squeezed segmented buttons into slivers.
27
+ - A field that grew 32 px on blur moved the button under a person's tap.
28
+
29
+ ### Navigation metadata
30
+
31
+ **Check:** open each inner page directly and by link, and see its Up arrow
32
+ and title. Some navigation declarations look right but switch Up off, and
33
+ some shapes leave pages the navigation rules cannot judge
34
+ ([Material 3](/guide/framework/material-3)):
35
+
36
+ - a parent declared beside `jskit.surface` is never read; the surface goes on
37
+ the layout page and the parent on the page it renders (`nav/surface-parent`
38
+ reports the two on one record);
39
+ - an inner page repeating its surface becomes a root of its own, with no Up;
40
+ - until a main-navigation placement is literal data, no page is judged for a
41
+ missing parent (`nav/placement-data`);
42
+ - a layout that also renders its pages without `ShellLayout` leaves those
43
+ pages unjudged by `nav/missing-parent`, so a missing parent passes;
44
+ - a package route is judged only when its client entry lists
45
+ `routeComponents` literally; a built map passes unjudged.
46
+
47
+ **Example:** a design-system update moved a page title into a top app bar
48
+ that one page did not have, so its title vanished; only the browser suite
49
+ caught it.
50
+
51
+ ### Generated action and tool contracts
52
+
53
+ **Check:** call each assistant tool the way the model will, with sparse
54
+ `fields` and an `include`, and compare the result with the output contract it
55
+ advertises. An action is a tool only when it has an input contract and a
56
+ truthful model-facing output; the assistant validates the result against
57
+ `extensions.assistant.output` and rejects unexpected fields, so a contract
58
+ that promises what execution cannot return fails at run time, not at review
59
+ ([Assistant](/guide/app-extras/assistant#database-surface-action-tools)).
60
+
61
+ **Example:** sparse primary fields stay sparse, and an included record is read
62
+ from its lookup container (`items[0].lookups.pet.name`); a contract that
63
+ expects the relationship id or an unselected field never gets it.
64
+
65
+ ## Applying each check
66
+
67
+ ### Every row
68
+
69
+ - An unpaged JSON:API query returns the first 20 rows, and a page above the
70
+ resource's `queryMaxLimit` (100 unless it sets one) is cut to that size,
71
+ both without an error; CRUD `queryDocuments()` refuses such a `limit`. A
72
+ read its callers treat as complete uses `queryAllJsonRestCollectionRows()`
73
+ or the repository's `queryEveryDocument()`, because the missing rows
74
+ otherwise vanish from every picker and delivery built on it
75
+ ([Every row](/guide/framework/typed-features#every-row)).
76
+ - A lookup by a unique key matches at most one row, so it may read `rows[0]`
77
+ of a plain query.
78
+
79
+ ### Query counts and batches
80
+
81
+ - Count a path's queries with the test Knex instance's `query` event at a few
82
+ rows and at a realistic number; the count should follow tables and pages,
83
+ not rows ([Query counts at realistic
84
+ sizes](/guide/framework/typed-features#query-counts-at-realistic-sizes)).
85
+ - A JSON:API read adds a few lookup and visibility queries per page and per
86
+ relationship field. That follows pages, so it is expected.
87
+ - `getDocumentById()`, `queryDocuments()` or a service `get` inside a loop is
88
+ the usual per-row read; batch it with an id-list `searchSchema` filter or
89
+ `include`, grouped with `Map.groupBy()` ([Lists read related rows in
90
+ batches](/guide/framework/typed-features#lists-read-related-rows-in-batches)).
91
+ - A `rowPolicy` adds one correlated predicate to the query it is given, so
92
+ its cost follows the rows read, not the subject's history. It runs on every
93
+ single-record read, including a write's default `returning: "full"` read
94
+ back, so a write that discards its result passes `returning: "minimal"` and
95
+ a batch of creates uses `createDocuments()`, in the owner's transaction
96
+ ([Bounded access
97
+ checks](/guide/framework/typed-features#bounded-access-checks)).
98
+
99
+ ### Tests
100
+
101
+ - `jskit test` runs test files in parallel within the machine's test budget
102
+ (`V64_TEST_CONCURRENCY`, else half the CPUs), and Playwright's `workers` and
103
+ Vitest's `maxWorkers` come from `testConcurrency()`. `jskit check` reports a
104
+ script or config that runs files one at a time; a file that must run alone
105
+ says why on its first line, `// jskit-test: alone - <reason>` ([Test files
106
+ run in parallel](/guide/framework/typed-features#test-files-run-in-parallel)).
107
+ - Tests use `TEST_DB_NAME` and `BROWSER_TEST_DB_NAME`, both different from
108
+ `DB_NAME`, because tests write and delete rows and would destroy the
109
+ developer's data. A skip for want of a test database is a failure.
110
+ - Test databases restore a stored snapshot checked by a digest of the
111
+ migration and seed sources, and the harness reports each preparation phase's
112
+ duration, so slow preparation shows ([Test databases are prepared
113
+ once](/guide/framework/typed-features#test-databases-are-prepared-once),
114
+ [integration test
115
+ databases](/guide/app-setup/database-layer#integration-test-databases)).
116
+
117
+ ### Inputs and access
118
+
119
+ - An action input declared `mode: "patch"` makes every field optional. Use it
120
+ only for an action that updates part of a record, and send that action
121
+ nothing to see what it does, because a missing field is then never refused.
122
+ - A route that repetition can abuse declares `rateLimit`, which the kernel
123
+ refuses with 429 and `kernel.http.rateLimited`; auth's limits come from
124
+ `config.auth.rateLimits` ([Sign-in
125
+ protection](/guide/app-setup/authentication#sign-in-protection)). Each
126
+ limit needs a test expecting the 429, or a dropped one goes unseen.
127
+ - Who acts comes from `resolveActor(context)` and the workspace from the
128
+ context's resolvers ([One reader per
129
+ fact](/guide/framework/typed-features#one-reader-per-fact)); send another
130
+ workspace's id in the body and see the context win.
131
+
132
+ ### Refactors
133
+
134
+ Before changing a JSKIT feature, list where its behaviour lives, with the test
135
+ that covers each item, and walk the list again after: routes, actions and
136
+ their input schemas, row policies, CRUD fields, lookups and hooks, placements
137
+ and navigation meta, messages and their codes, events, jobs and migrations.
138
+ These are the places a consolidation drops behaviour without a failing build.
139
+
140
+ ### Changes others depend on
141
+
142
+ - After updating JSKIT packages, read [Upgrade
143
+ JSKIT](/guide/app-setup/upgrading-jskit) and each package changelog's
144
+ "Upgrading" notes, then boot, sign in and run the browser tests before
145
+ anything else, because a changed default shows only at run time.
146
+
147
+ ### Screens
148
+
149
+ - A long list renders what is visible, with pages or `v-virtual-scroll`,
150
+ because every row rendered costs DOM nodes and main-thread time.
151
+ - Command feedback goes through the shell's snackbar path: `ShellErrorHost`
152
+ keeps it clear of the FAB and the actions it reports on, and a full-screen
153
+ dialog puts its confirm action in the top bar, where a snackbar never covers
154
+ it ([Material 3](/guide/framework/material-3)).
155
+ - Up is the shell's, from `meta.jskit.parent`; the navigation rules forbid
156
+ `router.back()` and hand-made back controls, so Up lands inside the app
157
+ even on a page opened from a link.
158
+
159
+ ### Production settings
160
+
161
+ - `NODE_ENV=production` is JSKIT's only production switch. Without it a
162
+ deployed server runs as development: no first-account setup code, and
163
+ development-only settings accepted.
164
+ - On an empty production database, local auth asks for the first account's
165
+ setup code, so whoever reaches a new deployment first cannot own it ([The
166
+ first account in
167
+ production](/guide/app-setup/authentication#the-first-account-in-production-local-auth)).
168
+ Give any registration of your own the same protection.
169
+ - `config.securityHeaders` sends the security headers, with the content
170
+ security policy reported, not enforced, by default; read them on a
171
+ production build ([Security
172
+ headers](/guide/app-setup/initial-scaffolding#security-headers)).
173
+ - Email confirmation needs `auth.local.confirmation-sender` in production, or
174
+ the server refuses to start while anyone may register ([Email
175
+ confirmation](/guide/app-setup/authentication#email-confirmation-local-auth)).
176
+ - Changing the assistant's instructions needs `assistant.settings.update` or
177
+ the surface's own permission ([Who may change the
178
+ instructions](/guide/app-extras/assistant#who-may-change-the-instructions)).
179
+ Its tools run as the typing user, through the `automation` channel, so an
180
+ action kept off that channel is never a tool.
@@ -193,6 +193,10 @@ and params to `details.fieldErrorCodes`, so an untranslated app shows exactly
193
193
  the English it showed before. The failed check's own params (for example
194
194
  `limit`) are sent too, and the coded entry's `params` win over them. JSKIT's
195
195
  own schemas use this form, which is why their field messages translate.
196
+ With json-rest-schema 1.0.18 or later, which checks them, an array field's
197
+ `minItems` and `maxItems` failures send `kernel.validation.field.minItems` and
198
+ `kernel.validation.field.maxItems` with `{ min | max, actual }`, and its
199
+ `messages.minItems` and `messages.maxItems` override them.
196
200
 
197
201
  ## Placement labels
198
202