@jskit-ai/agent-docs 0.1.19 → 0.1.21

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.
@@ -16,8 +16,8 @@ So the real order is always:
16
16
  This chapter uses three examples, in increasing complexity:
17
17
 
18
18
  - `contacts`: the baseline full CRUD walkthrough
19
- - `addresses`: a child CRUD with its own routed list page
20
- - `comments`: a child CRUD that lives inside the parent page, with only the child routes being routed
19
+ - `addresses`: a child CRUD with its own routed list page reached from the parent record view
20
+ - `comments`: a child CRUD that lives under the parent record host as a child page
21
21
 
22
22
  The examples assume the guide app already has the database and workspace/admin setup from the earlier chapters. That is why the route roots below live under `w/[workspaceSlug]/admin/...`.
23
23
 
@@ -350,7 +350,7 @@ That is the shape to learn first.
350
350
 
351
351
  Now move to a child CRUD with its own URL.
352
352
 
353
- This is the right pattern when the child collection deserves its own list page.
353
+ This is the right pattern when the child collection deserves its own list page, but should still be reached from a specific parent record.
354
354
 
355
355
  Example route:
356
356
 
@@ -358,12 +358,23 @@ Example route:
358
358
  .../contacts/3/addresses
359
359
  ```
360
360
 
361
- In the guide app's real route tree, that becomes:
361
+ In the guide app's real route tree, the visible URL becomes:
362
362
 
363
363
  ```text
364
364
  w/[workspaceSlug]/admin/contacts/[contactId]/addresses
365
365
  ```
366
366
 
367
+ This example is intentionally **not** an in-page child list and **not** a contact subtab.
368
+
369
+ The intended UX is:
370
+
371
+ - `addresses` gets its own routed list page
372
+ - the page lives under a specific contact
373
+ - the user reaches it from an explicit link or button on the contact view
374
+ - it does **not** appear in the global left menu
375
+
376
+ That makes it different from the later `comments` example, where the child list stays inside the parent page.
377
+
367
378
  ### Step 1: create the child table
368
379
 
369
380
  Example:
@@ -382,12 +393,16 @@ CREATE TABLE addresses (
382
393
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
383
394
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
384
395
  KEY idx_addresses_workspace_id (workspace_id),
385
- KEY idx_addresses_contact_id (contact_id)
396
+ KEY idx_addresses_contact_id (contact_id),
397
+ CONSTRAINT fk_addresses_contact_id
398
+ FOREIGN KEY (contact_id) REFERENCES contacts(id)
386
399
  );
387
400
  ```
388
401
 
389
402
  The important extra column here is `contact_id`. This is what makes the CRUD a child of a contact instead of a top-level resource.
390
403
 
404
+ The foreign key is not optional in this example. It is what lets `crud-server-generator` emit `contactId` as a real lookup relation in `packages/addresses/src/shared/addressResource.js`.
405
+
391
406
  ### Step 2: scaffold the server package
392
407
 
393
408
  ```bash
@@ -398,17 +413,104 @@ npx jskit generate crud-server-generator scaffold \
398
413
  --table-name addresses
399
414
  ```
400
415
 
401
- ### Step 3: scaffold the UI at the child route root
416
+ Then install the generated local package:
417
+
418
+ ```bash
419
+ npm install
420
+ ```
421
+
422
+ If you are developing against a local JSKIT checkout, relink your app to the local packages before you run the UI:
423
+
424
+ ```bash
425
+ npm run devlinks
426
+ ```
427
+
428
+ ### Step 3: refine the generated lookup metadata by hand
429
+
430
+ Open `packages/addresses/src/shared/addressResource.js` and add `labelKey: "fullName"` to the generated `contactId` relation:
431
+
432
+ ```js
433
+ contactId: {
434
+ type: "id",
435
+ required: true,
436
+ search: true,
437
+ relation: {
438
+ kind: "lookup",
439
+ namespace: "contacts",
440
+ valueKey: "id",
441
+ labelKey: "fullName"
442
+ },
443
+ belongsTo: "contacts",
444
+ as: "contact",
445
+ ui: { formControl: "autocomplete" },
446
+ ...
447
+ }
448
+ ```
449
+
450
+ This part is still manual today.
451
+
452
+ The scaffold can infer the lookup **relationship** from the foreign key, but it cannot infer which contact field should be used as the human-readable label. Without `labelKey: "fullName"`, the page falls back to generic headings like `Addresses for Contact #1`.
453
+
454
+ ### Step 4: scaffold the UI route tree
402
455
 
