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}
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}
+
+ {/if}
+ {edit ? `Update variable at ${initialPath}` : 'Add a variable'}
+
{@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