From 042f2cf4449850a003441bcb21e6cfe2c222fc5f Mon Sep 17 00:00:00 2001 From: Alejandro Perez Date: Fri, 3 Jul 2026 10:53:08 +0100 Subject: [PATCH] app: board density default follows live window width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The table-vs-cards default sampled window.innerWidth once in a useState initializer, but the Tauri window opens at its configured 1200px and is maximized after mount — so wide screens were locked to cards for the whole session and the D1 triage table never appeared by default. The width default is now a live media query at the 1400px breakpoint; an explicit toggle is persisted and always wins. --- app/src/App.tsx | 19 ++++++++++++++++--- app/src/lib/board-density.test.ts | 14 ++++++++++++++ app/src/lib/board-density.ts | 19 ++++++++++++------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index 8402993..d6a6d52 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -47,7 +47,11 @@ import { collectFleetReviews } from "./lib/fleet"; import { agentActionLine } from "./lib/agent-event"; import { useMediaQuery } from "./hooks/useMediaQuery"; import type { BoardDensity } from "./lib/board-density"; -import { loadDensity, saveDensity } from "./lib/board-density"; +import { + loadStoredDensity, + saveDensity, + WIDE_BREAKPOINT, +} from "./lib/board-density"; import type { GateState } from "./bindings/GateState"; import type { DiffSide } from "./bindings/DiffSide"; import type { Review } from "./bindings/Review"; @@ -279,10 +283,19 @@ function App() { // the same pending request never double-notifies (one notification per id). const notifiedPermissionIds = useRef>(new Set()); - const [density, setBoardDensity] = useState(loadDensity); + // The user's explicit density choice (persisted); null = never toggled. + const [storedDensity, setStoredDensity] = useState( + loadStoredDensity, + ); + // Without an explicit choice the width default is LIVE: the Tauri window + // opens at its configured 1200px and is usually maximized after mount, so a + // width sampled once at mount would lock "cards" on wide screens forever. + const wideBoard = useMediaQuery(`(min-width: ${String(WIDE_BREAKPOINT)}px)`); + const density: BoardDensity = + storedDensity ?? (wideBoard ? "table" : "cards"); const setDensity = useCallback((next: BoardDensity) => { - setBoardDensity(next); + setStoredDensity(next); saveDensity(next); }, []); diff --git a/app/src/lib/board-density.test.ts b/app/src/lib/board-density.test.ts index edbb3a1..ea6b057 100644 --- a/app/src/lib/board-density.test.ts +++ b/app/src/lib/board-density.test.ts @@ -1,7 +1,9 @@ import { describe, it, expect } from "vitest"; import { + BOARD_DENSITY_KEY, densityDefault, initialDensity, + loadStoredDensity, WIDE_BREAKPOINT, } from "./board-density"; @@ -36,3 +38,15 @@ describe("initialDensity", () => { expect(initialDensity(1000, "garbage")).toBe("cards"); }); }); + +describe("loadStoredDensity", () => { + it("returns only a valid explicitly-stored value, else null", () => { + localStorage.removeItem(BOARD_DENSITY_KEY); + expect(loadStoredDensity()).toBeNull(); + localStorage.setItem(BOARD_DENSITY_KEY, "bogus"); + expect(loadStoredDensity()).toBeNull(); + localStorage.setItem(BOARD_DENSITY_KEY, "table"); + expect(loadStoredDensity()).toBe("table"); + localStorage.removeItem(BOARD_DENSITY_KEY); + }); +}); diff --git a/app/src/lib/board-density.ts b/app/src/lib/board-density.ts index 39a0182..7b37b0f 100644 --- a/app/src/lib/board-density.ts +++ b/app/src/lib/board-density.ts @@ -6,7 +6,7 @@ * `table` on wide windows (>= {@link WIDE_BREAKPOINT}), `cards` below — but a * user's explicit toggle is persisted and always wins. The default decision is * factored into the pure {@link initialDensity} so it is unit-testable without a - * DOM; the thin {@link loadDensity} / {@link saveDensity} wrappers read and write + * DOM; the thin {@link loadStoredDensity} / {@link saveDensity} wrappers read and write * the browser environment. */ @@ -44,12 +44,17 @@ export function initialDensity( return parseDensity(stored) ?? densityDefault(width); } -/** Read the effective initial density from `localStorage` + the window width. */ -export function loadDensity(): BoardDensity { - return initialDensity( - window.innerWidth, - localStorage.getItem(BOARD_DENSITY_KEY), - ); +/** + * Read the user's explicitly persisted density, or null when they have never + * toggled (malformed values read as null). + * + * Deliberately width-free: the Tauri window opens at its configured 1200px and + * is typically maximized only after React mounts, so any width sampled at + * mount is a lie. Callers combine this with a live media query — a stored + * preference wins, otherwise the default follows the window as it resizes. + */ +export function loadStoredDensity(): BoardDensity | null { + return parseDensity(localStorage.getItem(BOARD_DENSITY_KEY)); } /** Persist the user's explicit density choice. */