403
456
  ```bash
404
457
  npx jskit generate crud-ui-generator crud \
405
458
  w/[workspaceSlug]/admin/contacts/[contactId]/addresses \
406
459
  --resource-file packages/addresses/src/shared/addressResource.js \
407
460
  --id-param addressId \
461
+ --parent-title contextual \
408
462
  --display-fields label,line1,postcode
409
463
  ```
410
464
 
411
- That gives you a proper child CRUD with its own list, view, new, and edit pages under the contact route.
465
+ That gives you a normal child route tree:
466
+
467
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/addresses/index.vue`
468
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/addresses/new.vue`
469
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/addresses/[addressId]/index.vue`
470
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/addresses/[addressId]/edit.vue`
471
+ - shared `_components` files under the same route root
472
+
473
+ ### Step 5: remove the generated shell placement by hand
474
+
475
+ `crud-ui-generator` currently generates a list-page placement block in `src/placement.js`.
476
+
477
+ For this example, that block is **not** what you want.
478
+
479
+ Delete the generated `addresses` placement block from `src/placement.js`.
480
+
481
+ It will be the block added for the `w/[workspaceSlug]/admin/contacts/[contactId]/addresses` page link.
482
+
483
+ 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.
484
+
485
+ This is an intentional manual cleanup for now:
486
+
487
+ - keep the routed pages
488
+ - remove the auto menu link
489
+ - navigate to the page from the contact view instead
490
+
491
+ ### Step 6: add a manual link from the contact view
492
+
493
+ Open `src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue` and add a button that links to `./addresses`.
494
+
495
+ The simplest version is:
496
+
497
+ ```vue
498
+ <v-btn
499
+ color="primary"
500
+ variant="tonal"
501
+ :to="{ path: view.resolveParams(UI_ADDRESSES_URL), query: $route.query }"
502
+ >
503
+ Addresses
504
+ </v-btn>
505
+ ```
506
+
507
+ Then define the URL template in the script:
508
+
509
+ ```js
510
+ const UI_ADDRESSES_URL = "./addresses";
511
+ ```
512
+
513
+ That gives you a standalone child page with a clear entry point from the parent contact view, without mixing record-scoped pages into the global shell menu.
412
514
 
413
515
  ### The important refinement: parent title handling
414
516
 
@@ -416,8 +518,8 @@ This is where nested CRUD starts to feel real.
416
518
 
417
519
  An addresses list page should not show a generic heading like `Addresses`. It should usually show whose addresses these are, for example:
418
520
 
419
- - `Jane Smith's addresses`
420
- - `Acme Pty Ltd's addresses`
521
+ - `Addresses for Jane Smith`
522
+ - `Addresses for Acme Pty Ltd`
421
523
 
422
524
  The awkward case is when there are **no child rows yet**. If the list is empty, you cannot derive the parent title from the first address record.
423
525
 
@@ -433,37 +535,36 @@ const parentTitle = useCrudListParentTitle({
433
535
  });
