User-feedback: kommentar-inhalt aus app.html erschien sichtbar im
body der detail-seiten. Ursache: SvelteKit ersetzt jeden vorkommen
von %sveltekit.head%, auch innerhalb von HTML-kommentaren. Mein
beschreibender kommentar erwaehnte das wort als beispiel — wurde
dann zur build-zeit durch tatsaechliche head-tags ersetzt, was den
kommentar-block aufbrach (eingefuegte tags enthalten >, das schliesst
den kommentar) und den rest des kommentar-textes in den body
wandern liess.
Fix: kommentar umformuliert, sodass der platzhalter-name nicht mehr
woertlich vorkommt ("via SvelteKit-head-injection" statt "via
%sveltekit.head%").
Live-verifiziert auf banksy-high-court-prophet — body startet jetzt
sauber mit <header>.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Damit DEPLOY_TARGET=svelte/staging/prod immer mit aktuellem
snapshot/output baut. Ohne diesen step wuerde ein veralteter
snapshot ins HTML wandern, frische posts oder uebersetzungen
saessen erst beim naechsten deploy drin.
Schlaegt der snapshot fehl (relays down, env fehlt), bricht das
skript ab — bewusst hard-fail, damit kein verfaelschter build
hochgeladen wird.
Live-verifiziert auf https://svelte.joerg-lohrer.de/bibel-selfies/:
- HTTP 200, og-tags + hreflang + json-ld korrekt
- __SITE_URL__-substitution greift sauber
- <title>, og:title, json-ld stimmen mit snapshot ueberein
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drei build-blocker beim ersten prerender-versuch identifiziert und gefixt:
1. svelte.config.js: handleHttpError + handleMissingId fuer den
prerender-crawler. Der crawler folgt zur build-zeit allen hrefs/srcs
im HTML — sieht dabei
- __SITE_URL__-platzhalter in canonical/hreflang (werden im deploy
per sed ersetzt, sind keine echten routes)
- relative bild-paths in alten posts (z.B. h01-json-import.png)
- anchor-links auf headings ohne id-attribute (#ACF-JSON-Export)
Alle drei sind keine echten 404s — handlers ignorieren sie.
2. +page.svelte: <script type="application/ld+json">{jsonLd}</script>
in <svelte:head> rendert {jsonLd} als literalen string, weil svelte
den script-tag-inhalt nicht als expression evaluiert. Zurueck zu
{@html ...} mit </script>-escape-hardening, damit titel oder
beschreibungen mit </script> den output nicht aufbrechen koennen.
3. app.html behaelt seine homepage-defaults fuer og:title/og:url/
og:description/canonical — der prerender-crawler rendert nur
detail-routen (/<slug>/), die homepage bleibt SPA-only und braucht
die defaults im app.html-template, weil dort kein svelte:head greift.
Detail-routen ueberschreiben per <svelte:head>; last-wins greift bei
LinkedIn/Mastodon/Browser. Facebook/Twitter (first-wins) haetten
einen homepage-prerender-schritt noetig — folge-aufgabe.
Plus snapshot/deno.lock committed — deno empfiehlt lockfile-commit fuer
reproduzierbare CI-builds, analog package-lock.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Snapshot-pfad bekommt feature-paritaet mit dem runtime-fallback:
- Sprach-switcher (inline, gleiche optik wie LanguageAvailability,
ohne neue i18n-keys — verwendet snapshot.translations direkt)
- Tag-liste mit links auf /tag/<name>/
- Reactions, ExternalClientLinks, ReplyComposer, ReplyList
(alle dtag-basiert, brauchen keine NostrEvent-konstruktion)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bei der umsetzung von task 4.2 stellte sich heraus, dass svelte
<svelte:head> nicht in einem {#if}-block stehen darf. Plan-code
korrigiert von
{#if snapshot}<svelte:head>...</svelte:head>{/if}
zu
<svelte:head>{#if snapshot}...{/if}</svelte:head>
Semantisch identisch (head-content erscheint nur wenn snapshot da ist),
aber svelte-konform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Snapshot-pfad: page+head komplett aus json, mit og/twitter/jsonld/hreflang.
Runtime-fallback: falls data.snapshot null, loadPost+PostView wie bisher.
Reactions/replies kommen im naechsten task.
svelte:head auf top-level verschoben (svelte-constraint: keine meta-tags
innerhalb von {#if}-bloecken erlaubt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lokaler override des global ssr=false. entries() liest aus
snapshot/output/index.json, load() pro-slug aus posts/<slug>.json.
runtime-fallback bleibt fuer slugs ausserhalb des snapshots.
@types/node als devDependency ergaenzt, da node:fs/promises-Typen
fuer den SSR-Pfad benoetigt werden.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Etappe 3 des prerender-snapshot-plans (variante 1: minimal — kein
neuer workflow, deploy bleibt manuell via scripts/deploy-svelte.sh):
- 'Snapshot'-step laeuft nach publish, ruft deno-snapshot-cli auf
- output (index.json + posts/*.json + .last-snapshot.json) wird als
github-actions-artifact fuer 30 tage aufgehoben — debug-pfad falls
ein deploy-bug nachvollzogen werden muss
- AUTHOR_PUBKEY_HEX + BOOTSTRAP_RELAY werden aus existierenden secrets
uebernommen, keine neuen secrets noetig
Reihenfolge "publish dann snapshot": neue events muessen erst auf den
relays sein, bevor sie gesnapshottet werden koennen. Bei publish-fail
laeuft snapshot nicht — gewollt, weil unklarer relay-stand zu
fehlerhaftem snapshot-output fuehren wuerde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review-feedback aus etappe 2.9/2.10:
1. cli.ts: deletedCoords im cache wird ab jetzt akkumuliert statt
ersetzt. Vorher wurden bei einem run nur die aktuell von relays
gelieferten kind:5-coords geschrieben — wenn ein relay beim
naechsten run die alten deletions nicht mehr liefert (GC,
relay-tausch), waere die geschichte verloren und newDeletionsCount
im naechsten lauf wieder "neu" -> false-positive hard-fail im
drop-check.
2. relays.ts: kommentar zum belt-and-suspenders-setTimeout neben dem
RxJS-timeout-operator, damit der zweck (handle-cleanup falls beide
subscribe-callbacks verschluckt werden) klar ist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher konnten malformed tags wie ['t'] (ohne second element)
undefined ins string[]-array werfen, das im JSON als null landete.
Code-review-feedback aus etappe 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per NIP-09 darf ein deletion nur events mit created_at <= deletion.created_at
loeschen. Vorher wurde ein re-publizierter post nach geloeschtem vorgaenger
stumm wegfiltern. Code-review-feedback aus etappe 2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dead-code aus etappe 1 nachgezogen:
- dompurify + @types/dompurify aus package.json (jetzt isomorphic-dompurify
als einziger sanitizer, bringt eigene typen mit)
- design-rationale-kommentar fuer markedInstance zurueckgebracht
(Spec §3: lokale ersetzbarkeit der engine — nicht aus dem code ablesbar)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Funktioniert jetzt sowohl in Browser/jsdom als auch in Node (SvelteKit-Build).
Schritt 1 der prerender-snapshot-migration. Verhalten in der SPA unveraendert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-feedback: <script type="application/ld+json"> kann svelte direkt
rendern. Kein @html-Notausgang noetig, sauberere variante mit
auto-escape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frischer plan gegen die ueberarbeitete spec (fallback_url raus,
OG-default joerg-profil-2024.webp, prerender+runtime-fallback klar
als adapter-static-default). Sechs etappen, jede einzeln testbar
und rollback-bar:
1. renderMarkdown auf isomorphic-dompurify (node-faehig)
2. Snapshot-modul (deno) mit 9 testbaren cores + cli
3. Snapshot in CI
4. Detail-route auf prerender mit runtime-fallback
5. Runtime-fallback entfernen (snapshot ist quelle)
6. lftp mirror in drei phasen (optional, nicht-blockierend)
Alter plan bleibt im archive als geschichte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fallback_url im cover_image rausgenommen (YAGNI; primary url
reicht, blossom ist content-addressed). Beim ersten konkreten
bedarf nachreichen.
- Site-default-OG-bild auf app/static/joerg-profil-2024.webp
festgelegt (vorhandenes asset, nicht spekulativ).
- prerender + runtime-fallback klarer formuliert: das ist das
default-verhalten von adapter-static mit fallback, kein
workaround. ReplyList/ReplyComposer-hydration als
verifikations-punkt im plan-schritt 4 vermerkt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README/STATUS/HANDOFF/CLAUDE jeweils ein rollensatz oben:
- README = aussenseite
- STATUS = logbuch (stand + erledigt-chronologie)
- HANDOFF = konventions-handbuch (workflows + stolperfallen)
- CLAUDE.md = session-einstieg
HANDOFF erhaelt zusaetzlich eine Single-Source-of-Truth-sektion,
damit klar ist welche fakten wo gepflegt werden.
README-linkliste auf 3 specs eingedampft (SPA, pipeline, multilingual)
und auf das plan-archiv verlinkt statt einzelplaene. CLAUDE.md
verlinkt zusaetzlich die prerender-snapshot-spec mit kennzeichnung.
STATUS.md verzeichnis-baum spiegelt das archiv wider.
Wiki-entwuerfe + redaktions-doku + github-ci-setup-doku haben
explizite status-stempel (eingefroren / schnappschuss / aktuell-aber-zu-ersetzen),
damit man nicht mehr raten muss.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Alle 6 plaene unter docs/superpowers/plans/archive/. Davon 5 abgearbeitet
(SPA, publish-pipeline, 3x multilingual), 1 eingefroren (prerender-snapshot).
Checkboxen wurden waehrend der umsetzung nicht gepflegt — verbindlicher
stand ist STATUS.md und die jeweilige spec, das archive/README.md macht
die zuordnung sichtbar.
plans/ bleibt mit .gitkeep als verzeichnis bestehen, damit neue plaene
sofort wieder dort landen koennen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Status-Header aller specs nach neuer konvention:
Entwurf | In Umsetzung | Umgesetzt (live seit YYYY-MM-DD) | Eingefroren | Verworfen.
Die zwei aeltesten specs (SPA, publish-pipeline) standen formal noch
auf "ausstehende User-Freigabe", obwohl seit 2026-04-18 live —
nun korrekt als umgesetzt markiert. Bild-metadaten-spec als
Phase 1 umgesetzt mit Phase-2-vermerk. Multilingual-spec angeglichen.
Prerender-snapshot-spec von "Stand:" auf "Status: Entwurf" mit
hinweis auf eingefrorenen plan im archiv.
Cleanup-spec selbst dokumentiert die konvention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Snapshot liefert content_markdown, nicht content_html. Rendering
(marked + DOMPurify + highlight.js) passiert im SvelteKit-Prerender-
Schritt über das bereits existierende \$lib/render/markdown.ts —
keine Duplikation in Deno, kein gemeinsames Policy-Modul nötig.
Für Blaupausen-Nutzung ist rohes Markdown portabler: alternative
Renderer (Astro, Eleventy) bringen eigenen Markdown-Prozessor mit.
Konsequenz für Migration: Schritt 1 ist jetzt \"renderMarkdown
Node-kompatibel machen\" (isomorphic-dompurify) statt \"shared/
markdown-policy.ts ergänzen\".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nachschärfungen nach Review:
- content_html als primary im Snapshot-JSON (marked + DOMPurify +
highlight.js, gemeinsame Policy in shared/markdown-policy.ts)
- content_markdown bleibt daneben (Debug + alternative Renderer)
- translations[] um title ergänzt (SPA-Switcher ohne Relay-Fetch)
- published_at vs. created_at semantisch klar getrennt (OG vs. Update)
- cover_image.fallback_url mit dokumentiertem Nutzungsszenario
- Fallback-Politik für fehlende summary/image/published_at
- --allow-shrink-Flag und kind:5-gestützte automatische Override
- Upload-Reihenfolge für Hash-benannte Bundles (Assets → HTML → Delete)
- /tag/<name>/-Verhalten in Nicht-Zielen erwähnt
- Edge-Case „Repo-Post + alle Relay-Events gelöscht" in Fehlertabelle
- Migrations-Weg um shared/markdown-policy.ts als Schritt 1 erweitert
- pro Migrations-Schritt explizite Rollback-Strategie
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Design-Dokument für die dreistufige Pipeline
publish → snapshot → build+deploy, die Post-Detailseiten mit echten
OG-Metadaten und prerendered Content versorgt, ohne Runtime-Relay-Fetch
und ohne Node-/Go-Server auf dem Shared-Hosting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README: neue plan-referenzen (3x multilingual), repo-struktur auf
content/posts/<lang>/<slug>/, svelte-i18n + translations im app-tree
- STATUS: event-count 27 (26 de + 1 en), kurzfassung um mehrsprachigkeit,
multilingual-abschnitt in „erledigt" (pipeline, spa, i18n + bugfixes)
- HANDOFF: option D entfernt (erledigt), neuer abschnitt „wie man eine
übersetzung anlegt", frontmatter-template um a:-platzhalter,
deploy-target-stolperfalle verschärft, vr-post-pfad aktualisiert
- Multilingual-spec: status von „noch nicht implementiert" auf „umgesetzt"
+ anmerkung zum post-switcher (📖 DE | EN statt text-hinweis)
- CLAUDE.md neu: knapper einstieg für agent-sessions mit commit-konvention,
deploy-falle, zsh-globbing, forgejo-mirror-timing
- workflow-skill generalüberholt: post-cutover-stand, multilingual,
publish-pipeline, activeLocale, 73 pipeline-tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
statt text-hinweis "auch verfügbar in: ..." zeigt der post jetzt einen
kompakten switcher (📖 aktiver-code | anderer-code). klick auf den
anderen code setzt die ui-sprache global und navigiert zur sprach-
variante — alles konsistent.
language names raus (unused): displayLanguage + tests entfernt, da die
darstellung nun nur noch sprachcodes (DE/EN) zeigt. auch i18n-keys
lang.de/lang.en und post.also_available_in aufgeräumt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>