From 35861641f807a02b5c205608fb592e20ee7cad7f Mon Sep 17 00:00:00 2001
From: hugocasa
Date: Wed, 1 Jul 2026 16:42:02 +0200
Subject: [PATCH 1/5] fix(offboarding): make global reassignment per-workspace
and optional (#9863)
* fix(offboarding): make global reassignment per-workspace and optional
Co-Authored-By: Claude Opus 4.8 (1M context)
* fix(offboarding): handle sole-member workspaces in workspace-level removal
Co-Authored-By: Claude Opus 4.8 (1M context)
* fix(offboarding): show close action instead of dead-end in reassign-only sole-member case
Co-Authored-By: Claude Opus 4.8 (1M context)
* fix(offboarding): prevent no-op success in global reassign-only with no reassignable workspace
Co-Authored-By: Claude Opus 4.8 (1M context)
---------
Co-authored-by: Claude Opus 4.8 (1M context)
---
.../GlobalUserOffboardingModal.svelte | 102 ++++++++++++------
.../components/UserOffboardingModal.svelte | 69 +++++++-----
2 files changed, 113 insertions(+), 58 deletions(-)
diff --git a/frontend/src/lib/components/GlobalUserOffboardingModal.svelte b/frontend/src/lib/components/GlobalUserOffboardingModal.svelte
index 12af797140..bc2cc5b996 100644
--- a/frontend/src/lib/components/GlobalUserOffboardingModal.svelte
+++ b/frontend/src/lib/components/GlobalUserOffboardingModal.svelte
@@ -34,6 +34,7 @@
let wsConfigs: Record<
string,
{
+ reassign: boolean
targetKind: 'user' | 'folder'
selectedUser: string | undefined
selectedFolder: string | undefined
@@ -71,16 +72,21 @@
UserService.listUsernames({ workspace: wp.workspace_id }),
FolderService.listFolders({ workspace: wp.workspace_id })
])
+ const users = usernamesList
+ .filter((u: string) => u !== wp.username)
+ .map((u: string) => ({ label: u, value: u }))
return {
workspace_id: wp.workspace_id,
config: {
+ // Reassignment requires another workspace user to own items and back
+ // triggers/runnables; with no other user (e.g. single-member forks) it
+ // is impossible, so default off and leave items as-is.
+ reassign: users.length > 0,
targetKind: 'user' as const,
selectedUser: undefined as string | undefined,
selectedFolder: undefined as string | undefined,
selectedOperator: undefined as string | undefined,
- users: usernamesList
- .filter((u: string) => u !== wp.username)
- .map((u: string) => ({ label: u, value: u })),
+ users,
folders: foldersList.map((f: { name: string }) => ({
label: f.name,
value: f.name
@@ -112,15 +118,28 @@
: undefined
}
+ // At least one workspace can be reassigned (has another assignable user).
+ let anyReassignableWorkspace = $derived(
+ workspacesWithItems.some((wp) => (wsConfigs[wp.workspace_id]?.users.length ?? 0) > 0)
+ )
+ // At least one workspace is actually selected for reassignment.
+ let anyWorkspaceReassigned = $derived(
+ workspacesWithItems.some((wp) => wsConfigs[wp.workspace_id]?.reassign)
+ )
+
let canSubmit = $derived(
- !doReassign ||
+ (!doReassign ||
workspacesWithItems.every((wp) => {
- const target = getReassignTo(wp.workspace_id)
const cfg = wsConfigs[wp.workspace_id]
+ if (!cfg?.reassign) return true
+ const target = getReassignTo(wp.workspace_id)
if (!target) return false
if (!cfg?.selectedOperator) return false
return true
- })
+ })) &&
+ // Reassign-only runs (no deletion) must reassign at least one workspace,
+ // otherwise the request is an empty no-op reported as success.
+ (deleteUser || anyWorkspaceReassigned)
)
async function submit() {
@@ -129,13 +148,16 @@
try {
const reassignments: Record =
{}
- for (const wp of workspacesWithItems) {
- const target = getReassignTo(wp.workspace_id)
- const cfg = wsConfigs[wp.workspace_id]
- if (target) {
- reassignments[wp.workspace_id] = {
- reassign_to: target,
- new_on_behalf_of_user: cfg?.selectedOperator
+ if (doReassign) {
+ for (const wp of workspacesWithItems) {
+ const cfg = wsConfigs[wp.workspace_id]
+ if (!cfg?.reassign) continue
+ const target = getReassignTo(wp.workspace_id)
+ if (target) {
+ reassignments[wp.workspace_id] = {
+ reassign_to: target,
+ new_on_behalf_of_user: cfg?.selectedOperator
+ }
}
}
}
@@ -222,7 +244,7 @@
{/if}
{#if doReassign}
- {#each workspacesWithItems as wp}
+ {#each workspacesWithItems as wp (wp.workspace_id)}
{@const cfg = wsConfigs[wp.workspace_id]}
@@ -230,23 +252,39 @@
{wp.workspace_id}
({wp.username})
+ {#if cfg}
+
+ {/if}
{#if cfg}
-
+ {#if cfg.users.length === 0}
+
+ No other users in this workspace. Items will be left as-is.
+
+ {:else if cfg.reassign}
+
+ {:else}
+
Items will be left as-is.
+ {/if}
{/if}
{/each}
@@ -283,7 +321,7 @@
different user/folder.
- {#each conflicts as conflict}
+ {#each conflicts as conflict, i (i)}
- {conflict}
{/each}
@@ -293,7 +331,11 @@
{/if}
- {#if workspacesWithItems.length > 0 || deleteUser}
+ {#if !deleteUser && workspacesWithItems.length > 0 && !anyReassignableWorkspace}
+ {#if !loading}
+
+ {/if}
+ {:else if workspacesWithItems.length > 0 || deleteUser}