434
536
  ```
435
537
 
436
- That helper does the nice thing automatically:
538
+ That helper does the right thing automatically:
437
539
 
438
- - if the child list already has rows, it derives the parent title from the first child record
540
+ - if the child list already has rows, it derives the parent title from the first child record when lookup data is available
439
541
  - if the child list is empty, it loads the parent record directly
440
542
 
441
- This is exactly the pattern used in the real app codebase for nested CRUD lists like a contact's pets. It avoids the common broken state where an empty child list loses all context about the parent it belongs to.
442
-
443
- So the `addresses` example is important because it teaches the first truly practical nested-CRUD problem:
543
+ So the `addresses` example is important because it teaches the first truly practical child-page problem:
444
544
 
445
545
  - the route is child-scoped
446
546
  - the data is child-scoped
447
- - but the page still needs a reliable parent identity even when the child list is empty
547
+ - the page is reached from a parent action, not from the main shell menu
548
+ - the page still needs a reliable parent identity even when the child list is empty
448
549
 
449
- ## Example 3: `comments` in-page
550
+ ## Example 3: `comments` under the contact host
450
551
 
451
552
  This is the advanced example.
452
553
 
453
- Sometimes a child resource is real enough to deserve its own CRUD operations, but **not** important enough to deserve its own full-screen list page.
554
+ Sometimes a child resource should stay attached to the parent record, but still deserves its own routed child page.
454
555
 
455
556
  Comments are a good example:
456
557
 
457
558
  - they matter
458
559
  - they need persistence
459
- - they may need `view`, `new`, or `edit` routes
460
- - but the main screen is still the contact view page
560
+ - they benefit from their own list, `new`, `view`, and `edit` routes
561
+ - but they still belong inside the contact experience rather than as a top-level destination
461
562
 
462
563
  So the pattern is:
463
564
 
464
565
  - keep the contact page as the real host
465
- - render the comments list inside that page
466
- - route only the child operations underneath it
566
+ - keep the child route tree under that host
567
+ - let the generated placement surface the comments page as a contact child page
467
568
 
468
569
  ### Step 1: create the table
469
570
 
@@ -474,12 +575,13 @@ CREATE TABLE comments (
474
575
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
475
576
  workspace_id BIGINT UNSIGNED NOT NULL,
476
577
  contact_id BIGINT UNSIGNED NOT NULL,
477
- author_user_id BIGINT UNSIGNED NULL,
478
578
  body TEXT NOT NULL,
479
579
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
480
580
  updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
481
581
  KEY idx_comments_workspace_id (workspace_id),
482
- KEY idx_comments_contact_id (contact_id)
582
+ KEY idx_comments_contact_id (contact_id),
583
+ CONSTRAINT fk_comments_contact_id
584
+ FOREIGN KEY (contact_id) REFERENCES contacts(id)
483
585
  );
484
586
  ```
485
587
 
@@ -493,7 +595,45 @@ npx jskit generate crud-server-generator scaffold \
493
595
  --table-name comments
494
596
  ```
495
597
 
496
- ### Step 3: make the parent page able to host routed children
598
+ Then install the generated local package:
599
+
600
+ ```bash
601
+ npm install
602
+ ```
603
+
604
+ If you are developing against a local JSKIT checkout, relink the app before you run the UI:
605
+
606
+ ```bash
607
+ npm run devlinks
608
+ ```
609
+
610
+ ### Step 3: refine the generated lookup metadata by hand
611
+
612
+ Open `packages/comments/src/shared/commentResource.js` and add `labelKey: "fullName"` to the generated `contactId` relation:
613
+
614
+ ```js
615
+ contactId: {
616
+ type: "id",
617
+ required: true,
618
+ search: true,
619
+ relation: {
620
+ kind: "lookup",
621
+ namespace: "contacts",
622
+ valueKey: "id",
623
+ labelKey: "fullName"
624
+ },
625
+ belongsTo: "contacts",
626
+ as: "contact",
627
+ ui: { formControl: "autocomplete" },
628
+ ...
629
+ }
630
+ ```
631
+
632
+ This is the same manual refinement used in the `addresses` example: the scaffold can infer the lookup relationship from the foreign key, but it cannot infer which parent field should be used as the display label.
633
+
634
+ The next command also makes the parent-title mode explicit on purpose. That way changing the heading behavior later is just changing the value and rerunning with `--force`, not adding a new flag.
635
+
636
+ ### Step 4: make the parent page able to host routed children
497
637
 
498
638
  If the contact view page should stay visible while child comment routes render underneath it, first upgrade it into a routed host:
499
639
 
@@ -509,63 +649,71 @@ That is the point where the comments example deliberately overlaps with the `ui-
509
649
  - CRUD scaffolding for the comments resource
510
650
  - a routed host in the parent contact page
511
651
 
512
- ### Step 4: generate only the routed child operations
652
+ ### Step 4.5: make `comments` the default child page
513
653
 
514
- Now generate the comments UI without a list page:
654
+ This is a very common next step.
515
655
 
