From dd4e60d77e84de8a1b232cdc20dbc45d2ce5185d Mon Sep 17 00:00:00 2001 From: hZsFN <66200546+hZsFN@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:13:32 +0800 Subject: [PATCH 1/2] fix(client): guard observationTabOpen against a missing right-column tree DSH 0.1.5 (with better-sidebar 0.19+) moved the right column to dsh native Sidebar, so the sidebar snapshot no longer carries `splits`. observationTabOpen still walked [state.splits, state.bottomSplits] and fed each root straight into leafNodes(), which dereferences node.kind - with `splits` absent that throws "TypeError: Cannot read properties of undefined (reading kind)" on every render. Because the observation tab registers inside better-sidebar render tree, the error surfaces under better-sidebar error boundary as a persistent red banner ("dsh-better-sidebar: Cannot read properties of undefined (reading kind)") even though the faulty walk lives here. Mark `splits` optional and skip absent roots before walking. Verified on dsh 0.1.5-rc.2 + better-sidebar 0.19.0-alpha.1: with this guard in the built client bundle, the banner and the console TypeError are gone and observation tab detection behaves as before. --- .../src/client/observation-sidebar.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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; } From 7bf0cdf5a06e2fd9aacd5afb3262cedb3988efd1 Mon Sep 17 00:00:00 2001 From: hZsFN <66200546+hZsFN@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:57:28 +0800 Subject: [PATCH 2/2] test(client): cover observationTabOpen without a right-column tree Fixes the typecheck failure introduced by marking `splits` optional: withTabOpened keeps a missing `splits` as-is instead of feeding it to the walker, and a regression test asserts observationTabOpen does not dereference an absent right-column tree (DSH 0.1.5 / better-sidebar 0.19+). --- .../tests/client/observation-sidebar.test.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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", () => {