Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -279,10 +283,19 @@ function App() {
// the same pending request never double-notifies (one notification per id).
const notifiedPermissionIds = useRef<Set<string>>(new Set<string>());

const [density, setBoardDensity] = useState<BoardDensity>(loadDensity);
// The user's explicit density choice (persisted); null = never toggled.
const [storedDensity, setStoredDensity] = useState<BoardDensity | null>(
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);
}, []);

Expand Down
14 changes: 14 additions & 0 deletions app/src/lib/board-density.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { describe, it, expect } from "vitest";
import {
BOARD_DENSITY_KEY,
densityDefault,
initialDensity,
loadStoredDensity,
WIDE_BREAKPOINT,
} from "./board-density";

Expand Down Expand Up @@ -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);
});
});
19 changes: 12 additions & 7 deletions app/src/lib/board-density.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/

Expand Down Expand Up @@ -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. */
Expand Down
Loading