diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index 7af4061937..bec8b28e4b 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -336,9 +336,12 @@ if (!ws || !initialPath) return if (ws in states) return untrack(() => { + // `actingUserIn` answers from `$userStore` for the navigation workspace, so only + // another one is worth asking. + const needsUser = ws !== $workspaceStore Promise.all([ ResourceService.getResource({ workspace: ws, path: initialPath, getDraft: true }), - getUserExt(ws) + needsUser ? getUserExt(ws) : undefined ]).then(([r, user]) => { // `.draft` already holds the editor's `ResourceState` shape. const savedDraftState = (r as any).draft as ResourceState | undefined @@ -366,7 +369,7 @@ // Draft-only paths (`no_deployed`) have no row — saving must // CREATE, not update (update 404s). existedInitially[ws] = !(r as any).no_deployed - perWsUser[ws] = user + if (needsUser) perWsUser[ws] = user // Keep resource_type in sync for the base workspace (controls the schema) if (ws === effectiveWorkspace) { resource_type = r.resource_type diff --git a/frontend/src/lib/components/ResourceEditorDrawer.svelte b/frontend/src/lib/components/ResourceEditorDrawer.svelte index 3f7022fa41..ea3bd1407c 100644 --- a/frontend/src/lib/components/ResourceEditorDrawer.svelte +++ b/frontend/src/lib/components/ResourceEditorDrawer.svelte @@ -69,9 +69,13 @@ () => (path && historyWorkspace !== $workspaceStore ? historyWorkspace : undefined), async (ws) => (ws ? await getUserExt(ws) : undefined) ) - const historyUser = $derived( - historyWorkspace === $workspaceStore ? $userStore : otherWsUser.current - ) + const historyUser = $derived.by(() => { + if (historyWorkspace === $workspaceStore) return $userStore + const u = otherWsUser.current + // `resource` keeps the previous result across a refetch, and a superseded lookup can + // still land last, so a user only answers for the workspace they were fetched for. + return u?.workspace_id === historyWorkspace ? u : undefined + }) // Clearing is irreversible and the backend gates it on ownership, not write access, so the // verdict has to come from the membership `historyWorkspace` knows about. An unresolved // user gets no Clear button rather than one computed from another workspace's rights. diff --git a/frontend/src/lib/components/VariableEditor.svelte b/frontend/src/lib/components/VariableEditor.svelte index 0e0f8369b5..d1cc858fc4 100644 --- a/frontend/src/lib/components/VariableEditor.svelte +++ b/frontend/src/lib/components/VariableEditor.svelte @@ -181,6 +181,9 @@ if (!ws || !p) return if (ws in states) return untrack(() => { + // `actingUserIn` answers from `$userStore` for the navigation workspace, so only + // another one is worth asking. + const needsUser = ws !== $workspaceStore Promise.all([ VariableService.getVariable({ workspace: ws, @@ -188,7 +191,7 @@ decryptSecret: false, getDraft: true }), - getUserExt(ws) + needsUser ? getUserExt(ws) : undefined ]).then(([v, user]) => { // `.draft` already holds the editor's `VariableState` shape. const savedDraftState = (v as any).draft as VariableState | undefined @@ -212,7 +215,7 @@ // CREATE, not update (update 404s). existedInitially[ws] = !(v as any).no_deployed extraPerms[ws] = v.extra_perms ?? {} - perWsUser[ws] = user + if (needsUser) perWsUser[ws] = user }) }) }) diff --git a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte index f2ce5dd186..1a1b2421ae 100644 --- a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte +++ b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte @@ -147,9 +147,13 @@ ) // `undefined` while that lookup is in flight or after it failed; the checks below then // refuse rather than fall back to rights that belong to another workspace. - const actingUser: UserExt | undefined = $derived( - wsId === $workspaceStore ? $userStore : otherWsUser.current - ) + const actingUser: UserExt | undefined = $derived.by(() => { + if (wsId === $workspaceStore) return $userStore + const u = otherWsUser.current + // `resource` keeps the previous result across a refetch, and a superseded lookup can + // still land last, so a user only answers for the workspace they were fetched for. + return u?.workspace_id === wsId ? u : undefined + }) const can_write = $derived( permsPath === undefined ? true : canWrite(permsPath, extraPerms, actingUser) ) @@ -859,7 +863,7 @@ namePlaceholder="schedule" kind="schedule" disableEditing={!can_write} - {actingUser} + actingUser={actingUser ?? null} /> {:else}