516
- ```bash
517
- npx jskit generate crud-ui-generator crud \
518
- w/[workspaceSlug]/admin/contacts/[contactId]/index/comments \
519
- --resource-file packages/comments/src/shared/commentResource.js \
520
- --operations view,new,edit \
521
- --id-param commentId
656
+ If the contact page should open directly on `Comments`, add an explicit redirect to the contact host page:
657
+
658
+ ```vue
659
+ <script setup>
660
+ import { redirectToChild } from "@jskit-ai/kernel/client/pageRedirects";
661
+
662
+ definePage({
663
+ redirect: redirectToChild("comments")
664
+ });
665
+ </script>
522
666
  ```
523
667
 
524
- This is the key move.
668
+ In this example, that edit belongs in:
525
669
 
526
- By leaving `list` out:
670
+ ```text
671
+ src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue
672
+ ```
527
673
 
528
- - you do **not** generate a standalone comments list page
529
- - the host contact page stays responsible for showing the comment list
530
- - the generated child routes still handle things like `new`, `view`, and `edit`
674
+ This is the recommended pattern. Do **not** try to infer the default child page from placement order or "the first tab". Keep it explicit.
531
675
 
532
- ### What the host page does
676
+ When this redirect is present:
533
677
 
534
- The host page then renders the comment list directly, usually with lower-level CRUD composables such as:
678
+ - opening `/w/<workspaceSlug>/admin/contacts/<contactId>` lands on `/w/<workspaceSlug>/admin/contacts/<contactId>/comments`
679
+ - the parent contact page still stays visible
680
+ - the child page renders underneath it
535
681
 
536
- - `useCrudList()`
537
- - `useCrudAddEdit()`
538
- - `useCrudView()` when needed
682
+ That last point matters. The contact page is still the host page. The redirect only changes which child route becomes the default landing destination.
539
683
 
540
- There is one more runtime worth knowing about early:
684
+ ### Step 5: generate the full child CRUD page
541
685
 
542
- - `useCommand()`
686
+ Now generate the comments UI under the contact host:
543
687
 
544
- Use it for live actions that are **not** full forms, such as:
688
+ ```bash
689
+ npx jskit generate crud-ui-generator crud \
690
+ w/[workspaceSlug]/admin/contacts/[contactId]/index/comments \
691
+ --resource-file packages/comments/src/shared/commentResource.js \
692
+ --id-param commentId \
693
+ --parent-title none \
694
+ --display-fields body
695
+ ```
545
696
 
546
- - checkbox toggles
547
- - archive / reopen buttons
548
- - quick PATCH / POST / DELETE actions on one record
697
+ Do **not** pass `--operations view,new,edit` here. That would omit the list page and leave you with only child operation routes.
549
698
 
550
- So the practical split is:
699
+ `--parent-title none` is the important difference from `addresses`.
551
700
 
552
- - `useCrudList()` / `useCrudView()`
553
- - routed list/view loading
554
- - `useCrudAddEdit()`
555
- - real create/edit forms
556
- - `useCommand()`
557
- - live actions inside the page
701
+ The contact host page already shows the parent identity, so a generated heading like `Comments for Tony Mobily` is redundant. If you later decide you do want that heading, rerun the same command with `--parent-title contextual --force`.
558
702
 
559
- The `todo` app uses that last pattern for "mark item done" checkboxes. The deeper best-practices explanation is in [Advanced CRUDs](/guide/generators/advanced-cruds).
703
+ This command gives you:
560
704
 
561
- That is the same general pattern already used in the real app for embedded child records like pet notes: the record list lives inside the main view page, while routed child pages handle the operations that still deserve their own URLs.
705
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/index/comments/index.vue`
706
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/index/comments/new.vue`
707
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/index/comments/[commentId]/index.vue`
708
+ - `w/[workspaceSlug]/admin/contacts/[contactId]/index/comments/[commentId]/edit.vue`
562
709
 
563
- This pattern is useful when the child records are supporting detail rather than a destination in their own right.
710
+ Because this route root lives under `.../[contactId]/index/comments`, the generated placement is the desired one here: `Comments` appears as a child page under the contact host.
564
711
 
565
712
  That is the real lesson of the `comments` example:
566
713
 
567
- - a child CRUD does **not** have to mean "make another full-screen list page"
568
- - you can keep the parent page as the main experience and route only the child operations that benefit from their own URLs
714
+ - the contact page stays the routed host
715
+ - the child CRUD gets its own list page
716
+ - the route still stays under the parent record experience
569
717
 
570
718
  ## When `scaffold-field` matters
571
719
 
@@ -597,8 +745,8 @@ It patches the canonical `schema` inside the shared resource file. That matters
597
745
  The important thing is not memorizing commands. It is learning the three shapes:
598
746
 
