@jskit-ai/agent-docs 0.1.115 → 0.1.116

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.
@@ -671,6 +671,23 @@ import {
671
671
  - whether the drawer is open right now
672
672
  - whether the drawer should open by default on load
673
673
 
674
+ The closed presentation is adaptive and uses Vuetify's Material navigation
675
+ components. On compact/mobile layouts the temporary drawer closes completely.
676
+ On medium and expanded layouts it collapses to a navigation rail by default,
677
+ so primary navigation remains visible. Opening it restores the full drawer.
678
+
679
+ The app-owned `ShellLayout` can opt into a fully hidden wide drawer when the
680
+ product has another discoverable navigation affordance:
681
+
682
+ ```vue
683
+ <ShellLayout desktop-drawer-closed-mode="hidden">
684
+ <RouterView />
685
+ </ShellLayout>
686
+ ```
687
+
688
+ The default is `desktop-drawer-closed-mode="rail"`. Prefer that Material 3
689
+ pattern; do not imitate a rail with custom CSS or a second menu registry.
690
+
674
691
  `useShellErrorPresentationStore()` exposes the current banner, snackbar, and dialog presentation state behind `ShellErrorHost`.
675
692
 
676
693
  The simplest direct store usage looks like this:
@@ -38,7 +38,18 @@ If you deliberately need the older bare scaffold, use `--minimal` or `--template
38
38
  npx @jskit-ai/create-app exampleapp --minimal --tenancy-mode none
39
39
  ```
40
40
 
41
- Minimal apps can still install the standard shell later with `npx jskit add package shell-web`, as long as the starter files it claims have not been edited first.
41
+ Minimal apps can still install the standard shell later with
42
+ `npx --no-install jskit add package shell-web`, as long as the starter files it
43
+ claims have not been edited first. After the initial `npm install`, prefer
44
+ `npx --no-install jskit` for every local JSKIT command so a missing local CLI
45
+ fails clearly.
46
+
47
+ When a minimal app's first feature is generated CRUD, do not pre-install the
48
+ shell as a workaround. Add the database runtime, run `npm install`, create the
49
+ live disposable table, run `crud-server-generator scaffold`, run
50
+ `npm install` again, and then run `crud-ui-generator crud`. The server
51
+ generator installs the shell/realtime dependency closure in the correct order.
52
+ The complete Notes command lane is in [CRUD Generators](/guide/generators/crud-generators#fresh-minimal-notes-app-the-complete-command-order).
42
53
 
43
54
  If you already know you want a small non-workspace baseline right after the scaffold, this is the shortest reproducible path:
44
55
 
@@ -45,6 +45,20 @@ That lock file is the source of truth for installed JSKIT-managed package state.
45
45
 
46
46
  That is why commands such as `update`, `remove`, `position`, `migrations`, and `doctor` all care about `.jskit/lock.json`.
47
47
 
48
+ ### App-owned does not mean disposable
49
+
50
+ A path recorded in `.jskit/lock.json` remains managed while its owning package
51
+ is installed. “App-owned” means customizable, not disposable while the package
52
+ remains installed. You may edit the file; you may not delete or rename it
53
+ without changing the managed package contract.
54
+
55
+ Never delete or rename a recorded managed path. Adapt generated infrastructure
56
+ tests in place. If a starter product route is replaced, update the scaffold
57
+ smoke test to exercise the new canonical route instead of deleting baseline
58
+ browser coverage. In particular, preserve managed tests such as
59
+ `tests/e2e/base-shell.spec.ts` and `tests/e2e/adaptive-shell.spec.ts`. Doctor
60
+ must continue to flag a managed test that is missing.
61
+
48
62
  ## JSKIT-managed app maintenance scripts
49
63
 
50
64
  The scaffolded app also has a small set of `npm run` shortcuts that are really wrappers around JSKIT-owned maintenance behavior.
@@ -31,6 +31,73 @@ It is meant to answer:
31
31
 
32
32
  Once that workflow is clear, continue with [Advanced CRUDs](/guide/generators/advanced-cruds) for the generated package anatomy, ownership model, and customization boundaries.
33
33
 
34
+ ## Fresh minimal Notes app: the complete command order
35
+
36
+ The dependency install boundaries are part of the workflow. After the first
37
+ install, use `npx --no-install jskit` so the command fails if the app-local CLI
38
+ is unavailable instead of fetching a different copy.
39
+
40
+ ```bash
41
+ npx @jskit-ai/create-app notes \
42
+ --target . \
43
+ --force \
44
+ --tenancy-mode none \
45
+ --minimal
46
+ npm install
47
+
48
+ npx --no-install jskit add package database-runtime-mysql
49
+ npm install
50
+
51
+ # Create and select a fresh disposable database, then create the live `notes` table.
52
+
53
+ npx --no-install jskit generate crud-server-generator scaffold \
54
+ --namespace notes \
55
+ --surface home \
56
+ --ownership-filter public \
57
+ --access public \
58
+ --table-name notes
59
+ npm install
60
+
61
+ npx --no-install jskit generate crud-ui-generator crud notes \
62
+ --resource-file packages/notes/src/shared/noteResource.js \
63
+ --id-param noteId \
64
+ --display-fields title,body \
65
+ --parent-title contextual \
66
+ --navigation-role primary \
67
+ --delete-confirmation
68
+ ```
69
+
70
+ The server generator resolves the complete package closure. `shell-web`
71
+ establishes and owns `src/placement.js` before `realtime` contributes its
72
+ placement, so do not pre-install the shell as a workaround.
73
+
74
+ ## Existing-app migration checklist
75
+
76
+ JSKIT is still version 0, so this release uses a direct code migration rather
77
+ than a legacy compatibility layer:
78
+
79
+ 1. Commit the app's current work, then run `npm run jskit:update`.
80
+ 2. If the app declares `json-rest-schema` directly, install
81
+ `json-rest-schema@^1.0.17`.
82
+ 3. Replace resource-bound JavaScript `Date` values with strings. `date` is
83
+ `YYYY-MM-DD`; `time` is offset-free `HH:MM[:SS[.fraction]]`; and `dateTime`
84
+ is RFC 3339 with seconds and a `Z` or numeric offset. Replace the removed
85
+ `timestamp` type with `epochMilliseconds` or `epochSeconds` only after
86
+ checking the existing numeric unit. Preserve `temporalPrecision`.
87
+ 4. For a standard generated view delete action, rerun the original
88
+ `crud-ui-generator crud` command with `--delete-confirmation`. Add `--force`
89
+ only when you deliberately want to replace unchanged generated page output.
90
+ For a customized view, preserve the customization and add the public
91
+ `CrudViewScreen` `actions` slot plus `useCrudDeleteAction()` integration
92
+ described below.
93
+ 5. Keep every managed path recorded in `.jskit/lock.json`. Adapt managed shell
94
+ and browser tests in place; never delete them because a starter route was
95
+ replaced.
96
+
97
+ Generated generic CRUD repositories convert database temporal values at the
98
+ resource boundary. Custom repositories still need to return strict temporal
99
+ strings and write ISO/RFC 3339 strings explicitly.
100
+
34
101
  ## The two generator packages
35
102
 
36
103
  ### `crud-server-generator` `@jskit-ai/crud-server-generator` `(0.1.47)`
@@ -449,7 +516,8 @@ npx jskit generate crud-ui-generator crud \
449
516
  w/[workspaceSlug]/admin/contacts \
450
517
  --resource-file packages/contacts/src/shared/contactResource.js \
451
518
  --id-param contactId \
452
- --display-fields fullName,email,phone
519
+ --display-fields fullName,email,phone \
520
+ --delete-confirmation
453
521
  ```
454
522
 
455
523
  That creates the baseline CRUD route tree:
@@ -465,6 +533,20 @@ The mirrored component root is intentional. The configured file router scans
465
533
  Vue files below `src/pages/`, so reusable Vue helpers must stay outside that
466
534
  directory or they become browser routes.
467
535
 
536
+ `--delete-confirmation` is opt-in. When present, the generated view extends
537
+ the public `CrudViewScreen` `actions` slot with a destructive Delete button and
538
+ a Vuetify alert dialog. The public `useCrudDeleteAction()` composable resolves
539
+ the current route id through the CRUD runtime, runs the shared resource's
540
+ `DELETE` operation through `useCommand()`, disables duplicate submission,
541
+ keeps a useful error on the record screen, invalidates the list query, and
542
+ navigates to the generated list route after success. It supports custom
543
+ `--id-param` names.
544
+
545
+ The generator rejects this option when list or view is omitted, or when the
546
+ shared resource has no `DELETE` operation. Without the flag, no delete control
547
+ is generated. Do not substitute raw `fetch()` or import private `users-web`
548
+ modules.
549
+
468
550
  Generated list, view, and lookup reads use the resource contract as their
469
551
  response authority. They return every field declared for output by default,
470
552
  including the target resource's declared output when a lookup relation is
@@ -40,3 +40,6 @@ It starts with a fast reproducible Quickstart, then steps back to the scaffold-f
40
40
  - Use `App Extras` once the base app structure is in place and you want optional runtime packages such as the Android shell, realtime, or assistant.
41
41
  - Jump into `Generators` if you already understand the runtime packages and want app-owned scaffolding workflows.
42
42
  - Inside `Generators`, read `CRUD Generators` before `Advanced CRUDs`: the first chapter teaches the workflow, and the second explains the generated anatomy and customization points.
43
+ - Updating an existing app for strict temporal values or generated record
44
+ deletion? Go straight to the
45
+ [Existing-app migration checklist](/guide/generators/crud-generators#existing-app-migration-checklist).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.115",
3
+ "version": "0.1.116",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
package/patterns/INDEX.md CHANGED
@@ -18,17 +18,17 @@ How to use it:
18
18
  - `page-scaffolding.md`
19
19
  - child crud, nested crud, embedded list, subroute, separate page, parent/child layout
20
20
  - `child-cruds.md`
21
- - crud scaffold, crud server, crud ui, table creation, migrations, direct knex, weird-custom persistence, `crud-server-generator`, `crud-ui-generator`
21
+ - crud scaffold, crud server, crud ui, table creation, migrations, direct knex, weird-custom persistence, fresh minimal app order, `crud-server-generator`, `crud-ui-generator`, `--delete-confirmation`
22
22
  - `crud-scaffolding.md`
23
23
  - CRUD links, record placeholders, `paths.page()`, `resolveViewUrl`, `resolveEditUrl`, `resolveParams`
24
24
  - `crud-links.md`
25
25
  - `definePage`, redirect, child redirect, settings landing, `redirectToChild`
26
26
  - `page-redirects.md`
27
- - live actions, checkbox, toggle, patch button, inline action, `useCommand()`
27
+ - live actions, checkbox, toggle, patch button, delete confirmation, destructive action, inline action, `useCommand()`, `useCrudDeleteAction()`
28
28
  - `live-actions.md`
29
29
  - ajax, fetch, API call, request, endpoint, HTTP client, `useCrudListScreen()`, `useCrudViewScreen()`, `useCrudAddEditScreen()`, `useList()`, `useView()`, `useAddEdit()`, `useEndpointResource()`, `usersWebHttpClient`
30
30
  - `client-requests.md`
31
- - playwright, browser test, e2e, ui verification, authenticated ui test, test auth, dev login as, dev auth bypass
31
+ - playwright, browser test, e2e, ui verification, managed test, `.jskit/lock.json`, authenticated ui test, test auth, dev login as, dev auth bypass
32
32
  - `ui-testing.md`
33
33
  - generated UI contract, design contract, navigation roles, density, placeholder copy, card shells, shared CRUD screens, row actions, synthetic rows, detail slots
34
34
  - `generated-ui-contract-tracking.md`
@@ -36,7 +36,7 @@ How to use it:
36
36
  - `filters.md`
37
37
  - `searchSchema`, `search: true`, `applyFilter`, server search, query validators, backend filters, internal JSON REST filters
38
38
  - `server-search.md`
39
- - `actualField`, `storage.writeSerializer`, `storage.virtual`, computed field, virtual field, projection, `createCrudResourceRuntime`, `remainingBatchWeight`, datetime write serialization
39
+ - `actualField`, `storage.writeSerializer`, `storage.virtual`, computed field, virtual field, projection, `createCrudResourceRuntime`, `remainingBatchWeight`, datetime write serialization, temporalPrecision, json-rest-schema dates
40
40
  - `crud-repository-mapping.md`
41
41
  - row policy, permission filtering, visibility before pagination, recursive CTE, descendants, package cycle, `RowPolicyPlugin`
42
42
  - `row-policies.md`
@@ -113,6 +113,9 @@ If the resource module is server-only, a field may also declare `storage.queryPr
113
113
  What CRUD core does for you:
114
114
  - default select columns include only column-backed output fields
115
115
  - create/update write payloads serialize standard writable `date-time` fields centrally
116
+ - database reads serialize `date`, `time`, and `dateTime` values to their
117
+ strict `json-rest-schema` string contracts before resource validation,
118
+ respecting `temporalPrecision`
116
119
  - create/update write payloads also apply any explicit field write serializers centrally
117
120
  - `list`, `findById`, `listByIds`, and `listByForeignIds` apply registered virtual projections automatically
118
121
  - search and parent-filter fallback derivation only use column-backed fields
@@ -131,3 +134,5 @@ Review checks:
131
134
  - computed fields use `storage: { virtual: true }`
132
135
  - repository runtime registers matching `virtualFields`, or the JSON REST provider registers matching `queryFields`
133
136
  - no per-method projection duplication when generic CRUD reads already cover the field
137
+ - no JavaScript `Date` object crosses a strict resource validation boundary;
138
+ custom repositories return ISO/RFC 3339 strings explicitly
@@ -14,6 +14,26 @@ Check first:
14
14
  - `jskit show crud-server-generator --details`
15
15
  - whether the request is server-only CRUD or server-plus-UI CRUD
16
16
 
17
+ ## Fresh minimal-app order
18
+
19
+ After `create-app`, run `npm install` before invoking the local CLI, and then
20
+ use `npx --no-install jskit` so a missing local CLI fails clearly. For a fresh
21
+ minimal CRUD app, the complete order is:
22
+
23
+ 1. create the app
24
+ 2. `npm install`
25
+ 3. add the database runtime
26
+ 4. `npm install`
27
+ 5. create the live table in a fresh disposable development database
28
+ 6. run `crud-server-generator scaffold`
29
+ 7. `npm install`
30
+ 8. run `crud-ui-generator crud`
31
+
32
+ The server generator resolves its complete package dependency closure. In
33
+ particular, `shell-web` owns and establishes `src/placement.js` before
34
+ `realtime` appends its placement. Do not pre-install `shell-web` as a
35
+ workaround.
36
+
17
37
  ## Non-negotiable database contract
18
38
 
19
39
  Before database, schema, CRUD, repository, or persistence work, read this
@@ -75,6 +95,29 @@ Rules:
75
95
  - Bulk actions should be declared in the generated page-local `listBulkActions.js`. The generated list owns selection state, keeps selection controls hidden until actions exist, and exposes selected ids/records to action handlers.
76
96
  - Structured filters should use shared filter definitions and collapse to compact filter controls/sheets when they outgrow simple search. Do not stack dense desktop filter bars on phone widths.
77
97
  - Use `--navigation-role` for CRUD list placement intent. Main resources can stay `primary`; nested/detail/workflow CRUD routes should usually be `secondary`, `workflow`, or `none`.
98
+ - Add `--delete-confirmation` when the generated view needs the standard
99
+ destructive record action. The flag requires list and view pages and a
100
+ resource with a `DELETE` operation; it uses the public view-screen action
101
+ slot and command composable rather than generated raw request code.
102
+
103
+ ## Temporal values at resource boundaries
104
+
105
+ `json-rest-schema` 1.0.17 accepts strict string temporal values. Resource
106
+ validators no longer coerce JavaScript `Date` objects:
107
+
108
+ - `date` is `YYYY-MM-DD`
109
+ - `time` is offset-free `HH:MM[:SS[.fraction]]`
110
+ - `dateTime` is RFC 3339 with seconds and `Z` or a numeric offset
111
+
112
+ The removed `timestamp` type must become `epochMilliseconds` or
113
+ `epochSeconds` after checking the stored unit. Preserve the field's
114
+ `temporalPrecision`; do not truncate meaningful fractional seconds.
115
+
116
+ Generated generic CRUD repositories serialize database temporal outputs
117
+ before resource validation. App-owned/custom repositories must return strict
118
+ strings and must write ISO/RFC 3339 strings rather than passing `Date` objects.
119
+ This is a version-0 breaking contract: update application code instead of
120
+ adding a legacy temporal parser.
78
121
 
79
122
  ## Baseline generation versus later schema evolution
80
123
 
@@ -12,6 +12,25 @@ Rules:
12
12
  - Prefer `useCommand()` for live actions.
13
13
  - Prefer form runtimes such as `useCrudAddEdit()` or `useAddEdit()` for real forms.
14
14
  - Prefer `useCrudList()` and `useCrudView()` for routed CRUD loading and URL resolution.
15
+ - For ordinary routed CRUD record deletion, generate the supported lane instead
16
+ of rebuilding it page by page:
17
+
18
+ ```bash
19
+ npx --no-install jskit generate crud-ui-generator crud notes \
20
+ --resource-file packages/notes/src/shared/noteResource.js \
21
+ --id-param noteId \
22
+ --display-fields title,body \
23
+ --parent-title contextual \
24
+ --navigation-role primary \
25
+ --delete-confirmation
26
+ ```
27
+
28
+ `--delete-confirmation` requires generated list and view pages plus a shared
29
+ resource with a `DELETE` operation. It extends the view through the public
30
+ `CrudViewScreen` `actions` slot and `useCrudDeleteAction()`: Vuetify owns the
31
+ alert dialog, `useCommand()` owns the request state, the shared resource owns
32
+ the DELETE contract, and successful deletion invalidates the list query and
33
+ navigates to the generated list route.
15
34
 
16
35
  Good live-action pattern:
17
36
 
@@ -30,4 +49,6 @@ Examples:
30
49
  Avoid:
31
50
 
32
51
  - manually hand-rolling fetch logic for a standard live action when `useCommand()` fits
52
+ - inspecting private `users-web` internals or creating a page-local transport
53
+ for generated record deletion
33
54
  - pushing derived write rules into the client just because the action is small
@@ -19,6 +19,18 @@ Rules:
19
19
  - Vibe64 supplies an authenticated context through `VIBE64_PLAYWRIGHT_STORAGE_STATE`. Treat that file as a temporary secret: do not commit it, print it, or retain it after the run.
20
20
  - Do not install a browser when the environment provides a managed browser runner.
21
21
 
22
+ ## Preserve managed baseline tests
23
+
24
+ “App-owned” means customizable, not disposable while the package remains
25
+ installed. Never delete or rename a test path recorded in `.jskit/lock.json`.
26
+ Generated and managed infrastructure tests must be adapted in place.
27
+
28
+ When the starter product route is replaced, update the scaffold smoke test to
29
+ visit and assert the new canonical route. Do not delete baseline browser
30
+ coverage such as `tests/e2e/base-shell.spec.ts` or
31
+ `tests/e2e/adaptive-shell.spec.ts`. JSKIT Doctor must continue to flag a
32
+ managed test that is missing.
33
+
22
34
  ## Direct local authentication
23
35
 
24
36
  Use the development-only dev auth bypass when Playwright is talking directly to an app running on localhost.
@@ -223,7 +223,7 @@ Exports
223
223
  - `requireCrudTableName(tableName, { context = "crudRepository" } = {})`
224
224
  - `deriveRepositoryMappingFromResource(resource = {}, { context = "crudRepository" } = {})`
225
225
  - `applyCrudListQueryFilters(query, { idColumn = "id", cursor = "", applyCursor = true, q = "", searchColumns = [], parentFilters = {}, parentFilterColumns = {} } = {})`
226
- - `mapRecordRow(row, fieldKeys = [], overrides = {}, { recordIdKeys = [] } = {})`
226
+ - `mapRecordRow(row, fieldKeys = [], overrides = {}, { recordIdKeys = [], serializerByKey = {} } = {})`
227
227
  - `buildWritePayload(sourcePayload = {}, fieldKeys = [], overrides = {}, { serializerByKey = {} } = {})`
228
228
  - `resolveColumnName(fieldKey, overrides = {})`
229
229
  - `resolveCrudIdColumn(idColumn, { fallback = "id" } = {})`
@@ -33,6 +33,7 @@ Local functions
33
33
  - `parseOperationsOption(options)`
34
34
  - `parseDisplayFieldsOption(options)`
35
35
  - `parseParentTitleOption(options)`
36
+ - `resolveBooleanFlagOption(options = {}, optionName = "")`
36
37
  - `shouldCreateNavigationLink(options = {}, inferenceContext = {})`
37
38
  - `resolveNavigationRoleLinkPlacement(options = {}, inferenceContext = {})`
38
39
  - `validateDisplayFieldsForOperation(selectedFieldKeys, fields, operationName)`
@@ -60,6 +61,9 @@ Local functions
60
61
  - `resolveCrudRelativePath(namespace = "")`
61
62
  - `buildListParentTitleImportLine(parentTitleMode = "contextual")`
62
63
  - `buildListHeadingTitleSetup({ parentTitleMode = "contextual", resourceNamespace = "", routeTitle = "Records" } = {})`
64
+ - `buildViewDeleteActionSlot({ resourceNamespace = "resource", resourceSingularTitle = "Record" } = {})`
65
+ - `buildViewDeleteDialog({ resourceNamespace = "resource", resourceSingularTitle = "Record" } = {})`
66
+ - `buildViewDeleteSetup({ resourceNamespace = "resource" } = {})`
63
67
 
64
68
  ### `src/server/resourceSupport.js`
65
69
  Exports
@@ -95,6 +99,7 @@ Local functions
95
99
  - `resolveSchemaReference(ref = "", rootSchema = {}, { context = "ui-generator", contextLabel = "schema" } = {})`
96
100
  - `resolveObjectSchema(schema = {}, contextLabel, { context = "ui-generator", rootSchema = schema } = {})`
97
101
  - `resolveJsonRestCastType(schema = {})`
102
+ - `resolveTemporalPrecision(...schemas)`
98
103
  - `resolveSchemaType(schema)`
99
104
  - `toFieldLabel(key)`
100
105
  - `isSupportedSelectOptionValue(value)`
@@ -108,6 +113,7 @@ Local functions
108
113
  - `toLookupRelation(fieldContractMap = {}, fieldKey = "", { lookupContainerKey = "lookups" } = {})`
109
114
  - `resolveFormInputType(fieldType, fieldFormat)`
110
115
  - `resolveFormFieldComponent(fieldType, relation = null)`
116
+ - `resolveTemporalInputStep(field = {})`
111
117
  - `buildDefaultNullableBooleanOptions()`
112
118
  - `toPositiveInteger(value)`
113
119
  - `toAccessorExpression(baseName, fieldKey)`
@@ -53,9 +53,18 @@ Exports
53
53
  - `toInsertDateTime(dateLike, fallback = new Date())`
54
54
  - `toNullableDateTime(value)`
55
55
  - `toDatabaseDateTimeUtc(value)`
56
+ - `toJsonDate(value)`
57
+ - `toJsonTime(value, { temporalPrecision } = {})`
58
+ - `toJsonDateTime(value, { temporalPrecision } = {})`
56
59
  Local functions
57
60
  - `toDateOrThrow(value)`
58
61
  - `pad(value, size = 2)`
62
+ - `requireValidDateParts(year, month, day)`
63
+ - `requireValidTimeParts(hours, minutes, seconds = 0)`
64
+ - `parseDateParts(value)`
65
+ - `normalizeTemporalPrecision(value)`
66
+ - `formatFraction(milliseconds, temporalPrecision)`
67
+ - `requireAllowedFraction(fraction, temporalPrecision)`
59
68
 
60
69
  ### `src/shared/dialect.js`
61
70
  Exports
@@ -84,6 +93,9 @@ Exports
84
93
  - `toInsertDateTime`
85
94
  - `toNullableDateTime`
86
95
  - `toDatabaseDateTimeUtc`
96
+ - `toJsonDate`
97
+ - `toJsonTime`
98
+ - `toJsonDateTime`
87
99
  - `normalizeDialect`
88
100
  - `detectDialectFromClient`
89
101
  - `normalizeText`
@@ -66,6 +66,7 @@ Exports
66
66
  - None
67
67
  Local functions
68
68
  - `handlePullPointerDown(event)`
69
+ - `handleDrawerVisibilityChange(open)`
69
70
  - `handlePullPointerMove(event)`
70
71
  - `handlePullPointerEnd(event)`
71
72
  - `handlePullPointerCancel(event)`
@@ -404,6 +405,13 @@ Local functions
404
405
  Exports
405
406
  - `useShellLayoutStore`
406
407
 
408
+ ### `src/client/support/drawerPresentation.js`
409
+ Exports
410
+ - `DESKTOP_DRAWER_CLOSED_MODES`
411
+ - `normalizeDesktopDrawerClosedMode(value = "rail")`
412
+ - `resolveShellDrawerPresentation({ compact = false, open = false, desktopClosedMode = "rail" } = {})`
413
+ - `resolveShellDrawerToggleLabel({ compact = false, open = false } = {})`
414
+
407
415
  ### `src/client/support/menuLinkTarget.js`
408
416
  Exports
409
417
  - `normalizeMenuLinkPathname(pathname = "")`
@@ -209,7 +209,8 @@ Local functions
209
209
  - `toTimeInputValue(value)`
210
210
  - `toDateTimeLocalInputValue(value)`
211
211
  - `toDateInputValue(value)`
212
- - `toIsoUtcDateTimeValue(value)`
212
+ - `applyDateTimePrecision(isoValue, temporalPrecision)`
213
+ - `toIsoUtcDateTimeValue(value, temporalPrecision)`
213
214
  - `resolveFormFieldInitialValue(field = {})`
214
215
  - `shouldSerializeClearedFieldAsNull(field = {})`
215
216
 
@@ -488,6 +489,15 @@ Exports
488
489
  Local functions
489
490
  - `normalizeProvidedScreen(screen = null)`
490
491
 
492
+ ### `src/client/composables/useCrudDeleteAction.js`
493
+ Exports
494
+ - `requireCrudDeleteOperation(resource = null)`
495
+ - `resolveDeleteApiSuffix(screen, apiUrlTemplate = "")`
496
+ - `useCrudDeleteAction({ screen = null, resource = null, resourceNamespace = "", apiUrlTemplate = "", access = "auto", client = null, router: routerOverride = null, fallbackDeleteError = "Unable to delete record." } = {})`
497
+ Local functions
498
+ - `requireCrudViewScreen(screen = null)`
499
+ - `resolveListLocation(screen)`
500
+
491
501
  ### `src/client/composables/useCrudListBulkActions.js`
492
502
  Exports
493
503
  - `useCrudListBulkActions(actions = [], { resolveRecordId = null, resolveContext = null } = {})`
@@ -616,6 +626,7 @@ Exports
616
626
  - `CrudViewScreen`
617
627
  - `normalizeCrudApiAccess`
618
628
  - `resolveCrudHttpClient`
629
+ - `useCrudDeleteAction`
619
630
  - `clientProviders`
620
631
 
621
632
  ### `src/client/lib/bootstrap.js`
@@ -1,13 +1,12 @@
1
1
  ---
2
2
  name: jskit
3
- description: Build, extend, troubleshoot, review, deslop, and verify JSKIT applications using the JSKIT CLI, runtime packages, generators, surfaces, placements, and managed-app conventions. Use for JSKIT scaffolding, pages, routes, UI, authentication, databases, CRUDs, users, workspaces, console features, migrations, package changes, upgrades, or pre-sign-off review.
3
+ description: Build, extend, troubleshoot, review, deslop, and verify JSKIT apps using its CLI, packages, generators, surfaces, placements, CRUDs, managed files, and verification conventions.
4
4
  ---
5
5
 
6
6
  # JSKIT
7
7
 
8
- Use JSKIT's native CLI, generators, installed packages, and app-local
9
- contracts. This skill explains the technology; the request and application
10
- files define the product.
8
+ Use JSKIT's CLI, generators, packages, and app-local contracts. The request
9
+ and app files define the product.
11
10
 
12
11
  ## Exact caller lanes
13
12
 
@@ -24,9 +23,7 @@ Discover only a missing fact or exact-command failure, then resume the lane.
24
23
  1. Read the request and nearest `AGENTS.md`.
25
24
  2. Inspect `package.json`, `.jskit/lock.json`, the existing tree, and the
26
25
  current diff when reviewing changes.
27
- 3. Read `.jskit/APP_BLUEPRINT.md` when present for durable product and
28
- architecture decisions. Do not invent or expand product requirements from
29
- this skill.
26
+ 3. Read `.jskit/APP_BLUEPRINT.md` when present; do not invent requirements.
30
27
  4. Load only the task-relevant direct reference:
31
28
  - For creation, package selection, CLI use, or generators, read
32
29
  [application operations](references/app-operations.md).
@@ -36,73 +33,43 @@ Discover only a missing fact or exact-command failure, then resume the lane.
36
33
  [UI operations](references/ui-operations.md).
37
34
 
38
35
  Those files are the complete operational references required by this skill.
39
- Do not depend on sibling package documentation being present. Do not load
40
- irrelevant references.
36
+ Do not depend on sibling docs. Do not load irrelevant references.
41
37
 
42
38
  Do not invent missing tenancy, authentication, database, surface, ownership,
43
39
  or permission decisions when they would materially change the application.
44
40
 
45
41
  ## Discovery fallback
46
42
 
47
- - For a missing fact, use only the narrowest applicable JSKIT CLI query.
48
- - Prefer an existing JSKIT package, generator, placement, or high-level
49
- composable over hand-wired local infrastructure.
50
- - Treat `.jskit/lock.json` and JSKIT-owned projections as managed state. Do not
51
- hand-edit the lock or bypass managed-file lifecycle checks.
43
+ Use the narrowest CLI query for a missing fact. Prefer existing packages,
44
+ generators, placements, and high-level composables. Never hand-edit
45
+ `.jskit/lock.json` or bypass managed-file lifecycle checks.
52
46
 
53
47
  ## Implement a change
54
48
 
55
- 1. Use the caller-selected seam, or discover only a genuinely missing seam.
56
- 2. Read the matching local reference above.
57
- 3. Implement the smallest complete vertical slice at documented app-owned
58
- seams.
59
- 4. Install dependencies and run migrations only when the selected operation
60
- requires them.
61
- 5. For schema work, use only a fresh disposable development database; never
62
- alter production, legacy, historical, or otherwise valuable data.
63
-
64
- For user-facing work, respect the selected surface and semantic placements,
65
- handle compact layouts and loading, empty, error, permission, and ownership
66
- states, and verify meaningful behavior in the browser.
49
+ Read the matching reference and implement the smallest complete slice at
50
+ documented seams. Install dependencies/migrate only when required. Use only a
51
+ fresh disposable development database for schema work—never valuable data.
52
+ For UI, respect surface/placements, compact layout, all operational states,
53
+ permissions/ownership, and browser verification.
67
54
 
68
55
  ## Caller-owned verification
69
56
 
70
- When a calling orchestrator explicitly owns final tests, migrations, server
71
- lifecycle, browser checks, and sign-off, honor that division of work. Stay
72
- within its wall-time and action limits, implement the requested slice, return
73
- the requested manifest or summary, and stop. Do not start a dev server,
57
+ When a caller owns tests, migrations, server lifecycle, browser checks, or
58
+ sign-off, honor that division and its limits. Do not start a dev server,
74
59
  browser, Playwright, broad verifier, migration rebuild, or exploratory review
75
- unless that caller asks for it in the current task.
60
+ unless requested in the current task.
76
61
 
77
62
  ## Review or deslop
78
63
 
79
- Review the requested chunk or whole changeset. If the request is review-only,
80
- report findings without editing.
81
-
82
- Check for:
83
-
84
- - Duplicated helpers, dead code, placeholders, accidental abstractions, and
85
- incomplete states.
86
- - Missed JSKIT packages, generators, high-level composables, placements, or
87
- runtime seams.
88
- - Invalid surface, route, ownership, permission, migration, or managed-file
89
- choices.
90
- - Weak Material Design or Vuetify hierarchy, responsiveness, actions, and
91
- state handling.
92
- - Verification proportional to scope, including Playwright for meaningful
93
- user-facing flows.
94
-
95
- Present findings first, ordered by severity, with file references. Say
96
- explicitly when there are no findings.
64
+ For review-only work, report without editing. Check duplicated/dead/wrong code,
65
+ accidental abstraction, incomplete states, missed high-level JSKIT seams,
66
+ invalid routing/ownership/permission/migration/managed-file choices, weak
67
+ Vuetify/Material behavior, and proportional verification. Put findings first
68
+ by severity with file references; state when none exist.
97
69
 
98
70
  ## Verify
99
71
 
100
- - Run focused tests for the changed slice and broad regression checks for a
101
- whole changeset.
102
- - Run `npx jskit doctor` when managed state changed.
103
- - Run `npm run verify` before sign-off.
104
- - Rebuild migrations from zero against a fresh disposable database when
105
- schema or persistence changed.
106
- - Run the relevant Playwright flow when user-facing behavior changed.
107
- - Report files reviewed or changed, commands run, and anything still
108
- unverified.
72
+ Run focused tests for a slice and broad checks for a whole changeset. Run
73
+ Doctor for managed state, `npm run verify` before sign-off, rebuild changed
74
+ persistence from zero in a disposable database, and use Playwright for UI.
75
+ Report files, commands, and anything unverified.
@@ -1,11 +1,10 @@
1
1
  # Application operations
2
2
 
3
- Read this reference for application creation, CLI discovery, package changes,
4
- and generator selection.
3
+ Read this for app creation, CLI discovery, packages, and generators.
5
4
 
6
5
  ## Create an application
7
6
 
8
- Confirm the application name and tenancy mode, then run:
7
+ Confirm the name and tenancy mode:
9
8
 
10
9
  ```bash
11
10
  npx @jskit-ai/create-app <app-name> --tenancy-mode <tenancy-mode>
@@ -13,29 +12,56 @@ cd <app-name>
13
12
  npm install
14
13
  ```
15
14
 
16
- Generated applications require Node.js 26. Use `--target . --force` only to
17
- promote a known JSKIT `ai-seed` directory, never to overwrite an arbitrary
18
- application. Use `--minimal` only for a deliberately bare package-development
19
- or unusual integration baseline. After creation, follow the generated
20
- `AGENTS.md`.
15
+ Generated apps require Node.js 26. Use `--target . --force` only to promote a
16
+ known JSKIT `ai-seed`, never to overwrite an arbitrary app. `--minimal` is for
17
+ deliberately bare package-development or unusual integrations. Follow the
18
+ generated `AGENTS.md`.
21
19
 
22
- Do not add authentication, users, workspaces, console, example routes, sample
23
- records, or another database adapter unless the request requires them.
20
+ After the first install, use `npx --no-install jskit ...`; a missing local CLI
21
+ must fail instead of silently fetching another version. Do not add auth, users,
22
+ workspaces, console, sample data, or another database adapter unless requested.
23
+
24
+ ## Fresh minimal database CRUD order
25
+
26
+ Use this order exactly: create-app, install, add the database runtime, install,
27
+ create the live table in a fresh disposable development database, generate the
28
+ server CRUD, install, then generate the UI.
29
+
30
+ ```bash
31
+ npx @jskit-ai/create-app notes \
32
+ --target . --force --tenancy-mode none --minimal
33
+ npm install
34
+ npx --no-install jskit add package database-runtime-mysql
35
+ npm install
36
+ # Create/select a fresh disposable database and create its live `notes` table.
37
+ npx --no-install jskit generate crud-server-generator scaffold \
38
+ --namespace notes \
39
+ --surface home \
40
+ --ownership-filter public \
41
+ --access public \
42
+ --table-name notes
43
+ npm install
44
+ npx --no-install jskit generate crud-ui-generator crud notes \
45
+ --resource-file packages/notes/src/shared/noteResource.js \
46
+ --id-param noteId \
47
+ --display-fields title,body \
48
+ --parent-title contextual \
49
+ --navigation-role primary \
50
+ --delete-confirmation
51
+ ```
52
+
53
+ The server generator owns its dependency closure. Do not pre-install
54
+ `shell-web` as a placement workaround.
24
55
 
25
56
  ## Select and apply technology
26
57
 
27
- - Inspect available capabilities with `npx jskit list` and inspect a specific
28
- entry with `npx jskit show <id> --details`.
29
- - Install reusable runtime capability with `npx jskit add package <id>`.
30
- - Inspect a bundle before installing it with `npx jskit add bundle <id>`.
31
- - Run tooling packages with
32
- `npx jskit generate <generator> <action> ...`; do not install generators as
33
- runtime packages.
34
- - Let `jskit` own JSKIT application mutations, use `npm install` for dependency
35
- installation, and use `npm run db:migrate` for database migrations.
36
- - Review generated files and continue only at documented app-owned seams.
37
-
38
- Packages own reusable framework behavior. Generators create application-owned
39
- pages, components, placements, migrations, and support files. Prefer the
40
- narrowest installed package or generator that implements the requested
41
- vertical slice; do not build a parallel local framework around it.
58
+ - Discover with `npx --no-install jskit list` and
59
+ `npx --no-install jskit show <id> --details`.
60
+ - Install runtime capability with `npx --no-install jskit add package <id>`;
61
+ inspect a bundle before adding it.
62
+ - Run tooling with `npx --no-install jskit generate <generator> <action> ...`;
63
+ do not install generators as runtime packages.
64
+ - JSKIT owns app mutations, npm owns dependency installation, and
65
+ `npm run db:migrate` owns database migration execution.
66
+ - Continue only at documented app-owned seams. Prefer the narrowest existing
67
+ package or generator over a parallel local framework.
@@ -1,47 +1,36 @@
1
1
  # CRUD operations
2
2
 
3
- Read this reference completely before database, schema, CRUD, repository, or
4
- persistence work.
3
+ Read this completely before database, schema, CRUD, repository, or persistence
4
+ work.
5
5
 
6
6
  ## Establish the contract
7
7
 
8
- Determine the selected database adapter, surface, access rule, and ownership
9
- model from the request and app authority. Inspect only a generator whose exact
10
- lane or option values are missing, or whose supplied command failed:
8
+ Take the database adapter, surface, access, and ownership from the request and
9
+ app authority. Inspect only a generator whose exact lane or option values are
10
+ missing, or whose supplied command failed:
11
11
 
12
12
  ```bash
13
- npx jskit show crud-server-generator --details
14
- npx jskit show crud-ui-generator --details
13
+ npx --no-install jskit show crud-server-generator --details
14
+ npx --no-install jskit show crud-ui-generator --details
15
15
  ```
16
16
 
17
17
  Never run these merely to reconfirm caller-supplied facts.
18
18
 
19
- For normal app-owned CRUD tables:
20
-
21
- - Use one non-null integer primary key, normally
22
- `id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY`.
23
- - Make every foreign key single-column and target the referenced table's
24
- single-column primary key. Use multi-column unique indexes only for business
25
- uniqueness, never as relationship targets.
26
- - Use only direct `workspace_id` and/or `user_id` columns for generated JSKIT
27
- ownership and choose the ownership filter matching those columns exactly.
28
- - Treat names such as `recipient_user_id`, `created_by_user_id`, and
29
- `assignee_user_id` as domain relationships, not ownership aliases.
30
- - Express tenant-safe relationships as direct ownership plus a normal
31
- `parent_id -> parent.id` relation. Test both allowed and cross-workspace
32
- cases.
33
-
34
- Stop before generation if the schema uses composite relationship keys or if
35
- the ownership filter and reserved ownership columns disagree.
19
+ Normal app-owned CRUD tables use one non-null integer primary key. Every
20
+ foreign key is single-column and targets that key; multi-column unique indexes
21
+ are business constraints, never relationship targets. Only direct
22
+ `workspace_id` and `user_id` columns are generated ownership. Names such as
23
+ `recipient_user_id` are domain relationships. Match the ownership filter to
24
+ the reserved columns exactly, and test allowed plus cross-workspace cases.
25
+ Stop before generation when these contracts disagree.
36
26
 
37
27
  ## Conventional one-table CRUD
38
28
 
39
- In a fresh disposable development database, create the validated table first.
40
- The server generator reads its live shape. Scaffold the server contract before
41
- the UI:
29
+ Create the validated table first in a fresh disposable development database;
30
+ the server generator reads its live shape:
42
31
 
43
32
  ```bash
44
- npx jskit generate crud-server-generator scaffold \
33
+ npx --no-install jskit generate crud-server-generator scaffold \
45
34
  --namespace <resource> \
46
35
  --surface <surface> \
47
36
  --ownership-filter <public|user|workspace|workspace_user> \
@@ -49,63 +38,82 @@ npx jskit generate crud-server-generator scaffold \
49
38
  --table-name <table>
50
39
  ```
51
40
 
52
- Use `--access public` only with a non-workspace surface and public ownership;
53
- omit both role-grant flags. For workspace-required CRUDs, choose exactly one
54
- of `--grant-role <role>` or `--no-role-grant`. Never invent a role to satisfy
55
- the generator. Use `--internal` when the entity needs the generated
56
- repository/service/resource/migration ownership chain but no public HTTP CRUD
57
- routes.
41
+ Use public access only on a non-workspace surface with public ownership. A
42
+ workspace CRUD chooses exactly one of `--grant-role <role>` or
43
+ `--no-role-grant`; never invent a role. `--internal` keeps the generated
44
+ repository/service/resource ownership chain but suppresses public HTTP routes.
58
45
 
59
- Run `npm install`, then scaffold the UI from the generated shared resource:
46
+ Run `npm install`, then generate UI from the exact shared resource:
60
47
 
61
48
  ```bash
62
- npx jskit generate crud-ui-generator crud \
49
+ npx --no-install jskit generate crud-ui-generator crud \
63
50
  <pages-root>/<plural-route> \
64
51
  --resource-file packages/<namespace>/src/shared/<singular>Resource.js \
65
52
  --parent-title contextual
66
53
  ```
67
54
 
68
55
  The target is relative to `src/pages/`, starts with the selected surface's
69
- nonempty configured `pagesRoot`, and has no leading slash (for example
70
- `home/books`). For a surface deliberately configured with an empty root, use
71
- only the plural route (for example `books`). Use the exact singular resource
72
- filename emitted by the server generator; do not guess it.
56
+ nonempty configured `pagesRoot` (for example `home/books`), and has no leading
57
+ slash. For a surface deliberately configured with an empty root, use
58
+ only the plural route. Use the exact singular resource filename emitted by the server generator; do not guess it.
59
+
60
+ That resource is canonical. Do not hand-build routes, validators, HTTP helpers,
61
+ or UI before it exists. Prefer `useCrudListScreen()`, `useCrudViewScreen()`, and
62
+ `useCrudAddEditScreen()` for routed screens; the corresponding `useCrud*()`
63
+ composables for routed behavior; and `useList()`, `useView()`, `useAddEdit()`,
64
+ `useCommand()`, or `useEndpointResource()` for non-standard contracts. Standard
65
+ CRUD derives JSON:API transport from the resource—never use raw `fetch()`.
66
+
67
+ ## Generated record deletion
68
+
69
+ Request ordinary routed deletion explicitly:
73
70
 
74
- Treat that shared resource as the canonical CRUD contract. Do not hand-build
75
- routes, validators, HTTP helpers, or UI before the server resource exists.
71
+ ```bash
72
+ npx --no-install jskit generate crud-ui-generator crud notes \
73
+ --resource-file packages/notes/src/shared/noteResource.js \
74
+ --id-param noteId \
75
+ --display-fields title,body \
76
+ --parent-title contextual \
77
+ --navigation-role primary \
78
+ --delete-confirmation
79
+ ```
80
+
81
+ `--delete-confirmation` requires generated list and view pages and a shared
82
+ resource with a `DELETE` operation. It supports a custom `--id-param` and fails
83
+ clearly when the contract is unsupported. The view uses the public
84
+ `CrudViewScreen` `actions` slot and `useCrudDeleteAction()`. A Vuetify alert
85
+ dialog provides Cancel/Delete; `useCommand()` owns pending/error state and the
86
+ resource request; success invalidates the CRUD list and navigates there. Do not
87
+ inspect private `users-web` code, add a page transport, or use raw `fetch()`.
76
88
 
77
- Use generated high-level seams where they fit:
89
+ ## Strict temporal values
78
90
 
79
- - `useCrudListScreen()`, `useCrudViewScreen()`, and
80
- `useCrudAddEditScreen()` for routed screens.
81
- - `useCrudList()`, `useCrudView()`, and `useCrudAddEdit()` for routed CRUD
82
- behavior.
83
- - `useList()`, `useView()`, `useAddEdit()`, `useCommand()`, and
84
- `useEndpointResource()` for non-standard contracts.
91
+ With `json-rest-schema` 1.0.17, temporal resource values are strings:
85
92
 
86
- CRUD hooks derive their JSON:API transport from the shared resource. Do not
87
- pass a custom transport or use raw `fetch()` for standard CRUD behavior.
93
+ - `date`: `YYYY-MM-DD`
94
+ - `time`: offset-free `HH:MM[:SS[.fraction]]`
95
+ - `dateTime`: RFC 3339 with seconds and `Z` or a numeric offset
96
+
97
+ Do not pass JavaScript `Date` objects through resource validation; convert at
98
+ the boundary (normally `toISOString()` for `dateTime`). `timestamp` is removed:
99
+ after checking the existing unit, use `epochMilliseconds` or `epochSeconds`.
100
+ Honor `temporalPrecision` without silently truncating fractions. Generated CRUD
101
+ serializes supported database temporal output; custom repositories must return
102
+ strict strings and write ISO/RFC 3339 strings themselves. There is no compatibility alias.
88
103
 
89
104
  ## Migration ownership
90
105
 
91
- Do not hand-write a competing migration for a table the CRUD server generator
92
- will own. Never modify or replace its installed baseline migration. Express a
93
- later schema change as a new immutable additive migration owned by the
94
- app-local package:
106
+ Never compete with or alter a generator-owned baseline migration. Later schema
107
+ changes are immutable additive migrations owned by the app-local package:
95
108
 
96
109
  ```bash
97
- npx jskit create migration \
98
- --package <package-id> \
99
- --id <migration-id>
100
- npx jskit migrations package <package-id>
110
+ npx --no-install jskit create migration --package <package-id> --id <id>
111
+ npx --no-install jskit migrations package <package-id>
101
112
  npm run db:migrate
102
113
  ```
103
114
 
104
- If ordinary persisted data genuinely cannot use the generated CRUD lane, stop
105
- and obtain explicit developer approval. Record the approved exception in
106
- `.jskit/WORKBOARD.md` and `.jskit/table-ownership.json`; also record it in
107
- `.jskit/APP_BLUEPRINT.md` when it changes durable architecture.
108
-
109
- Before sign-off, rebuild the full migration chain in a fresh disposable
110
- database, compare the recreated schema with the intended schema, test relevant
111
- ownership boundaries, run JSKIT Doctor, and run the project verifier.
115
+ An exceptional persistence lane requires explicit developer approval recorded
116
+ in `.jskit/WORKBOARD.md` and `.jskit/table-ownership.json`, plus
117
+ `.jskit/APP_BLUEPRINT.md` when architectural. Before sign-off, rebuild from
118
+ zero in a fresh disposable database, compare schema, test ownership boundaries,
119
+ run Doctor, and run the verifier.
@@ -1,73 +1,67 @@
1
1
  # UI operations
2
2
 
3
- Read this reference for routes, pages, surfaces, placements, responsive UI, or
4
- browser verification.
3
+ Read this for routes, pages, surfaces, placements, responsive UI, and browser
4
+ verification.
5
5
 
6
6
  ## Pages, surfaces, and placements
7
7
 
8
- Take the intended surface from the request and app authority; do not silently
9
- default new functionality to `app`. Surface choice controls routes, access,
10
- placement visibility, and often ownership.
11
-
12
- For a normal app-owned non-CRUD page, first inspect the generator and semantic
13
- placements, then generate the page:
8
+ Take the surface from the request/app authority; it controls routes, access,
9
+ placement visibility, and often ownership. For a normal non-CRUD page:
14
10
 
15
11
  ```bash
16
- npx jskit show ui-generator --details
17
- npx jskit list-placements
18
- npx jskit generate ui-generator page <route-file> --name <name>
12
+ npx --no-install jskit show ui-generator --details
13
+ npx --no-install jskit list-placements
14
+ npx --no-install jskit generate ui-generator page <route-file> --name <name>
19
15
  ```
20
16
 
21
- Use `--navigation-role primary` for main destinations and `secondary`,
22
- `detail`, `workflow`, or `none` as appropriate for other routes. Use
23
- `--link-placement <semantic-id>` when the link belongs in a non-default slot.
24
- Use `npx jskit list-placements --concrete` only when diagnosing concrete
25
- outlets. Author normal app UI against semantic placements such as
26
- `shell.primary-nav` or `page.section-nav`, not raw `host:position` outlets.
17
+ Choose the truthful `--navigation-role`. Override with semantic
18
+ `--link-placement <area.slot>` when needed; use concrete placements only for
19
+ diagnosis. Let the generator create the route and placement before adapting
20
+ app-owned output. State why before hand-writing a normal page.
21
+
22
+ ## Managed app-owned files
27
23
 
28
- Let the generator create both the route and placement entry before adapting
29
- app-owned output. If a normal page cannot use the generator, state the concrete
30
- reason before hand-writing it.
24
+ “App-owned” means customizable, not disposable while its package is installed.
25
+ Never delete or rename a path recorded in `.jskit/lock.json`; adapt managed
26
+ infrastructure tests in place. When replacing a starter route, update its smoke
27
+ test to the new canonical route instead of deleting baseline browser coverage.
28
+ Doctor must continue to report a missing managed test.
31
29
 
32
30
  ## Screen behavior
33
31
 
34
- - Keep app screens phone/task-first, primary actions drawer-independent, and
35
- 48 px tap targets.
36
- - Use a page header and direct `v-sheet`, not nested-card architecture.
37
- - Provide resource-named loading, empty, error, permission, and retry states.
38
- - Use compact searchable cards and medium/expanded tables for generated CRUD
39
- lists where appropriate.
32
+ - Keep screens phone/task-first with drawer-independent primary actions and
33
+ 48 px targets. Use a page header and direct `v-sheet`, not nested cards.
34
+ - Provide named loading, empty, error, permission, and retry states. Generated
35
+ lists use searchable compact cards and medium/expanded tables where suitable.
40
36
  - Extend shared CRUD screens through slots. For custom sibling/child links,
41
37
  resolve current dynamic params with their runtime to an absolute URL/route
42
38
  object; never bind its route-template/relative string raw to Vue Router `to`.
43
- - Use `defineCrudListRowActions(...)` and the generated page-local row-action
44
- seam for row commands. Use shared filter definitions for structured filters.
45
- - Keep ordinary read failures local with runtime `loadError` and retry state.
46
- Use `useCommand()` or `useUiFeedback()` for user-triggered action feedback.
39
+ - Use page-local row-action/filter definitions. Keep read failures local; use
40
+ `useCommand()` or `useUiFeedback()` for user-triggered action feedback.
47
41
 
48
- ## Browser verification
42
+ ## Adaptive shell drawer
49
43
 
50
- Any change to user-facing behavior needs a Playwright flow exercising that
51
- behavior. Check compact, medium, and expanded widths for generated/template UI
52
- changes, including overflow, clipped text, duplicate navigation, route
53
- placement, primary actions, and tap targets.
44
+ Use Vuetify Material navigation. Compact close dismisses the temporary drawer.
45
+ Wider layouts default to `desktopDrawerClosedMode="rail"`, retaining primary
46
+ navigation as a rail. Use `desktopDrawerClosedMode="hidden"` only when another
47
+ discoverable navigation affordance exists. Do not create a second drawer/menu
48
+ registry or imitate the rail with CSS.
54
49
 
55
- Use relative URLs in tests. The shared JSKIT Playwright config owns the base
56
- URL, server lifecycle, and storage state. When `PLAYWRIGHT_BASE_URL` is set,
57
- do not start another server. When
58
- `VIBE64_PLAYWRIGHT_STORAGE_STATE` is supplied, do not print, commit, or retain
59
- it and do not use local login bypasses. Do not install a browser when the
60
- environment supplies a managed runner.
50
+ ## Browser verification
61
51
 
62
- For a direct localhost app with development auth bypass explicitly enabled,
63
- use `loginAsExistingUser()` from `@jskit-ai/auth-web/test/playwright`; never
64
- send the bypass secret through browser globals, query parameters, or client
65
- environment variables.
52
+ Exercise user-facing changes with Playwright at compact, medium, and expanded
53
+ widths. Check overflow, clipped text, duplicate navigation, route placement,
54
+ actions, and target sizes. Use relative URLs; shared JSKIT config owns base URL,
55
+ server, and storage state. With `PLAYWRIGHT_BASE_URL`, start no server. Never
56
+ print/commit `VIBE64_PLAYWRIGHT_STORAGE_STATE`, use a local bypass with it, or
57
+ install a browser when a managed runner supplies one.
66
58
 
67
- After a successful UI test, record the verification when the app supports it:
59
+ For explicitly enabled direct-local auth, use `loginAsExistingUser()` from
60
+ `@jskit-ai/auth-web/test/playwright`; never expose its secret to browser code,
61
+ URLs, or client env. Record a successful supported run with:
68
62
 
69
63
  ```bash
70
- npx jskit app verify-ui \
64
+ npx --no-install jskit app verify-ui \
71
65
  --command "<exact successful Playwright command>" \
72
66
  --feature "<changed behavior>" \
73
67
  --auth-mode <dev-auth-login-as|session-bootstrap>