From 1b840dbd7f8974fe4943d9cf7c4b9ef72e506c6d Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Sat, 5 Sep 2026 15:26:27 +0200 Subject: [PATCH] feat(sessions): offer the way back to the list from an entity editor tab Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4 --- .../components/ResourceEditorDrawer.svelte | 26 +++++++++++++---- .../src/lib/components/VariableEditor.svelte | 26 +++++++++++++---- .../components/sessions/PreviewTabHost.svelte | 22 +++++++++++++-- .../sessions/ResourceEditorView.svelte | 7 +++-- .../sessions/ScheduleEditorView.svelte | 28 +++++++++++++++++-- .../sessions/VariableEditorView.svelte | 7 +++-- .../lib/components/sessions/previewRouter.ts | 8 ++++++ 7 files changed, 104 insertions(+), 20 deletions(-) diff --git a/frontend/src/lib/components/ResourceEditorDrawer.svelte b/frontend/src/lib/components/ResourceEditorDrawer.svelte index 61bc30d618..045803ba05 100644 --- a/frontend/src/lib/components/ResourceEditorDrawer.svelte +++ b/frontend/src/lib/components/ResourceEditorDrawer.svelte @@ -3,7 +3,7 @@ import DrawerContent from './common/drawer/DrawerContent.svelte' - import { History, Loader2, Save } from 'lucide-svelte' + import { ArrowLeft, History, Loader2, Save } from 'lucide-svelte' import WsSpecificVersions from './WsSpecificVersions.svelte' import { userStore, workspaceStore } from '$lib/stores' import { isOwner } from '$lib/utils' @@ -24,7 +24,8 @@ disableChatOffset = false, onRestored = undefined, onSaved = undefined, - useDrawer = true + useDrawer = true, + onBack = undefined }: { workspace?: string disableChatOffset?: boolean @@ -39,6 +40,9 @@ * drawer to open, so it simply takes effect. */ useDrawer?: boolean + /** Inline only: offered in the header when the host replaced something the + * user should be able to get back to (a session tab that took over the list). */ + onBack?: () => void } = $props() let drawer: Drawer | undefined = $state() @@ -233,9 +237,21 @@ {:else}
- {mode == 'edit' ? 'Edit ' + path : addResourceTitle(resource_type)} +
+ {#if onBack} +
{@render editorActions()}
diff --git a/frontend/src/lib/components/VariableEditor.svelte b/frontend/src/lib/components/VariableEditor.svelte index 277df7f44f..7bb54c4597 100644 --- a/frontend/src/lib/components/VariableEditor.svelte +++ b/frontend/src/lib/components/VariableEditor.svelte @@ -15,7 +15,7 @@ import Alert from './common/alert/Alert.svelte' import { sendUserToast } from '$lib/toast' import { canWrite } from '$lib/utils' - import { Save } from 'lucide-svelte' + import { ArrowLeft, Save } from 'lucide-svelte' import VariableForm from './VariableForm.svelte' import { invalidateWorkspacePaths } from './PathNameAutocomplete.svelte' import WsSpecificVersions from './WsSpecificVersions.svelte' @@ -41,7 +41,8 @@ // rather than in the navigation workspace. Defaults to $workspaceStore. let { workspace = undefined, - useDrawer = true + useDrawer = true, + onBack = undefined }: { workspace?: string /** @@ -51,6 +52,9 @@ * no drawer to open, so it simply takes effect. */ useDrawer?: boolean + /** Inline only: offered in the header when the host replaced something the + * user should be able to get back to (a session tab that took over the list). */ + onBack?: () => void } = $props() let curWs = $derived(workspace ?? $workspaceStore) @@ -409,9 +413,21 @@ {:else}
- {edit ? `Update variable at ${initialPath}` : 'Add a variable'} +
+ {#if onBack} +
{@render editorActions()}
diff --git a/frontend/src/lib/components/sessions/PreviewTabHost.svelte b/frontend/src/lib/components/sessions/PreviewTabHost.svelte index 9574a1aaef..ed438c6133 100644 --- a/frontend/src/lib/components/sessions/PreviewTabHost.svelte +++ b/frontend/src/lib/components/sessions/PreviewTabHost.svelte @@ -12,6 +12,8 @@ import type { SessionRuntime } from './sessionRuntime.svelte' import { Loader2 } from 'lucide-svelte' import { + entityListPage, + pageHref, resolvePreviewTab, parsePreviewItemRoute, parsePreviewSelectedId, @@ -142,6 +144,20 @@ active ? 'z-10 opacity-100 pointer-events-auto' : 'z-0 opacity-0 pointer-events-none' ) + // An entity editor replaced the list its tab was opened from (the row is the + // tab now, not a drawer over the list), so it has to offer the way back. + // Re-points this tab rather than opening another: the list is where the tab + // came from, not a second destination. + const backToList = $derived( + slot.kind === 'entity' && runtime + ? () => { + const page = entityListPage(slot.entityKind) + if (page) + runtime.previewTabs.navigate({ type: 'page', href: pageHref(page.path), label: page.label }) + } + : undefined + ) + // Overlays a tab opens (drawers, modals, popovers) anchor here rather than to the // document, so they stay within this tab and hide with it when another tab takes over. // Every branch that renders content in-realm must bind this — an unbound host makes the @@ -324,19 +340,19 @@ {#await import('./ScheduleEditorView.svelte')} {@render editorLoading()} {:then Module} - + {/await} {:else if slot.entityKind === 'resource'} {#await import('./ResourceEditorView.svelte')} {@render editorLoading()} {:then Module} - + {/await} {:else} {#await import('./VariableEditorView.svelte')} {@render editorLoading()} {:then Module} - + {/await} {/if}
diff --git a/frontend/src/lib/components/sessions/ResourceEditorView.svelte b/frontend/src/lib/components/sessions/ResourceEditorView.svelte index 68ba37e910..7aeec5932e 100644 --- a/frontend/src/lib/components/sessions/ResourceEditorView.svelte +++ b/frontend/src/lib/components/sessions/ResourceEditorView.svelte @@ -4,13 +4,16 @@ let { path, - workspaceId + workspaceId, + onBack }: { /** The resource this tab edits (the row its location deep-links). */ path: string /** The session's acting workspace, which the editor operates on instead of * `$workspaceStore` (the nav workspace, which a session leaves put). */ workspaceId: string + /** Back to the list this editor was reached through; the tab replaced it. */ + onBack?: () => void } = $props() let editor = $state() @@ -25,4 +28,4 @@ }) - + diff --git a/frontend/src/lib/components/sessions/ScheduleEditorView.svelte b/frontend/src/lib/components/sessions/ScheduleEditorView.svelte index 9853c7a76f..e70e205a06 100644 --- a/frontend/src/lib/components/sessions/ScheduleEditorView.svelte +++ b/frontend/src/lib/components/sessions/ScheduleEditorView.svelte @@ -1,17 +1,22 @@ - + diff --git a/frontend/src/lib/components/sessions/previewRouter.ts b/frontend/src/lib/components/sessions/previewRouter.ts index d76c179f16..581bcd6edf 100644 --- a/frontend/src/lib/components/sessions/previewRouter.ts +++ b/frontend/src/lib/components/sessions/previewRouter.ts @@ -133,6 +133,14 @@ const IN_REALM_ENTITY_PAGES: Partial> = { [VARIABLES_PATH]: 'variable' } +/** The list page an entity editor was reached through, so its host can offer the + * way back. Undefined only if a kind is registered above without a curated page, + * which `PREVIEW_PAGES` covers for all three today. */ +export function entityListPage(kind: EntityEditorKind): PreviewPage | undefined { + const path = Object.keys(IN_REALM_ENTITY_PAGES).find((p) => IN_REALM_ENTITY_PAGES[p] === kind) + return path ? matchPreviewPage(path) : undefined +} + /** The workspace entity a preview location opens the editor of — a list page * with a row deep-linked in its hash, for the pages whose editor mounts in * process. Undefined for the bare list page (an iframe of the list) and for