Files
HealthLog/src/lib/command-palette/recent.ts
T
Marc-André Bombeck 715b733164 feat(shell): add a command palette for pages, settings, modules and actions
Cmd/Ctrl+K or the search control at the end of the top bar opens a
combobox over a client-side index built from the lists the navigation
already renders: pages, Insights pages, every Settings section and card
(deep-linked by a new anchor prop on SettingsCardHeader), the Appearance
sub-pages, module switches and a few actions. Entries follow the module
map, the Coach capability, the admin role and a shared record's grant;
inside somebody else's record no cards, switches or backup are offered.

Ranking is prefix, then word start, then substring, then a small edit
distance, then subsequence, with a short multilingual synonym list. The
empty state shows recent places (per account, in localStorage) and the
main actions. The palette is its own lazily loaded chunk; the top bar
shows a search field where it has room and a magnifier otherwise. The
module switch mutation moves into a shared hook used by the Modules hub
and the palette.
2026-10-09 16:35:21 +02:00

50 lines
1.4 KiB
TypeScript

/**
* The places a person last opened from the palette, per account and per
* browser: a convenience for the empty palette, nothing more. Storage can be
* missing, full or refused (a private window, a blocked site); every access
* is wrapped, and the palette works the same without it.
*/
const PREFIX = "healthlog-palette-recent:";
export const RECENT_LIMIT = 5;
function storage(): Storage | null {
try {
return typeof window === "undefined" ? null : window.localStorage;
} catch {
return null;
}
}
/** The ids, newest first, for `account`. */
export function readRecent(account: string | null | undefined): string[] {
if (!account) return [];
try {
const raw = storage()?.getItem(PREFIX + account);
if (!raw) return [];
const parsed: unknown = JSON.parse(raw);
return Array.isArray(parsed)
? parsed.filter((id): id is string => typeof id === "string")
: [];
} catch {
return [];
}
}
/** Put `id` first, keeping the list short and without repeats. */
export function pushRecent(
account: string | null | undefined,
id: string,
): void {
if (!account) return;
try {
const next = [id, ...readRecent(account).filter((x) => x !== id)].slice(
0,
RECENT_LIMIT,
);
storage()?.setItem(PREFIX + account, JSON.stringify(next));
} catch {
// A refused write only costs the next empty state its history.
}
}