Files
kandev/apps/web/lib/settings/settings-menu-mode.ts
T
2026-09-04 12:44:45 +00:00

67 lines
2.8 KiB
TypeScript

import { getLocalStorage, setLocalStorage } from "@/lib/local-storage";
import { STORAGE_KEYS } from "@/lib/settings/constants";
/**
* How the settings menu renders — a per-device preference.
*
* Deliberately *not* part of user settings: the menu belongs to the surface
* you are looking at, not to the account. A phone reaching the same install
* can choose a different mode from the desktop beside it, and the
* value has to be readable before the store hydrates (the sidebar paints the
* menu on the first frame — a mode arriving one render later is a visible
* flip). Both rule out `updateUserSettings`, which is async and account-wide.
*
* `accordion` is the default and follows the current route through the
* workspace, agent, and executor branches.
*/
export const SETTINGS_MENU_MODES = ["flat", "accordion", "persistent"] as const;
export type SettingsMenuMode = (typeof SETTINGS_MENU_MODES)[number];
export const DEFAULT_SETTINGS_MENU_MODE: SettingsMenuMode = "accordion";
/** True when `mode` renders branches at all — i.e. anything but `flat`. */
export function isTreeSettingsMenuMode(mode: SettingsMenuMode): boolean {
return mode !== "flat";
}
function isSettingsMenuMode(value: unknown): value is SettingsMenuMode {
return SETTINGS_MENU_MODES.includes(value as SettingsMenuMode);
}
/**
* The stored value, or the default. Re-validated rather than trusted: a mode
* removed in an upgrade would otherwise leave the menu rendering nothing.
*/
export function readSettingsMenuMode(): SettingsMenuMode {
const stored = getLocalStorage<string>(
STORAGE_KEYS.SETTINGS_MENU_MODE,
DEFAULT_SETTINGS_MENU_MODE,
);
return isSettingsMenuMode(stored) ? stored : DEFAULT_SETTINGS_MENU_MODE;
}
export function writeSettingsMenuMode(mode: SettingsMenuMode): void {
setLocalStorage(STORAGE_KEYS.SETTINGS_MENU_MODE, mode);
}
/**
* Branch keys the persistent tree should reopen on this device.
*
* Keys are node identities, not routes, so a key naming a workspace, agent or
* executor that has since been deleted simply never matches a rendered branch:
* stale entries are inert rather than wrong. They are also never pruned — the
* expansion set is only ever added to or narrowed by an explicit collapse, and
* nothing here knows which records still exist — so the stored list grows by
* one string per deleted record and is cleared only by "Collapse all".
*/
export function readSettingsMenuExpandedKeys(): string[] {
const stored = getLocalStorage<string[]>(STORAGE_KEYS.SETTINGS_MENU_EXPANDED, []) as unknown;
if (!Array.isArray(stored)) return [];
return stored.filter((key): key is string => typeof key === "string");
}
export function writeSettingsMenuExpandedKeys(keys: readonly string[]): void {
setLocalStorage(STORAGE_KEYS.SETTINGS_MENU_EXPANDED, [...keys]);
}