@jskit-ai/workspaces-web 0.1.138 → 0.1.139-oldline.2
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 +492 -0
- package/package.json +9 -564
- package/src/client/account-settings/accountSettingsInvitesRuntime.js +2 -2
- package/src/client/account-settings/useAccountSettingsInvitesSectionRuntime.js +3 -3
- package/src/client/components/MembersAdminClientElement.vue +1 -1
- package/src/client/components/{WorkspaceProfileSurfaceSwitchMenuItem.vue → UsersProfileSurfaceSwitchMenuItem.vue} +1 -1
- package/src/client/components/{WorkspaceMembersMenuItem.vue → UsersWorkspaceMembersMenuItem.vue} +2 -2
- package/src/client/components/{WorkspacePermissionMenuItem.vue → UsersWorkspacePermissionMenuItem.vue} +3 -3
- package/src/client/components/{WorkspaceSelector.vue → UsersWorkspaceSelector.vue} +1 -1
- package/src/client/components/{WorkspaceSettingsMenuItem.vue → UsersWorkspaceSettingsMenuItem.vue} +2 -2
- package/src/client/components/WorkspaceInviteLanding.vue +4 -4
- package/src/client/components/WorkspaceMembersClientElement.vue +6 -6
- package/src/client/components/WorkspacesClientElement.vue +13 -13
- package/src/client/composables/useWorkspaceRouteContext.js +1 -1
- package/src/client/lib/httpClient.js +10 -0
- package/src/client/lib/permissions.js +27 -0
- package/src/client/lib/surfaceAccessPolicy.js +1 -4
- package/src/client/providers/WorkspacesWebClientProvider.js +11 -11
- package/src/client/runtime/bootstrapPlacementRuntime.js +1 -1
- package/src/client/runtime/bootstrapPlacementRuntimeHelpers.js +2 -2
- package/test/{packageMetadataOwnership.test.js → packageDescriptorOwnership.test.js} +2 -4
- package/test/settingsPlacementContract.test.js +20 -22
- package/test/{workspaceSelectorContract.test.js → usersWorkspaceSelectorContract.test.js} +3 -3
- /package/src/client/components/{WorkspaceToolsWidget.vue → UsersWorkspaceToolsWidget.vue} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
-
import { useSurfaceRouteContext } from "@jskit-ai/
|
|
4
|
-
import { hasPermission, normalizePermissionList } from "@jskit-ai/
|
|
5
|
-
import { usePaths } from "@jskit-ai/
|
|
3
|
+
import { useSurfaceRouteContext } from "@jskit-ai/users-web/client/composables/useSurfaceRouteContext";
|
|
4
|
+
import { hasPermission, normalizePermissionList } from "@jskit-ai/users-web/client/lib/permissions";
|
|
5
|
+
import { usePaths } from "@jskit-ai/users-web/client/composables/usePaths";
|
|
6
6
|
|
|
7
7
|
const props = defineProps({
|
|
8
8
|
label: {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { TENANCY_MODE_NONE } from "@jskit-ai/workspaces-core/shared/tenancyProfile";
|
|
10
10
|
import { mdiBriefcaseOutline } from "@mdi/js";
|
|
11
11
|
import { findWorkspaceBySlug, normalizeWorkspaceEntry, normalizeWorkspaceList } from "../lib/bootstrap.js";
|
|
12
|
-
import { usePaths } from "@jskit-ai/
|
|
12
|
+
import { usePaths } from "@jskit-ai/users-web/client/composables/usePaths";
|
|
13
13
|
import { resolveSurfaceSwitchTargetsFromPlacementContext, surfaceRequiresWorkspaceFromPlacementContext } from "../lib/workspaceSurfaceContext.js";
|
|
14
14
|
import { resolveWorkspaceSurfaceIdFromPlacementPathname, extractWorkspaceSlugFromSurfacePathname } from "../lib/workspaceSurfacePaths.js";
|
|
15
15
|
|
package/src/client/components/{WorkspaceSettingsMenuItem.vue → UsersWorkspaceSettingsMenuItem.vue}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { mdiCogOutline } from "@mdi/js";
|
|
3
|
-
import
|
|
3
|
+
import UsersWorkspacePermissionMenuItem from "./UsersWorkspacePermissionMenuItem.vue";
|
|
4
4
|
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
label: {
|
|
@@ -28,7 +28,7 @@ const WORKSPACE_SETTINGS_MENU_PERMISSIONS = Object.freeze([
|
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
<template>
|
|
31
|
-
<
|
|
31
|
+
<UsersWorkspacePermissionMenuItem
|
|
32
32
|
:label="props.label"
|
|
33
33
|
:to="props.to"
|
|
34
34
|
:icon="props.icon"
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
import { computed, onMounted } from "vue";
|
|
63
63
|
import { useRoute, useRouter } from "vue-router";
|
|
64
64
|
import { useAuthStore } from "@jskit-ai/auth-web/client";
|
|
65
|
-
import { useEndpointResource } from "@jskit-ai/
|
|
65
|
+
import { useEndpointResource } from "@jskit-ai/users-web/client/composables/useEndpointResource";
|
|
66
66
|
import {
|
|
67
67
|
WORKSPACE_INVITATION_RESOLVE_TRANSPORT,
|
|
68
68
|
WORKSPACE_INVITE_REDEEM_TRANSPORT
|
|
69
69
|
} from "@jskit-ai/workspaces-core/shared/jsonApiTransports";
|
|
70
|
-
import {
|
|
70
|
+
import { workspacesWebHttpClient } from "../lib/httpClient.js";
|
|
71
71
|
|
|
72
72
|
const route = useRoute();
|
|
73
73
|
const router = useRouter();
|
|
@@ -84,7 +84,7 @@ const inviteResource = useEndpointResource({
|
|
|
84
84
|
queryKey: computed(() => ["workspaces-web", "invite-landing", token.value]),
|
|
85
85
|
path: "/api/workspace/invitations/resolve",
|
|
86
86
|
enabled: computed(() => Boolean(token.value)),
|
|
87
|
-
client:
|
|
87
|
+
client: workspacesWebHttpClient,
|
|
88
88
|
readQuery: inviteQuery,
|
|
89
89
|
transport: WORKSPACE_INVITATION_RESOLVE_TRANSPORT,
|
|
90
90
|
fallbackLoadError: "Unable to load invitation."
|
|
@@ -94,7 +94,7 @@ const redeemResource = useEndpointResource({
|
|
|
94
94
|
queryKey: computed(() => ["workspaces-web", "invite-landing", "redeem", token.value]),
|
|
95
95
|
path: "/api/workspace/invitations/redeem",
|
|
96
96
|
enabled: false,
|
|
97
|
-
client:
|
|
97
|
+
client: workspacesWebHttpClient,
|
|
98
98
|
writeMethod: "POST",
|
|
99
99
|
transport: WORKSPACE_INVITE_REDEEM_TRANSPORT,
|
|
100
100
|
fallbackSaveError: "Unable to accept invitation."
|
|
@@ -41,12 +41,12 @@ import {
|
|
|
41
41
|
WORKSPACE_SETTINGS_TRANSPORT
|
|
42
42
|
} from "@jskit-ai/workspaces-core/shared/jsonApiTransports";
|
|
43
43
|
import MembersAdminClientElement from "./MembersAdminClientElement.vue";
|
|
44
|
-
import { useCommand } from "@jskit-ai/
|
|
45
|
-
import { useList } from "@jskit-ai/
|
|
46
|
-
import { useView } from "@jskit-ai/
|
|
47
|
-
import { usePaths } from "@jskit-ai/
|
|
48
|
-
import { useAccess } from "@jskit-ai/
|
|
49
|
-
import { useUiFeedback } from "@jskit-ai/
|
|
44
|
+
import { useCommand } from "@jskit-ai/users-web/client/composables/useCommand";
|
|
45
|
+
import { useList } from "@jskit-ai/users-web/client/composables/useList";
|
|
46
|
+
import { useView } from "@jskit-ai/users-web/client/composables/useView";
|
|
47
|
+
import { usePaths } from "@jskit-ai/users-web/client/composables/usePaths";
|
|
48
|
+
import { useAccess } from "@jskit-ai/users-web/client/composables/useAccess";
|
|
49
|
+
import { useUiFeedback } from "@jskit-ai/users-web/client/composables/runtime/useUiFeedback";
|
|
50
50
|
import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContext.js";
|
|
51
51
|
import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
|
|
52
52
|
import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
WORKSPACES_TRANSPORT,
|
|
13
13
|
WORKSPACE_INVITE_REDEEM_TRANSPORT
|
|
14
14
|
} from "@jskit-ai/workspaces-core/shared/jsonApiTransports";
|
|
15
|
-
import { useCommand } from "@jskit-ai/
|
|
16
|
-
import { useView } from "@jskit-ai/
|
|
17
|
-
import { usePaths } from "@jskit-ai/
|
|
18
|
-
import { useRealtimeQueryInvalidation } from "@jskit-ai/
|
|
15
|
+
import { useCommand } from "@jskit-ai/users-web/client/composables/useCommand";
|
|
16
|
+
import { useView } from "@jskit-ai/users-web/client/composables/useView";
|
|
17
|
+
import { usePaths } from "@jskit-ai/users-web/client/composables/usePaths";
|
|
18
|
+
import { useRealtimeQueryInvalidation } from "@jskit-ai/users-web/client/composables/useRealtimeQueryInvalidation";
|
|
19
19
|
import { useWorkspaceSurfaceId } from "../composables/useWorkspaceSurfaceId.js";
|
|
20
20
|
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
21
21
|
|
|
@@ -76,7 +76,7 @@ const redeemInviteModel = reactive({
|
|
|
76
76
|
token: "",
|
|
77
77
|
decision: ""
|
|
78
78
|
});
|
|
79
|
-
const bootstrapQueryKey = Object.freeze(["
|
|
79
|
+
const bootstrapQueryKey = Object.freeze(["users-web", "bootstrap", "__none__"]);
|
|
80
80
|
const OWNERSHIP_PUBLIC = ROUTE_VISIBILITY_PUBLIC;
|
|
81
81
|
|
|
82
82
|
const bootstrapView = useView({
|
|
@@ -179,12 +179,12 @@ function reportFeedback({
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
errorRuntime.report({
|
|
182
|
-
source: "
|
|
182
|
+
source: "users-web.workspaces-view",
|
|
183
183
|
message: normalizedMessage,
|
|
184
184
|
intent: "action-feedback",
|
|
185
185
|
severity,
|
|
186
186
|
channel,
|
|
187
|
-
dedupeKey: dedupeKey || `
|
|
187
|
+
dedupeKey: dedupeKey || `users-web.workspaces-view:${severity}:${normalizedMessage}`,
|
|
188
188
|
dedupeWindowMs: 3000
|
|
189
189
|
});
|
|
190
190
|
}
|
|
@@ -235,7 +235,7 @@ async function openWorkspace(workspaceSlug) {
|
|
|
235
235
|
reportFeedback({
|
|
236
236
|
message: "Workspace surface is not configured.",
|
|
237
237
|
severity: "error",
|
|
238
|
-
dedupeKey: "
|
|
238
|
+
dedupeKey: "users-web.workspaces-view:workspace-surface-missing"
|
|
239
239
|
});
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
@@ -259,7 +259,7 @@ async function openWorkspace(workspaceSlug) {
|
|
|
259
259
|
reportFeedback({
|
|
260
260
|
message: String(error?.message || "Unable to open workspace."),
|
|
261
261
|
severity: "error",
|
|
262
|
-
dedupeKey: `
|
|
262
|
+
dedupeKey: `users-web.workspaces-view:open-workspace:${normalizedSlug}`
|
|
263
263
|
});
|
|
264
264
|
} finally {
|
|
265
265
|
selectingWorkspaceSlug.value = "";
|
|
@@ -301,7 +301,7 @@ async function respondToInvite(invite, decision) {
|
|
|
301
301
|
reportFeedback({
|
|
302
302
|
message: "Invitation refused.",
|
|
303
303
|
severity: "success",
|
|
304
|
-
dedupeKey: `
|
|
304
|
+
dedupeKey: `users-web.workspaces-view:invite-refused:${token}`
|
|
305
305
|
});
|
|
306
306
|
} catch (error) {
|
|
307
307
|
reportFeedback({
|
|
@@ -309,7 +309,7 @@ async function respondToInvite(invite, decision) {
|
|
|
309
309
|
error?.message || (normalizedDecision === "accept" ? "Unable to accept invite." : "Unable to refuse invite.")
|
|
310
310
|
),
|
|
311
311
|
severity: "error",
|
|
312
|
-
dedupeKey: `
|
|
312
|
+
dedupeKey: `users-web.workspaces-view:invite-${normalizedDecision}:${token}`
|
|
313
313
|
});
|
|
314
314
|
} finally {
|
|
315
315
|
inviteAction.value = {
|
|
@@ -339,7 +339,7 @@ async function createWorkspace() {
|
|
|
339
339
|
reportFeedback({
|
|
340
340
|
message: "Workspace name is required.",
|
|
341
341
|
severity: "error",
|
|
342
|
-
dedupeKey: "
|
|
342
|
+
dedupeKey: "users-web.workspaces-view:create-workspace-name-required"
|
|
343
343
|
});
|
|
344
344
|
return;
|
|
345
345
|
}
|
|
@@ -358,7 +358,7 @@ async function createWorkspace() {
|
|
|
358
358
|
reportFeedback({
|
|
359
359
|
message: String(error?.message || "Unable to create workspace."),
|
|
360
360
|
severity: "error",
|
|
361
|
-
dedupeKey: "
|
|
361
|
+
dedupeKey: "users-web.workspaces-view:create-workspace-error"
|
|
362
362
|
});
|
|
363
363
|
}
|
|
364
364
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
|
-
import { useSurfaceRouteContext } from "@jskit-ai/
|
|
2
|
+
import { useSurfaceRouteContext } from "@jskit-ai/users-web/client/composables/useSurfaceRouteContext";
|
|
3
3
|
import { readWorkspaceRouteScope } from "../support/workspaceScopeSupport.js";
|
|
4
4
|
|
|
5
5
|
function useWorkspaceRouteContext() {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createTransientRetryHttpClient } from "@jskit-ai/http-runtime/client";
|
|
2
|
+
|
|
3
|
+
const workspacesWebHttpClient = createTransientRetryHttpClient({
|
|
4
|
+
credentials: "include",
|
|
5
|
+
csrf: {
|
|
6
|
+
sessionPath: "/api/session"
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export { workspacesWebHttpClient };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { hasPermission, normalizePermissionList } from "@jskit-ai/kernel/shared/support";
|
|
2
|
+
|
|
3
|
+
function toPermissionSet(values) {
|
|
4
|
+
const normalized = normalizePermissionList(values);
|
|
5
|
+
if (normalized.length < 1) {
|
|
6
|
+
return new Set();
|
|
7
|
+
}
|
|
8
|
+
return new Set(normalized);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function arePermissionListsEqual(left, right) {
|
|
12
|
+
const leftSet = toPermissionSet(left);
|
|
13
|
+
const rightSet = toPermissionSet(right);
|
|
14
|
+
if (leftSet.size !== rightSet.size) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const permission of leftSet) {
|
|
19
|
+
if (!rightSet.has(permission)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { normalizePermissionList, arePermissionListsEqual, hasPermission };
|
|
@@ -4,10 +4,7 @@ import {
|
|
|
4
4
|
normalizeRecord,
|
|
5
5
|
normalizeWorkspaceBootstrapStatusValue
|
|
6
6
|
} from "../support/runtimeNormalization.js";
|
|
7
|
-
import {
|
|
8
|
-
hasPermission,
|
|
9
|
-
normalizePermissionList
|
|
10
|
-
} from "@jskit-ai/http-web/client/lib/permissions";
|
|
7
|
+
import { hasPermission, normalizePermissionList } from "./permissions.js";
|
|
11
8
|
|
|
12
9
|
const WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND = "not_found";
|
|
13
10
|
const WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN = "forbidden";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import UsersProfileSurfaceSwitchMenuItem from "../components/UsersProfileSurfaceSwitchMenuItem.vue";
|
|
2
|
+
import UsersWorkspaceSelector from "../components/UsersWorkspaceSelector.vue";
|
|
3
|
+
import UsersWorkspaceToolsWidget from "../components/UsersWorkspaceToolsWidget.vue";
|
|
4
|
+
import UsersWorkspaceSettingsMenuItem from "../components/UsersWorkspaceSettingsMenuItem.vue";
|
|
5
|
+
import UsersWorkspaceMembersMenuItem from "../components/UsersWorkspaceMembersMenuItem.vue";
|
|
6
6
|
import MembersAdminClientElement from "../components/MembersAdminClientElement.vue";
|
|
7
7
|
import { registerBootstrapPayloadHandler } from "@jskit-ai/shell-web/client/bootstrap";
|
|
8
8
|
import { createBootstrapPlacementRuntime } from "../runtime/bootstrapPlacementRuntime.js";
|
|
@@ -13,18 +13,18 @@ import {
|
|
|
13
13
|
|
|
14
14
|
class WorkspacesWebClientProvider {
|
|
15
15
|
static id = "workspaces.web.client";
|
|
16
|
-
static
|
|
16
|
+
static dependsOn = ["users.web.client"];
|
|
17
17
|
|
|
18
18
|
register(app) {
|
|
19
19
|
if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
|
|
20
20
|
throw new Error("WorkspacesWebClientProvider requires application singleton()/tag().");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
app.singleton("workspaces.web.profile.menu.surface-switch-item", () =>
|
|
24
|
-
app.singleton("workspaces.web.workspace.selector", () =>
|
|
25
|
-
app.singleton("workspaces.web.workspace.tools.widget", () =>
|
|
26
|
-
app.singleton("workspaces.web.workspace-settings.menu-item", () =>
|
|
27
|
-
app.singleton("workspaces.web.workspace-members.menu-item", () =>
|
|
23
|
+
app.singleton("workspaces.web.profile.menu.surface-switch-item", () => UsersProfileSurfaceSwitchMenuItem);
|
|
24
|
+
app.singleton("workspaces.web.workspace.selector", () => UsersWorkspaceSelector);
|
|
25
|
+
app.singleton("workspaces.web.workspace.tools.widget", () => UsersWorkspaceToolsWidget);
|
|
26
|
+
app.singleton("workspaces.web.workspace-settings.menu-item", () => UsersWorkspaceSettingsMenuItem);
|
|
27
|
+
app.singleton("workspaces.web.workspace-members.menu-item", () => UsersWorkspaceMembersMenuItem);
|
|
28
28
|
app.singleton("workspaces.web.members-admin.element", () => MembersAdminClientElement);
|
|
29
29
|
app.singleton("workspaces.web.bootstrap-placement.runtime", (scope) => createBootstrapPlacementRuntime({ app: scope }));
|
|
30
30
|
registerBootstrapPayloadHandler(app, "workspaces.web.bootstrap.handler", (scope) => {
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
findWorkspaceBySlug,
|
|
3
3
|
normalizeWorkspaceList
|
|
4
4
|
} from "../lib/bootstrap.js";
|
|
5
|
-
import { normalizePermissionList } from "
|
|
5
|
+
import { normalizePermissionList } from "../lib/permissions.js";
|
|
6
6
|
import {
|
|
7
7
|
persistBootstrapThemePreference,
|
|
8
8
|
resolveBootstrapThemeName,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "@jskit-ai/shell-web/client/placement";
|
|
8
8
|
import { parseWorkspacePathname } from "@jskit-ai/workspaces-core/shared/support/workspacePathModel";
|
|
9
9
|
import { extractWorkspaceSlugFromSurfacePathname } from "../lib/workspaceSurfacePaths.js";
|
|
10
|
-
import {
|
|
10
|
+
import { workspacesWebHttpClient } from "../lib/httpClient.js";
|
|
11
11
|
import { buildBootstrapApiPath } from "../lib/bootstrap.js";
|
|
12
12
|
import {
|
|
13
13
|
normalizeWorkspaceBootstrapStatusValue
|
|
@@ -127,7 +127,7 @@ function countPendingInvites(entries = []) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async function fetchBootstrapPayload(workspaceSlug = "") {
|
|
130
|
-
return
|
|
130
|
+
return workspacesWebHttpClient.request(buildBootstrapApiPath(workspaceSlug), {
|
|
131
131
|
method: "GET"
|
|
132
132
|
});
|
|
133
133
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
const packageMetadata = packageJson.jskit;
|
|
3
|
+
import descriptor from "../package.descriptor.mjs";
|
|
6
4
|
|
|
7
5
|
test("workspaces-web leaves editable workspace scaffolds app-owned", () => {
|
|
8
|
-
const fileMutations =
|
|
6
|
+
const fileMutations = descriptor.mutations.files;
|
|
9
7
|
|
|
10
8
|
assert.ok(fileMutations.length > 0);
|
|
11
9
|
for (const mutation of fileMutations) {
|
|
@@ -4,15 +4,13 @@ import test from "node:test";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { assertGeneratedUiSourceContract } from "@jskit-ai/kernel/shared/support/generatedUiContract";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
const packageMetadata = packageJson.jskit;
|
|
7
|
+
import descriptor from "../package.descriptor.mjs";
|
|
10
8
|
|
|
11
9
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
12
10
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
13
11
|
|
|
14
12
|
function readOutlets(target = "") {
|
|
15
|
-
const outlets =
|
|
13
|
+
const outlets = descriptor?.metadata?.ui?.placements?.outlets;
|
|
16
14
|
const normalizedTarget = String(target || "").trim();
|
|
17
15
|
return Array.isArray(outlets)
|
|
18
16
|
? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
|
|
@@ -20,7 +18,7 @@ function readOutlets(target = "") {
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
function findTopology(id, owner = "") {
|
|
23
|
-
const placements =
|
|
21
|
+
const placements = descriptor?.metadata?.ui?.placements?.topology?.placements;
|
|
24
22
|
const normalizedId = String(id || "").trim();
|
|
25
23
|
const normalizedOwner = String(owner || "").trim();
|
|
26
24
|
return Array.isArray(placements)
|
|
@@ -33,28 +31,28 @@ function findTopology(id, owner = "") {
|
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
function findContribution(id) {
|
|
36
|
-
const contributions =
|
|
34
|
+
const contributions = descriptor?.metadata?.ui?.placements?.contributions;
|
|
37
35
|
return Array.isArray(contributions)
|
|
38
36
|
? contributions.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
39
37
|
: null;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
function findTextMutation(id) {
|
|
43
|
-
const textMutations =
|
|
41
|
+
const textMutations = descriptor?.mutations?.text;
|
|
44
42
|
return Array.isArray(textMutations)
|
|
45
43
|
? textMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
46
44
|
: null;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
function findSourceMutation(id) {
|
|
50
|
-
const sourceMutations =
|
|
48
|
+
const sourceMutations = descriptor?.mutations?.source;
|
|
51
49
|
return Array.isArray(sourceMutations)
|
|
52
50
|
? sourceMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
53
51
|
: null;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
function findFileMutation(id) {
|
|
57
|
-
const fileMutations =
|
|
55
|
+
const fileMutations = descriptor?.mutations?.files;
|
|
58
56
|
return Array.isArray(fileMutations)
|
|
59
57
|
? fileMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
60
58
|
: null;
|
|
@@ -98,13 +96,13 @@ test("workspaces-web installs an app-owned account invites section wrapper", asy
|
|
|
98
96
|
source,
|
|
99
97
|
/@jskit-ai\/workspaces-web\/client\/components\/AccountSettingsInvitesSection/
|
|
100
98
|
);
|
|
101
|
-
assert.deepEqual(findFileMutation("
|
|
99
|
+
assert.deepEqual(findFileMutation("users-web-main-component-account-settings-invites-section"), {
|
|
102
100
|
from: "templates/packages/main/src/client/components/AccountSettingsInvitesSection.vue",
|
|
103
101
|
to: "packages/main/src/client/components/AccountSettingsInvitesSection.vue",
|
|
104
102
|
ownership: "app",
|
|
105
103
|
reason: "Install app-owned account invites section scaffold for multihoming account settings.",
|
|
106
104
|
category: "workspaces-web",
|
|
107
|
-
id: "
|
|
105
|
+
id: "users-web-main-component-account-settings-invites-section"
|
|
108
106
|
});
|
|
109
107
|
});
|
|
110
108
|
|
|
@@ -158,7 +156,7 @@ test("workspaces-web resource load states expose local retry actions", async ()
|
|
|
158
156
|
}
|
|
159
157
|
});
|
|
160
158
|
|
|
161
|
-
test("workspaces-web command loading state uses
|
|
159
|
+
test("workspaces-web command loading state uses users-web proxyRefs contract", async () => {
|
|
162
160
|
const expectations = new Map([
|
|
163
161
|
[
|
|
164
162
|
"src/client/components/WorkspaceMembersClientElement.vue",
|
|
@@ -204,13 +202,13 @@ test("workspaces-web installs an account invites cue scaffold that reads placeme
|
|
|
204
202
|
assert.doesNotMatch(source, /\buseQuery\b/);
|
|
205
203
|
assert.match(source, /placementContext\.value\?\.pendingInvitesCount/);
|
|
206
204
|
assert.match(source, /placementContext\.value\?\.workspaceInvitesEnabled/);
|
|
207
|
-
assert.deepEqual(findFileMutation("
|
|
205
|
+
assert.deepEqual(findFileMutation("users-web-main-component-account-pending-invites-cue"), {
|
|
208
206
|
from: "templates/packages/main/src/client/components/AccountPendingInvitesCue.vue",
|
|
209
207
|
to: "packages/main/src/client/components/AccountPendingInvitesCue.vue",
|
|
210
208
|
ownership: "app",
|
|
211
209
|
reason: "Install app-owned account pending invites cue component scaffold.",
|
|
212
210
|
category: "workspaces-web",
|
|
213
|
-
id: "
|
|
211
|
+
id: "users-web-main-component-account-pending-invites-cue"
|
|
214
212
|
});
|
|
215
213
|
});
|
|
216
214
|
|
|
@@ -284,7 +282,7 @@ test("workspaces-web starter surfaces avoid instructional placeholder copy", asy
|
|
|
284
282
|
assert.doesNotMatch(adminSource, /Use this area|Privileged workspace workflows/);
|
|
285
283
|
});
|
|
286
284
|
|
|
287
|
-
test("workspaces-web
|
|
285
|
+
test("workspaces-web descriptor metadata advertises admin settings outlets", () => {
|
|
288
286
|
assert.deepEqual(
|
|
289
287
|
readOutlets("admin-settings:primary-menu"),
|
|
290
288
|
[
|
|
@@ -301,7 +299,7 @@ test("workspaces-web packageMetadata metadata advertises admin settings outlets"
|
|
|
301
299
|
{
|
|
302
300
|
target: "admin-cog:primary-menu",
|
|
303
301
|
surfaces: ["admin"],
|
|
304
|
-
source: "src/client/components/
|
|
302
|
+
source: "src/client/components/UsersWorkspaceToolsWidget.vue"
|
|
305
303
|
}
|
|
306
304
|
]
|
|
307
305
|
);
|
|
@@ -418,29 +416,29 @@ test("workspaces-web packageMetadata metadata advertises admin settings outlets"
|
|
|
418
416
|
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /outlet: "admin-settings:primary-menu"/);
|
|
419
417
|
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /id: "admin\.tools-menu"/);
|
|
420
418
|
assert.match(findTextMutation("workspaces-web-admin-placement-topology")?.value || "", /outlet: "admin-cog:primary-menu"/);
|
|
421
|
-
assert.deepEqual(findFileMutation("
|
|
419
|
+
assert.deepEqual(findFileMutation("users-web-page-admin-workspace-settings"), {
|
|
422
420
|
from: "templates/src/pages/admin/workspace/settings/index.vue",
|
|
423
421
|
toSurface: "admin",
|
|
424
422
|
toSurfacePath: "workspace/settings/index.vue",
|
|
425
423
|
ownership: "app",
|
|
426
424
|
reason: "Install workspace settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
427
425
|
category: "workspaces-web",
|
|
428
|
-
id: "
|
|
426
|
+
id: "users-web-page-admin-workspace-settings",
|
|
429
427
|
when: {
|
|
430
428
|
config: "tenancyMode",
|
|
431
429
|
in: ["personal", "workspaces"]
|
|
432
430
|
}
|
|
433
431
|
});
|
|
434
|
-
assert.deepEqual(findSourceMutation("
|
|
432
|
+
assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-section-import"), {
|
|
435
433
|
op: "ensure-import",
|
|
436
434
|
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
437
435
|
defaultImport: "AccountSettingsInvitesSection",
|
|
438
436
|
from: "../components/AccountSettingsInvitesSection.vue",
|
|
439
437
|
reason: "Bind app-owned account invites section component into local main client provider imports.",
|
|
440
438
|
category: "workspaces-web",
|
|
441
|
-
id: "
|
|
439
|
+
id: "users-web-main-client-provider-account-settings-section-import"
|
|
442
440
|
});
|
|
443
|
-
assert.deepEqual(findSourceMutation("
|
|
441
|
+
assert.deepEqual(findSourceMutation("users-web-main-client-provider-account-settings-section-register"), {
|
|
444
442
|
op: "ensure-call",
|
|
445
443
|
file: "packages/main/src/client/providers/MainClientProvider.js",
|
|
446
444
|
callee: "registerMainClientComponent",
|
|
@@ -448,6 +446,6 @@ test("workspaces-web packageMetadata metadata advertises admin settings outlets"
|
|
|
448
446
|
beforeClass: "MainClientProvider",
|
|
449
447
|
reason: "Bind app-owned account invites section component token into local main client provider registry.",
|
|
450
448
|
category: "workspaces-web",
|
|
451
|
-
id: "
|
|
449
|
+
id: "users-web-main-client-provider-account-settings-section-register"
|
|
452
450
|
});
|
|
453
451
|
});
|
|
@@ -6,9 +6,9 @@ import { readFile } from "node:fs/promises";
|
|
|
6
6
|
|
|
7
7
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
9
|
-
const COMPONENT_PATH = path.join(PACKAGE_DIR, "src", "client", "components", "
|
|
9
|
+
const COMPONENT_PATH = path.join(PACKAGE_DIR, "src", "client", "components", "UsersWorkspaceSelector.vue");
|
|
10
10
|
|
|
11
|
-
test("
|
|
11
|
+
test("UsersWorkspaceSelector passes workspaceSlug through generic page params", async () => {
|
|
12
12
|
const source = await readFile(COMPONENT_PATH, "utf8");
|
|
13
13
|
|
|
14
14
|
assert.match(
|
|
@@ -18,7 +18,7 @@ test("WorkspaceSelector passes workspaceSlug through generic page params", async
|
|
|
18
18
|
assert.doesNotMatch(source, /workspaceSlug:\s*normalizedSlug,\s*mode:\s*"workspace"/s);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test("
|
|
21
|
+
test("UsersWorkspaceSelector activator preserves the generated tap-target contract", async () => {
|
|
22
22
|
const source = await readFile(COMPONENT_PATH, "utf8");
|
|
23
23
|
|
|
24
24
|
assert.match(source, /class="users-workspace-selector__activator text-none"/);
|
|
File without changes
|