From 00dbf6fd8550e689c7273f8526f1e53face1952b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 14 Aug 2026 00:19:31 -0700 Subject: [PATCH] fix(ui): zone writes during startup no longer erase persisted Workboard pins (#123475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reconciler dropped workboard entries from the canonical persisted list whenever workboardEnabled was false — but enablement reads as disabled until the runtime config snapshot loads, so any pin/unpin/drag in that window permanently deleted every workboard pin from synced prefs. The boards-not- ready branch already preserved slots for exactly this ambiguity; disabled now folds into it. Prod net −1. --- ui/src/lib/sidebar-zone.test.ts | 11 ++++++++--- ui/src/lib/sidebar-zone.ts | 11 +++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ui/src/lib/sidebar-zone.test.ts b/ui/src/lib/sidebar-zone.test.ts index c36a6686649e..0a65010b2c90 100644 --- a/ui/src/lib/sidebar-zone.test.ts +++ b/ui/src/lib/sidebar-zone.test.ts @@ -64,7 +64,7 @@ describe("reconcileSidebarZone", () => { ).toEqual(["route:usage"]); }); - it("keeps active Workboard boards and drops stale or plugin-off pins", () => { + it("keeps active Workboard boards, drops stale ids, and preserves plugin-off pins", () => { const boards = [ { id: "default", total: 0, active: 0, archived: 0, byStatus: {} }, { id: "ops", total: 0, active: 0, archived: 0, byStatus: {} }, @@ -80,6 +80,8 @@ describe("reconcileSidebarZone", () => { true, ).sidebarEntries, ).toEqual(["workboard:ops", "route:usage"]); + // Disabled is indistinguishable from an unloaded config snapshot at + // startup; a zone write then must not erase persisted pins. expect( reconcileSidebarZone( ["workboard:ops", "route:usage"], @@ -89,8 +91,11 @@ describe("reconcileSidebarZone", () => { boards, false, true, - ).sidebarEntries, - ).toEqual(["route:usage"]); + ), + ).toEqual({ + entries: [{ type: "route", route: "usage" }], + sidebarEntries: ["workboard:ops", "route:usage"], + }); }); it("preserves Workboard pins until the active plugin's board catalog is authoritative", () => { diff --git a/ui/src/lib/sidebar-zone.ts b/ui/src/lib/sidebar-zone.ts index 55dd41e061ee..2e3f01f42c07 100644 --- a/ui/src/lib/sidebar-zone.ts +++ b/ui/src/lib/sidebar-zone.ts @@ -51,14 +51,13 @@ export function reconcileSidebarZone( continue; } if (entry.type === "workboard") { - if (!workboardEnabled) { - continue; - } seen.add(canonicalKey); canonical.push(canonicalKey); - // An unloaded catalog cannot distinguish deletion from startup. Preserve - // the slot but render nothing until the active plugin returns its ids. - if (!workboardBoardsReady) { + // Disabled reads exactly like startup: the runtime config snapshot is + // unloaded until the gateway answers, so a zone write in that window + // would erase every persisted pin. Preserve the slot, render nothing; + // only a loaded catalog that positively lacks the id deletes below. + if (!workboardEnabled || !workboardBoardsReady) { continue; } if (!validBoardIds.has(entry.boardId)) {