hermes-agent/apps/desktop/src/lib/storage.ts
Brooklyn Nicholson 1b01fa3acf feat(desktop): long-press a rail profile to pick its color
Hold (~450ms) a profile square — or right-click → Color… — to open a
shadcn Popover of swatches and override its rail color, with Auto to fall
back to the deterministic hue. The hold timer rides alongside the dnd
pointer listener (a real drag cancels it, the trailing click is
suppressed), so reorder/select/recolor stay distinct gestures.

Overrides persist in localStorage ($profileColors), resolved via
resolveProfileColor (override wins, else the name-hashed hue). Cosmetic
and gated on the multi-profile rail, so single-profile users are
unaffected. Adds a reusable ui/popover.tsx (radix-ui umbrella).
2026-06-04 20:12:37 -05:00

107 lines
2.5 KiB
TypeScript

export function storedBoolean(key: string, fallback: boolean): boolean {
try {
const value = window.localStorage.getItem(key)
return value === null ? fallback : value === 'true'
} catch {
return fallback
}
}
export function persistBoolean(key: string, value: boolean) {
try {
window.localStorage.setItem(key, String(value))
} catch {
// Local storage is a convenience; ignore failures in restricted contexts.
}
}
export function storedString(key: string): null | string {
try {
return window.localStorage.getItem(key)
} catch {
return null
}
}
export function persistString(key: string, value: null | string) {
try {
if (value === null) {
window.localStorage.removeItem(key)
} else {
window.localStorage.setItem(key, value)
}
} catch {
// Storage is best-effort.
}
}
export function storedStringArray(key: string): string[] {
try {
const value = window.localStorage.getItem(key)
if (!value) {
return []
}
const parsed = JSON.parse(value)
if (!Array.isArray(parsed)) {
return []
}
return parsed.filter((item): item is string => typeof item === 'string' && item.length > 0)
} catch {
return []
}
}
export function persistStringArray(key: string, value: string[]) {
try {
window.localStorage.setItem(key, JSON.stringify(value))
} catch {
// Pins are a local preference; restricted storage should not break chat.
}
}
export function storedStringRecord(key: string): Record<string, string> {
try {
const value = window.localStorage.getItem(key)
if (!value) {
return {}
}
const parsed = JSON.parse(value)
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return {}
}
return Object.fromEntries(
Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
)
} catch {
return {}
}
}
export function persistStringRecord(key: string, value: Record<string, string>) {
try {
window.localStorage.setItem(key, JSON.stringify(value))
} catch {
// Local preference; restricted storage should not break the app.
}
}
export function arraysEqual(left: string[], right: string[]) {
return left.length === right.length && left.every((item, index) => item === right[index])
}
export function insertUniqueId(ids: string[], id: string, index: number) {
const next = ids.filter(item => item !== id)
const boundedIndex = Math.min(Math.max(index, 0), next.length)
next.splice(boundedIndex, 0, id)
return next
}