599
747
  - `contacts`: a normal top-level CRUD
600
- - `addresses`: a child CRUD with its own routed list page
601
- - `comments`: a child CRUD that lives inside the parent page, with only the child operations routed
748
+ - `addresses`: a child CRUD with its own routed list page reached from the parent record view
749
+ - `comments`: a child CRUD that lives under the parent record host as a child page
602
750
 
603
751
  Once you understand those three shapes, the two generator packages stop feeling like separate tools.
604
752
 
@@ -257,6 +257,37 @@ The route page is still a normal page file. What changes is the inferred placeme
257
257
 
258
258
  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.
259
259
 
260
+ ### Making a child page the default landing route
261
+
262
+ This is important enough to state explicitly: if a routed host has child pages, and you want the bare parent URL to open one child immediately, use an explicit redirect.
263
+
264
+ Do **not** try to make the app "guess the first tab". Do **not** infer it from placement order. Keep the target explicit.
265
+
266
+ The standard pattern is:
267
+
268
+ ```vue
269
+ <script setup>
270
+ import { redirectToChild } from "@jskit-ai/kernel/client/pageRedirects";
271
+
272
+ definePage({
273
+ redirect: redirectToChild("exports")
274
+ });
275
+ </script>
276
+ ```
277
+
278
+ If this is placed on the host page, opening `/reports` lands on `/reports/exports`.
279
+
280
+ This is also the right pattern when the host page still renders shared content such as a title, tabs, summary panel, or `RouterView`. The parent route remains the host, and the child page simply becomes the default destination under it.
281
+
282
+ Why this is the recommended pattern:
283
+
284
+ - the destination is explicit and stable
285
+ - it survives later placement reordering
286
+ - it does not depend on which child link happens to render first
287
+ - it is easy to change later by editing one child segment
288
+
289
+ This is one of the most common things people want once they start using child-page hosts. Treat it as normal JSKIT routing, not a special hack.
290
+
260
291
  ## Nested pages under an `index.vue` host
261
292
 
262
293
  Once `Reports` is a host, create a child page under it:
@@ -4,12 +4,13 @@
4
4
 
5
5
  This guide is the main hands-on path through JSKIT.
6
6
 
7
- It starts with the smallest scaffold, then explains how to work with the JSKIT CLI itself before adding the shell and authentication. 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 realtime and assistant. Finally, the guide breaks out generator-specific workflows into their own section.
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 realtime and assistant. Finally, the guide breaks out generator-specific workflows into their own section.
8
8
 
9
9
  ## Table of Contents
10
10
 
11
11
  ### App Setup
12
12
 
13
+ - [Quickstart](/guide/app-setup/quickstart)
13
14
  - [Initial Scaffolding](/guide/app-setup/initial-scaffolding)
14
15
  - [Working With The JSKIT CLI](/guide/app-setup/working-with-the-jskit-cli)
15
16
  - [A More Interesting Shell](/guide/app-setup/a-more-interesting-shell)
@@ -33,7 +34,8 @@ It starts with the smallest scaffold, then explains how to work with the JSKIT C
33
34
 
34
35
  ## How to use this guide
35
36
 
36
- - Start with `App Setup` if you are building up a JSKIT app from scratch.
37
+ - Start with `Quickstart` if you want the fastest route to a real workspace-enabled app and the first page-extension patterns.
38
+ - Start with the rest of `App Setup` if you want to understand the base scaffold layer by layer.
37
39
  - Use `App Extras` once the base app structure is in place and you want optional runtime packages.
38
40
  - Jump into `Generators` if you already understand the runtime packages and want app-owned scaffolding workflows.
39
41
  - Inside `Generators`, read `CRUD Generators` before `Advanced CRUDs`: the first chapter teaches the workflow, and the second explains the generated anatomy and customization points.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Distributed JSKIT agent workflows, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -6,16 +6,18 @@ Use when:
6
6
  - writing `definePage({ redirect: ... })`
7
7
  - seeding settings landing pages such as `/home/settings`
8
8
  - wiring a section shell that should land on a real child page first
9
+ - making a routed host page open a default child tab such as `comments`
9
10
 
10
11
  Check first:
11
12
 
12
- - whether the page is only a landing route for child pages
13
+ - whether the page is only a landing route for child pages or a host page that should always land on one child first
13
14
  - whether the destination child route is explicit and stable
