@jskit-ai/agent-docs 0.1.27 → 0.1.28

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 (40) hide show
  1. package/DISTR_AGENT.md +1 -0
  2. package/guide/agent/app-extras/assistant.md +2 -2
  3. package/guide/agent/app-extras/mobile-capacitor.md +10 -3
  4. package/guide/agent/app-extras/realtime.md +11 -11
  5. package/guide/agent/app-setup/a-more-interesting-shell.md +52 -44
  6. package/guide/agent/app-setup/authentication.md +40 -22
  7. package/guide/agent/app-setup/console.md +5 -5
  8. package/guide/agent/app-setup/database-layer.md +15 -15
  9. package/guide/agent/app-setup/initial-scaffolding.md +37 -5
  10. package/guide/agent/app-setup/multi-homing.md +28 -28
  11. package/guide/agent/app-setup/users.md +22 -22
  12. package/guide/agent/app-setup/working-with-the-jskit-cli.md +13 -6
  13. package/guide/agent/generators/advanced-cruds.md +111 -68
  14. package/guide/agent/generators/crud-generators.md +5 -5
  15. package/guide/agent/generators/ui-generators.md +15 -13
  16. package/guide/agent/index.md +1 -1
  17. package/package.json +1 -1
  18. package/patterns/INDEX.md +4 -1
  19. package/patterns/client-requests.md +23 -6
  20. package/patterns/crud-repository-mapping.md +1 -1
  21. package/patterns/crud-scaffolding.md +10 -1
  22. package/patterns/filters.md +28 -10
  23. package/patterns/generated-ui-contract-tracking.md +62 -0
  24. package/patterns/page-scaffolding.md +17 -0
  25. package/patterns/placements.md +60 -0
  26. package/patterns/surfaces.md +5 -1
  27. package/patterns/ui-testing.md +3 -0
  28. package/reference/autogen/KERNEL_MAP.md +30 -1
  29. package/reference/autogen/packages/assistant.md +1 -1
  30. package/reference/autogen/packages/crud-ui-generator.md +19 -8
  31. package/reference/autogen/packages/kernel.md +38 -6
  32. package/reference/autogen/packages/realtime.md +1 -0
  33. package/reference/autogen/packages/shell-web.md +35 -3
  34. package/reference/autogen/packages/ui-generator.md +26 -5
  35. package/reference/autogen/packages/users-core.md +2 -0
  36. package/reference/autogen/packages/users-web.md +92 -3
  37. package/reference/autogen/packages/workspaces-web.md +3 -1
  38. package/reference/autogen/tooling/jskit-cli.md +24 -8
  39. package/templates/app/AGENTS.md +1 -0
  40. package/workflow/bootstrap.md +1 -0
@@ -249,6 +249,8 @@ packages/contacts/
249
249
 
250
250
  src/pages/w/[workspaceSlug]/admin/contacts/
251
251
  index.vue
252
+ listBulkActions.js
253
+ listFilters.js
252
254
  new.vue
253
255
  [contactId]/index.vue
254
256
  [contactId]/edit.vue
@@ -439,6 +441,8 @@ src/pages/w/[workspaceSlug]/admin/contacts/
439
441
  new.vue
440
442
  [contactId]/index.vue
441
443
  [contactId]/edit.vue
444
+ listBulkActions.js
445
+ listFilters.js
442
446
  _components/CrudAddEditForm.vue
443
447
  _components/CrudAddEditFormFields.js
