feat: add validated appearance palettes

This commit is contained in:
2026-08-20 06:26:52 +02:00
parent fd90b60430
commit 6643c8fc1e
11 changed files with 196 additions and 17 deletions
+14 -4
View File
@@ -3,7 +3,7 @@ import { lazy, useEffect, useMemo, useState } from "react";
import { fetchSession, fetchShellAuth, updateProfile } from "./api/auth";
import { fetchPlatformModules, fetchPlatformPublicModules, fetchPlatformStatus } from "./api/platform";
import { AUTH_REQUIRED_EVENT, isApiError, loadApiSettings, saveApiSettings, type AuthRequiredEventDetail } from "./api/client";
import type { ApiSettings, AuthInfo, AuthSessionInfo, AuthUpdate, AuthUser, EffectiveViewProjection, LoginResponse, PlatformModuleInfo, PlatformPublicModuleInfo, PlatformWebModule, UserUiPreferences, ViewsRuntimeUiCapability } from "./types";
import type { ApiSettings, AuthInfo, AuthSessionInfo, AuthUpdate, AuthUser, EffectiveViewProjection, LoginResponse, PlatformModuleInfo, PlatformPublicModuleInfo, PlatformWebModule, UserUiPalette, UserUiPreferences, ViewsRuntimeUiCapability } from "./types";
import AppShell from "./layout/AppShell";
import PublicLandingPage from "./features/auth/PublicLandingPage";
import LoginModal from "./features/auth/LoginModal";
@@ -35,7 +35,8 @@ const DEFAULT_UI_PREFERENCES: UserUiPreferences = {
show_inline_help_hints: true,
reduce_motion: false,
sticky_section_sidebars: true,
theme: "system"
theme: "system",
palette: "default"
};
export default function App() {
@@ -389,6 +390,7 @@ export default function App() {
root.classList.toggle("ui-hide-help-hints", !preferences.show_inline_help_hints);
root.classList.toggle("ui-reduce-motion", preferences.reduce_motion);
root.classList.toggle("ui-no-sticky-section-sidebars", !preferences.sticky_section_sidebars);
root.dataset.palette = normalizeUiPalette(preferences.palette);
const systemDarkQuery = window.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
const applyTheme = () => {
@@ -413,7 +415,8 @@ export default function App() {
auth?.user.ui_preferences?.show_inline_help_hints,
auth?.user.ui_preferences?.reduce_motion,
auth?.user.ui_preferences?.sticky_section_sidebars,
auth?.user.ui_preferences?.theme
auth?.user.ui_preferences?.theme,
auth?.user.ui_preferences?.palette
]);
useEffect(() => {
@@ -716,10 +719,17 @@ function normalizeUiPreferences(value: Partial<UserUiPreferences> | null | undef
show_inline_help_hints: Boolean(value?.show_inline_help_hints ?? DEFAULT_UI_PREFERENCES.show_inline_help_hints),
reduce_motion: Boolean(value?.reduce_motion ?? DEFAULT_UI_PREFERENCES.reduce_motion),
sticky_section_sidebars: Boolean(value?.sticky_section_sidebars ?? DEFAULT_UI_PREFERENCES.sticky_section_sidebars),
theme
theme,
palette: normalizeUiPalette(value?.palette)
};
}
function normalizeUiPalette(value: UserUiPalette | string | null | undefined): UserUiPalette {
return value === "civic_blue" || value === "forest" || value === "plum"
? value
: "default";
}
function mergeWebModules(localModules: PlatformWebModule[], remoteModules: PlatformWebModule[]): PlatformWebModule[] {
if (remoteModules.length === 0) return localModules;
const seen = new Set(localModules.map((module) => module.id));