// Telegram CloudStorage sync for the device-independent client display preferences — theme, // reduce-motion and board labels — so they follow the user across their Telegram devices. The // interface language is intentionally excluded: it has its own server-side sync // (Profile.preferredLanguage) plus an on-launch reconciler, and mixing it in here would fight that. // The pure encode/decode is kept free of the SDK and the DOM so it unit-tests in the node // environment; the CloudStorage transport wrappers live in telegram.ts and the wiring (mirror on // save, reconcile on launch) in app.svelte.ts. import type { ThemePref } from './theme'; import type { BoardLabelMode } from './boardlabels'; /** ClientPrefs is the subset of preferences synced across devices via Telegram CloudStorage. */ export interface ClientPrefs { theme: ThemePref; reduceMotion: boolean; boardLabels: BoardLabelMode; } /** CLOUD_PREFS_KEY is the Telegram CloudStorage key holding the JSON-encoded ClientPrefs. */ export const CLOUD_PREFS_KEY = 'prefs'; /** encodeClientPrefs serialises the synced client prefs (and only those — never the locale). */ export function encodeClientPrefs(p: ClientPrefs): string { return JSON.stringify({ theme: p.theme, reduceMotion: p.reduceMotion, boardLabels: p.boardLabels }); } /** * decodeClientPrefs parses a CloudStorage payload into a partial ClientPrefs, keeping only valid * fields and dropping anything unknown, mistyped or malformed — so a value written by a newer or * older build, or a corrupt entry, never throws and never applies a bad setting. A missing field * stays absent, so the caller leaves the corresponding local value untouched. */ export function decodeClientPrefs(raw: string | null | undefined): Partial { if (!raw) return {}; let o: Record; try { o = JSON.parse(raw) as Record; } catch { return {}; } if (!o || typeof o !== 'object') return {}; const out: Partial = {}; if (o.theme === 'auto' || o.theme === 'light' || o.theme === 'dark') out.theme = o.theme; if (typeof o.reduceMotion === 'boolean') out.reduceMotion = o.reduceMotion; if (o.boardLabels === 'beginner' || o.boardLabels === 'classic' || o.boardLabels === 'none') { out.boardLabels = o.boardLabels; } return out; }