@jskit-ai/users-web 0.1.73 → 0.1.75
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/package.descriptor.mjs +7 -7
- package/package.json +7 -7
- package/src/client/composables/internal/crudListParentTitleSupport.js +4 -0
- package/src/client/composables/runtime/addEditUiRuntime.js +7 -6
- package/src/client/composables/useCrudListParentTitle.js +21 -1
- package/test/addEditUiRuntime.test.js +17 -0
- package/test/useCrudListParentTitle.test.js +106 -0
- package/templates/src/components/account/settings/vibe-coding-todo.todo +0 -20
package/package.descriptor.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { HOME_COG_OUTLET } from "./src/shared/toolsOutletContracts.js";
|
|
|
3
3
|
export default Object.freeze({
|
|
4
4
|
packageVersion: 1,
|
|
5
5
|
packageId: "@jskit-ai/users-web",
|
|
6
|
-
version: "0.1.
|
|
6
|
+
version: "0.1.75",
|
|
7
7
|
kind: "runtime",
|
|
8
8
|
description: "Users web module: account/profile UI plus shared users web widgets.",
|
|
9
9
|
dependsOn: [
|
|
@@ -183,12 +183,12 @@ export default Object.freeze({
|
|
|
183
183
|
runtime: {
|
|
184
184
|
"@tanstack/vue-query": "5.92.12",
|
|
185
185
|
"@mdi/js": "^7.4.47",
|
|
186
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
187
|
-
"@jskit-ai/realtime": "0.1.
|
|
188
|
-
"@jskit-ai/kernel": "0.1.
|
|
189
|
-
"@jskit-ai/shell-web": "0.1.
|
|
190
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
191
|
-
"@jskit-ai/users-core": "0.1.
|
|
186
|
+
"@jskit-ai/http-runtime": "0.1.59",
|
|
187
|
+
"@jskit-ai/realtime": "0.1.59",
|
|
188
|
+
"@jskit-ai/kernel": "0.1.60",
|
|
189
|
+
"@jskit-ai/shell-web": "0.1.59",
|
|
190
|
+
"@jskit-ai/uploads-image-web": "0.1.38",
|
|
191
|
+
"@jskit-ai/users-core": "0.1.70",
|
|
192
192
|
vuetify: "^4.0.0"
|
|
193
193
|
},
|
|
194
194
|
dev: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.75",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@tanstack/vue-query": "5.92.12",
|
|
36
36
|
"@mdi/js": "^7.4.47",
|
|
37
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
38
|
-
"@jskit-ai/kernel": "0.1.
|
|
39
|
-
"@jskit-ai/realtime": "0.1.
|
|
40
|
-
"@jskit-ai/shell-web": "0.1.
|
|
41
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
42
|
-
"@jskit-ai/users-core": "0.1.
|
|
37
|
+
"@jskit-ai/http-runtime": "0.1.59",
|
|
38
|
+
"@jskit-ai/kernel": "0.1.60",
|
|
39
|
+
"@jskit-ai/realtime": "0.1.59",
|
|
40
|
+
"@jskit-ai/shell-web": "0.1.59",
|
|
41
|
+
"@jskit-ai/uploads-image-web": "0.1.38",
|
|
42
|
+
"@jskit-ai/users-core": "0.1.70",
|
|
43
43
|
"vuetify": "^4.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
@@ -133,7 +133,11 @@ function resolveCrudListParentTitleFromItems(items = [], descriptor = null) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
for (const item of sourceItems) {
|
|
136
|
+
const rawParentValue = toRouteParamValue(item?.[descriptor.fieldKey]);
|
|
136
137
|
const resolvedTitle = normalizeText(resolveLookupFieldDisplayValue(item, descriptor.fieldDescriptor));
|
|
138
|
+
if (resolvedTitle && rawParentValue && resolvedTitle === rawParentValue) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
137
141
|
if (resolvedTitle) {
|
|
138
142
|
return resolvedTitle;
|
|
139
143
|
}
|
|
@@ -57,19 +57,20 @@ function createAddEditUiRuntime({
|
|
|
57
57
|
...currentRouteParams,
|
|
58
58
|
...asPlainObject(extraParams)
|
|
59
59
|
};
|
|
60
|
+
const currentRouteRecordId = toResolvedRecordId({
|
|
61
|
+
routeParams: currentRouteParams,
|
|
62
|
+
recordIdParam: normalizedRecordIdParam,
|
|
63
|
+
routeRecordId
|
|
64
|
+
});
|
|
60
65
|
const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
|
|
61
|
-
|
|
62
|
-
routeParams: currentRouteParams,
|
|
63
|
-
recordIdParam: normalizedRecordIdParam,
|
|
64
|
-
routeRecordId
|
|
65
|
-
});
|
|
66
|
+
currentRouteRecordId;
|
|
66
67
|
sourceParams[normalizedRecordIdParam] = resolvedRecordId;
|
|
67
68
|
const currentPathname = resolveScopedRoutePathname({
|
|
68
69
|
currentPathname: routePath,
|
|
69
70
|
params: currentRouteParams,
|
|
70
71
|
orderedParamNames: routeParamNames,
|
|
71
72
|
anchorParamName: normalizedRecordIdParam,
|
|
72
|
-
anchorParamValue:
|
|
73
|
+
anchorParamValue: currentRouteRecordId,
|
|
73
74
|
anchorMode: "after"
|
|
74
75
|
});
|
|
75
76
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, proxyRefs } from "vue";
|
|
2
2
|
import { useRoute } from "vue-router";
|
|
3
3
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
|
+
import { inferCrudLookupJsonApiTransport } from "./crud/crudJsonApiTransportSupport.js";
|
|
4
5
|
import {
|
|
5
6
|
resolveRouteParamsSource,
|
|
6
7
|
toRouteParamValue
|
|
@@ -59,6 +60,19 @@ function useCrudListParentTitle({
|
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
const initialParentDescriptor = parentDescriptor.value || {};
|
|
63
|
+
const parentTransport = (() => {
|
|
64
|
+
const baseTransport = inferCrudLookupJsonApiTransport({
|
|
65
|
+
namespace: initialParentDescriptor.relationNamespace
|
|
66
|
+
});
|
|
67
|
+
if (!baseTransport) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
...baseTransport,
|
|
73
|
+
responseKind: "record"
|
|
74
|
+
});
|
|
75
|
+
})();
|
|
62
76
|
const normalizedQueryKeyPrefix = normalizeQueryKeyPrefix(queryKeyPrefix);
|
|
63
77
|
const shouldLoadParentRecord = computed(() => {
|
|
64
78
|
const descriptor = parentDescriptor.value;
|
|
@@ -70,12 +84,18 @@ function useCrudListParentTitle({
|
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
const items = Array.isArray(listRuntime?.items) ? listRuntime.items : [];
|
|
73
|
-
|
|
87
|
+
if (items.length < 1) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const listTitle = resolveCrudListParentTitleFromItems(items, descriptor);
|
|
92
|
+
return !listTitle;
|
|
74
93
|
});
|
|
75
94
|
|
|
76
95
|
const parentView = viewRuntimeFactory({
|
|
77
96
|
adapter,
|
|
78
97
|
apiUrlTemplate: normalizeText(initialParentDescriptor.apiUrlTemplate),
|
|
98
|
+
transport: parentTransport,
|
|
79
99
|
readEnabled: shouldLoadParentRecord,
|
|
80
100
|
recordIdParam: normalizeText(initialParentDescriptor.routeParamKey) || "recordId",
|
|
81
101
|
includeRecordIdInQueryKey: true,
|
|
@@ -35,6 +35,23 @@ test("createAddEditUiRuntime resolves view urls for saved payload ids with neste
|
|
|
35
35
|
assert.equal(runtime.resolveSavedViewUrl({ id: 99 }), "/contacts/7/addresses/99");
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
test("createAddEditUiRuntime keeps nested child routes stable when the saved child id matches a parent id", () => {
|
|
39
|
+
const runtime = createAddEditUiRuntime({
|
|
40
|
+
recordIdParam: "addressId",
|
|
41
|
+
routeParams: ref({
|
|
42
|
+
workspaceSlug: "tonymobily",
|
|
43
|
+
contactId: "1"
|
|
44
|
+
}),
|
|
45
|
+
routeParamNames: ref(["workspaceSlug", "contactId"]),
|
|
46
|
+
routePath: ref("/w/tonymobily/admin/contacts/1/addresses/new"),
|
|
47
|
+
viewUrlTemplate: "../:addressId",
|
|
48
|
+
listUrlTemplate: ".."
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
assert.equal(runtime.listUrl.value, "/w/tonymobily/admin/contacts/1/addresses");
|
|
52
|
+
assert.equal(runtime.resolveSavedViewUrl({ id: 1 }), "/w/tonymobily/admin/contacts/1/addresses/1");
|
|
53
|
+
});
|
|
54
|
+
|
|
38
55
|
test("createAddEditUiRuntime resolves edit-page relative list and cancel links", () => {
|
|
39
56
|
const runtime = createAddEditUiRuntime({
|
|
40
57
|
recordIdParam: "addressId",
|
|
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createSchema } from "json-rest-schema";
|
|
4
4
|
import { resolveCrudListParentDescriptor, resolveCrudListParentRecordTitle, resolveCrudListParentTitleFromItems } from "../src/client/composables/internal/crudListParentTitleSupport.js";
|
|
5
|
+
import { useCrudListParentTitle } from "../src/client/composables/useCrudListParentTitle.js";
|
|
5
6
|
|
|
6
7
|
const contactChildResource = Object.freeze({
|
|
7
8
|
contract: {
|
|
@@ -106,6 +107,32 @@ test("resolveCrudListParentTitleFromItems uses the hydrated lookup label", () =>
|
|
|
106
107
|
assert.equal(title, "Jessica Dickinson");
|
|
107
108
|
});
|
|
108
109
|
|
|
110
|
+
test("resolveCrudListParentTitleFromItems ignores raw lookup ids when no hydrated label is present", () => {
|
|
111
|
+
const descriptor = resolveCrudListParentDescriptor({
|
|
112
|
+
resource: contactChildResource,
|
|
113
|
+
route: {
|
|
114
|
+
matched: [{ path: "/w/:workspaceSlug/admin/contacts/:contactId/availabilities" }],
|
|
115
|
+
params: {
|
|
116
|
+
workspaceSlug: "dogandgroom",
|
|
117
|
+
contactId: "538779"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
recordIdParam: "availabilityRuleId"
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const title = resolveCrudListParentTitleFromItems(
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
id: 1,
|
|
127
|
+
contactId: 538779
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
descriptor
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
assert.equal(title, "");
|
|
134
|
+
});
|
|
135
|
+
|
|
109
136
|
test("resolveCrudListParentRecordTitle falls back to entity label plus id", () => {
|
|
110
137
|
const title = resolveCrudListParentRecordTitle(
|
|
111
138
|
{
|
|
@@ -154,3 +181,82 @@ test("resolveCrudListParentDescriptor supports parentRouteParamKey aliases", ()
|
|
|
154
181
|
assert.equal(descriptor?.fieldKey, "staffContactId");
|
|
155
182
|
assert.equal(descriptor?.routeParamKey, "contactId");
|
|
156
183
|
});
|
|
184
|
+
|
|
185
|
+
test("useCrudListParentTitle loads the parent record when child rows only expose the raw parent id", () => {
|
|
186
|
+
const runtime = useCrudListParentTitle({
|
|
187
|
+
listRuntime: {
|
|
188
|
+
items: [
|
|
189
|
+
{
|
|
190
|
+
id: 1,
|
|
191
|
+
contactId: 538779
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
isInitialLoading: false,
|
|
195
|
+
loadError: ""
|
|
196
|
+
},
|
|
197
|
+
resource: contactChildResource,
|
|
198
|
+
recordIdParam: "availabilityRuleId",
|
|
199
|
+
route: {
|
|
200
|
+
matched: [{ path: "/w/:workspaceSlug/admin/contacts/:contactId/availabilities" }],
|
|
201
|
+
params: {
|
|
202
|
+
workspaceSlug: "dogandgroom",
|
|
203
|
+
contactId: "538779"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
viewRuntimeFactory: () => ({
|
|
207
|
+
record: {
|
|
208
|
+
id: 538779,
|
|
209
|
+
firstName: "Jessica",
|
|
210
|
+
lastName: "Dickinson"
|
|
211
|
+
},
|
|
212
|
+
isLoading: false,
|
|
213
|
+
loadError: ""
|
|
214
|
+
})
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
assert.equal(runtime.shouldLoadParentRecord, true);
|
|
218
|
+
assert.equal(runtime.title, "Jessica Dickinson");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("useCrudListParentTitle requests the parent through JSON:API record transport", () => {
|
|
222
|
+
let capturedTransport = null;
|
|
223
|
+
|
|
224
|
+
useCrudListParentTitle({
|
|
225
|
+
listRuntime: {
|
|
226
|
+
items: [
|
|
227
|
+
{
|
|
228
|
+
id: 1,
|
|
229
|
+
contactId: 538779
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
isInitialLoading: false,
|
|
233
|
+
loadError: ""
|
|
234
|
+
},
|
|
235
|
+
resource: contactChildResource,
|
|
236
|
+
recordIdParam: "availabilityRuleId",
|
|
237
|
+
route: {
|
|
238
|
+
matched: [{ path: "/w/:workspaceSlug/admin/contacts/:contactId/availabilities" }],
|
|
239
|
+
params: {
|
|
240
|
+
workspaceSlug: "dogandgroom",
|
|
241
|
+
contactId: "538779"
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
viewRuntimeFactory: (options = {}) => {
|
|
245
|
+
capturedTransport = options.transport;
|
|
246
|
+
return {
|
|
247
|
+
record: {
|
|
248
|
+
id: 538779,
|
|
249
|
+
fullName: "Jessica Dickinson"
|
|
250
|
+
},
|
|
251
|
+
isLoading: false,
|
|
252
|
+
loadError: ""
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
assert.deepEqual(capturedTransport, {
|
|
258
|
+
kind: "jsonapi-resource",
|
|
259
|
+
responseType: "contacts",
|
|
260
|
+
responseKind: "record"
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
This note was split.
|
|
2
|
-
|
|
3
|
-
Use these repo-level docs instead:
|
|
4
|
-
|
|
5
|
-
- `docs/jskit-product-lanes-rulebook.md`
|
|
6
|
-
- `docs/jskit-product-lanes-workboard.md`
|
|
7
|
-
|
|
8
|
-
Why:
|
|
9
|
-
|
|
10
|
-
- the previous file had become a giant mixed artifact
|
|
11
|
-
- the durable product direction now lives in the rulebook
|
|
12
|
-
- the prioritized execution plan now lives in the workboard
|
|
13
|
-
- this template-path file is intentionally reduced to a pointer so the catch-all handoff does not keep living inside a shipped template location
|
|
14
|
-
|
|
15
|
-
If a future agent needs to continue the work:
|
|
16
|
-
|
|
17
|
-
1. read `AGENTS.md`
|
|
18
|
-
2. read the JSKIT pattern/workflow files named in the rulebook
|
|
19
|
-
3. use the rulebook for architecture and product direction
|
|
20
|
-
4. use the workboard for prioritization and execution planning
|