feat(settings): add theme persistence across devices

Introduce a user-preferences module and database schema to sync theme
settings (theme, accent, radius, background, brightness) across devices.
Users can now enable theme sync to persist their customization choices.

Closes #146
This commit is contained in:
neon
2026-05-30 17:27:08 +00:00
parent 100334773c
commit a1fe471e83
29 changed files with 11509 additions and 70 deletions
+1
View File
@@ -17,6 +17,7 @@ export type ProvisioningMethod = (typeof ProvisioningMethod)[keyof typeof Provis
export interface UserSettings {
syncReaderPreferences?: boolean;
syncThemePreferences?: boolean;
statisticsConfig?: import("./statistics").StatisticsSettings;
onboarding?: {
tourCompleted?: boolean;
+1 -1
View File
@@ -73,7 +73,7 @@ export interface DuplicateScanResponse {
pageSize: number;
}
export type DuplicateScanState = 'idle' | 'computing' | 'done' | 'error';
export type DuplicateScanState = "idle" | "computing" | "done" | "error";
export interface DuplicateScanStatus {
entityType: EntityType;
+1
View File
@@ -1,5 +1,6 @@
export * from "./audit";
export * from "./auth";
export * from "./theme";
export * from "./permissions";
export * from "./content-filter";
export * from "./metadata-preferences";
+4 -1
View File
@@ -134,7 +134,10 @@ function normalizeResolverOptions(options?: PathResolverOptions): Required<PathR
}
function sanitizePathSegmentValue(value: string, replacementCharacter: "_" | "-"): string {
let sanitized = value.replace(INVALID_SEGMENT_CHARS_REGEX, replacementCharacter).trim().replace(/[. ]+$/g, "");
let sanitized = value
.replace(INVALID_SEGMENT_CHARS_REGEX, replacementCharacter)
.trim()
.replace(/[. ]+$/g, "");
if (!sanitized || sanitized === "." || sanitized === "..") {
sanitized = replacementCharacter;
}
+74
View File
@@ -0,0 +1,74 @@
export const THEME_IDS = ["light", "dark"] as const;
export type Theme = (typeof THEME_IDS)[number];
export const ACCENT_IDS = [
"grey",
"rose",
"orange",
"amber",
"yellow",
"lime",
"green",
"emerald",
"teal",
"cyan",
"sky",
"blue",
"indigo",
"violet",
"fuchsia",
"pink",
"white",
"coral",
"peach",
"butter",
"lemon",
"celadon",
"sage",
"mint",
"seafoam",
"powder",
"mist",
"periwinkle",
"wisteria",
"lavender",
"orchid",
"blush",
] as const;
export type Accent = (typeof ACCENT_IDS)[number];
export const RADIUS_IDS = ["sharp", "default", "rounded", "pill"] as const;
export type Radius = (typeof RADIUS_IDS)[number];
export const BACKGROUND_IDS = [
"none",
"dots",
"cross",
"terminal",
"millimeter",
"blueprint",
"brushed",
"scanlines",
"carbon",
"vinyl",
"perforated",
"aurora",
"horizon",
"glow",
"mesh",
"elevation",
"prism",
"spectrum",
"spectrum-x",
"spectrum-plus",
"eclipse",
] as const;
export type Background = (typeof BACKGROUND_IDS)[number];
export interface ThemePreferences {
theme: Theme;
accent: Accent;
radius: Radius;
background: Background;
brightness: number;
}