444
448
  ```
@@ -449,13 +453,13 @@ This is the list-page container.
449
453
 
450
454
  Its job is usually to:
451
455
 
452
- - call `useCrudList()`
453
- - bind `records.searchQuery`
454
- - render list rows
456
+ - call `useCrudListScreen()`
457
+ - pass page-local `listFilters` and `listBulkActions`
458
+ - render the shared `CrudListScreen`
455
459
  - resolve list/view/edit/new URLs
456
460
  - pass route query state through when navigating deeper
457
461
 
458
- The actual list machinery lives uphill in `users-web` composables and the shared resource contract.
462
+ The actual list machinery lives in `users-web` shared screen composables and the shared resource contract.
459
463
 
460
464
  ### `[contactId]/index.vue`
461
465
 
@@ -463,8 +467,8 @@ This is the view-page container.
463
467
 
464
468
  Its job is usually to:
465
469
 
466
- - call `useCrudView()`
467
- - render the selected record
470
+ - call `useCrudViewScreen()`
471
+ - render the shared `CrudViewScreen`
468
472
  - resolve "back" and "edit" navigation
469
473
 
470
474
  Again, the runtime behavior is mostly uphill. The page is a route-level composition layer.
@@ -475,9 +479,9 @@ These are add/edit route wrappers.
475
479
 
476
480
  They usually:
477
481
 
478
- - call `useCrudAddEdit()`
482
+ - call `useCrudAddEditScreen()`
479
483
  - wire lookup runtime for lookup-backed fields
480
- - hand the form runtime into the shared form component
484
+ - hand the form runtime into the shared `CrudAddEditScreen`
481
485
 
482
486
  These files are mostly containers. That is deliberate.
483
487
 
@@ -735,15 +739,14 @@ The safe mental model is:
735
739
 
736
740
  ### `_components/CrudAddEditForm.vue`
737
741
 
738
- This is the shared rendering shell for the add/edit form.
742
+ This is the generated field bridge for the shared add/edit screen.
739
743
 
740
744
  It owns:
741
745
 
742
- - the card/surface layout
743
- - save/cancel buttons
744
746
  - which set of generated form fields is rendered in `new` vs `edit`
747
+ - lookup field prop forwarding into those fields
745
748
 
746
- It does **not** own persistence logic.
749
+ It does **not** own persistence logic or the shared screen chrome. `CrudAddEditScreen` from `users-web` owns the common title, load state, retry action, save/cancel action row, and form surface.
747
750
 
748
751
  ### `_components/CrudAddEditFormFields.js`
749
752
 
@@ -766,8 +769,8 @@ That is navigation wiring, not CRUD logic.
766
769
 
767
770
  A generated CRUD works because several layers cooperate:
768
771
 
769
- 1. the route page calls `useCrudList()`, `useCrudView()`, or `useCrudAddEdit()`
770
- 2. those composables use the shared resource contract and runtime helpers from `users-web`
772
+ 1. the route page calls `useCrudListScreen()`, `useCrudViewScreen()`, or `useCrudAddEditScreen()`
773
+ 2. those screen composables configure the lower-level list/view/add-edit runtimes from `users-web`
771
774
  3. the request hits the HTTP route from `registerRoutes.js`
772
775
  4. the route executes an action from `actions.js`
773
776
  5. the action delegates to the service in `service.js`
@@ -789,8 +792,9 @@ Use this rule of thumb when deciding where to edit:
789
792
  | Change default ordering, searchable fields, or ownership autofilter | `contactResource.js` | The shared resource is the single source of truth for both CRUD contract and internal JSON:API resource config |
790
793
  | Change SQL, joins, parent filters, or advanced search | `repository.js` | This is the data-access layer |
791
794
  | Add cross-record or domain rules on save/delete | `service.js` | This is business logic |
792
- | Change page layout and display behavior | the route pages and app-owned composables | This is presentation |
793
- | Change form field layout and inputs | `_components/CrudAddEditForm.vue` and `CrudAddEditFormFields.js` | This is the generated form layer |
795
+ | Change shared CRUD screen chrome, load states, or retry behavior | `users-web` shared screen components | Generated pages consume the shared screen contract |
796
+ | Change page-specific display behavior | the route pages, generated slots, and app-owned composables | This is presentation |
797
+ | Change form field layout and inputs | `_components/CrudAddEditForm.vue` and `CrudAddEditFormFields.js` | This is the generated form field layer |
794
798
 
795
799
  ## How mature CRUDs grow
796
800
 
@@ -824,10 +828,10 @@ This is the default generated list-page pattern for the `contacts` resource from
824
828
 
825
829
  #### Client side
826
830
 
827
- Enable query search in the list page:
831
+ The generated list page delegates search wiring to the shared list screen:
828
832
 
829
833
  ```js
