A cookie-carrying mutation without Origin and Referer (or with an
unparsable one) is now rejected with 403 csrf_origin_mismatch instead
of passing unchecked. The exception for non-browser clients stays
structural: PAT/bearer requests carry no session cookie and never reach
the check, and a request that does carry the cookie is always checked.
The test harness injects the matching Origin (supertest simulates a
browser page of this instance) with an explicit suppression header for
the negative cases; the Playwright fixture contexts send the header on
their manual seeding calls; release-qa.sh pins APP_BASE_URL and sends
the matching Origin. Dedicated spec covers: missing headers 403,
mismatch 403, unparsable 403, match passes, GETs untouched, PAT
mutation without headers passes, cookie+bearer still checked.
Refs #189
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
COLLAB_TOKEN_SECRET becomes a root key: every purpose derives its own
HKDF-SHA-256 subkey (deriveTokenKey), and no code path signs with the
root key directly. Collaboration tokens are signed and verified by jose
with HS256 as an explicit allowlist; the sign/verify API turns async at
its three call sites. Unsubscribe tokens move from a purpose-prefix
string to the structural subkey, with a documented dual-verify window
(legacy derivation accepted until 2026-11-01, covering the 90-day TTL
of links in already-sent mail).
The cross-runtime property that justified the homegrown implementation
is now proven by a test: the built CJS and ESM dist artefacts round-trip
tokens in both directions in child processes (jose v6 reaches CJS via
Node's require(esm), pinned Node 22 images). Negative tests cover
cross-purpose subkeys, root-key-signed tokens, alg:none and RS256.
Refs #188 (ADR 0020)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
Reviewer-facing document derived from ADR 0019: per base function
(encryption, media protection, network termination, authentication,
integrity) what the application does, what it deliberately does not,
and which party provides it — every claim traceable to code via the
ist-aufnahme. Includes the operator-duty handover (with the IndexedDB
endpoint copy named explicitly, I-25) and the delta list mapping every
divergence from the target state to its closing issue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
Add docs/vs-nfd/: the analysis brief, the as-is assessment (42 findings,
all verified against the code), the prioritized action plan rev. 2 with
issue references written back to every checkbox, the two-stage issue/ADR
brief, and the full reviewed draft used to create the forge state.
Add eight proposed ADRs 0019-0026 covering the VS-NfD architecture
decisions: no security base functions (par. 52 VSA anchor), HKDF token
key separation, external authentication, page classification, read-access
audit trail (variant A), reproducible offline deployment, plugin trust
model, and backup target restriction.
Forge state created alongside this commit: 11 labels, milestones M24-M31,
issues #188-#236 (docs-only change, no code touched).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
pondSettingsSchema gains theme = { accent: '#rrggbb' | null } (null =
inherit the viewer's theme), exposed as a top-level key of the flat
updatePondInputSchema and included in the PondsService settings merge
(the known silent-no-op pitfall). The server validates only the hex;
conformance arises at render time: PondThemeScope (mounted around the
page content next to PondFontScope) derives the accent pair for the
EFFECTIVE mode via useEffectiveTheme and sets it as inline custom
properties — inline beats both tokens.css and the user-theme <style>,
which IS the cascade precedence pond > user > default.
Pond settings get a PondThemeSection (inherit | presets | custom color
with per-mode preview swatches, explicit save like the font manager);
AccentSwatches extracted for reuse; i18n de+en. The no-JS public shell
stays deliberately un-themed (ADR 0018 amendment).
Tests: pond DB test (theme merge keeps fonts, invalid hex 400), e2e
pond-theme.spec (scope boundary content vs. chrome, per-mode
re-derivation, axe on the pond settings page; resets the fixture pond).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
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
Dependency-free packages/shared/src/theme.ts: relativeLuminance /
contrastRatio (WCAG 2.1), deriveAccentTokens(hex, mode) keeps hue and
saturation and binary-searches lightness until the accent clears 4.5:1
against the mode's bg, bg-subtle AND surface (a passing hex is kept
verbatim; accent-contrast follows by symmetry). THEME_PRESETS (pond
green = default), BASE_PALETTE as the canonical backgrounds. A sweep
test (36 hues x 3 saturations x 3 lightnesses x both modes) fences the
by-construction guarantee for arbitrary input.
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
CI run 453 caught it: settings-nav.spec.ts pins the user-settings section
count, which #180's Appearance section raised from 8 to 9. Verified
locally against a fresh e2e environment (both pack tests green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
theme-contrast.test.ts parses tokens.css and asserts every real UI colour
pairing (4.5:1 text, 3:1 UI) for BOTH palettes, so palette drift fails
unit tests instead of review. theme.test.ts covers resolve/apply logic
(Node >= 22 ships a shadowing undefined localStorage global — the test
brings its own in-memory storage). The a11y pack now runs its four scans
in light AND dark via emulateMedia; the new theme pack exercises the
three-way switch end to end (instant apply, reload persistence, live OS
follow in system mode, override beats OS). ADR 0018 records the theming
model broadly: modes now, accent themes by derivation later.
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
New tokens (light values, dark arrives with the theme block): surface-muted
(was fallback-only), danger-contrast, danger-strong, badge ok/error/warn
pairs, chip-outline. Replace the phantom --color-primary with the real
--color-accent and unify the three danger reds (#a02818/#b91c1c/#ab091e)
on --color-danger. attachments-panel maps to bg-subtle, matching its old
6%-grey fallback. Light rendering is visually unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRtCnB3uLdQtFmvp9HXcRX
Die Trennlinie unter der Aktionen-Spalte endete auf Höhe der Icon-Reihe
statt am Zeilenende: display:flex direkt auf dem td nahm der Zelle ihr
table-cell-Verhalten, sie wuchs nicht mehr auf Zeilenhöhe. Das Flex-
Layout liegt jetzt auf einem Innen-Wrapper (.user-row__actions-inner);
gemessen: 0 px Bottom-Delta über alle Zellen jeder Zeile.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Die Zeilen-Aktionen der Personenverwaltung sind jetzt IconButtons
(lucide): MailCheck (Bestätigung erneut senden, nur bei Ausstehend),
ShieldPlus/ShieldMinus (Zum Admin machen / Admin entfernen),
UserX/UserCheck (Deaktivieren/Aktivieren), Trash2 (Löschen) — in dieser
Reihenfolge. Das zweistufige Löschen bleibt: die Bestätigung ist
weiterhin ein roter Text-Button und erhält beim Umschalten den Fokus
(kein Fokusverlust, ADR 0017). Lokalisierte Namen kommen unverändert
aus users.json via IconButton (aria-label+title), Icons aria-hidden.
Der Admin-Bereich ist neu im a11y-CI-Pack (axe WCAG A/AA auf /admin).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jede künftige UI-Änderung entwickelt Barrierefreiheit direkt mit
(Stefans Vorgabe nach Abschluss des WCAG-2.1-AA-Audits): verbindliche
Checkliste als ADR, Kurzfassung in CLAUDE.md für jede Dev-Session,
Eintrag im ADR-Index.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
@axe-core/playwright als devDependency (exakt +2 Lockfile-Pakete,
axe-core hat null Runtime-Dependencies; Freigabe durch Stefan im Chat).
Neuer e2e-Pack a11y.spec.ts scannt Login, Lesemodus, aktiven Editor und
Nutzer-Einstellungen gegen WCAG 2.1 A/AA — jede neue Verletzung bricht
den Build (Allowlist bewusst leer, nur mit Begründung erweiterbar);
Best-Practice-Regeln bleiben außen vor. In ci.yml als eigener Schritt
mit Rate-Limit-Reset nach dem Muster der übrigen Packs verdrahtet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
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
Task-Checkboxen tragen in beiden Renderpfaden einen Namen: docToHtml
setzt aria-label aus dem Aufgabentext, die Editor-NodeView ebenso. Die
NodeView rendert ihr Host-Element jetzt selbst als li (ReactNodeView-
Renderer as/attrs) — TipTaps zusätzliches div-Host-Element zwischen ul
und li brach die Listensemantik; der Wrapper flacht per display:contents
ab, die #137-Pixel-Abstimmung bleibt erhalten (Selektor auf die neue
Tiefe nachgeführt, Ausrichtung nachgemessen: 1px-Versatz unverändert).
Der Wissensgraph-SVG bekommt ein beschreibendes aria-label inklusive
Verweis auf die Backlinks als gleichwertige Listenform. Der
Bild-Alt-Editor existierte bereits (Bild-Controls bei Auswahl) — kein
Änderungsbedarf. Hinweis: gecachte Seiten übernehmen das
Checkbox-Label wie bei jeder docToHtml-Änderung erst mit dem nächsten
Persist ihrer Inhalte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Der Field-Baustein verdrahtet Hinweis/Fehler jetzt per aria-describedby
und aria-invalid mit dem Eingabefeld (cloneElement auf das einzelne
Kind; Fragmente bleiben unangetastet) — Screenreader nennen den Fehler
damit auch beim Feld-Fokus. Quota-Typ-Select mit Namen; die leeren
Aktions-/Erledigt-Spaltenköpfe in API-Tokens, Feed-Tokens, Sitzungen
und der Aufgabenübersicht (NodeView UND Server-Renderpfad) tragen
visually-hidden-Beschriftungen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Die öffentliche Server-Shell bekommt AA-geprüfte Dark-Mode-Farben
(color-scheme: light dark hatte den UA dunkel rendern lassen, Links
fielen durch 1.4.3; Text 14,8:1, Links 10,1:1, Muted 8,5:1). Wikilinks
tragen eine permanente Unterstreichung — Farbe allein war das einzige
Link-Merkmal bei nur 2,5:1 Abstand zum Fließtext (1.4.1). Neues Token
--color-border-input (#7d8a97, 3,5:1/3,3:1) für Eingabefeld-Ränder
(1.4.11); Wächter-Kommentar am Favoriten-Gold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Skip-Link als erster Tab-Stopp springt auf #main; die angemeldete
Seitenansicht bekommt ein visually-hidden h1 (der sichtbare Titel ist
ein Input, der jetzt auch ein aria-label trägt); der Sidebar-Resizer
wandert in die nav-Landmarke (absolut an der Kante positioniert), damit
kein Inhalt außerhalb von Landmarken liegt. Zwei e2e-Locator auf das
Sidebar-Formular gescoped — das Editor-Titelfeld matcht seit dem neuen
Label ebenfalls auf /title|titel/i.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Vier Ursachen des seitenweiten Horizontal-Scrollens behoben: die Topbar
saß mit min-content-Breite in der Grid-Spalte (min-width: 0 nach dem
#100-Muster) und wickelt auf schmalen Viewports auf eine zweite Zeile
(Grid-Zeile minmax, Suchtext wird zum Icon); die aufgeklappte Sidebar
liegt unter 40rem als Overlay über dem Inhalt statt ihn auf einen
Streifen zu quetschen; Footer wickelt; Titel-Input und Settings-Spalte
schrumpfen (min-width bzw. align-items: stretch im Schmal-Layout).
Der Haupt-Scrollbereich ist per tabindex=0 tastatur-scrollbar — auf den
Rechtstext-Seiten gab es sonst keinen Weg, den Inhalt zu scrollen.
Gemessen: 10 Ansichten bei 320 px ohne Dokument-Überlauf (vorher 892 px
Inhaltsbreite); e2e content/settings-nav/sidebar/legal/page-tree/search
grün.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Die Editorfläche bekommt einen lokalisierten zugänglichen Namen und ist
im Lesemodus role=document statt eines unbenannten Textfelds (setOptions
im selben Layout-Effekt wie setEditable). Eingeklappte Sidebar zusätzlich
inert (aria-hidden allein ließ fokussierbare Kinder im Tab-Weg). Die
li-Zwischenknoten der Listboxen (Wikilink-/Mention-Autocomplete,
Suchergebnisse) sind role=presentation, damit listbox→option wieder eine
gültige Eltern-Kind-Beziehung ist. Toolbar: Pfeiltasten-Navigation über
die Controls (native Selects behalten ihre Pfeiltasten) und ein
sprechendes Toolbar-Label statt des Absatz-Buttons-Labels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
i18n spiegelt die aktive Sprache auf <html lang> (Init + languageChanged;
der User-Locale-Wechsel in auth-context läuft über dasselbe Event). Neuer
useDocumentTitle-Hook setzt je Route einen sprechenden Titel
(Seite — Teich — Dorfteich), verdrahtet in allen Routen-Komponenten;
dynamische Titel folgen den geladenen Daten.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Gemeinsamer useModalFocus-Hook: Initialfokus in den Dialog, Tab/Shift-Tab
zyklisch gefangen, Fokus-Rückgabe an den Auslöser (bzw. returnFocusRef,
wenn der öffnende Menüpunkt mit dem Menü unmountet). Dialoge tragen jetzt
aria-labelledby auf ihre Überschrift und tabindex=-1 als Fokus-Fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
Die NodeView las editor.isEditable nur beim Mount. Die Seite mountet
immer im Lesemodus, und der Moduswechsel läuft über setEditable() —
das emittiert in TipTap nur ein update-Event, aber keine Transaction,
weshalb React-NodeViews nie neu rendern (geprüft in @tiptap/react
3.27.1: updateProps feuert nur bei Node-Änderung und Selektions-
Wechsel). Folge: die Block-Leiste blieb ohne Bearbeiten-Knopf, für
alle Block-Plugins (ChordPro, Mermaid, Excalidraw, draw.io).
Fix: useEditorEditable abonniert das update-Event und liest
isEditable reaktiv; verliert die Seite die Editierbarkeit, während
die Editier-UI des Plugins offen ist, fällt der Block auf render
zurück (der Lesemodus blendet die Leiste aus, es gäbe sonst keinen
Weg mehr heraus). Damit stimmt auch die setData-Schreibrecht-Prüfung
(editableRef) wieder.
Regressionstest im plugin-blocks-Pack: Block existiert bereits,
Seite lädt im Lesemodus, Wechsel in den Edit-Modus zeigt den Knopf
(fiel ohne Fix reproduzierbar durch); Rückweg Lesemodus→render
mitgeprüft. Die bisherigen Tests fügten Blöcke immer erst nach dem
Moduswechsel ein und konnten den Fall nicht sehen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neues Referenz-Plugin packages/plugins/chordpro nach dem
mermaid-Muster: bewusst ohne Fremdbibliothek (Supply-Chain-Lehre aus
#136) — eigener minimaler ChordPro-Parser (Direktiven title/subtitle/
artist/key/capo/tempo/comment, Chorus-Fences, [Akkord]-Marker,
#-Kommentare) plus SVG-Formatter mit Monospace-Raster: Akkorde über dem
Text, Titelkopf, Chorus-Einrückung, XML-escaped. Persistenz {source,
svg} — der Snapshot bedient Lesemodus, Public-Ansicht und Exporte über
den PluginFallbackRenderer. Edit-Modus: Textarea + debounced
Live-Preview. ZIP 20 KB (Limits 64/256 MiB), 4 Parser-/Formatter-Tests,
i18n de+en, Doku-Listen (site-admin en+de, plugin-architecture)
ergänzt. Prod-Installation wie üblich per Site-Admin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Der animierte scrollIntoView landete auf einer veralteten Zielposition,
wenn Query-Sektionen (Sessions/Tokens) während der Animation noch
wuchsen — auf dem CI-Runner deterministisch rot. Jetzt springt die
Navigation sofort; der Spec lässt die asynchronen Inhalte vor dem Klick
settlen (networkidle) und lief lokal 10× ohne Retry grün.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Das Block-Menü ist seit dem eingebauten Aufgabenübersicht-Eintrag
(#154) immer sichtbar und teilt die Styling-Klasse
editor-toolbar__section-select — der section-styles-Pack adressiert
das Abschnitts-Select jetzt per :not(.editor-toolbar__block-select).
settings-nav: toBeInViewport bekommt 10 s für Smooth-Scroll auf
langsamen Runnern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neuer Notification-Typ mentioned; abgeleitete Tabelle page_mentions
(Migration), vom Collab-Persist transaktional neu geschrieben — der
Diff gegen den Vorzustand wird als pg_notify
(page_mentions_changed) emittiert, nur NEU Erwähnte lösen aus (kein
Spam bei Folge-Saves). Der api-Listener (erweitert um den zweiten
Kanal) ruft NotificationsService.fanoutMentions: Zustellung nur nach
canAccessPage-Recheck, die Autoren (pending contributors) benachrich-
tigen sich nie selbst; Payload wie gehabt mit Actor-Namen. API-seitig
erzeugte Seiten seeden page_mentions aus deriveContent. Glocken-Text
de+en; DB-Test (Leser ja / Outsider nein / Autor nein); kompletter
Loop live verifiziert (Tippen → Persist → NOTIFY → Notification).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neuer Block-Atom task_overview (Markdown-Fence dorfteich-tasks,
HTML-Placeholder). Shared extractTaskRows liest Task-Zeilen mit Text,
Mentions (#150) und Start-/Zieldaten (#152); TasksService sammelt zur
Lesezeit den Teilbaum (rekursiv via collectSubtreeIds, canAccessPage-
Filter je Quellseite) aus Basis-State + page_updates-Log — KEINE
abgeleitete Tabelle nötig (Teilbäume sind klein, kein Drift). Neuer
auth-Endpoint GET /read/:pond/:slug/tasks; die öffentliche Ansicht
expandiert den Placeholder serverseitig zur statischen Tabelle
(Instanz-Sprache). NodeView mit Live-Tabelle und Rückschreib-Checkboxen
(optimistisch, Override bis der debounced Collab-Persist nachzieht);
Einfügen über die Block-Auswahl (eingebauter Eintrag). Unit- + DB-Tests,
neuer CI-Pack tasks.spec (voller Loop inkl. Rückschreiben end-to-end),
User-Guide-Doku en+de.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
task_item bekommt ein optionales id-Attr (default null — Bestandsdocs
bleiben gültig), durchgereicht in toDOM/parseDOM und dem Lese-HTML;
der Editor vergibt/entdoppelt IDs lazy per appendTransaction
(TaskItemIds-Extension, auch gegen Copy/Paste). Neuer Kanal
TASK_TOGGLE_CHANNEL; POST /pages/:id/tasks/:taskId {checked} prüft
Schreibrecht, registriert den Toggler als pending contributor und
feuert pg_notify; neuer collab task-toggle-listener (Struktur =
restore-listener) öffnet eine DirectConnection und flippt das
checked-Attribut in einer Transaktion — offene Editoren konvergieren,
unbekannte taskId = geloggter No-op. DB-Test (NOTIFY-Payload,
Attribution, 403/404/400).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neuer Inline-Atom date_marker {kind: due|start, date: ISO}. Markdown
kanonisch ISO (>>2026-12-31), Eingabe-Kulanz dd.mm.yyyy; Block-Guard
vor blockquote hält zeilenführende >>Daten aus dem Zitat-Parser;
ungültige Kalenderdaten bleiben Text. Editor: InputRule beim Tippen
(+Leerzeichen), Anzeige per Intl.DateTimeFormat in Nutzersprache,
Überfällig-Färbung. Die User.locale-Verdrahtung existierte bereits
(auth-context, #17) — keine Änderung nötig. 6 Unit-Tests inkl.
Task-Listen-Zeile mit Marker und Mention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neuer Inline-Atom mention {userId, username}: Markdown-Regel @username
(E-Mail-sicher über Wortgrenzen), Serializer, HTML-Span dt-mention,
Plain-Text für die Suche, Extraktor extractMentionUserIds. Neue
Endpoints GET /users/search (auth, min. 2 Zeichen, Limit 10,
Rate-Limit) und GET /users/brief (Batch-Auflösung für live
Anzeigenamen; gelöschte Nutzer → toter Chip). Editor: MentionView mit
Live-displayName, MentionAutocomplete (Klon des Wikilink-Musters),
Chip-CSS. 5 Unit-Tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
GET /public/:pond/feed.xml (zuletzt geänderte Seiten) und
GET /public/:pond/:page/feed.xml (Versions-Historie), @Public mit
404-Semantik; öffentliche Teiche anonym, nicht-öffentliche über neues
read-only Feed-Token je Nutzer als ?token=dt_feed_… (neue Tabelle
feed_tokens + Migration, Verwaltung in den Nutzer-Einstellungen,
FeedTokensSection). Öffentliche HTML-Seiten annoncieren den Teich-Feed
per link rel=alternate. DB-Tests (anonym/privat/Token-Lifecycle) und
User-Guide-Doku en+de.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
GET /api/public/v1/me existiert bereits — OpenAPI-Summary nennt jetzt
ausdrücklich die User-ID, api-guide (en+de) ebenso. MCP war bereits
paritätisch (list_ponds + Token-Identität); kein neuer Endpoint nötig.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Neues pageListQuerySchema (ISO 8601, Kulanz für Datum ohne Zeit),
Query-Parameter auf interner und Public-API-Seitenliste, Prisma-where
mit gte; neue Indizes (pondId, createdAt)/(pondId, updatedAt) als
Migration. OpenAPI-Parameter, MCP-Parität (list_pages
created_since/updated_since), Doku (api-guide, mcp-guide,
public-api.md), DB-Test inkl. 400 bei ungültigem Datum.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
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
Neues bare-Attr am transclusion-Node; $-Präfix in Markdown-Regel,
Serializer und Autocomplete; HTML-Placeholder trägt
data-transclusion-bare, Server-Expansion und NodeView lassen bei bare
Rahmen und Titel weg. Gleiche Tiefen-/Zyklen-/Permission-Regeln,
zählt weiter als Link. Unit- und DB-Tests ergänzt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Aufgenommen auf Prod (v0.9.1, Handschrift-Fonts nach dem CSP-Fix aktiv)
auf der öffentlichen Seite konzerte-live — vervollständigt die neue
Excalidraw-Sektion analog zu den Mermaid-/drawio-Beispielbildern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Vom Fallback-Renderer inline eingesetzte SVGs (z. B. Excalidraw-
Skizzen) tragen feste Pixelmaße und liefen auf schmalen Viewports über
den Rand — die bestehende img-Regel greift für inline-<svg> nicht.
Neue Regel .dt-plugin-fallback svg { max-width:100%; height:auto }.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Excalidraw bettet beim Speichern die verwendeten Schrift-Subsets als
data:-URIs ins Snapshot-SVG ein. Die Seiten-CSP (nginx) und die
Plugin-Frame-CSP erlaubten aber nur `font-src 'self'` bzw. den
Asset-Pfad — die Handschrift fiel in der öffentlichen Ansicht und im
Snapshot-Render auf Serifen zurück (auf Prod an der ersten Demo-Skizze
sichtbar). Fix: `data:` in beiden font-src-Direktiven. data:-Fonts
lösen keinerlei Netzwerk-Request aus — die Zero-Third-Party-Garantie
(security.md) bleibt unberührt; fonts.ts-Kommentar entsprechend
präzisiert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Der Spec klickte nach page.reload() den Mode-Toggle „um den Edit-Modus zu
verlassen" — nach dem Reload ist die Seite aber schon im Lesemodus (React-
State resettet), der Klick wechselte also HINEIN. Das passierte jahrelang
folgenlos, weil der Fokus auf dem Toggle-Button blieb und „/" die Suche
öffnete. Seit dem Editor-Auto-Fokus (PR #140) landet der Fokus im Editor
und „/" wird dort zu Text — je nach Ausgang des Rennens gegen den rAF-
verzögerten Fokus mal grün (Läufe 381/385), mal rot (383/387/389/390).
Fix: den Klick streichen; „/" läuft im Lesemodus als globaler Shortcut.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Stefans Feedback nach dem Nachfahren-Selektor-Fix: „noch 3px weiter
runter". Die Zeilenmetrik des Editor-/Auth-NodeViews (label-Wrapper)
setzt die Checkbox ~3px tiefer als im öffentlichen docToHtml-Markup —
daher NUR für den label-Pfad `margin-top: calc(0.25em - 3px)`; die
öffentliche Ansicht (bare input, war korrekt) bleibt bei 0.25em.
Live per Injektion auf Test vermessen: Versatz −9 → −6px, Checkbox
mittig auf der Textzeile (Zoom-Screenshot).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Stefan sah die Checkbox-Verschiebung weiterhin — in der ANGEMELDETEN
Lese-/Bearbeiten-Ansicht. Dort rendert der TipTap-ReactNodeView das <p>
ZWEI Wrapper tief (`li > div[data-node-view-content] > div > p`), die
bisherige Kind-Kette `li > div > p` griff also nur im flachen
docToHtml-Markup der öffentlichen Ansicht. Fix: Nachfahren-Selektoren
(`li p:first-of-type` / `li p:last-of-type`) — robust gegen die
Wrapper-Tiefe beider Renderpfade.
Live am echten NodeView-DOM verifiziert (Injektion auf Test:
p-marginTop 16px→0, Checkbox bündig; öffentlicher Pfad unverändert ok).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Beim Umschalten in den Bearbeiten-Modus (Stift-Icon oder Shortcut „e")
landet der Cursor jetzt automatisch im Editor — bisher brauchte es einen
zusätzlichen Klick, der auf neuen/leeren Seiten zudem pixelgenau den
schmalen Inhaltsbereich treffen musste.
Effekt feuert, sobald der Editor editierbar wird (nach setEditable),
und überspringt den Fokus-Klau, wenn gerade ein Textfeld (z. B. der
Seitentitel) den Fokus hält — der Moduswechsel darf den Caret nicht aus
dem Titel reißen. TipTaps focus() stellt die letzte Auswahl wieder her
bzw. setzt den Caret an den Anfang einer leeren Seite.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Zwei im Bearbeiten-Modus gemeldete Layout-Bugs:
1. Die sticky Editor-Toolbar klebte an der Padding-Kante des Scroll-
Containers `.main` (padding-top: --space-6), also mit sichtbarer Lücke
unter der Navigation, durch die die scrollende Seite schien. Fix:
`top: calc(-1 * var(--space-6))` → die Toolbar pinnt bündig an die Nav.
2. Das „…"-Overflow-Menü der Navigation lag HINTER der Toolbar: `.topbar`
steht im DOM vor `.app-body`, hatte aber keinen Stacking-Kontext, also
malte die z-index-20-Toolbar in `.main` darüber und verdeckte
Menüeinträge. Fix: `.topbar { position: relative; z-index: 30 }` (> 20;
Modals mit 1100+ gewinnen weiterhin).
Beide am echten Test-Editor mit langer, scrollbarer Seite verifiziert
(gap_px=0 nach dem Scrollen; Menü vollständig über der Toolbar).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
Im Lesemodus-Markup ist das <input> das erste Kind des <li>, also ist das
<p> nie :first-child — die Regel `li > p:first-child { margin-top: 0 }`
griff daher NICHT, das <p> behielt seine ~1em-Obermarge und der Text saß
deutlich tiefer als die Checkbox (auf Test/Int sichtbar, kein Cache-Bug).
Fix: :first-of-type/:last-of-type treffen den ersten/letzten <p>
unabhängig vom vorangehenden <input>. Beide Renderpfade abgedeckt.
Verifiziert per Harness mit exaktem <li><input><p>-DOM (alt vs. neu).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
pondGraph-Test sortierte die EMPFANGENEN Phantom-Slugs, verglich aber
gegen ein UNsortiertes Literal. Da beide Slugs (ghost-<sfx>,
ghost-secret-<sfx>) den Zufalls-Suffix teilen, kippt ihre Sortierreihen-
folge auf ~1/5 der Suffixe (wenn sfx[0] > 's') → nicht-deterministischer
Fehlschlag. In CI-Lauf 372 traf es zu (Suffix „vpxwe…"). Fix: beide
Seiten sortieren. Vorbestehender Bug, unabhängig von M17–M19.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC