@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.
@@ -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.73",
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.57",
187
- "@jskit-ai/realtime": "0.1.57",
188
- "@jskit-ai/kernel": "0.1.58",
189
- "@jskit-ai/shell-web": "0.1.57",
190
- "@jskit-ai/uploads-image-web": "0.1.36",
191
- "@jskit-ai/users-core": "0.1.68",
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.73",
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.57",
38
- "@jskit-ai/kernel": "0.1.58",
39
- "@jskit-ai/realtime": "0.1.57",
40
- "@jskit-ai/shell-web": "0.1.57",
41
- "@jskit-ai/uploads-image-web": "0.1.36",
42
- "@jskit-ai/users-core": "0.1.68",
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
- toResolvedRecordId({
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: resolvedRecordId,
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
- return items.length < 1;
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