fix(sessions): follow a row opened inside the list frame onto the hosted editor

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
This commit is contained in:
Diego Imbert
2026-09-05 14:57:29 +02:00
co-authored by Claude Opus 5
parent cc8d55eb57
commit 4d9ec4b816
2 changed files with 38 additions and 1 deletions
@@ -7,6 +7,7 @@ import {
canonicalizeObservedLoc,
describeLocation,
matchPreviewPage,
parseEntityEditorRoute,
showsView,
parseArtifactRoute,
parsePipelineRoute,
@@ -599,6 +600,14 @@ export class SessionPreviewTabs {
const observed = describeLocation(canonical)
if (commanded.anchor && !observed.anchor && commanded.identity === observed.identity) {
t.url = t.url.split('#')[0]
} else if (!commanded.anchor && observed.anchor && parseEntityEditorRoute(canonical)) {
// The mirror case, for the entities whose editor this panel hosts in
// process: a drawer opened on a row inside the frame is that entity's
// editor, and the frame's copy is a realm apart from the chat's writes —
// it can only be brought up to date by reloading the whole page under it.
// Following the command re-resolves the tab onto the hosted editor, which
// shares the draft cell the chat writes and so tracks it as the user reads.
t.url = canonical
}
this.#flush()
}
@@ -9,7 +9,7 @@ import {
type PreviewTabsAdapter,
type PreviewTabsSnapshot
} from './sessionPreviewTabs.svelte'
import { artifactUrl, type PreviewTarget } from './previewRouter'
import { artifactUrl, resolvePreviewTab, type PreviewTarget } from './previewRouter'
import type { SessionPreviewTab } from './sessionState.svelte'
import { base } from '$lib/base'
@@ -352,6 +352,34 @@ describe('SessionPreviewTabs.open', () => {
// A legacy app owns its own hash (the editor reads it as `context.hash`), so the
// observer records app state into `loc`. Reading that as a drawer anchor would
// retarget on reopen, and a same-document retarget forces a reload that discards
// Opening a row inside the list frame is the user asking to edit that entity. The
// frame's copy of the editor is a realm apart from the chat's writes, so the tab
// follows onto the hosted one, which shares the draft cell those writes land in.
it('follows the command onto the hosted editor when a row is opened inside the frame', () => {
const o = owner()
o.open({ type: 'page', href: '/resources', label: 'Resources' })
const id = o.tabs[0].id
o.observeLocation(id, '/resources#/resource/u/me/db')
expect(o.tabs[0].url).toBe('/resources#/resource/u/me/db')
expect(resolvePreviewTab(o.tabs[0].url)).toEqual({
kind: 'entity',
entityKind: 'resource',
path: 'u/me/db'
})
})
// Anchored the same way, but nothing hosts its editor yet: re-commanding would
// point the tab at a row the frame is already showing, for no gain.
it('leaves the command alone for an anchored page with no hosted editor', () => {
const o = owner()
o.open({ type: 'page', href: '/kafka_triggers', label: 'Kafka triggers' })
const id = o.tabs[0].id
o.observeLocation(id, '/kafka_triggers#f/team/ingest')
expect(o.tabs[0].url).toBe('/kafka_triggers')
})
// the state the user was looking at.
it('focuses a legacy app whose own hash changed instead of reloading it', () => {
const o = owner()