dorfteich/apps/web/src/main.tsx
Claude Fable 5 83a2fe470e
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 4m41s
CI / Build container images (pull_request) Successful in 4m4s
CI / Auth e2e pack (pull_request) Successful in 11m40s
CI / Import/export fidelity gate (pull_request) Successful in 52s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
#184: user accent theming — presets and free color as one mechanism
apply-theme.ts derives BOTH modes' accent tokens from the stored choice
(ui.theme.accent: preset id or {custom:'#hex'}) and writes them as
<style id="user-theme"> with :root:root + :root:root[data-theme='dark']
blocks — the doubled :root beats tokens.css regardless of document
order, since theme-init.js injects the ui.theme.css cache during <head>
parsing, before the bundle styles. The default preset means NO override
(hand-tuned tokens.css values stay). main.tsx re-derives from the
choice at startup, healing stale caches after app updates.

Settings: accent radiogroup inside the Appearance section (visible
names, color never the only cue) with per-mode preview swatches on
each mode's canonical background, plus a custom color input; i18n
de+en. The second fieldset made bare .settings-fieldset locators
ambiguous — theme specs now scope via input[name] (fence stays).

Tests: apply-theme unit pack, BASE_PALETTE<->tokens.css drift fence in
theme-contrast.test.ts, e2e theme-accent.spec (instant apply, pre-paint
persistence, default removes override, axe smoke with garish yellow in
both modes). ADR 0018 amendment documents the stage-B details.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
2026-07-29 08:59:27 +02:00

56 lines
1.9 KiB
TypeScript

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { App } from './App';
import { AuthProvider } from './auth/auth-context';
import { ToastProvider } from './components/Toast';
import './i18n';
import { ApiError } from './lib/api';
import { initUserTheme } from './theme/apply-theme';
import { applyTheme, initSystemThemeListener, readStoredThemeMode } from './theme/theme';
import './styles/tokens.css';
import './styles/base.css';
// theme-init.js already themed the document pre-paint; re-applying here is
// a no-op safety net for contexts serving index.html without it, and the
// listener keeps 'system' users in sync with live OS scheme changes.
applyTheme(readStoredThemeMode());
initSystemThemeListener();
// Accent choice (#184): re-derive from the stored choice — heals a stale
// ui.theme.css cache after app updates; theme-init.js only bridges paint.
initUserTheme();
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// A 4xx won't turn into something else on retry (wrong permissions,
// page in trash, not found, …) — only retry on network/5xx errors.
retry: (failureCount, error) => {
if (error instanceof ApiError && error.status >= 400 && error.status < 500) return false;
return failureCount < 3;
},
},
},
});
const container = document.getElementById('root');
if (!container) {
throw new Error('index.html is missing the #root element');
}
createRoot(container).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<ToastProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ToastProvider>
</AuthProvider>
</QueryClientProvider>
</StrictMode>,
);