@jskit-ai/agent-docs 0.1.274 → 0.1.275

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 (104) hide show
  1. package/guide/agent/app-extras/assistant-conversation.md +7 -1
  2. package/guide/agent/app-extras/assistant-voice.md +5 -1
  3. package/guide/agent/app-setup/material-3-migration.md +180 -0
  4. package/guide/agent/app-setup/upgrading-jskit.md +5 -0
  5. package/guide/agent/framework/material-3.md +316 -73
  6. package/guide/agent/framework/ui-operations.md +18 -35
  7. package/guide/agent/framework/ui-pass.md +155 -0
  8. package/guide/agent/index.md +3 -0
  9. package/package.json +1 -1
  10. package/patterns/feature-package/example/booking-engine/package.json +1 -1
  11. package/patterns/minimal-foundation/example/package.json +5 -4
  12. package/patterns/minimal-foundation/example/src/main.js +5 -11
  13. package/patterns/minimal-foundation/example/src/pages/home/index.vue +5 -18
  14. package/patterns/minimal-foundation/example/src/theme/index.js +9 -0
  15. package/patterns/minimal-foundation/example/src/views/NotFound.vue +5 -4
  16. package/patterns/shell-foundation/example/package.json +6 -6
  17. package/patterns/shell-foundation/example/packages/main/package.json +1 -1
  18. package/patterns/shell-foundation/example/src/main.js +5 -11
  19. package/patterns/shell-foundation/example/src/pages/home/index.vue +21 -57
  20. package/patterns/shell-foundation/example/src/pages/home/settings/general/index.vue +24 -19
  21. package/patterns/shell-foundation/example/src/pages/home/settings.vue +27 -84
  22. package/patterns/shell-foundation/example/src/theme/index.js +9 -0
  23. package/patterns/shell-foundation/example/src/views/NotFound.vue +9 -7
  24. package/reference/autogen/PATTERN_INDEX.md +37 -37
  25. package/reference/autogen/README.md +1 -0
  26. package/reference/autogen/packages/agent-docs.md +8 -4
  27. package/reference/autogen/packages/assistant-core.md +1 -0
  28. package/reference/autogen/packages/console-web.md +5 -0
  29. package/reference/autogen/packages/http-web.md +29 -2
  30. package/reference/autogen/packages/kernel.md +2 -1
  31. package/reference/autogen/packages/material-web.md +54 -0
  32. package/reference/autogen/packages/shell-web.md +121 -9
  33. package/reference/autogen/tooling/jskit-catalog.md +81 -0
  34. package/reference/autogen/tooling/testUtils.md +7 -0
  35. package/skills/jskit/SKILL.md +11 -6
  36. package/skills/jskit/references/heuristics/INDEX.md +1 -1
  37. package/skills/jskit/references/heuristics/ui-contract.md +10 -7
  38. package/skills/jskit/references/heuristics/ui-testing.md +5 -3
  39. package/skills/jskit/references/material-3.md +315 -72
  40. package/skills/jskit/references/pattern-index.md +37 -37
  41. package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +5 -4
  42. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +5 -11
  43. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/pages/home/index.vue +5 -18
  44. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/theme/index.js +9 -0
  45. package/skills/jskit/references/patterns/app/minimal-foundation/example/src/views/NotFound.vue +5 -4
  46. package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +6 -6
  47. package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
  48. package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +5 -11
  49. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home/index.vue +21 -57
  50. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home/settings/general/index.vue +24 -19
  51. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home/settings.vue +27 -84
  52. package/skills/jskit/references/patterns/app/shell-foundation/example/src/theme/index.js +9 -0
  53. package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +9 -7
  54. package/skills/jskit/references/patterns/assistant/assistant-surface/example/src/placement.js +1 -1
  55. package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
  56. package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
  57. package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
  58. package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
  59. package/skills/jskit/references/patterns/console/console-surface/PATTERN.md +4 -2
  60. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console/index.vue +10 -40
  61. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console/settings/index.vue +3 -11
  62. package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console/settings.vue +24 -72
  63. package/skills/jskit/references/patterns/console/console-surface/example/src/placement.js +28 -0
  64. package/skills/jskit/references/patterns/crud/crud-screen-set/PATTERN.md +13 -0
  65. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookEditPage.vue +2 -0
  66. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookFormFields.vue +0 -6
  67. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookListPage.vue +5 -2
  68. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookNewPage.vue +2 -0
  69. package/skills/jskit/references/patterns/crud/crud-screen-set/example/books/BookViewPage.vue +5 -9
  70. package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
  71. package/skills/jskit/references/patterns/database/mysql-application/example/package.json +1 -1
  72. package/skills/jskit/references/patterns/database/postgres-application/example/package.json +1 -1
  73. package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
  74. package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
  75. package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +3 -1
  76. package/skills/jskit/references/patterns/shell/application-shell/example/expected-existing/src/pages/home/index.vue +5 -18
  77. package/skills/jskit/references/patterns/shell/application-shell/example/expected-existing/src/views/NotFound.vue +14 -0
  78. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home/index.vue +21 -57
  79. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home/settings/general/index.vue +24 -19
  80. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home/settings.vue +27 -84
  81. package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +15 -0
  82. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/components/SyncStatusElement.vue +2 -2
  83. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports/activity.vue +1 -1
  84. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports/overview.vue +1 -1
  85. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +6 -3
  86. package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placement.js +1 -1
  87. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +2 -8
  88. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -8
  89. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +5 -11
  90. package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsSignInMethodsSection.vue +3 -9
  91. package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/index.vue +7 -0
  92. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
  93. package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
  94. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +1 -2
  95. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/components/WorkspaceNotFoundCard.vue +5 -6
  96. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/admin/workspace/settings/index.vue +5 -12
  97. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/pages/admin/workspace/settings.vue +24 -72
  98. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/index.vue +3 -4
  99. package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/index.vue +3 -3
  100. package/skills/jskit/references/ui-operations.md +18 -35
  101. package/skills/jskit/references/ui-pass.md +150 -0
  102. package/patterns/shell-foundation/example/src/pages/home/settings/index.vue +0 -7
  103. package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home/settings/index.vue +0 -7
  104. package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home/settings/index.vue +0 -7
