@jskit-ai/agent-docs 0.1.5 → 0.1.7
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/DISTR_AGENT.md +2 -0
- package/guide/agent/generators/advanced-cruds.md +358 -51
- package/package.json +2 -1
- package/patterns/INDEX.md +36 -0
- package/patterns/child-cruds.md +30 -0
- package/patterns/client-requests.md +56 -0
- package/patterns/crud-links.md +36 -0
- package/patterns/filters.md +92 -0
- package/patterns/live-actions.md +33 -0
- package/patterns/placements.md +28 -0
- package/patterns/surfaces.md +30 -0
- package/reference/autogen/KERNEL_MAP.md +27 -0
- package/reference/autogen/packages/assistant-runtime.md +21 -8
- package/reference/autogen/packages/crud-core.md +22 -0
- package/reference/autogen/packages/crud-ui-generator.md +1 -0
- package/reference/autogen/packages/kernel.md +27 -0
- package/reference/autogen/packages/users-web.md +35 -0
- package/reference/autogen/packages/workspaces-core.md +4 -0
- package/reference/autogen/packages/workspaces-web.md +8 -0
- package/templates/app/AGENTS.md +31 -1
package/DISTR_AGENT.md
CHANGED
|
@@ -13,6 +13,7 @@ Use these references on demand:
|
|
|
13
13
|
|
|
14
14
|
- `reference/autogen/KERNEL_MAP.md`
|
|
15
15
|
- `reference/autogen/README.md`
|
|
16
|
+
- `patterns/INDEX.md`
|
|
16
17
|
- `guide/agent/index.md`
|
|
17
18
|
- `site/guide/index.md` when compressed guidance is ambiguous or missing nuance
|
|
18
19
|
- `templates/APP_BLUEPRINT.md`
|
|
@@ -28,6 +29,7 @@ Core rules:
|
|
|
28
29
|
- `Why this sticks: ...`
|
|
29
30
|
- `Not doing: ...`
|
|
30
31
|
- Keep that checkpoint compact. Do not expand it into a long preamble unless the developer asks for detail.
|
|
32
|
+
- When a request involves JSKIT UI, routing, surfaces, CRUDs, filters, placements, live actions, or similar implementation details, scan `patterns/INDEX.md` for matching keywords and read only the relevant pattern files.
|
|
31
33
|
- Reuse existing JSKIT helpers and runtime seams before adding new local helpers.
|
|
32
34
|
- A freshly scaffolded JSKIT app can still be in Stage 1. If platform decisions are not settled yet, continue with the initialize workflow before adding runtime packages.
|
|
33
35
|
- Do not treat a missing `config.tenancyMode` line or an untouched minimal scaffold as a final tenancy decision.
|
|
@@ -613,6 +613,57 @@ The safe mental model is:
|
|
|
613
613
|
- use command runtimes for actions
|
|
614
614
|
- keep the server as the source of truth for derived state
|
|
615
615
|
|
|
616
|
+
### Choosing the right client request seam
|
|
617
|
+
|
|
618
|
+
When you need client-side HTTP work in JSKIT, do not start with raw `fetch(...)`.
|
|
619
|
+
|
|
620
|
+
Choose the highest-level runtime that matches the interaction:
|
|
621
|
+
|
|
622
|
+
```js
|
|
623
|
+
// 1. Button/toggle/small mutation
|
|
624
|
+
const command = useCommand({ ... });
|
|
625
|
+
|
|
626
|
+
// 2. List endpoint
|
|
627
|
+
const list = useList({ ... });
|
|
628
|
+
|
|
629
|
+
// 3. Single-record endpoint
|
|
630
|
+
const view = useView({ ... });
|
|
631
|
+
|
|
632
|
+
// 4. Form save flow
|
|
633
|
+
const form = useAddEdit({ ... });
|
|
634
|
+
|
|
635
|
+
// 5. Truly custom endpoint
|
|
636
|
+
const resource = useEndpointResource({ ... });
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Use the CRUD wrappers when they fit:
|
|
640
|
+
|
|
641
|
+
- `useCrudList()` for routed CRUD lists
|
|
642
|
+
- `useCrudView()` for routed CRUD record loading
|
|
643
|
+
- `useCrudAddEdit()` for routed CRUD forms
|
|
644
|
+
|
|
645
|
+
Why this is the standard JSKIT shape:
|
|
646
|
+
|
|
647
|
+
- `useCommand()` resolves the correct scoped API path for the current route and surface.
|
|
648
|
+
- The higher-level list, view, add/edit, and command runtimes send requests through the standard HTTP runtime instead of ad hoc request code.
|
|
649
|
+
- The default client runtime uses `usersWebHttpClient`, which already handles credentials and CSRF token behavior.
|
|
650
|
+
- `useEndpointResource()` gives the shared endpoint primitive for loading, saving, and standard load/save error handling. Higher-level runtimes like `useCommand()` and `useAddEdit()` layer UI feedback and field-error behavior on top of that primitive.
|
|
651
|
+
|
|
652
|
+
If you need a custom scoped endpoint path outside the higher-level runtimes, prefer `usePaths().api(...)` rather than hand-building scoped URLs:
|
|
653
|
+
|
|
654
|
+
```js
|
|
655
|
+
const paths = usePaths();
|
|
656
|
+
const reportsApiPath = computed(() => paths.api("/reports"));
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
The safe mental model is:
|
|
660
|
+
|
|
661
|
+
- do not raw `fetch(...)` for normal app work
|
|
662
|
+
- do not invent ad hoc local AJAX helpers
|
|
663
|
+
- use the operation/runtime composable that matches the UI interaction
|
|
664
|
+
- drop to `usersWebHttpClient.request(...)` only for exceptional low-level cases
|
|
665
|
+
- use `usePaths().api(...)` when you need a custom scoped API path and the higher-level runtime does not already resolve it for you
|
|
666
|
+
|
|
616
667
|
### `_components/CrudAddEditForm.vue`
|
|
617
668
|
|
|
618
669
|
This is the shared rendering shell for the add/edit form.
|
|
@@ -678,7 +729,7 @@ The baseline generator output is only the start. As the tutorial's `contacts`, `
|
|
|
678
729
|
|
|
679
730
|
- `src/server/listQueryValidators.js` when a list needs extra query filters beyond `q`
|
|
680
731
|
- `src/server/service.test.js` once save/delete rules stop being trivial
|
|
681
|
-
- `src/
|
|
732
|
+
- `packages/contacts/src/shared/contactListFilters.js` when the contacts CRUD gains structured list filters that both server and client should share
|
|
682
733
|
- `src/composables/addresses/useAddressDisplay.js` when addresses need app-specific display formatting
|
|
683
734
|
- `src/composables/comments/useCommentsListRuntime.js` when an embedded comments list needs local UI state
|
|
684
735
|
|
|
@@ -686,6 +737,7 @@ That is the right direction of growth:
|
|
|
686
737
|
|
|
687
738
|
- server customizations stay in the CRUD package
|
|
688
739
|
- presentation and page-specific UI state stay in app-owned client files
|
|
740
|
+
- shared structured list filters live best in a CRUD-package shared module that both server and client can import
|
|
689
741
|
|
|
690
742
|
## Search and filters: the deep dive
|
|
691
743
|
|
|
@@ -782,18 +834,64 @@ Usually nothing changes. The client can keep sending `q`.
|
|
|
782
834
|
- Do not dump every text column into search just because you can.
|
|
783
835
|
- In this tutorial CRUD, `notes` is a good example of a field you might leave out if you want fast, predictable list search.
|
|
784
836
|
|
|
785
|
-
### Pattern 3: structured
|
|
837
|
+
### Pattern 3: structured list filters from one shared definition
|
|
786
838
|
|
|
787
|
-
This is the
|
|
839
|
+
This is the default pattern once a CRUD list needs real filters.
|
|
788
840
|
|
|
789
|
-
|
|
841
|
+
Do not hand-build:
|
|
842
|
+
|
|
843
|
+
- one filter shape in the page
|
|
844
|
+
- a second filter shape in `listQueryValidators.js`
|
|
845
|
+
- and a third filter shape in `repository.js`
|
|
846
|
+
|
|
847
|
+
Instead:
|
|
848
|
+
|
|
849
|
+
1. define the filters once in a shared CRUD-package module
|
|
850
|
+
2. build the server runtime from that definition with `createCrudListFilters(...)`
|
|
851
|
+
3. choose the route/action invalid-value contract explicitly with `createQueryValidator({ invalidValues: "reject" | "discard" })`
|
|
852
|
+
4. build the client runtime from that same definition with `useCrudListFilters(...)`
|
|
853
|
+
|
|
854
|
+
For example, a shared filter-definition module can look like this:
|
|
855
|
+
|
|
856
|
+
```js
|
|
857
|
+
export const CONTACTS_LIST_FILTER_DEFINITIONS = Object.freeze({
|
|
858
|
+
onlyStaff: {
|
|
859
|
+
type: "flag",
|
|
860
|
+
label: "Staff"
|
|
861
|
+
},
|
|
862
|
+
onlyVip: {
|
|
863
|
+
type: "flag",
|
|
864
|
+
label: "VIP"
|
|
865
|
+
},
|
|
866
|
+
onlyArchived: {
|
|
867
|
+
type: "flag",
|
|
868
|
+
label: "Archived"
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
#### Exact file checklist
|
|
874
|
+
|
|
875
|
+
For a generated CRUD, treat this as the concrete file plan:
|
|
876
|
+
|
|
877
|
+
- create `packages/contacts/src/shared/contactListFilters.js`
|
|
878
|
+
- update `packages/contacts/src/server/registerRoutes.js` so the list route query validator includes an explicit `createQueryValidator({ invalidValues: ... })` choice, unless you already extracted list-query composition into `packages/contacts/src/server/listQueryValidators.js`
|
|
879
|
+
- update `packages/contacts/src/server/actions.js` so the list action input validator includes that same explicit contract choice
|
|
880
|
+
- update `packages/contacts/src/server/repository.js` so the list query path builds the `createCrudListFilters(...)` runtime and calls `applyQuery(...)`
|
|
881
|
+
- 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
|
|
882
|
+
|
|
883
|
+
The only file in that list that is normally **new** is the shared module:
|
|
790
884
|
|
|
791
|
-
|
|
885
|
+
- `packages/contacts/src/shared/contactListFilters.js`
|
|
792
886
|
|
|
793
|
-
|
|
887
|
+
The others are normally edits to the generated CRUD package and the app-owned page layer that already exist.
|
|
888
|
+
|
|
889
|
+
#### Client side
|
|
890
|
+
|
|
891
|
+
Build the filter runtime directly from the shared definitions:
|
|
794
892
|
|
|
795
893
|
```js
|
|
796
|
-
const listFilters =
|
|
894
|
+
const listFilters = useCrudListFilters(CONTACTS_LIST_FILTER_DEFINITIONS);
|
|
797
895
|
|
|
798
896
|
const records = useCrudList({
|
|
799
897
|
resource: uiResource,
|
|
@@ -802,9 +900,7 @@ const records = useCrudList({
|
|
|
802
900
|
enabled: true,
|
|
803
901
|
mode: "query"
|
|
804
902
|
},
|
|
805
|
-
queryParams:
|
|
806
|
-
...listFilters.filters
|
|
807
|
-
})),
|
|
903
|
+
queryParams: listFilters.queryParams,
|
|
808
904
|
syncToRoute: {
|
|
809
905
|
enabled: true,
|
|
810
906
|
search: true,
|
|
@@ -814,65 +910,274 @@ const records = useCrudList({
|
|
|
814
910
|
});
|
|
815
911
|
```
|
|
816
912
|
|
|
817
|
-
|
|
913
|
+
That gives you, from one place:
|
|
914
|
+
|
|
915
|
+
- `listFilters.values`
|
|
916
|
+
- `listFilters.queryParams`
|
|
917
|
+
- `listFilters.presets`
|
|
918
|
+
- `listFilters.activeChips`
|
|
919
|
+
- `listFilters.hasActiveFilters`
|
|
920
|
+
- `listFilters.clearChip(...)`
|
|
921
|
+
- `listFilters.clearFilters()`
|
|
922
|
+
- `listFilters.toggle(...)` for flag filters
|
|
923
|
+
- `listFilters.applyPreset(...)`
|
|
924
|
+
- `listFilters.matchesPreset(...)`
|
|
818
925
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
-
|
|
926
|
+
So the same runtime owns:
|
|
927
|
+
|
|
928
|
+
- URL-synced query params
|
|
929
|
+
- filter chips
|
|
930
|
+
- reset logic
|
|
931
|
+
- preset application
|
|
932
|
+
- preset active-state matching
|
|
933
|
+
- small flag toggles
|
|
934
|
+
|
|
935
|
+
For relative-date quick filters, keep the date math in runtime presets instead of page-local helper state. `resolveValues(...)` runs at preset-apply time and receives `{ values, filters, presetKey, preset }`, so the preset can derive values from the current filter state and the normalized preset metadata:
|
|
936
|
+
|
|
937
|
+
```js
|
|
938
|
+
const listFilters = useCrudListFilters(
|
|
939
|
+
RECEIVAL_LIST_FILTER_DEFINITIONS,
|
|
940
|
+
{
|
|
941
|
+
presets: [
|
|
942
|
+
{
|
|
943
|
+
key: "today",
|
|
944
|
+
label: "Today",
|
|
945
|
+
resolveValues({ presetKey }) {
|
|
946
|
+
const today = formatDateInputValue(new Date());
|
|
947
|
+
return {
|
|
948
|
+
arrivalDate: {
|
|
949
|
+
from: today,
|
|
950
|
+
to: today
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
key: "last7",
|
|
957
|
+
label: "Last 7 Days",
|
|
958
|
+
resolveValues({ values }) {
|
|
959
|
+
const today = formatDateInputValue(new Date());
|
|
960
|
+
return {
|
|
961
|
+
arrivalDate: {
|
|
962
|
+
from: shiftDateInputValue(today, -6),
|
|
963
|
+
to: today
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
]
|
|
969
|
+
}
|
|
970
|
+
);
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
```vue
|
|
974
|
+
<v-chip
|
|
975
|
+
v-for="preset in listFilters.presets"
|
|
976
|
+
:key="preset.key"
|
|
977
|
+
:variant="listFilters.matchesPreset(preset.key) ? 'flat' : 'outlined'"
|
|
978
|
+
@click="listFilters.applyPreset(preset.key, { mode: 'merge' })"
|
|
979
|
+
>
|
|
980
|
+
{{ preset.label }}
|
|
981
|
+
</v-chip>
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
Use `mode: "merge"` when a preset should only change one filter group, such as the arrival date range, and should not clear the rest of the page's active filters.
|
|
985
|
+
|
|
986
|
+
`matchesPreset(...)` is strict by design. It compares the preset against the full current filter state after basic normalization, and it does **not** silently drop extra `enumMany` or `recordIdMany` values that were hydrated from the route and still appear as chips. If the URL contains `status=archived&status=bogus`, a preset for only `archived` should render as inactive until the extra `bogus` value is cleared.
|
|
822
987
|
|
|
823
988
|
#### Server side
|
|
824
989
|
|
|
825
|
-
|
|
990
|
+
Build the server runtime from the same shared definitions:
|
|
826
991
|
|
|
827
992
|
```js
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
),
|
|
837
|
-
normalize(payload = {}) {
|
|
838
|
-
const normalized = {};
|
|
839
|
-
if (Object.hasOwn(payload, "hasEmail")) normalized.hasEmail = 1;
|
|
840
|
-
if (Object.hasOwn(payload, "hasPhone")) normalized.hasPhone = 1;
|
|
841
|
-
if (Object.hasOwn(payload, "hasNotes")) normalized.hasNotes = 1;
|
|
842
|
-
return normalized;
|
|
993
|
+
const contactsListFiltersRuntime = createCrudListFilters(
|
|
994
|
+
CONTACTS_LIST_FILTER_DEFINITIONS,
|
|
995
|
+
{
|
|
996
|
+
columns: {
|
|
997
|
+
onlyStaff: "is_staff",
|
|
998
|
+
onlyVip: "vip",
|
|
999
|
+
onlyArchived: "archived"
|
|
1000
|
+
}
|
|
843
1001
|
}
|
|
1002
|
+
);
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
There is no default query-validator mode and no `runtime.queryValidator` alias. Create the validator that matches the contract you want at that route or action boundary.
|
|
1006
|
+
|
|
1007
|
+
Strict contract example:
|
|
1008
|
+
|
|
1009
|
+
```js
|
|
1010
|
+
const contactsListFiltersQueryValidator = contactsListFiltersRuntime.createQueryValidator({
|
|
1011
|
+
invalidValues: "reject" // malformed filter values should fail validation and return 400
|
|
1012
|
+
});
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
Lenient contract example:
|
|
1016
|
+
|
|
1017
|
+
```js
|
|
1018
|
+
const contactsListFiltersQueryValidator = contactsListFiltersRuntime.createQueryValidator({
|
|
1019
|
+
invalidValues: "discard" // malformed filter values should be ignored and dropped by normalize()
|
|
844
1020
|
});
|
|
845
1021
|
```
|
|
846
1022
|
|
|
847
|
-
Wire
|
|
1023
|
+
Wire the runtime into the list validator and the repository:
|
|
1024
|
+
|
|
1025
|
+
```js
|
|
1026
|
+
queryValidator: [
|
|
1027
|
+
listCursorPaginationQueryValidator,
|
|
1028
|
+
listSearchQueryValidator,
|
|
1029
|
+
contactsListFiltersQueryValidator,
|
|
1030
|
+
listParentFilterQueryValidator,
|
|
1031
|
+
lookupIncludeQueryValidator
|
|
1032
|
+
]
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
Use that same `contactsListFiltersQueryValidator` anywhere else the list query is validated, such as the list action input validator if your CRUD package validates query shape at both the route and action boundaries.
|
|
1036
|
+
|
|
1037
|
+
Choose the invalid-value contract deliberately:
|
|
1038
|
+
|
|
1039
|
+
- there is no default mode and no fallback alias, so every route or action that validates structured filters must call `createQueryValidator({ invalidValues: ... })` explicitly
|
|
1040
|
+
- use `invalidValues: "reject"` when malformed filter values should fail validation and produce a 400-style contract error
|
|
1041
|
+
- use `invalidValues: "discard"` when malformed filter values should be ignored and normalization should drop them
|
|
1042
|
+
- route query validation runs before auth, so this choice changes whether malformed unauthenticated requests fail at validation or fall through to auth
|
|
1043
|
+
- for normal HTTP CRUD handlers, route-level `discard` means the handler receives already-normalized query input, so the action layer will not see those discarded bad values again later
|
|
1044
|
+
- `jskit doctor` flags `createQueryValidator(...)` calls that do not spell out `invalidValues` directly at the call site
|
|
848
1045
|
|
|
849
1046
|
```js
|
|
850
1047
|
async function list(query = {}, callOptions = {}) {
|
|
851
1048
|
return crudRepositoryList(repositoryRuntime, knex, query, options, callOptions, {
|
|
852
1049
|
modifyQuery(dbQuery, context = {}) {
|
|
853
|
-
|
|
1050
|
+
return contactsListFiltersRuntime.applyQuery(dbQuery, context.query || {});
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
```
|
|
854
1055
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
1056
|
+
#### Best practices
|
|
1057
|
+
|
|
1058
|
+
- Put the filter definitions in the CRUD package, not the page. Both server and client need them.
|
|
1059
|
+
- Keep the filter keys identical all the way through: definition key, query param key, and repository meaning.
|
|
1060
|
+
- Do not expect a default `runtime.queryValidator` to exist. Every structured-filter validator must be created explicitly with `createQueryValidator({ invalidValues: ... })`.
|
|
1061
|
+
- 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(...)`.
|
|
1062
|
+
- Use `createCrudListFilters(...)` unless the list semantics are truly unusual.
|
|
1063
|
+
- Use `q` for free-text and explicit query params for structured filters.
|
|
1064
|
+
- Run `jskit doctor` after wiring filters. It flags inline/local filter-definition objects passed into `useCrudListFilters(...)` or `createCrudListFilters(...)`, and it flags `createQueryValidator(...)` calls that hide the invalid-value policy.
|
|
1065
|
+
|
|
1066
|
+
### Pattern 4: lookup-backed structured filters
|
|
1067
|
+
|
|
1068
|
+
This is the next real-world step: filters like `supplierContactId`, `locationId`, or `contactId` where the user needs:
|
|
1069
|
+
|
|
1070
|
+
- remote autocomplete search
|
|
1071
|
+
- URL-synced selected ids
|
|
1072
|
+
- readable chip labels instead of raw ids
|
|
1073
|
+
|
|
1074
|
+
The right pattern is:
|
|
1075
|
+
|
|
1076
|
+
1. keep the lookup filter in the shared definitions
|
|
1077
|
+
2. use `useCrudListFilters(...)` for state, chips, and query params
|
|
1078
|
+
3. use `useCrudListFilterLookups(...)` for option loading and label resolution
|
|
1079
|
+
|
|
1080
|
+
Example shared definition:
|
|
1081
|
+
|
|
1082
|
+
```js
|
|
1083
|
+
export const RECEIVAL_LIST_FILTER_DEFINITIONS = Object.freeze({
|
|
1084
|
+
supplierContactId: {
|
|
1085
|
+
type: "recordIdMany",
|
|
1086
|
+
label: "Supplier",
|
|
1087
|
+
lookup: {
|
|
1088
|
+
namespace: "contacts"
|
|
1089
|
+
}
|
|
1090
|
+
},
|
|
1091
|
+
pollenTypeId: {
|
|
1092
|
+
type: "recordIdMany",
|
|
1093
|
+
label: "Pollen Type",
|
|
1094
|
+
lookup: {
|
|
1095
|
+
namespace: "pollen-types",
|
|
1096
|
+
labelKey: "name"
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
#### Exact file checklist
|
|
1103
|
+
|
|
1104
|
+
Lookup-backed filters do **not** change the ownership model from Pattern 3. The file plan is still:
|
|
1105
|
+
|
|
1106
|
+
- keep the shared definition in `packages/receivals/src/shared/receivalListFilters.js`
|
|
1107
|
+
- update the same server validator and repository files from Pattern 3
|
|
1108
|
+
- update the app-owned list page or list-runtime composable so it creates both `useCrudListFilters(...)` and `useCrudListFilterLookups(...)`
|
|
1109
|
+
- bind the lookup UI, such as `v-autocomplete`, from `filterLookups.resolveLookup(...)`
|
|
1110
|
+
|
|
1111
|
+
Do **not** create a second page-local filter schema just because the UI needs remote autocomplete. The shared definition file stays the source of truth.
|
|
1112
|
+
|
|
1113
|
+
#### Client side
|
|
1114
|
+
|
|
1115
|
+
```js
|
|
1116
|
+
let filterLookups = null;
|
|
1117
|
+
|
|
1118
|
+
const listFilters = useCrudListFilters(
|
|
1119
|
+
RECEIVAL_LIST_FILTER_DEFINITIONS,
|
|
1120
|
+
{
|
|
1121
|
+
labelResolvers: {
|
|
1122
|
+
supplierContactId(value) {
|
|
1123
|
+
return filterLookups?.resolveLookupLabel("supplierContactId", value, "Supplier") || "";
|
|
860
1124
|
}
|
|
861
|
-
|
|
862
|
-
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
filterLookups = useCrudListFilterLookups(
|
|
1130
|
+
RECEIVAL_LIST_FILTER_DEFINITIONS,
|
|
1131
|
+
{
|
|
1132
|
+
values: listFilters.values,
|
|
1133
|
+
queryKeyPrefix: ["ui-generator", "receivals", "filters"],
|
|
1134
|
+
placementSourcePrefix: "ui-generator.receivals.list.filters",
|
|
1135
|
+
requestQueryParams: {
|
|
1136
|
+
supplierContactId: { limit: 25 }
|
|
1137
|
+
},
|
|
1138
|
+
labelResolvers: {
|
|
1139
|
+
supplierContactId(item = {}) {
|
|
1140
|
+
return `${item.firstName} ${item.lastName}`.trim();
|
|
863
1141
|
}
|
|
864
1142
|
}
|
|
865
|
-
}
|
|
866
|
-
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
const supplierFilterLookup = filterLookups.resolveLookup("supplierContactId");
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
Then bind the autocomplete:
|
|
1150
|
+
|
|
1151
|
+
```vue
|
|
1152
|
+
<v-autocomplete
|
|
1153
|
+
v-model="listFilters.values.supplierContactId"
|
|
1154
|
+
:items="supplierFilterLookup.options"
|
|
1155
|
+
:search="supplierFilterLookup.searchQuery"
|
|
1156
|
+
:loading="supplierFilterLookup.isLoading"
|
|
1157
|
+
item-title="label"
|
|
1158
|
+
item-value="value"
|
|
1159
|
+
multiple
|
|
1160
|
+
chips
|
|
1161
|
+
no-filter
|
|
1162
|
+
@update:search="supplierFilterLookup.setSearch"
|
|
1163
|
+
/>
|
|
867
1164
|
```
|
|
868
1165
|
|
|
1166
|
+
#### Why this is better than a page-local `useList()` wrapper
|
|
1167
|
+
|
|
1168
|
+
- the CRUD filter state still lives in `useCrudListFilters(...)`
|
|
1169
|
+
- the autocomplete loading logic lives in one reusable helper
|
|
1170
|
+
- the label resolution used by filter chips and the autocomplete stays consistent
|
|
1171
|
+
- a second screen can reuse the same pattern instead of rewriting it
|
|
1172
|
+
|
|
869
1173
|
#### Best practices
|
|
870
1174
|
|
|
871
|
-
-
|
|
872
|
-
- Use
|
|
873
|
-
-
|
|
1175
|
+
- Put lookup metadata in the shared filter definitions.
|
|
1176
|
+
- Use `useCrudListFilterLookups(...)` for remote filter autocompletes instead of building a custom `useList()` wrapper per screen.
|
|
1177
|
+
- Keep lookup label formatting on the client side. It is UI presentation, not repository logic.
|
|
1178
|
+
- Keep unusual SQL semantics, such as `pending = whereNull(...)`, in the server runtime `apply` override.
|
|
874
1179
|
|
|
875
|
-
### Pattern
|
|
1180
|
+
### Pattern 5: free-text search plus structured filters together
|
|
876
1181
|
|
|
877
1182
|
This is the most common real-world CRUD list.
|
|
878
1183
|
|
|
@@ -895,7 +1200,7 @@ Let the generic list search handle `q`, and let your custom validator/repository
|
|
|
895
1200
|
- Preserve the current route query when linking to view/edit pages so users can return to the same filtered list state.
|
|
896
1201
|
- Let list changes reset pagination; `useCrudList()` already does this for search and query-param changes.
|
|
897
1202
|
|
|
898
|
-
### Pattern
|
|
1203
|
+
### Pattern 6: parent-scoped child CRUD search for `addresses`
|
|
899
1204
|
|
|
900
1205
|
For nested CRUDs such as the `addresses` resource from the previous chapter, parent scoping and search usually work together.
|
|
901
1206
|
|
|
@@ -935,7 +1240,7 @@ That keeps child-list filtering grounded in the actual resource definition inste
|
|
|
935
1240
|
- Let the resource contract define parent filter shape.
|
|
936
1241
|
- Treat parent-scoped filtering as repository/query behavior, not as presentation logic.
|
|
937
1242
|
|
|
938
|
-
### Pattern
|
|
1243
|
+
### Pattern 7: local-only search for embedded `comments`
|
|
939
1244
|
|
|
940
1245
|
Sometimes server search is unnecessary.
|
|
941
1246
|
|
|
@@ -1038,10 +1343,12 @@ Use `scaffold-field` when it fits, then review the generated result.
|
|
|
1038
1343
|
|
|
1039
1344
|
Touch:
|
|
1040
1345
|
|
|
1041
|
-
-
|
|
1042
|
-
- `
|
|
1043
|
-
-
|
|
1044
|
-
- `
|
|
1346
|
+
- `packages/<crud>/src/shared/<crud>ListFilters.js` and make it the only authored filter-definition module
|
|
1347
|
+
- `packages/<crud>/src/server/registerRoutes.js` and `packages/<crud>/src/server/actions.js` so the list validator includes an explicit `createQueryValidator({ invalidValues: ... })` choice, or `packages/<crud>/src/server/listQueryValidators.js` if you extracted list-query composition there
|
|
1348
|
+
- `packages/<crud>/src/server/repository.js` so the list query applies the runtime
|
|
1349
|
+
- the app-owned list page or list-runtime composable that calls `useCrudList(...)`
|
|
1350
|
+
|
|
1351
|
+
If the filter is lookup-backed, touch that same client file again to wire `useCrudListFilterLookups(...)`.
|
|
1045
1352
|
|
|
1046
1353
|
### "I want a new save rule."
|
|
1047
1354
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/agent-docs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Distributed JSKIT agent workflows, guides, and generated reference maps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"DISTR_AGENT.md",
|
|
8
8
|
"guide",
|
|
9
|
+
"patterns",
|
|
9
10
|
"reference",
|
|
10
11
|
"skills",
|
|
11
12
|
"templates",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# JSKIT Patterns Index
|
|
2
|
+
|
|
3
|
+
Use this index as the first stop for recurring JSKIT implementation heuristics and workflow traps.
|
|
4
|
+
|
|
5
|
+
How to use it:
|
|
6
|
+
|
|
7
|
+
- Match the user request against the keywords below.
|
|
8
|
+
- Open only the relevant pattern files.
|
|
9
|
+
- Use patterns as implementation guidance, not as permission to skip normal scoping or user clarification.
|
|
10
|
+
|
|
11
|
+
## Keyword Map
|
|
12
|
+
|
|
13
|
+
- tabs, menu items, icons, shell links, profile links, subpage tabs, placements
|
|
14
|
+
- `placements.md`
|
|
15
|
+
- surfaces, app/admin/home/console, "which surface", route ownership, placement visibility
|
|
16
|
+
- `surfaces.md`
|
|
17
|
+
- child crud, nested crud, embedded list, subroute, separate page, parent/child layout
|
|
18
|
+
- `child-cruds.md`
|
|
19
|
+
- CRUD links, record placeholders, `paths.page()`, `resolveViewUrl`, `resolveEditUrl`, `resolveParams`
|
|
20
|
+
- `crud-links.md`
|
|
21
|
+
- live actions, checkbox, toggle, patch button, inline action, `useCommand()`
|
|
22
|
+
- `live-actions.md`
|
|
23
|
+
- ajax, fetch, API call, request, endpoint, HTTP client, `useList()`, `useView()`, `useAddEdit()`, `useEndpointResource()`, `usersWebHttpClient`
|
|
24
|
+
- `client-requests.md`
|
|
25
|
+
- filter, filters, search facets, chips, date range, enum filter, lookup filter, `useCrudListFilters`, `createCrudListFilters`
|
|
26
|
+
- `filters.md`
|
|
27
|
+
|
|
28
|
+
## Current Patterns
|
|
29
|
+
|
|
30
|
+
- [placements.md](./placements.md)
|
|
31
|
+
- [surfaces.md](./surfaces.md)
|
|
32
|
+
- [child-cruds.md](./child-cruds.md)
|
|
33
|
+
- [crud-links.md](./crud-links.md)
|
|
34
|
+
- [live-actions.md](./live-actions.md)
|
|
35
|
+
- [client-requests.md](./client-requests.md)
|
|
36
|
+
- [filters.md](./filters.md)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Child CRUD Layout Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- planning nested CRUDs
|
|
6
|
+
- adding records under a parent record
|
|
7
|
+
- deciding whether child records need their own page or stay inside the parent view
|
|
8
|
+
|
|
9
|
+
Rules:
|
|
10
|
+
|
|
11
|
+
- Before generating a child CRUD, ask how the user wants the child records laid out.
|
|
12
|
+
- Do not assume one layout pattern by default.
|
|
13
|
+
|
|
14
|
+
Clarify these options:
|
|
15
|
+
|
|
16
|
+
- embedded in the parent view
|
|
17
|
+
- often a list rendered directly inside the parent page
|
|
18
|
+
- embedded as child subroutes
|
|
19
|
+
- the parent page becomes a routed container or subpage host
|
|
20
|
+
- totally separate route/page
|
|
21
|
+
- the child list/view/edit flow lives in its own route area
|
|
22
|
+
|
|
23
|
+
Why this matters:
|
|
24
|
+
|
|
25
|
+
- the answer changes route structure, placements, host containers, and which generator flow fits best
|
|
26
|
+
- child CRUD layout mistakes are expensive to unwind later
|
|
27
|
+
|
|
28
|
+
Avoid:
|
|
29
|
+
|
|
30
|
+
- generating nested CRUD routes before the parent/child layout is agreed
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Client Request Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- deciding which JSKIT client function to use for HTTP work
|
|
6
|
+
- custom endpoint reads or writes
|
|
7
|
+
- AJAX questions
|
|
8
|
+
- replacing raw `fetch(...)` calls
|
|
9
|
+
- choosing between `useCommand()`, `useList()`, `useView()`, `useAddEdit()`, and `useEndpointResource()`
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
|
|
13
|
+
- Prefer the highest-level JSKIT runtime that matches the UI interaction.
|
|
14
|
+
- Do not hand-roll local AJAX helpers when an existing JSKIT runtime already fits.
|
|
15
|
+
- Do not use raw `fetch(...)` for normal app work.
|
|
16
|
+
- Use `usePaths().api(...)` for custom scoped API paths instead of concatenating route params into URLs by hand.
|
|
17
|
+
- Drop to `usersWebHttpClient.request(...)` only for exceptional low-level cases.
|
|
18
|
+
|
|
19
|
+
Choose the function like this:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
// 1. Button/toggle/small mutation
|
|
23
|
+
const command = useCommand({ ... });
|
|
24
|
+
|
|
25
|
+
// 2. List endpoint
|
|
26
|
+
const list = useList({ ... });
|
|
27
|
+
|
|
28
|
+
// 3. Single-record endpoint
|
|
29
|
+
const view = useView({ ... });
|
|
30
|
+
|
|
31
|
+
// 4. Form save flow
|
|
32
|
+
const form = useAddEdit({ ... });
|
|
33
|
+
|
|
34
|
+
// 5. Truly custom endpoint
|
|
35
|
+
const resource = useEndpointResource({ ... });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use the CRUD wrappers when they fit:
|
|
39
|
+
|
|
40
|
+
- `useCrudList()` for routed CRUD lists
|
|
41
|
+
- `useCrudView()` for routed CRUD record loading
|
|
42
|
+
- `useCrudAddEdit()` for routed CRUD forms
|
|
43
|
+
|
|
44
|
+
Why this is the standard JSKIT shape:
|
|
45
|
+
|
|
46
|
+
- `useCommand()` resolves the scoped API path for the current route and surface.
|
|
47
|
+
- The higher-level list, view, add/edit, and command runtimes send requests through the shared HTTP runtime.
|
|
48
|
+
- `usersWebHttpClient` already handles credentials and CSRF behavior.
|
|
49
|
+
- `useEndpointResource()` is the shared endpoint primitive for loading, saving, and standard load/save error handling. Higher-level runtimes add UI feedback and field-error handling on top.
|
|
50
|
+
|
|
51
|
+
Avoid:
|
|
52
|
+
|
|
53
|
+
- raw `fetch(...)` for standard page or component work
|
|
54
|
+
- page-local HTTP helpers that duplicate JSKIT runtime seams
|
|
55
|
+
- manually concatenating scoped route params into API URLs
|
|
56
|
+
- using a lower-level seam when a higher-level routed CRUD or command runtime already fits
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# CRUD Link Resolution Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- customizing generated CRUD pages
|
|
6
|
+
- wiring buttons or links on list/view/edit pages
|
|
7
|
+
- building nested CRUD links
|
|
8
|
+
|
|
9
|
+
Rules:
|
|
10
|
+
|
|
11
|
+
- Use `paths.page()` for surface-aware navigation that only needs surface params.
|
|
12
|
+
- Do not use `paths.page()` with CRUD record placeholders such as `:contactId`, `:addressId`, `:todoListId`, or `:todoItemId` in CRUD-bound route suffixes.
|
|
13
|
+
- For CRUD-bound links, use the runtime that owns the current CRUD scope.
|
|
14
|
+
|
|
15
|
+
Prefer:
|
|
16
|
+
|
|
17
|
+
- list pages
|
|
18
|
+
- `records.resolveViewUrl(record)`
|
|
19
|
+
- `records.resolveEditUrl(record)`
|
|
20
|
+
- `records.resolveParams(template, extraParams)`
|
|
21
|
+
- view pages
|
|
22
|
+
- `view.listUrl`
|
|
23
|
+
- `view.editUrl`
|
|
24
|
+
- `view.resolveParams(template, extraParams)`
|
|
25
|
+
- add/edit pages
|
|
26
|
+
- `formRuntime.addEdit.resolveParams(template, extraParams)`
|
|
27
|
+
|
|
28
|
+
Scope rule:
|
|
29
|
+
|
|
30
|
+
- Use the runtime anchored to the record that owns the action.
|
|
31
|
+
- On a parent record view with nested child routes, parent actions should still resolve from the parent `view` runtime even while a child route is active.
|
|
32
|
+
|
|
33
|
+
Avoid:
|
|
34
|
+
|
|
35
|
+
- `paths.page("/lists/:todoListId/items/new")`
|
|
36
|
+
- `paths.page("/lists/:todoListId/edit")`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Structured Filters
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
- a CRUD list needs flags, enums, date ranges, record-id filters, or lookup-backed filters
|
|
5
|
+
- the user asks to "add a filter", "make the list filterable", or "keep filters in the URL"
|
|
6
|
+
- a screen mixes free-text search with structured facets
|
|
7
|
+
|
|
8
|
+
Ask first:
|
|
9
|
+
- which fields are filterable
|
|
10
|
+
- whether each filter is a flag, enum, multi-enum, date/date-range, number-range, record id, or lookup-backed record id
|
|
11
|
+
- whether filters must sync to the route query
|
|
12
|
+
- whether the screen needs chips and clear/reset behavior
|
|
13
|
+
- whether lookup filters need remote autocomplete search
|
|
14
|
+
- whether there are presets such as "Today", "Last 7 Days", or "Only Archived"
|
|
15
|
+
|
|
16
|
+
Default JSKIT pattern:
|
|
17
|
+
1. Put shared filter definitions in the CRUD package.
|
|
18
|
+
Example path: `packages/<crud>/src/shared/<crud>ListFilters.js`
|
|
19
|
+
2. Build the server runtime from that module with `createCrudListFilters(...)`.
|
|
20
|
+
3. Build route/action validators explicitly with `createQueryValidator({ invalidValues: "reject" | "discard" })`, and use `applyQuery(...)` in the repository. There is no default validator mode and no `runtime.queryValidator` alias.
|
|
21
|
+
4. Build the client runtime from the same shared definitions with `useCrudListFilters(...)`. Presets can use static `values` or dynamic `resolveValues({ values, filters, presetKey, preset })`.
|
|
22
|
+
5. Pass `listFilters.queryParams` into `useCrudList(...)`.
|
|
23
|
+
6. For lookup-backed filters, use `useCrudListFilterLookups(...)` instead of hand-rolling `useList()` in each page.
|
|
24
|
+
|
|
25
|
+
Exact file checklist:
|
|
26
|
+
- create `packages/<crud>/src/shared/<crud>ListFilters.js`
|
|
27
|
+
- update `packages/<crud>/src/server/registerRoutes.js` and `packages/<crud>/src/server/actions.js` so the list query validator includes an explicit `createQueryValidator({ invalidValues: ... })` choice, or update `packages/<crud>/src/server/listQueryValidators.js` if that package already extracts list-query composition there
|
|
28
|
+
- update `packages/<crud>/src/server/repository.js` so list queries call the runtime's `applyQuery(...)`
|
|
29
|
+
- update the app-owned list page or list-runtime composable under `src/pages/...` or `src/composables/...` so it builds `useCrudListFilters(...)`, passes `queryParams` into `useCrudList(...)`, and binds chips / reset behavior
|
|
30
|
+
- for lookup-backed filters, update that same client file to build `useCrudListFilterLookups(...)` and bind the lookup control from `resolveLookup(...)`
|
|
31
|
+
|
|
32
|
+
Validation mode is part of the contract:
|
|
33
|
+
- there is no default mode and no fallback alias, so every route/action boundary that validates structured filters must call `createQueryValidator({ invalidValues: ... })` explicitly
|
|
34
|
+
- use `invalidValues: "reject"` when malformed filter values should fail validation and return a 400-style contract error
|
|
35
|
+
- use `invalidValues: "discard"` when malformed filter values should be ignored and normalization should drop them
|
|
36
|
+
- route query validation runs before auth, so this choice affects whether malformed unauthenticated requests fail at validation or fall through to auth
|
|
37
|
+
- for normal HTTP CRUD handlers, route-level `discard` means the action layer receives already-normalized query input; do not assume route `discard` plus action `reject` will still reject malformed HTTP query strings later
|
|
38
|
+
- `jskit doctor` flags `createQueryValidator(...)` calls that do not spell out `invalidValues` directly at the call site
|
|
39
|
+
|
|
40
|
+
Keep separate:
|
|
41
|
+
- free-text search uses `records.searchQuery` and `q`
|
|
42
|
+
- structured filters use explicit query params through `useCrudListFilters(...)`
|
|
43
|
+
|
|
44
|
+
Use `useCrudListFilterLookups(...)` when:
|
|
45
|
+
- a filter is `recordId` or `recordIdMany`
|
|
46
|
+
- the UI needs remote autocomplete search
|
|
47
|
+
- chips should show readable labels instead of raw ids
|
|
48
|
+
|
|
49
|
+
Use built-in `presence` when:
|
|
50
|
+
- a filter means null vs not-null, such as assigned vs unassigned storage
|
|
51
|
+
- the UI wants custom labels like "Assigned" / "Unassigned" but the transport contract can stay `present` / `missing`
|
|
52
|
+
- you do **not** need custom SQL beyond `whereNotNull(...)` / `whereNull(...)`
|
|
53
|
+
|
|
54
|
+
Use runtime presets when:
|
|
55
|
+
- the page has quick filters such as "Today", "Last 7 Days", or "Only Archived"
|
|
56
|
+
- the preset should reuse the same filter state/reset/query-param runtime as the rest of the page
|
|
57
|
+
- relative-date presets need dynamic `resolveValues()` instead of hard-coded dates
|
|
58
|
+
- the active-state UI should reflect the full current filter state through `matchesPreset(...)`, including extra route-hydrated values that still appear as chips
|
|
59
|
+
|
|
60
|
+
Put unusual SQL semantics on the server:
|
|
61
|
+
- examples: `pending` meaning `whereNull("ccp1_passed")`, or business-specific status buckets that combine multiple columns
|
|
62
|
+
- implement those in `createCrudListFilters(..., { apply: { ... } })`
|
|
63
|
+
- do not use custom `apply` just for null/not-null checks when `type: "presence"` fits
|
|
64
|
+
|
|
65
|
+
Avoid:
|
|
66
|
+
- local filter composables that duplicate the same keys the server already knows about
|
|
67
|
+
- a custom validator shape that does not match the page state
|
|
68
|
+
- assuming a default `runtime.queryValidator` exists; always create the validator explicitly with `createQueryValidator({ invalidValues: ... })`
|
|
69
|
+
- hand-rolled preset apply/reset/active-state helpers when `useCrudListFilters(..., { presets })`, `applyPreset(...)`, and `matchesPreset(...)` fit
|
|
70
|
+
- per-screen `useList()` wrappers for lookup-backed filters when `useCrudListFilterLookups(...)` fits
|
|
71
|
+
- a second page-local filter-definition file when `packages/<crud>/src/shared/<crud>ListFilters.js` should be the source of truth
|
|
72
|
+
- overloading `q` with structured filter meaning
|
|
73
|
+
- local or inline filter-definition objects passed into `useCrudListFilters(...)` or `createCrudListFilters(...)`; `jskit doctor` expects those runtimes to import from a CRUD shared `*ListFilters` module
|
|
74
|
+
|
|
75
|
+
Good shape:
|
|
76
|
+
- `packages/receivals/src/shared/receivalListFilters.js`
|
|
77
|
+
- `createCrudListFilters(RECEIVAL_LIST_FILTER_DEFINITIONS, ...)`
|
|
78
|
+
- `const listFilters = useCrudListFilters(RECEIVAL_LIST_FILTER_DEFINITIONS, { presets: [...] })`
|
|
79
|
+
- `const filterLookups = useCrudListFilterLookups(RECEIVAL_LIST_FILTER_DEFINITIONS, { values: listFilters.values, ... })`
|
|
80
|
+
- `queryParams: listFilters.queryParams`
|
|
81
|
+
|
|
82
|
+
Preset contract notes:
|
|
83
|
+
- `resolveValues({ values, filters, presetKey, preset })` receives the current filter state and the normalized preset metadata, so relative-date presets can derive values at apply time without page-local helper state
|
|
84
|
+
- `matchesPreset(...)` compares against the full current filter state after basic normalization; it does **not** silently drop extra `enumMany` or `recordIdMany` values that were hydrated from the route and still show up as active chips
|
|
85
|
+
- if the URL contains `status=archived&status=bogus`, a preset for only `archived` should not render as active while the `bogus` chip is still visible
|
|
86
|
+
|
|
87
|
+
Review checks:
|
|
88
|
+
- one shared filter definition source of truth
|
|
89
|
+
- server validator/repository logic derived from that source
|
|
90
|
+
- client query params/chips/reset logic derived from that source
|
|
91
|
+
- lookup-backed filters use the shared lookup helper, not a page-local mini-framework
|
|
92
|
+
- `jskit doctor` stays clean for filter ownership and explicit validator policy
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Live Action Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- wiring checkboxes
|
|
6
|
+
- toggles
|
|
7
|
+
- archive/delete/reopen/publish actions
|
|
8
|
+
- small inline PATCH/POST/DELETE actions
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
|
|
12
|
+
- Prefer `useCommand()` for live actions.
|
|
13
|
+
- Prefer form runtimes such as `useCrudAddEdit()` or `useAddEdit()` for real forms.
|
|
14
|
+
- Prefer `useCrudList()` and `useCrudView()` for routed CRUD loading and URL resolution.
|
|
15
|
+
|
|
16
|
+
Good live-action pattern:
|
|
17
|
+
|
|
18
|
+
- build a narrow payload
|
|
19
|
+
- call `command.run()`
|
|
20
|
+
- disable only the busy control while the command is running
|
|
21
|
+
- invalidate the relevant query key on success
|
|
22
|
+
- keep derived business rules on the server
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
|
|
26
|
+
- checkbox toggles
|
|
27
|
+
- inline status changes
|
|
28
|
+
- quick destructive or publish/unpublish actions
|
|
29
|
+
|
|
30
|
+
Avoid:
|
|
31
|
+
|
|
32
|
+
- manually hand-rolling fetch logic for a standard live action when `useCommand()` fits
|
|
33
|
+
- pushing derived write rules into the client just because the action is small
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Placement Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- changing tab icons
|
|
6
|
+
- changing menu link icons or labels
|
|
7
|
+
- moving shell links
|
|
8
|
+
- changing profile or settings menu entries
|
|
9
|
+
- debugging why a visible link does not match the page component
|
|
10
|
+
|
|
11
|
+
Check first:
|
|
12
|
+
|
|
13
|
+
- app `src/placement.js`
|
|
14
|
+
- package placement contributions
|
|
15
|
+
- linked component token props
|
|
16
|
+
|
|
17
|
+
Rules:
|
|
18
|
+
|
|
19
|
+
- In JSKIT, tab-like links and shell/menu entries are often placement-owned, not page-owned.
|
|
20
|
+
- When asked to change a tab or menu icon, inspect placement metadata before editing page components.
|
|
21
|
+
- Look at placement `props`, especially fields such as `icon`, `prependIcon`, `appendIcon`, or props passed into the linked component token.
|
|
22
|
+
- If the visible entry is contributed by a package, update the owning package placement contribution instead of patching a local page component.
|
|
23
|
+
- If the request is really about where a link appears, check the placement `target`, `surfaces`, `order`, and `when` fields before changing UI markup.
|
|
24
|
+
|
|
25
|
+
Avoid:
|
|
26
|
+
|
|
27
|
+
- editing the routed page just to change a shell/tab/menu icon that is actually placement-owned
|
|
28
|
+
- assuming a rendered tab label/icon lives in the page component
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Surface Patterns
|
|
2
|
+
|
|
3
|
+
Use when:
|
|
4
|
+
|
|
5
|
+
- adding a new screen or feature
|
|
6
|
+
- generating pages
|
|
7
|
+
- adding menu entries or widgets
|
|
8
|
+
- deciding whether something belongs in `home`, `app`, `admin`, `console`, or another surface
|
|
9
|
+
|
|
10
|
+
Check first:
|
|
11
|
+
|
|
12
|
+
- the blueprint surface plan
|
|
13
|
+
- existing route roots under `src/pages`
|
|
14
|
+
- placement visibility by `surfaces`
|
|
15
|
+
|
|
16
|
+
Rules:
|
|
17
|
+
|
|
18
|
+
- Always ask which surface the feature belongs to before generating routes, placements, or related UI.
|
|
19
|
+
- Do not silently default new functionality to `app`.
|
|
20
|
+
- Surface choice is architectural, not cosmetic. It controls routes, access, placement visibility, and often data ownership expectations.
|
|
21
|
+
- When a link or widget should only appear on certain surfaces, use placement `surfaces` first and keep runtime `when` conditions for behavior-specific gating.
|
|
22
|
+
|
|
23
|
+
Ask explicitly about:
|
|
24
|
+
|
|
25
|
+
- route pages
|
|
26
|
+
- menu entries
|
|
27
|
+
- top-left/top-right widgets
|
|
28
|
+
- settings pages
|
|
29
|
+
- CRUD screens
|
|
30
|
+
- helper components that only make sense on one surface
|
|
@@ -25,6 +25,33 @@ For the full repo inventory, read `reference/autogen/README.md` and the package
|
|
|
25
25
|
Exports
|
|
26
26
|
- `isContainerToken(value)`
|
|
27
27
|
|
|
28
|
+
### `support/crudListFilters.js`
|
|
29
|
+
Exports
|
|
30
|
+
- `CRUD_LIST_FILTER_TYPE_FLAG`
|
|
31
|
+
- `CRUD_LIST_FILTER_TYPE_ENUM`
|
|
32
|
+
- `CRUD_LIST_FILTER_TYPE_ENUM_MANY`
|
|
33
|
+
- `CRUD_LIST_FILTER_TYPE_RECORD_ID`
|
|
34
|
+
- `CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY`
|
|
35
|
+
- `CRUD_LIST_FILTER_TYPE_DATE`
|
|
36
|
+
- `CRUD_LIST_FILTER_TYPE_DATE_RANGE`
|
|
37
|
+
- `CRUD_LIST_FILTER_TYPE_NUMBER_RANGE`
|
|
38
|
+
- `CRUD_LIST_FILTER_TYPE_PRESENCE`
|
|
39
|
+
- `CRUD_LIST_FILTER_TYPES`
|
|
40
|
+
- `CRUD_LIST_FILTER_PRESENCE_PRESENT`
|
|
41
|
+
- `CRUD_LIST_FILTER_PRESENCE_MISSING`
|
|
42
|
+
- `CRUD_LIST_FILTER_PRESENCE_OPTIONS`
|
|
43
|
+
- `defineCrudListFilters(definitions = {})`
|
|
44
|
+
- `resolveCrudListFilterQueryKeys(definition = {})`
|
|
45
|
+
- `resolveCrudListFilterOptionLabel(definition = {}, value = "", { fallback = "" } = {})`
|
|
46
|
+
Local functions
|
|
47
|
+
- `normalizeCrudListFilterType(value = "")`
|
|
48
|
+
- `normalizeCrudListFilterOption(rawOption = null, { context = "filter option" } = {})`
|
|
49
|
+
- `normalizeCrudListFilterOptions(rawOptions = [], { context = "filter options" } = {})`
|
|
50
|
+
- `normalizeCrudListFilterPresenceOptions(rawOptions = [])`
|
|
51
|
+
- `normalizeCrudListFilterLookup(rawLookup = null)`
|
|
52
|
+
- `resolveCrudListFilterOptionSet(rawDefinition = {}, type = "")`
|
|
53
|
+
- `normalizeCrudListFilterDefinition(rawKey = "", rawDefinition = null)`
|
|
54
|
+
|
|
28
55
|
### `support/crudLookup.js`
|
|
29
56
|
Exports
|
|
30
57
|
- `DEFAULT_CRUD_LOOKUP_CONTAINER_KEY`
|
|
@@ -55,6 +55,13 @@ Exports
|
|
|
55
55
|
Exports
|
|
56
56
|
- `AssistantClientProvider`
|
|
57
57
|
|
|
58
|
+
### `src/client/support/workspaceScopeSupport.js`
|
|
59
|
+
Exports
|
|
60
|
+
- `EMPTY_WORKSPACE_WEB_SCOPE_SUPPORT`
|
|
61
|
+
- `WORKSPACES_WEB_SCOPE_SUPPORT_INJECTION_KEY`
|
|
62
|
+
- `isWorkspaceWebScopeSupport(value)`
|
|
63
|
+
- `useWorkspaceWebScopeSupport({ required = false } = {})`
|
|
64
|
+
|
|
58
65
|
### `src/server/actionIds.js`
|
|
59
66
|
Exports
|
|
60
67
|
- `actionIds`
|
|
@@ -79,16 +86,16 @@ Exports
|
|
|
79
86
|
Exports
|
|
80
87
|
- `registerRoutes(app)`
|
|
81
88
|
Local functions
|
|
82
|
-
- `buildRouteParamsValidator(requiresWorkspace)`
|
|
83
|
-
- `buildConversationMessagesRouteParamsValidator(requiresWorkspace)`
|
|
84
|
-
- `readWorkspaceInput(request, requiresWorkspace)`
|
|
89
|
+
- `buildRouteParamsValidator(requiresWorkspace, workspaceScopeSupport = null)`
|
|
90
|
+
- `buildConversationMessagesRouteParamsValidator(requiresWorkspace, workspaceScopeSupport = null)`
|
|
91
|
+
- `readWorkspaceInput(request, requiresWorkspace, workspaceScopeSupport = null)`
|
|
85
92
|
- `requireAssistantSurface(appConfig = {}, targetSurfaceId = "")`
|
|
86
93
|
- `requireHostSurfaceId(request)`
|
|
87
94
|
- `shouldExposeAppErrorDetails(errorCode = "")`
|
|
88
95
|
- `sendPreStreamErrorResponse(reply, error)`
|
|
89
|
-
- `resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}), kind = "runtime", requiresWorkspace = false } = {})`
|
|
90
|
-
- `registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {})`
|
|
91
|
-
- `registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false } = {})`
|
|
96
|
+
- `resolveRouteRequestState(request, { resolveCurrentAppConfig = () => ({}), kind = "runtime", requiresWorkspace = false, workspaceScopeSupport = null } = {})`
|
|
97
|
+
- `registerSettingsRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false, workspaceScopeSupport = null } = {})`
|
|
98
|
+
- `registerRuntimeRoutes(router, resolveCurrentAppConfig, { requiresWorkspace = false, workspaceScopeSupport = null } = {})`
|
|
92
99
|
|
|
93
100
|
### `src/server/repositories/assistantConfigRepository.js`
|
|
94
101
|
Exports
|
|
@@ -128,11 +135,11 @@ Local functions
|
|
|
128
135
|
|
|
129
136
|
### `src/server/services/assistantConfigService.js`
|
|
130
137
|
Exports
|
|
131
|
-
- `createService({ assistantConfigRepository, consoleService = null, appConfig = {}, resolveAppConfig = null } = {})`
|
|
138
|
+
- `createService({ assistantConfigRepository, consoleService = null, appConfig = {}, resolveAppConfig = null, workspaceScopeSupport = null } = {})`
|
|
132
139
|
|
|
133
140
|
### `src/server/services/chatService.js`
|
|
134
141
|
Exports
|
|
135
|
-
- `createChatService({ aiClientFactory, transcriptService, serviceToolCatalog, assistantConfigService, appConfig = {}, resolveAppConfig = null } = {})`
|
|
142
|
+
- `createChatService({ aiClientFactory, transcriptService, serviceToolCatalog, assistantConfigService, appConfig = {}, resolveAppConfig = null, workspaceScopeSupport = null } = {})`
|
|
136
143
|
Local functions
|
|
137
144
|
- `normalizeConversationId(value)`
|
|
138
145
|
- `normalizeHistory(history = [])`
|
|
@@ -183,6 +190,12 @@ Local functions
|
|
|
183
190
|
- `buildCatalogOptions(appConfig = {}, surfaceId = "")`
|
|
184
191
|
- `requireContextSurfaceId(context = {})`
|
|
185
192
|
|
|
193
|
+
### `src/server/support/workspaceScopeSupport.js`
|
|
194
|
+
Exports
|
|
195
|
+
- `WORKSPACES_SERVER_SCOPE_SUPPORT_TOKEN`
|
|
196
|
+
- `isWorkspaceServerScopeSupport(value)`
|
|
197
|
+
- `resolveWorkspaceServerScopeSupport(scope = null, { required = false, caller = "assistant-runtime" } = {})`
|
|
198
|
+
|
|
186
199
|
### `src/shared/assistantRuntimeConfig.js`
|
|
187
200
|
Exports
|
|
188
201
|
- `assistantRuntimeConfig`
|
|
@@ -132,6 +132,28 @@ Local functions
|
|
|
132
132
|
- `resolveRoleMatrixPolicy(matrix = {}, operation = "readable", input = {})`
|
|
133
133
|
- `applyReadableFieldPolicyToRecord(record, allowedFields, outputRules = null, { context = "crudFieldAccess" } = {})`
|
|
134
134
|
|
|
135
|
+
### `src/server/listFilters.js`
|
|
136
|
+
Exports
|
|
137
|
+
- `CRUD_LIST_FILTER_INVALID_VALUES_REJECT`
|
|
138
|
+
- `CRUD_LIST_FILTER_INVALID_VALUES_DISCARD`
|
|
139
|
+
- `createCrudListFilters(definitions = {}, { columns = {}, apply = {} } = {})`
|
|
140
|
+
Local functions
|
|
141
|
+
- `normalizeCrudListFilterInvalidValues(value = "")`
|
|
142
|
+
- `createSingleOrMultiValueSchema(itemSchema)`
|
|
143
|
+
- `firstValue(value)`
|
|
144
|
+
- `normalizeDateFilterValue(value)`
|
|
145
|
+
- `normalizeNumberFilterValue(value)`
|
|
146
|
+
- `normalizeRecordIdFilterValue(value)`
|
|
147
|
+
- `normalizeRecordIdFilterValues(value)`
|
|
148
|
+
- `resolveAllowedOptionValues(filter = {})`
|
|
149
|
+
- `normalizeAllowedTextValue(value, allowedValues = new Set())`
|
|
150
|
+
- `normalizeAllowedTextValues(value, allowedValues = new Set())`
|
|
151
|
+
- `addDaysToDateFilterValue(value = "", days = 0)`
|
|
152
|
+
- `createFilterQuerySchema(filter = {}, { invalidValues = CRUD_LIST_FILTER_INVALID_VALUES_REJECT } = {})`
|
|
153
|
+
- `normalizeFilterValue(filter = {}, source = {})`
|
|
154
|
+
- `normalizeColumnsMap(columns = {})`
|
|
155
|
+
- `applyDefaultFilterQuery(queryBuilder, filter = {}, value, column = "")`
|
|
156
|
+
|
|
135
157
|
### `src/server/listQueryValidators.js`
|
|
136
158
|
Exports
|
|
137
159
|
- `createCrudCursorPaginationQueryValidator(list = {})`
|
|
@@ -82,6 +82,7 @@ Local functions
|
|
|
82
82
|
- `toLookupRelation(fieldMetaMap = {}, fieldKey = "", { lookupContainerKey = "lookups" } = {})`
|
|
83
83
|
- `resolveFormInputType(fieldType, fieldFormat)`
|
|
84
84
|
- `resolveFormFieldComponent(fieldType, relation = null)`
|
|
85
|
+
- `buildDefaultNullableBooleanOptions()`
|
|
85
86
|
- `toPositiveInteger(value)`
|
|
86
87
|
- `toAccessorExpression(baseName, fieldKey)`
|
|
87
88
|
- `toOptionalAccessorExpression(baseName, fieldKey)`
|
|
@@ -199,6 +199,33 @@ Exports
|
|
|
199
199
|
Exports
|
|
200
200
|
- `isContainerToken(value)`
|
|
201
201
|
|
|
202
|
+
### `shared/support/crudListFilters.js`
|
|
203
|
+
Exports
|
|
204
|
+
- `CRUD_LIST_FILTER_TYPE_FLAG`
|
|
205
|
+
- `CRUD_LIST_FILTER_TYPE_ENUM`
|
|
206
|
+
- `CRUD_LIST_FILTER_TYPE_ENUM_MANY`
|
|
207
|
+
- `CRUD_LIST_FILTER_TYPE_RECORD_ID`
|
|
208
|
+
- `CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY`
|
|
209
|
+
- `CRUD_LIST_FILTER_TYPE_DATE`
|
|
210
|
+
- `CRUD_LIST_FILTER_TYPE_DATE_RANGE`
|
|
211
|
+
- `CRUD_LIST_FILTER_TYPE_NUMBER_RANGE`
|
|
212
|
+
- `CRUD_LIST_FILTER_TYPE_PRESENCE`
|
|
213
|
+
- `CRUD_LIST_FILTER_TYPES`
|
|
214
|
+
- `CRUD_LIST_FILTER_PRESENCE_PRESENT`
|
|
215
|
+
- `CRUD_LIST_FILTER_PRESENCE_MISSING`
|
|
216
|
+
- `CRUD_LIST_FILTER_PRESENCE_OPTIONS`
|
|
217
|
+
- `defineCrudListFilters(definitions = {})`
|
|
218
|
+
- `resolveCrudListFilterQueryKeys(definition = {})`
|
|
219
|
+
- `resolveCrudListFilterOptionLabel(definition = {}, value = "", { fallback = "" } = {})`
|
|
220
|
+
Local functions
|
|
221
|
+
- `normalizeCrudListFilterType(value = "")`
|
|
222
|
+
- `normalizeCrudListFilterOption(rawOption = null, { context = "filter option" } = {})`
|
|
223
|
+
- `normalizeCrudListFilterOptions(rawOptions = [], { context = "filter options" } = {})`
|
|
224
|
+
- `normalizeCrudListFilterPresenceOptions(rawOptions = [])`
|
|
225
|
+
- `normalizeCrudListFilterLookup(rawLookup = null)`
|
|
226
|
+
- `resolveCrudListFilterOptionSet(rawDefinition = {}, type = "")`
|
|
227
|
+
- `normalizeCrudListFilterDefinition(rawKey = "", rawDefinition = null)`
|
|
228
|
+
|
|
202
229
|
### `shared/support/crudLookup.js`
|
|
203
230
|
Exports
|
|
204
231
|
- `DEFAULT_CRUD_LOOKUP_CONTAINER_KEY`
|
|
@@ -104,6 +104,7 @@ Local functions
|
|
|
104
104
|
- `resolveStableFieldErrorList(fieldKey, message)`
|
|
105
105
|
- `resolveFormFieldType(field = {})`
|
|
106
106
|
- `resolveFormFieldFormat(field = {})`
|
|
107
|
+
- `isNullableFormField(field = {})`
|
|
107
108
|
- `padDateTimePart(value)`
|
|
108
109
|
- `normalizeTimeWhitespace(value)`
|
|
109
110
|
- `toTimeInputValue(value)`
|
|
@@ -112,6 +113,18 @@ Local functions
|
|
|
112
113
|
- `resolveFormFieldInitialValue(field = {})`
|
|
113
114
|
- `shouldSerializeClearedFieldAsNull(field = {})`
|
|
114
115
|
|
|
116
|
+
### `src/client/composables/internal/crudListFilterLookupSupport.js`
|
|
117
|
+
Exports
|
|
118
|
+
- `normalizeLookupQueryKeyPrefix(value = [])`
|
|
119
|
+
- `normalizeLookupLabelResolverMap(value = {})`
|
|
120
|
+
- `normalizeLookupRequestQueryParamsMap(value = {})`
|
|
121
|
+
- `resolveLookupSelectedValues(filter = {}, rawValue = undefined)`
|
|
122
|
+
- `createLookupOptionsFromItems(items = [], filter = {}, labelResolver = null)`
|
|
123
|
+
- `mergeSelectedLookupOptions(options = [], selectedValues = [], cachedOptions = new Map())`
|
|
124
|
+
- `resolveLookupOptionLabel(options = [], cachedOptions = new Map(), value = "", fallback = "Option")`
|
|
125
|
+
Local functions
|
|
126
|
+
- `createLookupOptionFromItem(item = {}, filter = {}, labelResolver = null)`
|
|
127
|
+
|
|
115
128
|
### `src/client/composables/internal/crudListParentTitleSupport.js`
|
|
116
129
|
Exports
|
|
117
130
|
- `resolveCrudListParentDescriptor({ resource = {}, route = null, recordIdParam = "recordId" } = {})`
|
|
@@ -339,6 +352,28 @@ Exports
|
|
|
339
352
|
Exports
|
|
340
353
|
- `useCommand({ ownershipFilter = ROUTE_VISIBILITY_WORKSPACE, surfaceId = "", access = "auto", apiSuffix = "", runPermissions = [], writeMethod = "POST", placementSource = "users-web.command", fallbackRunError = "Unable to complete action.", fieldErrorKeys = [], clearOnRouteChange = true, model, parseInput, buildRawPayload, buildCommandPayload, buildCommandOptions, onRunSuccess, onRunError, suppressSuccessMessage = false, messages = {}, realtime = null } = {})`
|
|
341
354
|
|
|
355
|
+
### `src/client/composables/useCrudListFilterLookups.js`
|
|
356
|
+
Exports
|
|
357
|
+
- `useCrudListFilterLookups(definitions = {}, { values = {}, adapter = null, recordIdParam = "recordId", queryKeyPrefix = [], placementSourcePrefix = "", requestQueryParams = {}, labelResolvers = {} } = {})`
|
|
358
|
+
|
|
359
|
+
### `src/client/composables/useCrudListFilters.js`
|
|
360
|
+
Exports
|
|
361
|
+
- `useCrudListFilters(definitions = {}, { labelResolvers = {}, chipLabels = {}, presets = [] } = {})`
|
|
362
|
+
Local functions
|
|
363
|
+
- `normalizeFunctionMap(value = {})`
|
|
364
|
+
- `createInitialFilterValue(filter = {})`
|
|
365
|
+
- `normalizePresetEntries(presets = [])`
|
|
366
|
+
- `resolvePresetValues(preset = {}, { values = {}, filters = {} } = {})`
|
|
367
|
+
- `normalizePresetFilterValue(filter = {}, rawValue)`
|
|
368
|
+
- `normalizeCurrentManyFilterValues(value)`
|
|
369
|
+
- `matchArrayValues(currentValue = [], expectedValue = [])`
|
|
370
|
+
- `matchesPresetFilterValue(filter = {}, currentValue, rawExpectedValue)`
|
|
371
|
+
- `resetFilterValue(values, filter = {})`
|
|
372
|
+
- `applyPresetFilterValue(values, filter = {}, rawValue)`
|
|
373
|
+
- `createQueryParams(values, filterEntries = [])`
|
|
374
|
+
- `resolveAtomicValueLabel(filter = {}, value = "", labelResolvers = {})`
|
|
375
|
+
- `defaultChipLabel(filter = {}, value, labelResolvers = {})`
|
|
376
|
+
|
|
342
377
|
### `src/client/composables/useCrudListParentTitle.js`
|
|
343
378
|
Exports
|
|
344
379
|
- `useCrudListParentTitle({ listRuntime = null, resource = {}, adapter = null, recordIdParam = "recordId", queryKeyPrefix = ["users-web", "crud-list-parent-title"], placementSource = "users-web.crud-list-parent-title", fallbackLoadError = "Unable to load parent record.", notFoundMessage = "Parent record not found.", route = null, viewRuntimeFactory = useView } = {})`
|
|
@@ -154,6 +154,10 @@ Exports
|
|
|
154
154
|
- `readWorkspaceSlugFromRouteParams(params = {})`
|
|
155
155
|
- `buildWorkspaceInputFromRouteParams(params = {})`
|
|
156
156
|
|
|
157
|
+
### `src/server/support/workspaceServerScopeSupport.js`
|
|
158
|
+
Exports
|
|
159
|
+
- `createWorkspaceServerScopeSupport()`
|
|
160
|
+
|
|
157
161
|
### `src/server/workspaceBootstrapContributor.js`
|
|
158
162
|
Exports
|
|
159
163
|
- `createWorkspaceBootstrapContributor({ workspaceService, workspacePendingInvitationsService, usersRepository, workspaceInvitationsEnabled = false, appConfig = {}, tenancyProfile = null } = {})`
|
|
@@ -313,6 +313,14 @@ Exports
|
|
|
313
313
|
Exports
|
|
314
314
|
- `buildWorkspaceQueryKey(kind = "", surfaceId = "", workspaceSlug = "")`
|
|
315
315
|
|
|
316
|
+
### `src/client/support/workspaceScopeSupport.js`
|
|
317
|
+
Exports
|
|
318
|
+
- `WORKSPACES_WEB_SCOPE_SUPPORT_INJECTION_KEY`
|
|
319
|
+
- `createWorkspaceScopeSupport()`
|
|
320
|
+
- `readWorkspaceRouteScope(routeContext = {})`
|
|
321
|
+
Local functions
|
|
322
|
+
- `unwrapRefValue(value)`
|
|
323
|
+
|
|
316
324
|
### `src/shared/toolsOutletContracts.js`
|
|
317
325
|
Exports
|
|
318
326
|
- `DEFAULT_TOOLS_LINK_COMPONENT_TOKEN`
|
package/templates/app/AGENTS.md
CHANGED
|
@@ -15,12 +15,43 @@ Use these references on demand:
|
|
|
15
15
|
|
|
16
16
|
- `node_modules/@jskit-ai/agent-docs/reference/autogen/KERNEL_MAP.md`
|
|
17
17
|
- `node_modules/@jskit-ai/agent-docs/reference/autogen/README.md`
|
|
18
|
+
- `node_modules/@jskit-ai/agent-docs/patterns/INDEX.md`
|
|
18
19
|
- `node_modules/@jskit-ai/agent-docs/guide/agent/index.md`
|
|
19
20
|
- `node_modules/@jskit-ai/agent-docs/site/guide/index.md` when compressed guidance is ambiguous or missing nuance
|
|
20
21
|
- `node_modules/@jskit-ai/agent-docs/templates/APP_BLUEPRINT.md`
|
|
21
22
|
- `node_modules/@jskit-ai/agent-docs/templates/WORKBOARD.md`
|
|
22
23
|
- `node_modules/@jskit-ai/agent-docs/skills/jskit-review/SKILL.md` for review passes when your Codex environment supports packaged skills
|
|
23
24
|
|
|
25
|
+
## Mandatory Start Gate
|
|
26
|
+
|
|
27
|
+
Before any non-trivial change:
|
|
28
|
+
|
|
29
|
+
1. Read this file and the workflow files above in the current turn. Do not rely on memory alone.
|
|
30
|
+
2. If the task involves JSKIT UI, routing, surfaces, CRUDs, filters, placements, live actions, or similar implementation details, scan `node_modules/@jskit-ai/agent-docs/patterns/INDEX.md` and read the relevant pattern files before editing.
|
|
31
|
+
3. For substantial or multi-chunk work, create or update `.jskit/WORKBOARD.md` before editing.
|
|
32
|
+
4. Before editing, print a compact read receipt with:
|
|
33
|
+
- `Read receipt: ...`
|
|
34
|
+
- `Active chunk: ...`
|
|
35
|
+
- `Generator decision: ...`
|
|
36
|
+
- `Relevant patterns: ...`
|
|
37
|
+
- `Active rules from docs: ...`
|
|
38
|
+
5. When files need to be created, prefer `jskit generate ...` over creating them from scratch, even if the generated output will need follow-up adaptation. If not using a generator, say why.
|
|
39
|
+
6. Do not edit code until the read receipt is printed and the workboard step is complete when it applies.
|
|
40
|
+
|
|
41
|
+
## Mandatory Done Gate
|
|
42
|
+
|
|
43
|
+
Before calling a chunk done, report:
|
|
44
|
+
|
|
45
|
+
- `Deslop review: ...`
|
|
46
|
+
- `JSKIT review: ...`
|
|
47
|
+
- `Material/Vuetify review: ...`
|
|
48
|
+
- `Verification: ...`
|
|
49
|
+
- `Files changed: ...`
|
|
50
|
+
- `Commands run: ...`
|
|
51
|
+
- `Remaining unverified: ...`
|
|
52
|
+
|
|
53
|
+
If a feature spans more than one chunk, repeat those passes on the whole changeset after the final chunk.
|
|
54
|
+
|
|
24
55
|
Core rules:
|
|
25
56
|
|
|
26
57
|
- Inspect the workspace before assuming a JSKIT app exists.
|
|
@@ -41,7 +72,6 @@ Core rules:
|
|
|
41
72
|
- For CRUD chunks, ask the developer which operations are allowed, which fields belong in the list view if one exists, and the intended look of the view and edit/new forms before generating code.
|
|
42
73
|
- Every user-facing screen must pass a Material Design and Vuetify quality review before the chunk is considered done.
|
|
43
74
|
- Do not implement app features before the blueprint has the database, surfaces, ownership model, and route/screen plan written down.
|
|
44
|
-
- For substantial or multi-chunk work, create or update `.jskit/WORKBOARD.md` as the per-request execution tracker.
|
|
45
75
|
- Break planned work into reviewable chunks before implementing. One CRUD is usually one chunk, but auth/platform setup, shell work, and cross-cutting integrations can be their own chunks.
|
|
46
76
|
- Do not move to the next chunk until the current chunk has passed implementation, deslop review, JSKIT best-practices review, Material Design/Vuetify review, and verification.
|
|
47
77
|
- If a feature spans more than one chunk, run a final whole-changeset deslop pass, JSKIT pass, Material Design/Vuetify pass, and verification pass after the last chunk.
|