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
An IconButton between the notifications bell and the user menu cycles
the theme mode in radio order (sun/moon/monitor mirror the CURRENT
choice). New useThemeMode() hook is the single write path (persist +
apply + same-document event), so the settings radios and the toggle
stay in sync; AppearanceSection now uses it too. Also rendered for
signed-out visitors — the mode is a device-local preference. i18n de+en;
unit tests for cycle/setter, theme.spec covers cycling, radio sync,
persistence, and the signed-out top bar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
The dark palette lives as a single :root[data-theme='dark'] block in
tokens.css; theme.ts and the pre-paint public/theme-init.js (external file
because the prod CSP forbids inline scripts) always resolve the stored
ui.theme.mode to a concrete data-theme, so 'system' needs no @media
duplicate and follows live OS changes via matchMedia. color-scheme flips
per theme (native controls/scrollbars), paired theme-color metas track the
effective theme, and the new Appearance settings section offers the
three-way choice as native radios (device-local, like #170). Label chips
gain a chip-outline ring so arbitrary user colors stay separated on the
dark canvas; useEffectiveTheme() is exported for the later pond-scoped
theming stage (ADR 0018).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
Toast-Standzeit 2,5s auf 6s (WCAG 2.2.1 — für Screenreader-/Zoom-Nutzer
kaum erfassbar). Neue Einstellungs-Sektion Bedienung mit dem Schalter
Einzeltasten-Kürzel deaktivieren (lokale Geräte-Einstellung); die
Handler von e und / prüfen sie beim Tastendruck (WCAG 2.1.4).
prefers-reduced-motion: CSS-Transitions kollabieren auf instant, die
Graph-Simulation rechnet ihr Layout synchron zu Ende statt zu animieren
(WCAG 2.2.2). settings-nav-Spec auf 8 Sektionen nachgeführt. Bewusst
KEIN zusätzliches role=status (legal.spec-Locator-Falle).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Neue SettingsLayout-Komponente leitet die Navigation per
MutationObserver aus den section>h2-Blöcken ab (erfasst konditionale
und komponenten-eigene Sektionen ohne Verdrahtung), sticky Leiste
neben dem Inhalt, auf schmalen Viewports horizontale Chip-Leiste;
aktive Sektion über Scroll-Position, am Seitenende gewinnt die letzte.
Auf allen vier Einstellungsseiten verdrahtet; die Admin-Grundeinstel-
lungen bekommen dafür eine eigene Überschrift. Neuer CI-Pack
settings-nav.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
The landing form reused the legal namespace's "Save legal pages" label,
so two buttons shared that text and the legal e2e's page-wide button
lookup hit a strict-mode violation. Use a dedicated settings-namespace
"Save landing page" label instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
The public home page (/) now renders Markdown the Site Admin stores in
the new home.content instance setting, through the same sanitizing
pipeline as the legal pages; empty falls back to the built-in welcome
text. New public GET /home/content, an Admin → Settings editor with
live preview, and an e2e test covering default/configured/escaping/
admin-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
A signed-in account can export all of its own data — profile, a list of
its memberships/grants, and the Markdown of its personal pond plus the
shared ponds it owns — as one ZIP. Foreign content never appears: only
owned ponds are bundled and the per-page read filter (reused from #65)
runs for each.
- Reuse the conversion-job queue as the async carrier: a `data_export`
job whose worker branch resolves DataExportService via a token (no DI
cycle), builds the ZIP, and stores it with an `expiresAt`. The download
link 404s past expiry and an hourly scheduled purge drops the bytes
(data minimization, security.md §Privacy).
- Extract ExportService.appendPondMarkdown so the pond ZIP (#65) and the
data export share one read-filtered pond archiver.
- Rate-limit requests per account (RateLimitService); POST
/users/me/data-export enqueues, GET /jobs/:id(/result) poll/download.
- Settings UI "Export my data" (de+en); web share pollJob/downloadJobResult
between the document and data export hooks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
The web app grows its account surface: login (with next-redirect,
unverified-hint + resend), signup (react-hook-form + shared Zod
schemas, field-level api errors, closed-registration state fed by the
new public GET /auth/registration), e-mail verification, forgot/reset
password; a settings page with profile (locale applies immediately),
password change, and active-session management; a Site-Admin page for
instance name, default locale, and registration mode. AuthProvider
holds /auth/me, applies the profile locale, and backs route guards
(RequireAuth/RequireAnonymous/RequireSiteAdmin); the top bar gains a
user menu. All strings ship in the new auth/settings namespaces (de+
en); the exception filter now preserves handler-specific error codes.
Verified live: signup → Mailpit → verify → login → profile through
the Vite proxy.
Closes#16Closes#17Closes#18Closes#19
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>