diff --git a/packages/dsh-plugin-browserskill/src/client/observation-sidebar.tsx b/packages/dsh-plugin-browserskill/src/client/observation-sidebar.tsx index a4fea16..465a58e 100644 --- a/packages/dsh-plugin-browserskill/src/client/observation-sidebar.tsx +++ b/packages/dsh-plugin-browserskill/src/client/observation-sidebar.tsx @@ -64,7 +64,12 @@ export interface SidebarSplitLike { export type SidebarNodeLike = SidebarLeafLike | SidebarSplitLike; export interface SidebarStateLike { - splits: SidebarNodeLike; + /** + * Right-column split tree. DSH 0.1.5 (better-sidebar 0.19+) moved the right + * column to dsh's native Sidebar and no longer emits this field, so it is + * optional here; earlier versions always provide it. + */ + splits?: SidebarNodeLike; bottomSplits: SidebarNodeLike; /** Whether the right panel is expanded (the merged drawer on narrow screens). */ panelOpen?: boolean; @@ -170,7 +175,11 @@ function* leafNodes(node: SidebarNodeLike): Generator { /** Whether a tab of our type is already open in either sidebar workbench. */ export function observationTabOpen(state: SidebarStateLike | undefined): boolean { if (state === undefined) return false; + // A root may be absent even when the snapshot exists: `splits` is gone on + // DSH 0.1.5+ (better-sidebar 0.19+), where the right column belongs to dsh's + // native Sidebar. Guard before walking so we do not dereference undefined. for (const root of [state.splits, state.bottomSplits]) { + if (root === undefined) continue; for (const leaf of leafNodes(root)) { if (leaf.tabs.some((tab) => tab.type === OBSERVATION_TAB_TYPE)) return true; } diff --git a/packages/dsh-plugin-browserskill/tests/client/observation-sidebar.test.tsx b/packages/dsh-plugin-browserskill/tests/client/observation-sidebar.test.tsx index cd95f62..9860017 100644 --- a/packages/dsh-plugin-browserskill/tests/client/observation-sidebar.test.tsx +++ b/packages/dsh-plugin-browserskill/tests/client/observation-sidebar.test.tsx @@ -117,7 +117,7 @@ function withTabOpened(state: SidebarStateLike, type: string): SidebarStateLike } return { ...node, children: node.children.map(walk) }; }; - return { ...state, splits: walk(state.splits) }; + return { ...state, splits: state.splits === undefined ? undefined : walk(state.splits) }; } function makeSidebar( @@ -280,6 +280,16 @@ describe("observationTabOpen", () => { }; expect(observationTabOpen(inBottom)).toBe(true); }); + + it("does not crash when the right-column tree is absent (DSH 0.1.5+)", () => { + // better-sidebar 0.19+ no longer emits `splits` (the right column moved to + // dsh's native Sidebar); the walk must skip absent roots, not dereference. + const noSplits: SidebarStateLike = { + bottomSplits: { kind: "leaf", id: "p2", active: null, tabs: [] }, + }; + expect(() => observationTabOpen(noSplits)).not.toThrow(); + expect(observationTabOpen(noSplits)).toBe(false); + }); }); describe("ObservationSidebarTab", () => {