@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.
- package/guide/agent/app-setup/authentication.md +8 -0
- package/guide/agent/app-setup/initial-scaffolding.md +14 -8
- package/guide/agent/app-setup/multi-homing.md +59 -0
- package/guide/agent/app-setup/quickstart.md +214 -0
- package/guide/agent/app-setup/users.md +8 -0
- package/guide/agent/app-setup/working-with-the-jskit-cli.md +16 -0
- package/guide/agent/generators/crud-generators.md +210 -62
- package/guide/agent/generators/ui-generators.md +31 -0
- package/guide/agent/index.md +4 -2
- package/package.json +1 -1
- package/patterns/page-redirects.md +5 -1
- package/patterns/ui-testing.md +8 -0
- package/reference/autogen/README.md +1 -0
- package/reference/autogen/packages/assistant-core.md +13 -0
- package/reference/autogen/packages/assistant-runtime.md +2 -0
- package/reference/autogen/packages/auth-provider-supabase-core.md +6 -0
- package/reference/autogen/packages/console-core.md +2 -0
- package/reference/autogen/packages/crud-core.md +4 -0
- package/reference/autogen/packages/crud-ui-generator.md +3 -0
- package/reference/autogen/packages/feature-server-generator.md +74 -0
- package/reference/autogen/packages/json-rest-api-core.md +1 -5
- package/reference/autogen/packages/users-web.md +1 -0
- package/reference/autogen/tooling/create-app.md +4 -16
- package/reference/autogen/tooling/jskit-cli.md +12 -1
- package/templates/app/AGENTS.md +1 -1
- package/workflow/feature-delivery.md +2 -0
- package/workflow/review.md +1 -0
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
420
|
-
- `Acme Pty Ltd
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
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`
|
|
550
|
+
## Example 3: `comments` under the contact host
|
|
450
551
|
|
|
451
552
|
This is the advanced example.
|
|
452
553
|
|
|
453
|
-
Sometimes a child resource
|
|
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
|
|
460
|
-
- but
|
|
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
|
-
-
|
|
466
|
-
-
|
|
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
|
-
|
|
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:
|
|
652
|
+
### Step 4.5: make `comments` the default child page
|
|
513
653
|
|
|
514
|
-
|
|
654
|
+
This is a very common next step.
|
|
515
655
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
668
|
+
In this example, that edit belongs in:
|
|
525
669
|
|
|
526
|
-
|
|
670
|
+
```text
|
|
671
|
+
src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue
|
|
672
|
+
```
|
|
527
673
|
|
|
528
|
-
|
|
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
|
-
|
|
676
|
+
When this redirect is present:
|
|
533
677
|
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
684
|
+
### Step 5: generate the full child CRUD page
|
|
541
685
|
|
|
542
|
-
|
|
686
|
+
Now generate the comments UI under the contact host:
|
|
543
687
|
|
|
544
|
-
|
|
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
|
-
|
|
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
|
-
|
|
699
|
+
`--parent-title none` is the important difference from `addresses`.
|
|
551
700
|
|
|
552
|
-
|
|
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
|
-
|
|
703
|
+
This command gives you:
|
|
560
704
|
|
|
561
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
568
|
-
-
|
|
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
|
|
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:
|
package/guide/agent/index.md
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
This guide is the main hands-on path through JSKIT.
|
|
6
6
|
|
|
7
|
-
It starts with
|
|
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 `
|
|
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
|
@@ -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
|
package/patterns/ui-testing.md
CHANGED
|
@@ -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`
|
|
@@ -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
|