@@ -2,51 +2,251 @@
2
2
 
3
3
  # Material 3
4
4
 
5
- Read completely before any JSKIT Vue/Vuetify UI task.
5
+ Read completely before any JSKIT Vue/Vuetify UI task. For a UI pass over a
6
+ task's changes, a section or the whole app, also read [UI pass](ui-pass.md).
6
7
 
7
8
  ## Authority
8
9
 
9
- JSKIT uses Vue and the installed Vuetify 4 release. Material 3 is the UI
10
- contract; packages, shared screens, placements, shell components, and
11
- app-owned Vuetify configuration are its implementation seams.
10
+ Material 3 (m3.material.io) is the UI contract, and it is exact: "close to
11
+ Material 3" is a failure. A new JSKIT app is exact Material 3 in light and dark,
12
+ on phone, tablet and desktop, with no app CSS. An app chooses a seed colour and
13
+ writes its own screens. If an app has to fight JSKIT to look right, that is a
14
+ JSKIT defect: report the exact JSKIT change, never patch it with app CSS,
15
+ `!important` or a copied package file.
16
+
17
+ JSKIT uses Vue and the installed Vuetify 4 release with its `md3` blueprint.
12
18
  Use the installed public APIs rather than adding `@material/web`, another UI
13
- framework, or a parallel theme/component system.
14
-
15
- The request and Blueprint own brand, content, routes, and behavior. Material 3
16
- does not authorize a redesign.
17
- An established JSKIT prop or component wins over generic Material advice.
18
-
19
- ## Implement through owned seams
20
-
21
- Use this order:
22
-
23
- 1. Reuse the JSKIT package, source pattern, shared screen, shell, placement, or
24
- high-level composable that owns the behavior.
25
- 2. Use the matching Vuetify component and its public props, variants, slots,
26
- density, responsive, theme, and accessibility behavior.
27
- 3. Adapt app-owned wrappers or the central `createVuetify(...)` configuration.
28
- 4. Add scoped CSS only for product layout or a documented gap. Never style
29
- Vuetify internals or duplicate a component system in CSS.
30
-
31
- Do not replace a shared screen to restyle it. Use its public seams.
32
-
33
- ## Theme and visual hierarchy
34
-
35
- - Define palettes and theme selection once through the established Vuetify
36
- bootstrap. Preserve promised light, dark, or system behavior.
37
- - Prefer semantic roles such as primary, secondary, surface, surface variant,
38
- on-surface, outline, success, warning, and error. Use Vuetify theme props,
39
- classes, and `--v-theme-*` variables instead of repeated visual literals.
40
- - Keep contrast and meaning in every supported theme. Never communicate
41
- status, selection, validation, or permission through color alone.
42
- - Use Vuetify 4 Material 3 display, headline, title, body, and label roles. Do
43
- not recreate the old Material 2 type scale or a page-local typography system.
44
- - Use Vuetify spacing utilities and established JSKIT dimensions before raw
45
- values. Preserve JSKIT shell widths, target sizes, and spacing props.
46
- - Use elevation `0` through `5`, shape props, and component variants instead of
47
- arbitrary shadows and radii. Never add page headers or standalone headings
48
- unless explicitly requested. Use a direct `v-sheet` work region when needed;
49
- reserve cards and dialogs for genuinely grouped or elevated objects.
19
+ framework, or a parallel theme/component system. The request and Blueprint own
20
+ brand, content, routes, and behavior; Material 3 never authorizes a behavior
21
+ change. An established JSKIT prop or component wins over generic Material advice.
22
+
23
+ ## Navigation and Up
24
+
25
+ **MUST, in every JSKIT app (#241). The shell owns the Up arrow and the trail.
26
+ An app declares each inner page's parent, links through JSKIT navigation, and
27
+ writes no back logic of any kind. Plain `jskit check` fails on every rule in
28
+ this section, and nothing excuses one. Never improvise a Back.**
29
+
30
+ ### The model
31
+
32
+ **Back** is chronological and can leave the app; the browser provides it (its
33
+ button, gestures, Alt+Left), and the app never draws or imitates it. **Up** is
34
+ the app's own arrow, the leading icon of the top app bar; it moves within the
35
+ app and never leaves it.
36
+
37
+ - **Words:** a *top-level destination* is a main-navigation item (bar, rail or
38
+ drawer): Contacts, Suppliers, Settings. An *inner page* is any other page: a
39
+ record, sub-page, new or edit form, or a wizard step with its own URL. Its
40
+ *parent* is the page it belongs under (supplier → Suppliers, edit supplier →
41
+ that supplier). The *trail* is the in-app path taken in this browser tab.
42
+ - **What the person sees:** one Up arrow on every inner page, also when opened
43
+ directly: icon only (`$arrowBack`), accessible name "Back" (localized), a
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
46
+ layout showing the parent beside the page needs no arrow for that step;
47
+ collapsed to one pane, the record pane gets it back.
48
+ - **Where Up goes:** to the previous trail entry while it is valid, so inside
49
+ the app Up works like Back (drill-down, sideways, chains unwinding one step
50
+ at a time); otherwise to the declared parent (shared link, bookmark, new
51
+ tab). An entry whose record was deleted, whose permission was lost, or whose
52
+ surface, workspace or route is gone sends Up to the parent, never an error
53
+ page. Up never leaves the app or opens sign-in.
54
+ - **The trail:** one per tab; a refresh keeps it, a new tab or shared link
55
+ starts empty. Navigating to another page pushes an entry; Save and Cancel on
56
+ a form, redirects, and same-page changes (filters, search, sort, paging, an
57
+ in-page tab, a dialog) replace it. A top-level destination starts a fresh
58
+ trail. Going back restores filters, sort, page, scroll and focus. The
59
+ browser's Back and Forward stay in sync with it; external links never enter.
60
+
61
+ ### What an app declares
62
+
63
+ Exactly this, and nothing more:
64
+
65
+ 1. **Each inner page declares its parent once,** as route meta `jskit.parent`:
66
+ a static path on the page's own surface, resolved like `usePaths().page()`,
67
+ whose `:param` segments take the current route's values. Another surface's
68
+ page is an object, `{ surface: "home", path: "/" }`. No functions, no route
69
+ names.
70
+
71
+ ```vue
72
+ <script setup>
73
+ definePage({ meta: { jskit: { parent: "/suppliers/:supplierId" } } });
74
+ </script>
75
+ ```
76
+
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
79
+ (`settings.vue` rendering `<RouterView>` for `settings/`) declare nothing:
80
+ they inherit the nearest layout's declaration, and moving between them keeps
81
+ the trail.
82
+ 2. **Each top-level destination is a main-navigation item** (a `kind: "link"`
83
+ placement with `target: "shell.primary-nav"` or `"shell.secondary-nav"`) and
84
+ declares no parent; nor do its tabs. A shell page with no parent is
85
+ top-level: no arrow, a fresh trail.
86
+ 3. **Every in-app link and navigation goes through JSKIT navigation:**
87
+ `:to="paths.page('/suppliers')"` and `router.push(paths.page(...))` with
88
+ `usePaths()`, the CRUD runtime's `resolveViewUrl()`, `resolveEditUrl()` and
89
+ `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.
92
+ 4. **A custom new or edit page finishes through the shell:**
93
+ `useShellNavigation()` (`@jskit-ai/shell-web/client/navigation/useShellNavigation`)
94
+ gives `finish(target)` for Save (the target replaces the form in the trail)
95
+ 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.
98
+ 5. **A full page load goes through the shell too:** `reloadAppPage(path)`
99
+ (after sign-in or a user switch, to rebuild client state) and
100
+ `leaveApp(url)` (an external site in the same tab, such as a payment page),
101
+ from `@jskit-ai/shell-web/client/navigation/documentNavigation`.
102
+
103
+ ### Forbidden in app code
104
+
105
+ | Rule | Forbidden |
106
+ |---|---|
107
+ | `nav/history` | `history.back()`, `history.go()`, `history.pushState()`/`replaceState()`, `router.back()`, `router.go()` |
108
+ | `nav/window-location` | `window.location` assignments, `.href =`, `location.assign()`, `location.replace()` |
109
+ | `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: "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 |
112
+ | `nav/top-level-parent` | a main-navigation destination that declares a parent |
113
+ | `nav/allow` | any `m3-check: allow nav/...` comment: nothing excuses a navigation rule |
114
+
115
+ ### Each rule, right and wrong
116
+
117
+ - **Up follows the trail.** Right: contact → (link) supplier → Up shows **the
118
+ contact**; contact → supplier → product → Up = supplier, Up = contact, Up =
119
+ Contacts. Wrong: the supplier's Up opens the Suppliers list after the person
120
+ came from a contact.
121
+ - **Direct entry goes to the parent.** Right: a supplier opened from a pasted
122
+ link → Up = Suppliers list. Wrong: no arrow, a dead arrow, or an arrow that
123
+ leaves the app.
124
+ - **One arrow, on inner pages only.** Right: the Suppliers list (a drawer item)
125
+ has no arrow; a supplier has one, in the bar. Wrong: an arrow on the
126
+ Suppliers list, a second arrow in a card, or a "Back to suppliers" button.
127
+ - **Declare the parent.** Right: `definePage({ meta: { jskit: { parent:
128
+ "/suppliers" } } })` on the supplier. Wrong: no declaration, a parent on
129
+ Suppliers itself, or a page that computes its own "previous page".
130
+ - **Full loads through the shell.** Right: `reloadAppPage(returnTo)` after
131
+ sign-in; `leaveApp(checkoutUrl)` for the payment page. Wrong:
132
+ `window.location.replace(returnTo)` or `location.assign(checkoutUrl)`.
133
+ - **No history.** Right: the page declares its parent and nothing else.
134
+ Wrong: `@click="router.back()"`, `history.go(-1)`, or a `goBack()` helper.
135
+ - **Links through JSKIT.** Right: `<v-btn :to="records.resolveViewUrl(record)">`
136
+ or `:to="paths.page('/suppliers')"`. Wrong: `to="/home/suppliers"`,
137
+ `` :to="`/home/suppliers/${id}`" ``, `router.push("/home/suppliers")` or
138
+ `window.location.href = url`.
139
+ - **Forms replace themselves.** Right: record → Edit → Save calls
140
+ `finish(recordUrl)`; the record shows, and Up goes where the record's Up went
141
+ before. Wrong: `router.push(recordUrl)` after Save, so Up or Back reopens the
142
+ form.
143
+
144
+ ## Foundation
145
+
146
+ - **Theme from one seed:** the app's only theme file is `src/theme/index.js`,
147
+ `export const theme = { seed: "#6750A4", icons: {} }`, and `src/main.js` runs
148
+ `createVuetify(createMaterialVuetifyOptions(theme))` with
149
+ `createMaterialVuetifyOptions` from `@jskit-ai/material-web/client`, importing
150
+ `@jskit-ai/material-web/client/material.css`. JSKIT generates the light and
151
+ dark schemes from the seed at startup (Material Color Utilities
152
+ `SchemeTonalSpot`, every role in kebab-case such as `surface-container-low`,
153
+ plus success, warning and info with their containers), with
154
+ `defaultTheme: "system"` so the person's light, dark or system preference
155
+ applies. The `md3` blueprint and JSKIT's Material 3 Vuetify defaults live in
156
+ material-web; an app keeps no defaults file and never hand-picks a colour. The
157
+ seed is the only hex value.
158
+ - **Type:** Vuetify 4 Material 3 roles (`text-headline-small`,
159
+ `text-title-large`, `text-body-medium`, `text-label-large`). Never the
160
+ Material 2 scale (`text-h5`, `text-body-2`) or a page-local type system.
161
+ Roboto Flex is the font.
162
+ - **Icons:** Material Symbols Rounded through Vuetify `$` aliases: Vuetify's
163
+ standard ones (`$close`, `$menu`, `$delete`, `$prev`, `$next`) and JSKIT's
164
+ camelCase names (`$add`, `$edit`, `$search`, `$moreVert`, `$filterList`,
165
+ `$arrowBack`); `$homeFilled`-style `Filled` variants mark active navigation.
166
+ An app adds an icon under `theme.icons` in `src/theme/index.js` as
167
+ `{ name: "<24x24 path data>" }` from Material Symbols Rounded, then uses
168
+ `$name`. No `mdi-*` strings and no `@mdi/js` imports in app code.
169
+
170
+ ## Exact sizes
171
+
172
+ JSKIT ships these exact sizes; an app never re-implements or overrides them,
173
+ and needs no Sass and no `!important` (Vuetify 4 keeps its CSS in cascade
174
+ layers):
175
+
176
+ - 40dp buttons with a 48dp touch target, icon buttons included;
177
+ - 12dp cards;
178
+ - 28dp dialogs and bottom-sheet top corners, square full-screen dialogs;
179
+ - 8dp chips and 16dp FABs;
180
+ - emphasized easing, with reduced motion honoured.
181
+
182
+ So never set `height` or `min-height` on a button, or a radius on a card,
183
+ dialog, chip or FAB. A missing size is a JSKIT defect.
184
+
185
+ ## The top app bar carries the page
186
+
187
+ - **Title:** route meta `jskit.title` (plain text), or
188
+ `useShellPageTitle(title)` (`@jskit-ai/shell-web/client/composables/useShellPageTitle`)
189
+ for a runtime title such as the record's name. The shell renders it as the
190
+ app bar's only `h1`; pages add no `h1`, page header, title block or
191
+ standalone heading. A section inside a page uses a title role.
192
+ - **Up** sits in the bar on every inner page (see Navigation and Up). On phones
193
+ it replaces the menu icon.
194
+ - **Actions:** a screen's actions go in the bar inside `ShellPageActions`
195
+ (`@jskit-ai/shell-web/client/components/ShellPageActions`), each a
196
+ `ShellPageAction` with `icon`, `label` (also its accessible name and compact
197
+ tooltip), and `primary`, `to`, `replace`, `disabled`, `loading` or `color`.
198
+ On compact widths every action shows only its icon except the single
199
+ `primary` one.
200
+ - **Window size classes:** compact under 600 (navigation bar), medium 600–839
201
+ (navigation rail), expanded 840–1199 and large from 1200 (drawer). Read them
202
+ from `useShellWindowSizeClass()`
203
+ (`@jskit-ai/shell-web/client/composables/useShellWindowSizeClass`), never from
204
+ copied breakpoint numbers.
205
+
206
+ ## Colour
207
+
208
+ - Use semantic roles through Vuetify props, classes and
209
+ `rgb(var(--v-theme-<role>))`; never a raw colour value in a component.
210
+ - Status chips use `color="success"` (or `warning`, `info`, `error`) or the
211
+ container roles (`success-container` with `on-success-container`), and each
212
+ status meaning has its own hue. Never communicate status, selection, validation, or permission through
213
+ color alone.
214
+ - Surfaces are separated by tone (`surface-container-lowest` to
215
+ `-highest`), not borders: no `variant="outlined"` cards and no `border` on
216
+ cards or sheets. A VCard default `color` becomes the text colour on outlined
217
+ and tonal cards.
218
+ - Keep WCAG AA contrast in both schemes. Use elevation `0` through `5` and
219
+ component variants instead of arbitrary shadows.
220
+
221
+ ## Lists, records and forms
222
+
223
+ - **List:** an M3 search bar, then one row of filter chips, each opening its own
224
+ control; never a permanent panel of outlined fields. A card opens its record
225
+ when tapped (a stretched title link); a table row opens on click. Row actions
226
+ are trailing icon buttons (edit, a `$moreVert` overflow menu, a chevron to
227
+ open), never "View" or "Edit" text buttons on every row. "New" is an extended
228
+ FAB on compact and medium widths and a filled bar button on expanded. Empty
229
+ and error states carry an icon.
230
+ - **A list never scrolls sideways, at any width.** The list's own width (a
231
+ container query, not the window) chooses cards or table. Cards flow into a
232
+ grid; tables use a fixed layout with an ellipsis on long values, and fold
233
+ low-priority columns into a stronger column's second line. Lists show figures
234
+ ("120 kg", tabular and right-aligned) and names, never ids.
235
+ - **Record:** a header (avatar, title, status chips) and value-first detail
236
+ lists, never a grid of label/value text. Edit sits in the bar; the record's
237
+ next workflow step is the one filled button, shown in context rather than a
238
+ row of mostly disabled actions.
239
+ - **Form:** Cancel and Save sit in the bar. Fields use `hideDetails: "auto"`.
240
+ Nothing moves under a finger: a multi-select chip field keeps its input width
241
+ when it loses focus. A pick-one-of-N choice is a segmented button with
242
+ `role="radiogroup"` and `role="radio"`/`aria-checked` buttons.
243
+ - **Dialogs:** plain `v-dialog` and `v-bottom-sheet`; JSKIT gives them their
244
+ Material 3 shapes. A full-screen dialog (compact and medium) puts close, title
245
+ and its confirm action in a top bar, never at the bottom where a snackbar
246
+ covers them.
247
+ - **Menus and badges:** a menu's list has `role="menu"` and its items
248
+ `role="menuitem"`, with `aria-disabled` on unavailable ones; a badge has a
249
+ `label` such as "3 batches".
50
250
 
