/** * @file laneOrderStorage.ts * @description Persists the Workspace lane strip's user-chosen card order to * localStorage, the same read/validate/fallback convention as * splitViewStorage.ts. * * The order lives in the browser rather than the lanes table on purpose: it is * a per-viewer preference, like the split-pane layout beside it, and nothing * server-side (the CLI's `agenthub lanes`, the pipeline-stats picker) has an * opinion about it. Ceiling: the order does not follow the user to another * browser or to the CLI — move it to a `lanes.sort_order` column if it ever * must. * * @author Nguyễn Ngọc Trí Vĩ */ const STORAGE_KEY = "agenthub.workspace.laneOrder"; export function readLaneOrder(): number[] { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed: unknown = JSON.parse(raw); if (!Array.isArray(parsed)) return []; return parsed.filter((id): id is number => typeof id === "number"); } catch { return []; } } export function writeLaneOrder(ids: number[]): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(ids)); } catch { /* ignore quota / disabled storage */ } } /** * Applies a stored id order to a freshly fetched lane list. * * Ids the stored order never saw — a lane created since, or created in another * tab — keep their server order (id ascending) and land at the END, so a new * lane appears predictably rather than wherever a stale index puts it. Stored * ids naming a lane that no longer exists are simply skipped. * * @param lanes Lanes in server order. * @param order Stored id order. * @returns A new array; the input is never mutated. */ export function sortByStoredOrder(lanes: T[], order: number[]): T[] { const rank = new Map(order.map((id, i) => [id, i])); const known = lanes .filter((l) => rank.has(l.id)) .sort((a, b) => rank.get(a.id)! - rank.get(b.id)!); const fresh = lanes.filter((l) => !rank.has(l.id)); return [...known, ...fresh]; } /** * Moves the lane at `from` to index `to`, returning the resulting id order. * Out-of-range indices yield the unchanged order rather than a hole. */ export function moveLane(ids: number[], from: number, to: number): number[] { if (from === to || from < 0 || to < 0 || from >= ids.length || to >= ids.length) return ids; const next = [...ids]; next.splice(to, 0, ...next.splice(from, 1)); return next; }