From e17684b4711b293c92d43257f76effad6f1fd56f Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Wed, 9 Sep 2026 07:14:22 +0200 Subject: [PATCH] fix: re-ask a failed acting-user lookup when an editor opens a new session Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01YLxwAsiXJ1Au8CBDBmH7iY --- frontend/src/lib/actingUser.svelte.ts | 22 ++++++++++--------- .../components/ResourceEditorDrawer.svelte | 2 ++ .../src/lib/components/VariableEditor.svelte | 1 + .../schedules/ScheduleEditorInner.svelte | 2 ++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/actingUser.svelte.ts b/frontend/src/lib/actingUser.svelte.ts index 0ed4556d83..aa9e91dc51 100644 --- a/frontend/src/lib/actingUser.svelte.ts +++ b/frontend/src/lib/actingUser.svelte.ts @@ -25,18 +25,12 @@ export function useActingUser(workspace: () => string | undefined) { // A Map, not an object: a workspace may legitimately be named `constructor`, which a plain // object would answer for out of its prototype. const looked = new SvelteMap() - // The workspace last asked about, so one selection asks once — a failed lookup included, - // which `getWorkspaceRole` deliberately does not cache. Pointing back at it re-asks. - let asked: string | undefined - $effect(() => { const ws = workspace() - if (!ws || ws === navWorkspace.current) { - asked = undefined - return - } - if (ws === asked || looked.get(ws)?.kind === 'resolved') return - asked = ws + if (!ws || ws === navWorkspace.current) return + // Any settled answer stops the asking, a failure included — otherwise recording one + // would re-enter this effect and loop. + if (looked.has(ws)) return untrack(() => { // Memoized process-wide, so two components pointed at the same workspace share one // request rather than each issuing their own. @@ -60,6 +54,14 @@ export function useActingUser(workspace: () => string | undefined) { !!ws && (ws === navWorkspace.current ? navUser !== undefined : looked.has(ws)), get current(): UserExt | undefined { return userIn(workspace()) + }, + /** Drop the lookups that came back empty so they are asked again. A long-lived editor + * must call this when it starts a fresh session, or one `whoami` that happened to fail + * pins its workspace to "unknown user" for as long as the component lives. */ + forgetFailures(): void { + for (const [ws, lookup] of looked) { + if (lookup.kind === 'lookup_failed') looked.delete(ws) + } } } } diff --git a/frontend/src/lib/components/ResourceEditorDrawer.svelte b/frontend/src/lib/components/ResourceEditorDrawer.svelte index 15c14f4251..36d49d9149 100644 --- a/frontend/src/lib/components/ResourceEditorDrawer.svelte +++ b/frontend/src/lib/components/ResourceEditorDrawer.svelte @@ -88,6 +88,7 @@ // would still be standing when the next drawer session ends and would swallow that one's // anchor clear. Every session starts having to clear its own. keepAnchorOnClose = false + historyUser.forgetFailures() resource_type = undefined path = p selected = effectiveWorkspace @@ -101,6 +102,7 @@ nDefaultValues?: Record ): Promise { keepAnchorOnClose = false + historyUser.forgetFailures() path = undefined resource_type = resourceType defaultValues = nDefaultValues diff --git a/frontend/src/lib/components/VariableEditor.svelte b/frontend/src/lib/components/VariableEditor.svelte index bbec4b2cb9..b18b48161c 100644 --- a/frontend/src/lib/components/VariableEditor.svelte +++ b/frontend/src/lib/components/VariableEditor.svelte @@ -209,6 +209,7 @@ existedInitially = {} extraPerms = {} pathError = '' + acting.forgetFailures() } export function initNew(): void { diff --git a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte index b7b17c9db0..9745654676 100644 --- a/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte +++ b/frontend/src/lib/components/triggers/schedules/ScheduleEditorInner.svelte @@ -182,6 +182,7 @@ showLoading = true }, 100) // Do not show loading spinner for the first 100ms drawerLoading = true + acting.forgetFailures() try { drawer?.openDrawer() setPageDrawerAnchor(SCHEDULES_PATH, ePath) @@ -327,6 +328,7 @@ showLoading = true }, 100) // Do not show loading spinner for the first 100ms drawerLoading = true + acting.forgetFailures() try { let s: Schedule | undefined if (schedule_path) {