escapeHtml nutzte den textContent->innerHTML-Trick, der laut HTML-Spec nur
& < > escaped, aber keine Anführungszeichen (in Textknoten harmlos). linkify
steckt den Wert anschließend in ein href="..."-Attribut, wo ein rohes " das
Attribut vorzeitig schließt und beliebige Attribute (on...=, style=) am <a>
einschleusbar macht -- HTML-Injection. Die CSP (default-src 'self', kein
'unsafe-inline') verhinderte nur die Ausführung, nicht die Injektion.
Ersetzt durch einen spec-korrekten Entity-Escaper, der auch " und ' abdeckt,
sodass der Wert in Text- UND Attributkontext sicher ist.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strich neben dem Content wird über ein eigenes Flex-Element mit
align-self: stretch gezeichnet statt über eine Border am Content –
dadurch exakt so hoch wie der Inhalt, kein Über-/Unterstehen mehr.
Vorfahren und Antworten teilen denselben Mechanismus (Antworten ohne
Border). Strichposition und -dicke über CSS-Variablen an die halbe
Avatargröße gekoppelt, sodass die Linie mittig unter dem Byline-Avatar
sitzt. Antworten-Label in die Byline verschoben, vertikaler Abstand
unter den .body-Container.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- user.avatar-Spalte (Schema, migrate.sh, Migrationsdoku)
- POST /user/avatar (geschützt): Upload via storeImage, ersetzt/löscht altes Bild
- avatar in /u/{name}/info, /user/info und im entrySelect-Join (Feed-Karten)
- Account-Löschung entfernt auch das Avatar-File
- Frontend: Avatar im Profilkopf + Byline der Karten (50px), Upload-Form,
Platzhalterbild no_profile_pic.jpg als Fallback
- .card img per :not(.avatar) von Beitragsbildern getrennt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der IntersectionObserver feuert nur bei Zustandswechseln. Füllte die erste
Feed-Seite den Viewport nicht über den Sentinel hinaus (Hauptfeed mit 198
Roots), kam es nie zu einem Übergang und damit nie zum Nachladen. loadMore
prüft jetzt nach jedem Fetch, ob der Sentinel noch sichtbar ist, und lädt
aktiv weiter; rootMargin 300px lädt zudem vorausschauend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- "Antworten"-Überschrift entfernt; direkte Antworten sind eine flache Liste
- Thread-Strich nur noch bei der Ahnenkette (#ancestors), Antworten nur eingerückt
- font-display: swap + korrekte format()-Hinweise -> kein FOIT mehr beim Laden
- Interaktionsleiste als Grid: Vote-Buttons zentriert, "n Antworten" links daneben
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
migrate.sh erzeugt nicht-destruktiv eine frische kver.db aus original-kver.db
mit dem aktuellen Go-Schema (NULL/'none'-filepath normalisiert, Soft-Deletes
und Waisen verworfen). Das entry-Schema erzwingt jetzt NOT NULL DEFAULT '' auf
content/filepath – NULL-filepath ließ scanEntries Zeilen still überspringen.
Feed-Karten sind per Klick navigierbar (mit Hover-Highlight); DB-Artefakte
landen im .gitignore.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- entry.deleted; entrySelect LEFT JOIN + COALESCE(username)
- POST /entry/{pid}/delete: deleted=1, Inhalt/Bild/Autor geleert, Zeile bleibt
- Konto-Löschung soft-deletet die eigenen Beiträge (statt hartem DELETE),
damit fremde Antworten nicht verwaisen
- Frontend: [deleted]-Platzhalter im Feed, Löschen-Button auf Focus-Seite
- notes/migrations.md: ALTER TABLE deleted
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- entry: reply_to/reply_count/last_activity; reply_count bubbelt bis Root
- Hauptfeed nur Roots nach last_activity, Profil-Feed inkl. Antworten
- GET /entry/{pid}/thread (Ahnen+Antworten), GET /e/{pid} Focus-Seite
- Frontend: Antworten-Link im Feed, entry.html/entry.js Focus-Seite
- notes/migrations.md: ALTER TABLE fuer Prod
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>