14
15
  - whether the redirect should preserve incoming query and hash
15
16
 
16
17
  Rules:
17
18
 
18
19
  - For “index page lands on child page” redirects, use `redirectToChild()` from `@jskit-ai/kernel/client/pageRedirects`.
20
+ - The same helper is also the explicit pattern for “this host page should open child X by default”.
19
21
  - Do not hand-build child redirects with string surgery such as `` `${String(to.path || "").replace(/\\/$/, "")}/general` ``.
20
22
  - Keep the destination explicit. Do not infer it from placements, menu order, or “first child page wins” behavior.
21
23
  - If the redirect is just “go to this child page”, prefer:
@@ -28,6 +30,8 @@ definePage({
28
30
  });
29
31
  ```
30
32
 
33
+ - This can live on a host page that still renders its own shell and `RouterView`. The parent route remains matched and the child route renders underneath it.
34
+
31
35
  Why:
32
36
 
33
37
  - the helper centralizes slash handling
@@ -12,6 +12,8 @@ Rules:
12
12
  - Any chunk that adds or changes user-facing UI must include a Playwright flow that exercises the changed behavior before the chunk is done.
13
13
  - Record that Playwright run with `jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>`.
14
14
  - `jskit doctor` expects `.jskit/verification/ui.json` to match the current dirty UI file set when UI files are changed.
15
+ - For local pre-merge review, follow the recorded Playwright run with `jskit doctor --against <base-ref>` so `doctor` compares against the branch delta instead of only the local dirty worktree.
16
+ - Advanced CI setups may also use `--against <base-ref>`, but JSKIT does not scaffold hosted browser/auth/database verification by default.
15
17
  - Do not rely on a live external auth provider for Playwright verification of normal app features.
16
18
  - For authenticated UI in the standard JSKIT auth stack, use the development-only dev auth bypass route instead.
17
19
  - The standard route is `POST /api/dev-auth/login-as`.
@@ -31,6 +33,12 @@ npx jskit app verify-ui \
31
33
  --auth-mode dev-auth-login-as
32
34
  ```
33
35
 
36
+ Local pre-merge follow-up:
37
+
38
+ ```bash
39
+ npx jskit doctor --against origin/main
40
+ ```
41
+
34
42
  The Playwright command itself should follow this setup shape when login is needed:
35
43
 