51
251
  ## Components, state, and feedback
52
252
 
@@ -57,8 +257,7 @@ Do not replace a shared screen to restyle it. Use its public seams.
57
257
  icon-only actions without relying on color; give icon controls accessible
58
258
  names and fields meaningful labels, errors, and autocomplete behavior.
59
259
  - Preserve visible focus, logical tab order, keyboard operation, and at least
60
- 48 CSS-pixel interactive targets unless a tested dense operator surface owns
61
- another contract.
260
+ 48 CSS-pixel interactive targets.
62
261
  - Represent loading, empty, error, retry, disabled, selected, hover, focus,
63
262
  pressed, and success states through shared JSKIT behavior. All user-visible
64
263
  loading uses Material skeletons that reserve the final content geometry;
@@ -70,18 +269,69 @@ Do not replace a shared screen to restyle it. Use its public seams.
70
269
  field. A user-triggered command uses JSKIT's shared action
71
270
  feedback/snackbar path. Never insert a transient command-error alert above
72
271
  page content where it shifts the working layout.
73
- - Use established transitions and honor reduced motion. Do not claim
74
- unsupported Material 3 Expressive parity.
75
272
 
76
273
  ## Adaptive layout
77
274
 
78
- Start compact, then verify medium and expanded layouts. Use Vuetify display
79
- APIs and the JSKIT adaptive shell rather than copying breakpoint numbers.
80
- Keep primary tasks reachable without incidental chrome. Let navigation,
81
- tables, filters, supporting content, and action groups change presentation;
82
- do not merely shrink an expanded screen. Prevent overflow, clipped labels,
83
- overlapping actions, duplicate navigation, and unreachable dialogs. Persist
84
- screen context in the route when it must survive navigation.
275
+ Start compact, then check medium and expanded. Let navigation, tables, filters,
276
+ supporting content, and action groups change presentation; do not merely shrink
277
+ an expanded screen. Prevent overflow, clipped labels, overlapping actions,
278
+ duplicate navigation, and unreachable dialogs. Persist screen context such as
279
+ filters, tab and page in the route when it must survive navigation.
280
+
281
+ ## The conformance check
282
+
283
+ Two commands read application source (no server, no browser) and print each
284
+ finding with its file, line, rule and the section of this page:
285
+
286
+ ```bash
287
+ npx jskit check # in every app's verify: fails on any nav/* rule
288
+ npx jskit check m3 # the whole app: src/ and packages/
289
+ npx jskit check m3 src/pages/home/books # a section
290
+ npx jskit check m3 $(git diff --name-only --diff-filter=d main) # changed files
291
+ ```
292
+
293
+ Plain `jskit check` fails on the [navigation rules](#forbidden-in-app-code);
294
+ fix every one, because nothing excuses them. `jskit check m3` lists those and
295
+ the visual rules below. The visual rules are a report until every JSKIT app has
296
+ been converted to them; the JSKIT release after that makes plain `jskit check`,
297
+ and so every app's verify, fail on them too:
298
+
299
+ | Rule | Finds |
300
+ |---|---|
301
+ | `m3/duplicate-h1` | a second `h1`, or an in-page `h1` in an app that uses the JSKIT shell |
302
+ | `m3/raw-hex` | a hex colour outside `src/theme/` |
303
+ | `m3/dark-scheme` | no dark scheme, or the theme pinned to light |
304
+ | `m3/outlined-card` | `variant="outlined"` or `border` on a card or sheet |
305
+ | `m3/mdi-icon` | `mdi-*` icon strings and `@mdi/js` imports |
306
+ | `m3/fixed-button-height` | `height`/`min-height` on buttons, in props, styles or CSS |
307
+
308
+ Tests, fixtures, server code and migrations are not checked. A clean check is
309
+ necessary, not sufficient: it cannot see layout, spacing or contrast. A
310
+ [UI pass](ui-pass.md) is the guard for those, because it looks at every
311
+ affected screen at three widths in light and dark; an editor that runs one
312
+ automatically after each plan keeps that guard on every change.
313
+
314
+ ### Exceptions
315
+
316
+ Take the approved route first; none needs an exception:
317
+
318
+ - **Brand or partner colours** are theme custom colours, used by name:
319
+ `colors: { partner: "#0A7D3E" }` in `src/theme/index.js`, then
320
+ `<v-chip color="partner">`.
321
+ - **Chart and data colours** come from the theme: `const theme = useTheme()`,
322
+ then `["primary", "tertiary"].map((role) => theme.current.value.colors[role])`.
323
+ - **A third-party widget** lives in one wrapper component, such as
324
+ `src/components/RouteMap.vue`, that hands it theme colours; pages use only
325
+ the wrapper.
326
+
327
+ What is left may be excused one line at a time, on its line or the line
328
+ before: `<!-- m3-check: allow m3/raw-hex - the partner's contractual logo colour -->`
329
+ (`// m3-check: ...` in scripts, `/* ... */` in CSS). **Never add one on your
330
+ own:** ask the person first, naming the rule and the reason. A comment with no
331
+ reason (`m3/allow-reason`) or an unknown rule (`m3/allow-rule`), and any
332
+ `m3-check: allow nav/...` (`nav/allow`), is itself a finding; nothing turns the
333
+ check off for a file, a folder or the app. `jskit check m3` lists every
334
+ exception with its file, line and reason, and their total.
85
335
 
86
336
  ## Material 3 audit
87
337
 
@@ -89,29 +339,22 @@ Run this behavior-preserving audit over every affected screen:
89
339
 
90
340
  1. Identify the owning JSKIT surface, shared screen, shell, placement, theme,
91
341
  and Vuetify components before judging local markup.
92
- 2. Find parallel components, duplicated state chrome, page-local themes,
93
- transient command errors that shift page content, content loaders using
94
- generic spinners instead of geometry-preserving skeletons, raw visual
95
- constants, CSS against Vuetify internals, nested generic cards, legacy
96
- typography, elevation outside `0`-`5`, and desktop-only layout.
97
- 3. Check hierarchy, token use, component purpose, responsive presentation,
342
+ 2. Check navigation and Up first, as must-fix: plain `jskit check` passes;
343
+ every inner page has a parent and one Up arrow in the bar; main-menu
344
+ destinations have none; no inner link is hand-built; nothing writes back
345
+ logic.
346
+ 3. Find parallel components, duplicated state chrome, page-local themes, raw
347
+ visual constants, CSS against Vuetify internals, nested generic cards,
348
+ borders instead of tones, Material 2 typography, and desktop-only layout.
349
+ 4. Check hierarchy, token use, component purpose, responsive presentation,
98
350
  interaction states, keyboard/focus behavior, labels, targets, contrast, and
99
- reduced motion.
100
- 4. Audit Back on every affected page using [Back navigation](ui-operations.md#back-navigation):
101
- verify its presence or justified omission, actual previous-page destination,
102
- safe direct-entry fallback, and retained origin context. Inspect handlers and
103
- route bindings; a hardcoded parent/list link is an explicit exception, not
104
- the default. Check label, accessible name, consistent leading placement,
105
- keyboard order, responsive reachability, duplicates, and distinction from
106
- Cancel, Close, or Previous step. Report misleading navigation as a finding;
107
- fix behavior only within the requested scope, otherwise report it separately
108
- from behavior-preserving cleanup.
351
+ reduced motion against the rules above.
109
352
  5. Correct issues at the narrowest established owner. Consolidate only truly
110
353
  repeated policy; avoid pass-through wrappers and speculative abstractions.
111
- 6. Run focused tests and Playwright at compact, medium, and expanded widths.
112
- Navigate away and back with warm query data when the screen persists state.
354
+ 6. Run `jskit check` and `jskit check m3` on the affected files, then focused
355
+ tests and Playwright at compact, medium, and expanded widths in light and
356
+ dark (`shellBackButton(page)` and `pressShellBack(page)` from
357
+ `@jskit-ai/shell-web/test/shellNavigation` drive Up).
113
358
 
114
359
  Do not declare Material 3 compliance from visual resemblance alone. Report any
115
360
  unverified responsive, theme, accessibility, interaction, or browser behavior.
116
- For a missing or version-sensitive API, verify the installed Vuetify major and
117
- official docs; implement Material intent through supported JSKIT/Vuetify seams.