830
- const records = useCrudList({
834
+ const screen = useCrudListScreen({
831
835
  resource: uiResource,
832
836
  apiSuffix: "/contacts",
833
837
  search: {
@@ -841,15 +845,14 @@ const records = useCrudList({
841
845
  });
842
846
  ```
843
847
 
844
- Then bind the input:
848
+ Then render the shared screen:
845
849
 
846
850
  ```vue
847
- <v-text-field
848
- v-model="records.searchQuery"
849
- :loading="records.isSearchDebouncing"
850
- />
851
+ <CrudListScreen :screen="screen" />
851
852
  ```
852
853
 
854
+ The shared screen binds the search input and passes the search query into the list request.
855
+
853
856
  The client runtime debounces the search input, writes the query string to `q`, and trims the list back to the first page when search changes.
854
857
 
855
858
  #### Server side
@@ -1006,27 +1009,33 @@ Important limits:
1006
1009
 
1007
1010
  For the agent-facing quick rule, see `patterns/crud-repository-mapping.md`.
1008
1011
 
1009
- ### Pattern 3: structured list filters from one shared definition
1012
+ ### Pattern 3: client-side structured list filters
1013
+
1014
+ Generated CRUD list pages include a client-side filter seam by default. The generated page imports `./listFilters.js` and passes `listFilters` into `useCrudListScreen(...)`. The shared list screen builds the filter runtime, passes the resulting query params into the list request, and renders `CrudListFilterSurface`.
1015
+
1016
+ If `listFilters.js` is empty, the filter surface renders nothing.
1010
1017
 
1011
- This is the default pattern once a CRUD list needs real filters.
1018
+ Use this first when adding filters. The server still needs explicit support for any query params you declare; JSKIT does not infer server filter semantics from the UI.
1012
1019
 
1013
1020
  Do not hand-build:
1014
1021
 
1015
1022
  - one filter shape in the page
1016
- - a second filter shape in `listQueryValidators.js`
1017
- - and a third filter shape in `repository.js`
1023
+ - custom chip/reset/query-param state
1024
+ - a second copy of the same client filter definitions
1018
1025
 
1019
1026
  Instead:
1020
1027
 
1021
- 1. define the filters once in a shared CRUD-package module
1022
- 2. build the server runtime from that definition with `createCrudListFilters(...)`
1023
- 3. choose the route/action invalid-value contract explicitly on each structured filter field with `createCrudListFilterQueryField(...)`
1024
- 4. build the client runtime from that same definition with `useCrudListFilters(...)`
1028
+ 1. edit the generated page-local `listFilters.js`
1029
+ 2. let `useCrudListScreen(...)` wire the filter query params into the list request
1030
+ 3. let `CrudListFilterSurface` render controls, chips, clear-one, and clear-all behavior
1031
+ 4. add explicit server support separately for the same query params
1025
1032
 
1026
- For example, a shared filter-definition module can look like this:
1033
+ For example, a generated page-local filter-definition module can look like this:
1027
1034
 
1028
1035
  ```js
1029
- export const CONTACTS_LIST_FILTER_DEFINITIONS = Object.freeze({
1036
+ import { defineCrudListFilters } from "@jskit-ai/users-web/client/filters";
1037
+
1038
+ const listFilters = defineCrudListFilters({
1030
1039
  onlyStaff: {
1031
1040
  type: "flag",
1032
1041
  label: "Staff"
@@ -1040,60 +1049,94 @@ export const CONTACTS_LIST_FILTER_DEFINITIONS = Object.freeze({
1040
1049
  label: "Archived"
1041
1050
  }
1042
1051
  });
1052
+
1053
+ export { listFilters };
1054
+ ```
1055
+
1056
+ ### Pattern 3B: client-side bulk list actions
1057
+
1058
+ Generated CRUD list pages also include a client-side bulk-action seam by default. The generated page imports `./listBulkActions.js` and passes `listBulkActions` into `useCrudListScreen(...)`. The shared list screen builds the bulk-action runtime and renders `CrudListBulkActionSurface`.
1059
+
1060
+ If `listBulkActions.js` is empty, selection controls and the bulk action bar stay hidden.
1061
+
1062
+ Use this first when adding selected-record actions. JSKIT does not invent server operations such as delete, archive, approve, or export; the action definition owns that behavior.
1063
+
1064
+ For example:
1065
+
1066
+ ```js
1067
+ import { defineCrudListBulkActions } from "@jskit-ai/users-web/client/bulkActions";
1068
+
1069
+ const listBulkActions = defineCrudListBulkActions([
1070
+ {
1071
+ key: "archive",
1072
+ label: "Archive",
1073
+ async run({ selectedIds, clearSelection, reload }) {
1074
+ await archiveContacts(selectedIds);
1075
+ clearSelection();
1076
+ await reload();
1077
+ }
1078
+ }
1079
+ ]);
1080
+
1081
+ export { listBulkActions };
1043
1082
  ```
1044
1083
 
1084
+ The generated runtime passes action handlers:
1085
+
1086
+ - `selectedIds`
1087
+ - `ids` as an alias for `selectedIds`
1088
+ - `selectedRecords`
1089
+ - `clearSelection()`
1090
+ - `records`
1091
+ - `reload`
1092
+
1093
+ Keep bulk action definitions page-local unless another page needs to share them.
1094
+
1045
1095
  #### Exact file checklist
1046
1096
 
1047
1097
  For a generated CRUD, treat this as the concrete file plan:
1048
1098
 
1099
+ - edit `src/pages/.../contacts/listFilters.js`
1100
+ - make sure the matching server route/action/repository code already accepts and applies the declared query params
1101
+
1102
+ If the filter contract should be shared with server code, promote it into a CRUD package module and import it from both sides:
1103
+
1049
1104
  - create `packages/contacts/src/shared/contactListFilters.js`
1050
1105
  - update `packages/contacts/src/server/registerRoutes.js` so the list route query validator lists structured filter params explicitly with `createCrudListFilterQueryField(...)`, unless you already extracted list-query composition into `packages/contacts/src/server/listQueryValidators.js`
1051
1106
  - update `packages/contacts/src/server/actions.js` so the list action input validator includes that same explicit contract choice
1052
1107
  - update `packages/contacts/src/server/repository.js` so the list query path builds the `createCrudListFilters(...)` runtime and calls `applyQuery(...)`
1053
- - update the app-owned list page or list-runtime composable, usually under `src/pages/.../contacts/` or `src/composables/...`, so it builds `useCrudListFilters(...)`, passes `queryParams` into `useCrudList(...)`, and renders chips / reset behavior from that runtime
1054
-
1055
- The only file in that list that is normally **new** is the shared module:
1056
-
1057
- - `packages/contacts/src/shared/contactListFilters.js`
1058
-
1059
- The others are normally edits to the generated CRUD package and the app-owned page layer that already exist.
1060
1108
 
1061
1109
  #### Client side
1062
1110
 
1063
- Build the filter runtime directly from the shared definitions:
1111
+ Generated CRUD list pages pass the page-local filter definitions into the shared list screen:
1064
1112
 
1065
1113
  ```js
1066
- const listFilters = useCrudListFilters(CONTACTS_LIST_FILTER_DEFINITIONS);
1114
+ import { listFilters } from "./listFilters.js";
1067
1115
 
1068
- const records = useCrudList({
1116
+ const screen = useCrudListScreen({
1069
1117
  resource: uiResource,
1070
1118
  apiSuffix: "/contacts?include=pets",
1071
- search: {
1072
- enabled: true,
1073
- mode: "query"
1074
- },
1075
- queryParams: listFilters.queryParams,
1076
- syncToRoute: {
1077
- enabled: true,
1078
- search: true,
1079
- queryParams: true,
1080
- queryParamBlacklist: ["include", "cursor", "limit"]
1081
- }
1119
+ listFilters,
1120
+ routeQueryBlacklist: Object.freeze(["include", "cursor", "limit"])
1082
1121
  });
1083
1122
  ```
1084
1123
 
1085
- That gives you, from one place:
1124
+ ```vue
1125
+ <CrudListScreen :screen="screen" />
1126
+ ```
1127
+
1128
+ Inside that shared screen runtime, `useCrudListFilters(...)` gives the list:
1086
1129
 
1087
- - `listFilters.values`
1088
- - `listFilters.queryParams`
1089
- - `listFilters.presets`
1090
- - `listFilters.activeChips`
1091
- - `listFilters.hasActiveFilters`
1092
- - `listFilters.clearChip(...)`
1093
- - `listFilters.clearFilters()`
1094
- - `listFilters.toggle(...)` for flag filters
1095
- - `listFilters.applyPreset(...)`
1096
- - `listFilters.matchesPreset(...)`
1130
+ - `filterRuntime.values`
1131
+ - `filterRuntime.queryParams`
1132
+ - `filterRuntime.presets`
1133
+ - `filterRuntime.activeChips`
1134
+ - `filterRuntime.hasActiveFilters`
1135
+ - `filterRuntime.clearChip(...)`
1136
+ - `filterRuntime.clearFilters()`
1137
+ - `filterRuntime.toggle(...)` for flag filters
1138
+ - `filterRuntime.applyPreset(...)`
1139
+ - `filterRuntime.matchesPreset(...)`
1097
1140
 
1098
1141
  So the same runtime owns:
1099
1142
 
@@ -1247,13 +1290,13 @@ async function list(query = {}, callOptions = {}) {
1247
1290
 
1248
1291
  #### Best practices
1249
1292
 
1250
- - Put the filter definitions in the CRUD package, not the page. Both server and client need them.
1293
+ - Keep client-only filters in the generated page-local `listFilters.js`. Move definitions into a CRUD package only when server code or another page needs to share them.
1251
1294
  - Keep the filter keys identical all the way through: definition key, query param key, and repository meaning.
1252
1295
  - Prefer `createCrudListFilterQueryField(...)` plus `createCrudListFilterQuerySchema(...)` at route/action boundaries so accepted structured-filter params stay visible in app code.
1253
1296
  - Use `type: "presence"` for null/not-null filters such as assigned vs unassigned storage. Do not model those as custom enums plus `applyQuery(...)` overrides unless the SQL semantics are genuinely different from `whereNotNull(...)` / `whereNull(...)`.
1254
1297
  - Use `createCrudListFilters(...)` unless the list semantics are truly unusual.
1255
1298
  - Use `q` for free-text and explicit query params for structured filters.
1256
- - Run `jskit doctor` after wiring filters. It flags inline/local filter-definition objects passed into `useCrudListFilters(...)` or `createCrudListFilters(...)`.
1299
+ - Run `jskit doctor` after wiring filters. It flags inline filter-definition objects passed into `useCrudListFilters(...)` or `createCrudListFilters(...)`.
1257
1300
 
1258
1301
  ### Pattern 4: lookup-backed structured filters
1259
1302
 
@@ -1265,7 +1308,7 @@ This is the next real-world step: filters like `supplierContactId`, `locationId`
1265
1308
 
1266
1309
  The right pattern is:
1267
1310
 
1268
- 1. keep the lookup filter in the shared definitions
1311
+ 1. keep the lookup filter in `listFilters.js` or in shared definitions when server code also imports it
1269
1312
  2. use `useCrudListFilters(...)` for state, chips, and query params
1270
1313
  3. use `useCrudListFilterLookups(...)` for option loading and label resolution
1271
1314
 
@@ -262,7 +262,7 @@ npx jskit generate crud-server-generator scaffold \
262
262
 
263
263
  This creates an app-local package under `packages/contacts/`.
264
264
 
265
- If the table should be CRUD-owned now but should **not** expose public HTTP CRUD routes yet, add:
265
+ If the table should already be CRUD-owned but should **not** expose public HTTP CRUD routes yet, add:
266
266
 
267
267
  ```bash
268
268
  --internal
@@ -322,7 +322,7 @@ npm run devlinks
322
322
 
323
323
  The same rule applies after later server scaffolds such as `addresses` and `comments`. The UI generator can still read the generated resource file directly, but the app runtime needs the local package install boundary to be completed before the CRUD can boot normally.
324
324
 
325
- For standard CRUDs, that file is now intentionally compact. It uses `defineCrudResource(...)` from `@jskit-ai/resource-crud-core`, authors the canonical `schema` / `searchSchema` / `defaultSort` / `autofilter` shape once, and lets JSKIT derive the standard CRUD operation contracts from it.
325
+ For standard CRUDs, that file is intentionally compact. It uses `defineCrudResource(...)` from `@jskit-ai/resource-crud-core`, authors the canonical `schema` / `searchSchema` / `defaultSort` / `autofilter` shape once, and lets JSKIT derive the standard CRUD operation contracts from it.
326
326
 
327
327
  ### Step 3: scaffold the UI
328
328
 
@@ -498,7 +498,7 @@ It will be the block added for the `w/[workspaceSlug]/admin/contacts/[contactId]
498
498
 
499
499
  Depending on what outlets already exist in the app, that block may try to place `Addresses` in the shell menu or in the contact subpages outlet. For this example, remove it either way and keep navigation manual from the contact view.
500
500
 
501
- This is an intentional manual cleanup for now:
501
+ This workflow has one intentional manual cleanup:
502
502
 
503
503
  - keep the routed pages
504
504
  - remove the auto menu link
@@ -657,7 +657,7 @@ If the contact view page should stay visible while child comment routes render u
657
657
  npx jskit generate ui-generator add-subpages \
658
658
  w/[workspaceSlug]/admin/contacts/[contactId]/index.vue \
659
659
  --title "Contact" \
660
- --subtitle "View and manage this contact."
660
+ --subtitle "Contact activity and notes."
661
661
  ```
662
662
 
663
663
  That is the point where the comments example deliberately overlaps with the `ui-generator` chapter. This pattern needs both:
@@ -772,4 +772,4 @@ They become one workflow:
772
772
  2. scaffold the server/resource package
773
773
  3. scaffold only the UI shape that the feature actually needs
774
774
 
775
- The next chapter is [Advanced CRUDs](/guide/generators/advanced-cruds). Read it as the structural follow-on to this one: this chapter teaches how to generate the CRUD, and the next one teaches how to reason about the files you now own.
775
+ The next chapter is [Advanced CRUDs](/guide/generators/advanced-cruds). Read it as the structural follow-on to this one: this chapter teaches how to generate the CRUD, and the next one teaches how to reason about the generated files the app owns.
@@ -80,7 +80,7 @@ The important default is this:
80
80
  - if JSKIT sees no nearer routed host, the new page gets a normal shell/menu placement
81
81
  - if JSKIT does see a nearer routed host, the new page gets linked into that host instead
82
82
 
83
- Open the app and you now have a real `/reports` page plus a real shell link for it.
83
+ Open the app to see a real `/reports` page plus a real shell link for it.
84
84
 
85
85
  ### Customizing the generated menu link
86
86
 
@@ -217,7 +217,7 @@ After the command, the page gains three important pieces:
217
217
  - a `ShellOutlet` for the child-page tabs
218
218
  - `RouterView`
219
219
 
220
- That means the page can now keep rendering shared content while child routes render underneath it.
220
+ That lets the page keep rendering shared content while child routes render underneath it.
221
221
 
222
222
  This is the first big distinction in this chapter:
223
223
 
@@ -247,7 +247,7 @@ The route page is still a normal page file. What changes is the inferred placeme
247
247
  - no parent host found -> shell/menu entry
248
248
  - nearest parent host found -> child link inside that host
249
249
 
250
- So JSKIT is not switching to a different generator. It is switching the inferred placement behavior because the route tree now has a routed host above the new page.
250
+ So JSKIT is not switching to a different generator. The route tree has a routed host above the new page, so the inferred placement behavior follows that host.
251
251
 
252
252
  ### Making a child page the default landing route
253
253
 
@@ -358,7 +358,7 @@ Then upgrade that page into a host:
358
358
  npx jskit generate ui-generator add-subpages \
359
359
  home/contacts/[contactId].vue \
360
360
  --title "Contact" \
361
- --subtitle "View and manage this contact."
361
+ --subtitle "Contact activity and notes."
362
362
  ```
363
363
 
364
364
  This time the parent is a file route, not an `index.vue` route.
@@ -436,13 +436,13 @@ This is different from `page` in a very important way:
436
436
 
437
437
  So if the thing you are adding should live *inside* an existing shell region, not at its own route, `placed-element` is the right command.
438
438
 
439
- By default, the element goes into:
439
+ By default, the element targets the semantic status placement:
440
440
 
441
441
  ```text
442
- shell-layout:top-right
442
+ shell.status
443
443
  ```
444
444
 
445
- That is why this is such a good command for widgets, status panels, and compact shell extensions.
445
+ That is why this is such a good command for widgets, status panels, and compact shell extensions. The default shell topology maps `shell.status` to the concrete shell status outlet for each layout class.
446
446
 
447
447
  ### When `--surface` matters
448
448
 
@@ -469,19 +469,19 @@ A practical example is:
469
469
  npx jskit generate ui-generator placed-element \
470
470
  --name "Ops Panel" \
471
471
  --surface admin \
472
- --placement shell-layout:top-right
472
+ --placement shell.status
473
473
  ```
474
474
 
475
- `shell-layout:top-right` can exist across several surfaces. If your app has several enabled surfaces, `--surface admin` tells JSKIT which one this element is actually meant for.
475
+ `shell.status` can be global across several surfaces. If your app has several enabled surfaces, `--surface admin` tells JSKIT which one this element is actually meant for.
476
476
 
477
477
  So the rule of thumb is:
478
478
 
479
- - page-owned outlet target -> surface is often inferable
480
- - shared shell target in a multi-surface app -> pass `--surface`
479
+ - page-owned semantic placement target -> surface is often inferable
480
+ - shared shell semantic placement in a multi-surface app -> pass `--surface`
481
481
 
482
482
  ### `--placement`
483
483
 
484
- Use `--placement` when the default target `shell-layout:top-right` is not what you want.
484
+ Use `--placement` when the default target `shell.status` is not what you want.
485
485
 
486
486
  This is the option that answers:
487
487
 
@@ -489,6 +489,8 @@ This is the option that answers:
489
489
 
490
490
  In practice, it is the first override you will use for `placed-element`.
491
491
 
492
+ `--placement` expects a semantic placement id such as `shell.status`, `shell.global-actions`, or `settings.sections`. Concrete `host:position` outlets are exposed through topology, not used as normal placed-element authoring targets.
493
+
492
494
  ### `--path`
493
495
 
494
496
  Use `--path` when the component file should live somewhere other than `src/components`.
@@ -682,4 +684,4 @@ That is why the commands fit together cleanly:
682
684
  - `page` can then create nested child routes under those hosts
683
685
  - `placed-element` and `outlet` handle the non-routed side of the same UI system
684
686
 
685
- Once the UI you want is no longer just page structure and starts needing real database-backed list/view/new/edit behavior, move to the CRUD generators chapter.
687
+ Once the UI you want needs real database-backed list/view/new/edit behavior instead of only page structure, move to the CRUD generators chapter.
@@ -4,7 +4,7 @@
4
4
 
5
5
  This guide is the main hands-on path through JSKIT.
6
6
 
7
- It now starts with a fast reproducible Quickstart, then steps back to the smaller scaffold-first chapters that explain how the app is assembled piece by piece. After that, it introduces the database-backed users layer, expands into console and workspace-aware app structure, and adds a small `App extras` section for optional runtime packages such as the Android Capacitor shell, realtime, and assistant. Finally, the guide breaks out generator-specific workflows into their own section.
7
+ It starts with a fast reproducible Quickstart, then steps back to the smaller scaffold-first chapters that explain how the app is assembled piece by piece. After that, it introduces the database-backed users layer, expands into console and workspace-aware app structure, and adds a small `App extras` section for optional runtime packages such as the Android Capacitor shell, realtime, and assistant. Finally, the guide breaks out generator-specific workflows into their own section.
8
8
 
9
9
  ## Table of Contents
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "description": "Distributed JSKIT agent workflows, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
package/patterns/INDEX.md CHANGED
@@ -26,10 +26,12 @@ How to use it:
26
26
  - `page-redirects.md`
27
27
  - live actions, checkbox, toggle, patch button, inline action, `useCommand()`
28
28
  - `live-actions.md`
29
- - ajax, fetch, API call, request, endpoint, HTTP client, `useList()`, `useView()`, `useAddEdit()`, `useEndpointResource()`, `usersWebHttpClient`
29
+ - ajax, fetch, API call, request, endpoint, HTTP client, `useCrudListScreen()`, `useCrudViewScreen()`, `useCrudAddEditScreen()`, `useList()`, `useView()`, `useAddEdit()`, `useEndpointResource()`, `usersWebHttpClient`
30
30
  - `client-requests.md`
31
31
  - playwright, browser test, e2e, ui verification, authenticated ui test, test auth, dev login as, dev auth bypass
32
32
  - `ui-testing.md`
33
+ - generated UI contract, design contract, navigation roles, density, placeholder copy, card shells, shared CRUD screens
34
+ - `generated-ui-contract-tracking.md`
33
35
  - filter, filters, search facets, chips, date range, enum filter, lookup filter, `useCrudListFilters`, `createCrudListFilters`
34
36
  - `filters.md`
35
37
  - `searchSchema`, `search: true`, `applyFilter`, server search, query validators, backend filters, internal JSON REST filters
@@ -49,6 +51,7 @@ How to use it:
49
51
  - [live-actions.md](./live-actions.md)
50
52
  - [client-requests.md](./client-requests.md)
51
53
  - [ui-testing.md](./ui-testing.md)
54
+ - [generated-ui-contract-tracking.md](./generated-ui-contract-tracking.md)
52
55
  - [filters.md](./filters.md)
53
56
  - [server-search.md](./server-search.md)
54
57
  - [crud-repository-mapping.md](./crud-repository-mapping.md)
@@ -6,7 +6,7 @@ Use when:
6
6
  - custom endpoint reads or writes
7
7
  - AJAX questions
8
8
  - replacing raw `fetch(...)` calls
9
- - choosing between `useCommand()`, `useList()`, `useView()`, `useAddEdit()`, and `useEndpointResource()`
9
+ - choosing between shared CRUD screens, `useCommand()`, `useList()`, `useView()`, `useAddEdit()`, and `useEndpointResource()`
10
10
 
11
11
  Rules:
12
12
 
@@ -19,22 +19,31 @@ Rules:
19
19
  Choose the function like this:
20
20
 
21
21
  ```js
22
- // 1. Button/toggle/small mutation
22
+ // 1. Generated CRUD route screen
23
+ const screen = useCrudListScreen({ ... });
24
+
25
+ // 2. Button/toggle/small mutation
23
26
  const command = useCommand({ ... });
24
27
 
25
- // 2. List endpoint
28
+ // 3. List endpoint
26
29
  const list = useList({ ... });
27
30
 
28
- // 3. Single-record endpoint
31
+ // 4. Single-record endpoint
29
32
  const view = useView({ ... });
30
33
 
31
- // 4. Form save flow
34
+ // 5. Form save flow
32
35
  const form = useAddEdit({ ... });
33
36
 
34
- // 5. Truly custom endpoint
37
+ // 6. Truly custom endpoint
35
38
  const resource = useEndpointResource({ ... });
36
39
  ```
37
40
 
41
+ Use the shared CRUD screen wrappers when the route is a generated CRUD page:
42
+
43
+ - `useCrudListScreen()` plus `CrudListScreen` for list route pages
44
+ - `useCrudViewScreen()` plus `CrudViewScreen` for record view route pages
45
+ - `useCrudAddEditScreen()` plus `CrudAddEditScreen` for generated new/edit route pages
46
+
38
47
  Use the CRUD wrappers when they fit:
39
48
 
40
49
  - `useCrudList()` for routed CRUD lists
@@ -55,6 +64,13 @@ Why this is the standard JSKIT shape:
55
64
  - Use `requestQueryParams` for endpoint query strings on list, view, and add/edit runtimes.
56
65
  - Keep `apiUrlTemplate` path-only. Do not put `?include=...` or other query strings in URL templates.
57
66
 
67
+ Error presentation rules:
68
+
69
+ - Resource load failures should stay local to the screen with the runtime's `loadError` state and a retry action.
70
+ - Do not force global banners for ordinary list/view/add-edit load failures; the shell error policy treats `resource-load` as `silent` by default.
71
+ - Use `useUiFeedback()` or `useCommand()` for user-triggered action feedback. Those flows report `action-feedback`, and the app-owned shell error policy decides the channel.
72
+ - Use explicit shell error intents only for exceptional app-level cases: `app-recoverable` for recoverable shell/runtime failures and `blocking` for failures that require user attention.
73
+
58
74
  Avoid:
59
75
 
60
76
  - raw `fetch(...)` for standard page or component work
@@ -62,3 +78,4 @@ Avoid:
62
78
  - manually concatenating scoped route params into API URLs
63
79
  - using a lower-level seam when a higher-level routed CRUD or command runtime already fits
64
80
  - smuggling query params into `apiUrlTemplate`
81
+ - reporting routine resource load errors through hand-written global snackbar/banner calls
@@ -80,7 +80,7 @@ const repositoryRuntime = createCrudResourceRuntime(resource, {
80
80
  });
81
81
  ```
82
82
 
83
- What CRUD core now does for you:
83
+ What CRUD core does for you:
84
84
  - default select columns include only column-backed output fields
85
85
  - create/update write payloads serialize standard writable `date-time` fields centrally
86
86
  - create/update write payloads also apply any explicit field write serializers centrally
@@ -19,12 +19,20 @@ Rules:
19
19
  - For a CRUD-backed entity, start with `jskit generate crud-server-generator scaffold ...`.
20
20
  - Unless the table is already owned by a JSKIT baseline package or is an explicit narrow exception recorded in `.jskit/table-ownership.json`, every persisted app-owned table must go through that server CRUD step first.
21
21
  - That server scaffold is the crucial first step even if no CRUD UI will be created yet.
22
- - If the table should be CRUD-owned now but should not expose public CRUD HTTP routes yet, scaffold it with `jskit generate crud-server-generator scaffold ... --internal` instead of dropping to direct knex or a hand-built pseudo-repository.
22
+ - If the table should already be CRUD-owned but should not expose public CRUD HTTP routes yet, scaffold it with `jskit generate crud-server-generator scaffold ... --internal` instead of dropping to direct knex or a hand-built pseudo-repository.
23
23
  - Create the real table directly in the database before scaffolding. `crud-server-generator` reads the live table shape.
24
24
  - If `crud-server-generator` is going to own the CRUD, do not hand-write a separate CRUD migration for that table. The generator installs and manages the CRUD migration scaffold itself.
25
25
  - Do not scaffold CRUD UI, hand-build CRUD routes, or hand-build CRUD endpoints before the server CRUD package and shared resource file exist.
26
26
  - Treat the generated shared resource file as the canonical CRUD contract for later UI scaffolding and CRUD behavior changes.
27
27
  - `feature-server-generator` is not the default lane for ordinary persisted entities. Use it for workflows or orchestration that sit on top of CRUD-owned tables, or for rare explicit non-CRUD exceptions.
28
+ - Generated CRUD UI must be compact-first. Lists need searchable cards on compact widths and tables only for medium/expanded layouts.
29
+ - Generated CRUD list screens need real loading, empty, and error states. Empty copy should name the resource, such as "No customers yet", and offer the create action when available.
30
+ - Generated CRUD view/new/edit screens should use page headers plus direct sheet panels. Do not use generic card shells as the page architecture.
31
+ - Compact CRUD actions should be reachable without a drawer. Use a mobile-visible primary action or FAB for create flows.
32
+ - Row actions should collapse into an overflow/action menu on compact widths and can be inline on wider layouts.
33
+ - 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.
34
+ - 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.
35
+ - 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`.
28
36
 
29
37
  Meaning of `--internal`:
30
38
 
@@ -38,3 +46,4 @@ Avoid:
38
46
  - starting with `crud-ui-generator` before the server scaffold exists
39
47
  - hand-building CRUD routes or validators that duplicate the generated server resource contract
40
48
  - letting a new app-owned table exist in the live database without either generated CRUD ownership or a documented `.jskit/table-ownership.json` exception
49
+ - accepting table-only CRUD UI as "done" when the route is visible on phone widths