36
44
  ```ts
@@ -22,6 +22,7 @@ Startup navigation stays in `KERNEL_MAP.md`.
22
22
  - [database-runtime](/packages/agent-docs/reference/autogen/packages/database-runtime.md)
23
23
  - [database-runtime-mysql](/packages/agent-docs/reference/autogen/packages/database-runtime-mysql.md)
24
24
  - [database-runtime-postgres](/packages/agent-docs/reference/autogen/packages/database-runtime-postgres.md)
25
+ - [feature-server-generator](/packages/agent-docs/reference/autogen/packages/feature-server-generator.md)
25
26
  - [http-runtime](/packages/agent-docs/reference/autogen/packages/http-runtime.md)
26
27
  - [json-rest-api-core](/packages/agent-docs/reference/autogen/packages/json-rest-api-core.md)
27
28
  - [kernel](/packages/agent-docs/reference/autogen/packages/kernel.md)
@@ -216,6 +216,7 @@ Exports
216
216
  - `MAX_INPUT_CHARS`
217
217
  - `MAX_HISTORY_MESSAGES`
218
218
  - `assistantResource`
219
+ - `assistantConversationOutputValidator`
219
220
 
220
221
  ### `src/shared/assistantSettingsResource.js`
221
222
  Exports
@@ -245,14 +246,26 @@ Exports
245
246
  - `MAX_INPUT_CHARS`
246
247
  - `MAX_HISTORY_MESSAGES`
247
248
  - `assistantResource`
249
+ - `assistantConversationOutputValidator`
248
250
  - `MAX_SYSTEM_PROMPT_CHARS`
249
251
  - `assistantConfigResource`
252
+ - `ASSISTANT_SETTINGS_TRANSPORT`
253
+ - `ASSISTANT_SETTINGS_UPDATE_TRANSPORT`
254
+ - `ASSISTANT_CONVERSATIONS_TRANSPORT`
255
+ - `ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT`
250
256
  - `assistantSettingsEvents`
251
257
  - `ASSISTANT_CONVERSATION_STATUSES`
252
258
  - `normalizeConversationStatus`
253
259
  - `parseJsonObject`
254
260
  - `toPositiveInteger`
255
261
 
262
+ ### `src/shared/jsonApiTransports.js`
263
+ Exports
264
+ - `ASSISTANT_SETTINGS_TRANSPORT`
265
+ - `ASSISTANT_SETTINGS_UPDATE_TRANSPORT`
266
+ - `ASSISTANT_CONVERSATIONS_TRANSPORT`
267
+ - `ASSISTANT_CONVERSATION_MESSAGES_TRANSPORT`
268
+
256
269
  ### `src/shared/queryKeys.js`
257
270
  Exports
258
271
  - `ASSISTANT_QUERY_KEY_PREFIX`
@@ -100,6 +100,8 @@ Local functions
100
100
  - `sendPreStreamErrorResponse(reply, error)`
101
101
  - `resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}), kind = "runtime", requiresWorkspace = false, workspaceScopeSupport = null } = {})`
102
102
  - `buildChatStreamActionInput(routeInput = {}, requestBody = {})`
103
+ - `resolveAssistantSettingsRecordId(record = {})`
104
+ - `resolveAssistantConversationMessagesRecordId(record = {})`
103
105
  - `registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false, workspaceScopeSupport = null } = {})`
104
106
  - `registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false, workspaceScopeSupport = null } = {})`
105
107
 
@@ -190,6 +190,12 @@ Local functions
190
190
  - `cloneProfile(profile)`
191
191
  - `createEmailConflictError()`
192
192
 
193
+ ### `src/server/lib/supabaseClientOptions.js`
194
+ Exports
195
+ - `buildSupabaseServerClientOptions(options = {})`
196
+ Local functions
197
+ - `resolveRealtimeTransport({ nativeWebSocket = globalThis.WebSocket, fallbackTransport = WebSocket } = {})`
198
+
193
199
  ### `src/server/lib/test-utils.js`
194
200
  Exports
195
201
  - `createAccountFlows`
@@ -31,6 +31,8 @@ Exports
31
31
  ### `src/server/consoleSettings/bootConsoleSettingsRoutes.js`
32
32
  Exports
33
33
  - `bootConsoleSettingsRoutes(app)`
34
+ Local functions
35
+ - `resolveConsoleSettingsRecordId()`
34
36
 
35
37
  ### `src/server/consoleSettings/consoleService.js`
36
38
  Exports
@@ -304,6 +304,10 @@ Local functions
304
304
  - `createRecordRelationshipsResolver(definition = null)`
305
305
  - `createRequestRelationshipMapper(definition = null)`
306
306
  - `resolveOutputAttributeExcludeKeys(resource = {})`
307
+ - `resolveLookupContainerKey(resource = {})`
308
+ - `normalizeLookupId(value)`
309
+ - `normalizeIncludedLookupRecord(source = null, fallbackId = null)`
310
+ - `createLookupIncludedResolver(definition = null, { lookupContainerKey = "" } = {})`
307
311
 
308
312
  ### `src/server/serviceEvents.js`
309
313
  Exports
@@ -30,6 +30,7 @@ Local functions
30
30
  - `resolveListTargetFile(targetRoot = "")`
31
31
  - `parseOperationsOption(options)`
32
32
  - `parseDisplayFieldsOption(options)`
33
+ - `parseParentTitleOption(options)`
33
34
  - `validateDisplayFieldsForOperation(selectedFieldKeys, fields, operationName)`
34
35
  - `filterDisplayFields(selectedFieldKeys, fields)`
35
36
  - `rewriteGeneratedBlockIndent(source = "", { trimPrefix = "", addPrefix = "" } = {})`
@@ -46,6 +47,8 @@ Local functions
46
47
  - `resolveTargetRootRelativeRoutePath(pageTarget = {})`
47
48
  - `resolveMenuToPropLine(linkTo = "")`
48
49
  - `resolveCrudRelativePath(namespace = "")`
50
+ - `buildListParentTitleImportLine(parentTitleMode = "contextual")`
51
+ - `buildListHeadingTitleSetup({ parentTitleMode = "contextual", resourceNamespace = "", routeTitle = "Records" } = {})`
49
52
 
50
53
  ### `src/server/resourceSupport.js`
51
54
  Exports