@jskit-ai/agent-docs 0.1.18 → 0.1.19

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.
@@ -212,13 +212,17 @@ The account route itself is very small:
212
212
  <template>
213
213
  <AccountSettingsClientElement />
214
214
  </template>
215
+
216
+ <script setup>
217
+ import AccountSettingsClientElement from "@jskit-ai/users-web/client/components/AccountSettingsClientElement";
218
+ </script>
215
219
  ```
216
220
 
217
221
  That is a very JSKIT-style file.
218
222
 
219
223
  - the route policy is app-owned
220
224
  - the page wrapper is app-owned
221
- - the heavy UI is delegated to a reusable client element
225
+ - the heavy UI is delegated to a package-owned reusable client element
222
226
 
223
227
  So the route is simple, but it is already a real authenticated account screen rather than a placeholder card.
224
228
 
@@ -228,18 +232,21 @@ The account page is backed by:
228
232
 
229
233
  ```text
230
234
  src/components/account/settings/
231
- AccountSettingsClientElement.vue
232
235
  AccountSettingsProfileSection.vue
233
236
  AccountSettingsPreferencesSection.vue
234
237
  AccountSettingsNotificationsSection.vue
235
238
  ```
236
239
 
237
- The top-level `AccountSettingsClientElement.vue` uses `useAccountSettingsRuntime()` from `users-web` and wires those sections into a tabbed settings experience.
240
+ Those three section components stay app-owned so you can reshape the actual UI freely.
241
+
242
+ The host itself now lives in `users-web` and resolves every account section through the `account-settings:sections` placement target, including the default `profile`, `preferences`, and `notifications` entries.
243
+
244
+ So the screen follows the same rule as the rest of JSKIT UI: sections are added by placement rather than being hardcoded into an app-owned host component.
238
245
 
239
246
  That is worth noticing because the guide has now reached a new level of scaffolding:
240
247
 
241
248
  - earlier chapters mostly introduced shells and routes
242
- - this chapter introduces real app-owned feature UI that is already split into reusable sections
249
+ - this chapter introduces app-owned leaf section UI while the generic section host stays in the package
243
250
 
244
251
  ## Under the hood
245
252
 
@@ -666,6 +666,7 @@ In the current CLI, that includes checks such as:
666
666
  - installed package entries in `.jskit/lock.json`
667
667
  - whether managed files recorded in the lock still exist
668
668
  - whether installed packages are still visible in the package registry
669
+ - explicit `transport` passed to high-level CRUD hooks such as `useCrudList()`, `useCrudView()`, and `useCrudAddEdit()`, where the shared CRUD resource should derive the JSON:API transport automatically
669
670
  - certain JSKIT-specific app checks, such as invalid raw `mdi-*` icon literals in Vue templates when the app uses Vuetify's `mdi-svg` iconset
670
671
  - UI verification receipts for current dirty UI files in git, via `.jskit/verification/ui.json`
671
672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Distributed JSKIT agent workflows, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
package/patterns/INDEX.md CHANGED
@@ -28,6 +28,8 @@ How to use it:
28
28
  - `ui-testing.md`
29
29
  - filter, filters, search facets, chips, date range, enum filter, lookup filter, `useCrudListFilters`, `createCrudListFilters`
30
30
  - `filters.md`
31
+ - `searchSchema`, `search: true`, `applyFilter`, server search, query validators, backend filters, internal JSON REST filters
32
+ - `server-search.md`
31
33
  - `actualField`, `storage.writeSerializer`, `storage.virtual`, computed field, virtual field, projection, `createCrudResourceRuntime`, `remainingBatchWeight`, datetime write serialization
32
34
  - `crud-repository-mapping.md`
33
35
 
@@ -42,4 +44,5 @@ How to use it:
42
44
  - [client-requests.md](./client-requests.md)
43
45
  - [ui-testing.md](./ui-testing.md)
44
46
  - [filters.md](./filters.md)
47
+ - [server-search.md](./server-search.md)
45
48
  - [crud-repository-mapping.md](./crud-repository-mapping.md)
@@ -41,6 +41,11 @@ Use the CRUD wrappers when they fit:
41
41
  - `useCrudView()` for routed CRUD record loading
42
42
  - `useCrudAddEdit()` for routed CRUD forms
43
43
 
44
+ CRUD hook transport defaults:
45
+
46
+ - CRUD hooks derive the standard JSON:API transport from the shared CRUD `resource` automatically.
47
+ - Do not pass `transport` to CRUD hooks. If you need a non-standard wire contract, drop to `useList()`, `useView()`, `useAddEdit()`, or `usersWebHttpClient.request(...)` instead of the CRUD wrappers.
48
+
44
49
  Why this is the standard JSKIT shape:
45
50
 
46
51
  - `useCommand()` resolves the scoped API path for the current route and surface.
@@ -5,6 +5,10 @@ Use when:
5
5
  - the user asks to "add a filter", "make the list filterable", or "keep filters in the URL"
6
6
  - a screen mixes free-text search with structured facets
7
7
 
8
+ Server-side note:
9
+ - for the current CRUD server contract on the JSON REST path, see `server-search.md`
10
+ - this file is about the shared structured-filter runtime used by CRUD list pages and explicit list-filter modules
11
+
8
12
  Ask first:
9
13
  - which fields are filterable
10
14
  - whether each filter is a flag, enum, multi-enum, date/date-range, number-range, record id, or lookup-backed record id
@@ -0,0 +1,211 @@
1
+ # Server Search
2
+
3
+ Use when:
4
+ - a CRUD list accepts `q` or other server-side query params
5
+ - a resource needs alias filters such as `onlyArchived -> archived`
6
+ - a list needs access-scoped filters that the repository injects before querying
7
+ - the user asks where search belongs on the server in a current JSKIT app
8
+
9
+ Core rule:
10
+ - for current JSKIT CRUD apps, the server search contract lives in `resource.searchSchema` on the internal JSON REST path
11
+ - route validators decide which public query keys HTTP callers may send
12
+ - repositories may add internal-only filter keys before forwarding the query to JSON REST
13
+
14
+ Fresh generated CRUD repositories already follow this path:
15
+
16
+ ```js
17
+ return api.resources.contacts.query({
18
+ queryParams: buildJsonRestQueryParams(RESOURCE_TYPE, query),
19
+ simplified: false
20
+ }, createJsonRestContext(context));
21
+ ```
22
+
23
+ The normal layer split is:
24
+ 1. `actions.js` / `registerRoutes.js`: accept and validate public query keys
25
+ 2. `repository.js`: add internal-only filter keys such as visibility scope
26
+ 3. `*Resource.js`: define the backend filter behavior in `searchSchema`
27
+
28
+ ## Decision Rules
29
+
30
+ Use `search: true` on a schema field when:
31
+ - the public filter key should be the same as the field name
32
+ - direct field filtering is enough
33
+ - you are happy with the generated/effective search entry for that one field
34
+
35
+ Example:
36
+
37
+ ```js
38
+ schema: {
39
+ archived: {
40
+ type: "boolean",
41
+ search: true,
42
+ operations: {
43
+ output: { required: true }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ Good fit:
50
+ - `archived=true`
51
+ - `statusSid=active`
52
+ - direct field-by-field filtering with no aliasing
53
+
54
+ Do not use `search: true` when:
55
+ - you want a different public key than the storage field
56
+ - you need one key to search several fields
57
+ - you need custom SQL semantics
58
+ - you need an internal-only injected filter
59
+
60
+ Use explicit `searchSchema` when:
61
+ - the public key differs from the field name
62
+ - one key searches multiple fields
63
+ - the route/API contract should stay stable even if field names change
64
+ - the filter is internal-only and injected by the repository
65
+ - you need to override the simple `search: true` behavior
66
+
67
+ Example:
68
+
69
+ ```js
70
+ searchSchema: {
71
+ onlyArchived: {
72
+ type: "boolean",
73
+ actualField: "archived",
74
+ filterOperator: "="
75
+ },
76
+ q: {
77
+ type: "string",
78
+ oneOf: ["firstName", "lastName", "email"],
79
+ filterOperator: "like",
80
+ splitBy: " ",
81
+ matchAll: true
82
+ }
83
+ }
84
+ ```
85
+
86
+ Important merge rule:
87
+ - explicit `searchSchema` is the authored contract
88
+ - fields marked `search: true` are added to the effective search schema too
89
+ - if both define the same public key, explicit `searchSchema` wins
90
+
91
+ Use `applyFilter` when:
92
+ - `actualField`, `oneOf`, and normal operators are not enough
93
+ - the filter needs compound SQL
94
+ - the filter encodes visibility rules, status buckets, or null/not-null semantics that are more complex than a direct field comparison
95
+
96
+ Example:
97
+
98
+ ```js
99
+ searchSchema: {
100
+ "__viewerAccess": {
101
+ type: "string",
102
+ applyFilter(query, rawValue) {
103
+ const value = String(rawValue || "").trim();
104
+
105
+ query.whereNull("deleted_at");
106
+ if (value === "privileged") {
107
+ return;
108
+ }
109
+
110
+ const userId = value.startsWith("user:")
111
+ ? String(value.slice(5) || "").trim()
112
+ : "";
113
+
114
+ query.where(function () {
115
+ this.where("user_added", 0);
116
+ if (userId) {
117
+ this.orWhere("added_by_user_id", userId);
118
+ }
119
+ });
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+ Keep `applyFilter` focused on query semantics:
126
+ - build the SQL/knex filter there
127
+ - do not put permission checks there
128
+ - do not turn it into a second service layer
129
+
130
+ Add route validators when:
131
+ - the filter key is public and may arrive from HTTP query params
132
+ - the page/UI needs a stable, validated query contract
133
+ - malformed values should either be rejected or deliberately discarded
134
+
135
+ Example route layer:
136
+
137
+ ```js
138
+ input: composeSchemaDefinitions([
139
+ workspaceSlugParamsValidator,
140
+ listCursorPaginationQueryValidator,
141
+ listSearchQueryValidator,
142
+ vetsListFiltersQueryValidator,
143
+ ])
144
+ ```
145
+
146
+ That means:
147
+ - `q` and the structured filter keys are accepted publicly
148
+ - the repository may still add internal-only keys later
149
+
150
+ Do not add route validators for:
151
+ - repository-injected internal keys such as `"__viewerAccess"`
152
+ - filters that must never be supplied by the client directly
153
+
154
+ ## Recommended Shapes
155
+
156
+ Simple direct field exposure:
157
+
158
+ ```js
159
+ schema: {
160
+ vip: { type: "boolean", search: true }
161
+ }
162
+ ```
163
+
164
+ Public alias for a real field:
165
+
166
+ ```js
167
+ searchSchema: {
168
+ onlyVip: { type: "boolean", actualField: "vip", filterOperator: "=" }
169
+ }
170
+ ```
171
+
172
+ Grouped text search:
173
+
174
+ ```js
175
+ searchSchema: {
176
+ q: {
177
+ type: "string",
178
+ oneOf: ["name", "email", "phone"],
179
+ filterOperator: "like",
180
+ splitBy: " ",
181
+ matchAll: true
182
+ }
183
+ }
184
+ ```
185
+
186
+ Repository-injected internal filter:
187
+
188
+ ```js
189
+ function buildScopedQuery(query = {}, context = null) {
190
+ return {
191
+ ...(query || {}),
192
+ "__viewerAccess": resolveViewerAccess(context)
193
+ };
194
+ }
195
+ ```
196
+
197
+ ## Keep These Boundaries Clean
198
+
199
+ - Put backend filter behavior in `searchSchema`, not in page code.
200
+ - Put permission checks in the action/service layer, not in `applyFilter`.
201
+ - Put repository-only scoping in the repository, not in route query validation.
202
+ - Do not expose a query key publicly just because the repository uses it internally.
203
+ - Do not rely on route validators alone; a public key still needs a matching backend search definition.
204
+
205
+ ## Quick Rule Of Thumb
206
+
207
+ - Same key, same field, simple behavior: `search: true`
208
+ - Public alias or multi-field search: `searchSchema`
209
+ - Complex SQL semantics: `searchSchema` + `applyFilter`
210
+ - Public HTTP query key: add a route validator
211
+ - Internal-only repository key: inject it in `repository.js`, skip the public validator
@@ -244,6 +244,7 @@ Local functions
244
244
  - `resolveCommonDependencies(scope)`
245
245
  - `resolveRuntimeEnv(scope)`
246
246
  - `resolveOptionalRepositories(scope)`
247
+ - `isDeferredJsonRestBootGap(app, error)`
247
248
  - `applyAuthServiceDecorators(scope, authService)`
248
249
 
249
250
  ### root
@@ -296,6 +296,14 @@ Local functions
296
296
  ### `src/server/routeContracts.js`
297
297
  Exports
298
298
  - `createCrudJsonApiRouteContracts({ resource = {}, routeParamsValidator = null, listSearchQueryValidator = defaultListSearchQueryValidator, lookupIncludeQueryValidator = defaultLookupIncludeQueryValidator } = {})`
299
+ Local functions
300
+ - `isRecord(value)`
301
+ - `resolveSchemaFieldDefinitions(definition = null)`
302
+ - `resolveJsonApiRelationshipEntries(definition = null)`
303
+ - `createRecordAttributesResolver(definition = null, { excludeKeys = [] } = {})`
304
+ - `createRecordRelationshipsResolver(definition = null)`
305
+ - `createRequestRelationshipMapper(definition = null)`
306
+ - `resolveOutputAttributeExcludeKeys(resource = {})`
299
307
 
300
308
  ### `src/server/serviceEvents.js`
301
309
  Exports
@@ -132,6 +132,7 @@ Local functions
132
132
  - `requireVariableDeclarator(programNode, variableName = "", context = "crud-server-generator scaffold-field")`
133
133
  - `requireCrudResourceConfigObject(programNode, context = "crud-server-generator scaffold-field")`
134
134
  - `requireResourceSchemaObject(programNode, context = "crud-server-generator scaffold-field")`
135
+ - `assertNoExplicitCrudSchemaOverrides(resourceObject, context = "crud-server-generator scaffold-field")`
135
136
  - `findObjectPropertyByName(objectNode, propertyName = "")`
136
137
  - `hasObjectProperty(objectNode, propertyName = "")`
137
138
  - `insertObjectProperty(objectNode, propertyName = "", valueExpressionSource = "", { context = "crud-server-generator scaffold-field", insertBeforeComputed = false } = {})`
@@ -111,7 +111,10 @@ Local functions
111
111
  - `isRecord(value)`
112
112
  - `normalizeTransportKind(transport = null)`
113
113
  - `defaultEncodeAttributes(body = {})`
114
- - `simplifyResourceObject(resource = {})`
114
+ - `resolveRelationshipFieldKey(relationshipName = "", lookupFieldMap = null)`
115
+ - `createIncludedResourceIndex(document = {})`
116
+ - `simplifyRelationshipResource(linkage = {}, options = {})`
117
+ - `simplifyResourceObject(resource = {}, options = {})`
115
118
  - `assertPrimaryDataType(resource = {}, expectedType = "")`
116
119
  - `resolveCollectionPageMeta(document = {})`
117
120
 
@@ -262,18 +265,21 @@ Local functions
262
265
  ### `src/shared/validators/jsonApiRouteTransport.js`
263
266
  Exports
264
267
  - `JSON_API_ERROR_DOCUMENT_SCHEMA`
265
- - `createJsonApiResourceObjectTransportSchema({ type = "", attributes, requireId = true, includeLinks = false, includeMeta = false } = {})`
266
- - `createJsonApiResourceRequestBodyTransportSchema({ type = "", attributes, requireId = false } = {})`
267
- - `createJsonApiResourceSuccessTransportSchema({ type = "", attributes, kind = "record", includeLinks = false, includeMeta = false, includeIncluded = false } = {})`
268
+ - `createJsonApiResourceObjectTransportSchema({ type = "", attributes, requireId = true, includeLinks = false, includeMeta = false, excludeAttributeKeys = [], relationshipEntries = [], relationshipMembersRequired = false } = {})`
269
+ - `createJsonApiResourceRequestBodyTransportSchema({ type = "", attributes, requireId = false, excludeAttributeKeys = [], relationshipEntries = [] } = {})`
270
+ - `createJsonApiResourceSuccessTransportSchema({ type = "", attributes, kind = "record", includeLinks = false, includeMeta = false, includeIncluded = false, excludeAttributeKeys = [], relationshipEntries = [] } = {})`
268
271
  - `withJsonApiErrorResponses(successResponses, { includeValidation400 = false } = {})`
269
272
  - `createJsonApiResourceRouteTransport({ type = "", requestType = "", responseType = "", query = null, allowBodyId = false, successKind = "record", pointerPrefix = "/data/attributes", mapRequestRelationships = null, getRecordType = null, getRecordId = null, getRecordAttributes = null, getRecordRelationships = null, getRecordLinks = null, getRecordMeta = null, getIncluded = null, getDocumentLinks = null, getDocumentMeta = null, getCollectionItems = null } = {})`
270
- - `createJsonApiResourceRouteContract({ type = "", requestType = "", responseType = "", body = null, query = null, output = null, outputKind = "record", successStatus = 200, includeValidation400 = false, allowBodyId = false, pointerPrefix = "/data/attributes", getRecordType = null, getRecordId = null, getRecordAttributes = null, getRecordRelationships = null, getRecordLinks = null, getRecordMeta = null, getIncluded = null, getDocumentLinks = null, getDocumentMeta = null, getCollectionItems = null, mapRequestRelationships = null } = {})`
273
+ - `createJsonApiResourceRouteContract({ type = "", requestType = "", responseType = "", body = null, query = null, output = null, outputKind = "record", successStatus = 200, includeValidation400 = false, allowBodyId = false, pointerPrefix = "/data/attributes", bodyAttributeExcludeKeys = [], outputAttributeExcludeKeys = [], bodyRelationshipEntries = [], outputRelationshipEntries = [], getRecordType = null, getRecordId = null, getRecordAttributes = null, getRecordRelationships = null, getRecordLinks = null, getRecordMeta = null, getIncluded = null, getDocumentLinks = null, getDocumentMeta = null, getCollectionItems = null, mapRequestRelationships = null } = {})`
271
274
  Local functions
272
275
  - `isRecord(value)`
273
276
  - `createJsonApiTransportError(statusCode, message, code)`
274
277
  - `resolveRouteType(type = "")`
275
278
  - `resolveRouteTypes(value = {})`
276
- - `resolveEmbeddedAttributesTransportSchema(definition, { context = "JSON:API resource", defaultMode = "replace", removeId = false } = {})`
279
+ - `resolveEmbeddedAttributesTransportSchema(definition, { context = "JSON:API resource", defaultMode = "replace", removeId = false, removeKeys = [] } = {})`
280
+ - `normalizeRelationshipSchemaEntries(entries = [])`
281
+ - `createJsonApiRelationshipDataSchema(relationshipType = "", { nullable = false } = {})`
282
+ - `createJsonApiRelationshipsTransportSchema(entries = [], { includeRequired = false } = {})`
277
283
  - `createJsonApiMetaSuccessTransportSchema({ meta } = {})`
278
284
  - `defaultRecordTypeResolver(type)`
279
285
  - `defaultRecordIdResolver(record = {})`
@@ -24,7 +24,7 @@ Exports
24
24
  - `JSON_REST_AUTOFILTER_PRESETS`
25
25
  - `addResourceIfMissing(api, scopeName, resourceConfig)`
26
26
  - `buildJsonRestQueryParams(resourceType = "", query = {}, { include = undefined } = {})`
27
- - `createJsonApiInputRecord(resourceType = "", attributes = {}, { id = null, relationships = null } = {})`
27
+ - `createJsonApiInputRecord(resourceType = "", attributes = {}, { id = null, relationships = null, resource = null } = {})`
28
28
  - `createJsonApiRelationship(resourceType = "", id = null)`
29
29
  - `createJsonRestResourceScopeOptions(resource = {}, { writeSerializers = {}, normalizeId = null } = {})`
30
30
  - `createJsonRestContext(context = null)`
@@ -42,6 +42,7 @@ Local functions
42
42
  - `normalizeJsonRestText(value, { fallback = "" } = {})`
43
43
  - `normalizeJsonRestObject(value)`
44
44
  - `normalizeJsonRestList(value)`
45
+ - `extractJsonApiInputRelationships(attributes = {}, resource = null, relationships = null)`
45
46
  - `normalizeJsonApiResourceObject(resource = {})`
46
47
  - `buildJsonApiIncludedIndex(payload = {})`
47
48
  - `simplifyJsonApiRelationshipData(data, { includedIndex = null, seen = null } = {})`
@@ -878,6 +878,7 @@ Exports
878
878
  - `registerRoutes(fastify, { routes = [], app = null, applyRoutePolicy = defaultApplyRoutePolicy, missingHandler = defaultMissingHandler, enableRequestScope = true, requestScopeProperty = "scope", requestActionExecutorProperty = "executeAction", actionExecutorToken = "actionExecutor", requestActionDefaultChannel = "api", requestActionDefaultSurface = "", requestScopeIdPrefix = "http", requestIdResolver = null, middleware = {} } = {})`
879
879
  Local functions
880
880
  - `toFastifyRouteOptions(route)`
881
+ - `shouldStripFastifyBodySchema(route = null, schema = null)`
881
882
  - `normalizeHeaderValue(value)`
882
883
  - `normalizeMediaType(value = "")`
883
884
  - `routeDefinesRequestBody(route = null)`
@@ -54,8 +54,8 @@ Exports
54
54
  - `DEFAULT_SUBPAGES_POSITION`
55
55
  - `DEFAULT_COMPONENT_DIRECTORY`
56
56
  - `SECTION_CONTAINER_SHELL_COMPONENT`
57
- - `TAB_LINK_COMPONENT`
58
- - `TAB_LINK_COMPONENT_TOKEN`
57
+ - `SUBPAGES_LINK_COMPONENT`
58
+ - `SUBPAGES_LINK_COMPONENT_TOKEN`
59
59
  - `resolvePageTargetDetails`
60
60
  - `resolveNearestParentSubpagesHost`
61
61
  - `deriveDefaultSubpagesHost`
@@ -18,7 +18,6 @@ Use this on demand; do not load the full index at startup.
18
18
  Exports
19
19
  - `ACCOUNT_SETTINGS_SECTION_TARGET`
20
20
  - `EMPTY_ACCOUNT_SETTINGS_SECTIONS`
21
- - `RESERVED_ACCOUNT_SETTINGS_SECTION_VALUES`
22
21
  - `normalizeAccountSettingsSectionEntry(entry = null)`
23
22
  - `resolveAccountSettingsSections(entries = [])`
24
23
  - `sortAccountSettingsSections(entries = [])`
@@ -29,6 +28,13 @@ Exports
29
28
  - `createUsersBootstrapUserHandler()`
30
29
  - `registerUsersBootstrapPayloadHandlers(app)`
31
30
 
31
+ ### `src/client/components/AccountSettingsClientElement.vue`
32
+ Exports
33
+ - None
34
+ Local functions
35
+ - `normalizeSection(value)`
36
+ - `readRouteSection()`
37
+
32
38
  ### `src/client/components/ProfileClientElement.vue`
33
39
  Exports
34
40
  - None
@@ -75,6 +81,17 @@ Exports
75
81
  - `resolveCrudBindingValues(values, context = {})`
76
82
  - `resolveCrudBoundValues({ binding = {}, routeValues = {}, context = {} } = {})`
77
83
 
84
+ ### `src/client/composables/crud/crudJsonApiTransportSupport.js`
85
+ Exports
86
+ - `inferCrudJsonApiTransport(resource = null, { mode = "", operationName = "" } = {})`
87
+ - `resolveCrudJsonApiTransport(transport = null, resource = null, options = {})`
88
+ - `resolveLookupFieldMap(resource = null)`
89
+ Local functions
90
+ - `isRecord(value)`
91
+ - `resolveSchemaFieldDefinitions(definition = null)`
92
+ - `isJsonApiResourceTransport(transport = null)`
93
+ - `resolveCrudJsonApiResourceType(resource = null)`
94
+
78
95
  ### `src/client/composables/crud/crudLookupFieldLabelSupport.js`
79
96
  Exports
80
97
  - `resolveLookupItemLabel(item = {}, labelKey = "")`
@@ -84,6 +101,7 @@ Local functions
84
101
  - `hasDisplayValue(value)`
85
102
  - `resolveComposedLabel(source = {}, candidates = LOOKUP_LABEL_COMPOSITION_CANDIDATES)`
86
103
  - `resolveLookupFieldDescriptor(field = {}, relationKind = "", valueKey = "", labelKey = "")`
104
+ - `resolveFallbackLookupKey(key = "")`
87
105
 
88
106
  ### `src/client/composables/crud/crudLookupFieldRuntime.js`
89
107
  Exports
@@ -108,6 +126,7 @@ Local functions
108
126
  - `resolveFormFieldType(field = {})`
109
127
  - `resolveFormFieldFormat(field = {})`
110
128
  - `isNullableFormField(field = {})`
129
+ - `isLookupFormField(field = {})`
111
130
  - `padDateTimePart(value)`
112
131
  - `normalizeTimeWhitespace(value)`
113
132
  - `toTimeInputValue(value)`
@@ -166,7 +185,7 @@ Local functions
166
185
 
167
186
  ### `src/client/composables/records/useCrudView.js`
168
187
  Exports
169
- - `useCrudView({ paramBinding = null, route = null, ...viewOptions } = {})`
188
+ - `useCrudView({ resource = null, paramBinding = null, route = null, ...viewOptions } = {})`
170
189
 
171
190
  ### `src/client/composables/records/useList.js`
172
191
  Exports
@@ -433,6 +452,7 @@ Exports
433
452
  ### `src/client/index.js`
434
453
  Exports
435
454
  - `UsersWebClientProvider`
455
+ - `AccountSettingsClientElement`
436
456
  - `clientProviders`
437
457
 
438
458
  ### `src/client/lib/bootstrap.js`
@@ -486,13 +506,6 @@ Exports
486
506
 
487
507
  ### templates
488
508
 
489
- ### `templates/src/components/account/settings/AccountSettingsClientElement.vue`
490
- Exports
491
- - None
492
- Local functions
493
- - `normalizeSection(value)`
494
- - `readRouteSection()`
495
-
496
509
  ### `templates/src/components/account/settings/AccountSettingsNotificationsSection.vue`
497
510
  Exports
498
511
  - None
@@ -333,7 +333,6 @@ Local functions
333
333
  - `normalizePendingInvitesCount(value)`
334
334
  - `resolveReturnTo()`
335
335
  - `resolveReturnToHref()`
336
- - `countPendingInvites(entries = [])`
337
336
 
338
337
  ### `templates/packages/main/src/client/components/AccountSettingsInvitesSection.vue`
339
338
  Exports
@@ -368,6 +368,10 @@ Local functions
368
368
  ### `src/server/commandHandlers/appCommands/linkLocalPackages.js`
369
369
  Exports
370
370
  - `runAppLinkLocalPackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout })`
371
+ Local functions
372
+ - `collectDeclaredPackageNames(packageJson = {})`
373
+ - `verifySymlinkTarget(targetPath = "", sourceDir = "", { packageName = "" } = {})`
374
+ - `maybeLinkCompanionPackages({ appRoot = "", repoRoot = "", stdout, createCliError })`
371
375
 
372
376
  ### `src/server/commandHandlers/appCommands/release.js`
373
377
  Exports