Commit Graph

163 Commits

Author SHA1 Message Date
Jörg Lohrer 2ad27adf1f feat(spa): snapshot-pfad mit reactions/replies/langs/tags
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>
2026-04-28 08:46:12 +02:00
Jörg Lohrer 4e4a5efa42 docs: plan-korrektur — svelte:head muss top-level stehen
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>
2026-04-28 08:42:07 +02:00
Jörg Lohrer 63e59bffb9 feat(spa): post-detail rendert prerendered aus snapshot
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>
2026-04-28 08:41:11 +02:00
Jörg Lohrer b5772b8aa2 feat(spa): detail-route auf prerender + ssr=true
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>
2026-04-28 08:40:17 +02:00
Jörg Lohrer 3fa85fcb07 ci: snapshot-step nach publish + output als artifact
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>
2026-04-28 08:37:59 +02:00
Jörg Lohrer 2c4bceb768 fix(snapshot): cache akkumuliert deletedCoords + timeout-kommentar
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>
2026-04-28 08:27:12 +02:00
Jörg Lohrer d7bb62d469 feat(snapshot): cli-entrypoint verdrahtet alle module
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:23:55 +02:00
Jörg Lohrer d8a29ca389 feat(snapshot): relay-loader (kind:10002 + event-fetch)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:23:25 +02:00
Jörg Lohrer 10cb0d947d docs(snapshot): multi-lang-TODO fuer translation-inferenz
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:21:08 +02:00
Jörg Lohrer 49c740d908 docs(snapshot): dedup-tie-break dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:21:00 +02:00
Jörg Lohrer 63b68411e4 feat(snapshot): cache validiert format beim lesen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:20:51 +02:00
Jörg Lohrer 998e08e073 feat(snapshot): config validiert BOOTSTRAP_RELAY-prefix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:20:29 +02:00
Jörg Lohrer 1827817ad5 docs(snapshot): drop-check-semantik dokumentiert
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:20:01 +02:00
Jörg Lohrer 715c1f5e1e fix(snapshot): tagsAll filtert tags ohne value
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>
2026-04-28 08:19:51 +02:00
Jörg Lohrer 848cdf763e fix(snapshot): NIP-09-filter beachtet zeitliche reihenfolge
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>
2026-04-28 08:19:20 +02:00
Jörg Lohrer 0755498937 feat(snapshot): output-writer (index.json + posts/<slug>.json)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:12:01 +02:00
Jörg Lohrer a199f1daf1 feat(snapshot): cache-state fuer last-known-good
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:11:36 +02:00
Jörg Lohrer 2af44035b8 feat(snapshot): cover-image-HEAD-probe-modul
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:11:17 +02:00
Jörg Lohrer 4b2c157938 feat(snapshot): post-json-builder mit fallback-summary
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:10:55 +02:00
Jörg Lohrer 7e38b73785 feat(snapshot): plausibilitaets-checks (relay-quorum, drop, min-events)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:10:07 +02:00
Jörg Lohrer ccd7daf14d feat(snapshot): NIP-09-filter
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:09:37 +02:00
Jörg Lohrer 300cd9bea9 feat(snapshot): dedup-by-d-tag
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:09:14 +02:00
Jörg Lohrer 45df54f2b3 feat(snapshot): config-loader mit env-validierung
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:08:46 +02:00
Jörg Lohrer b6366ea1fe feat(snapshot): modul-skelett
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:08:17 +02:00
Jörg Lohrer c391df0d55 chore(render): alte dompurify-deps entfernt + design-rationale-kommentar
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>
2026-04-28 08:05:37 +02:00
Jörg Lohrer e0d723df14 feat(render): renderMarkdown auf isomorphic-dompurify umgestellt
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>
2026-04-28 08:00:56 +02:00
Jörg Lohrer f606748c3e test: failing node-test fuer renderMarkdown
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:00:13 +02:00
Jörg Lohrer 0b287f9ff6 docs: jsonld via svelte-tag statt {@html}
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>
2026-04-28 07:56:58 +02:00
Jörg Lohrer 7428930a76 docs: implementation-plan fuer prerender-snapshot
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>
2026-04-28 07:54:18 +02:00
Jörg Lohrer a278f65edf docs: prerender-snapshot-spec — drei klarstellungen
- 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>
2026-04-28 07:46:51 +02:00
Jörg Lohrer fdab93e829 docs: top-level-rollen geklaert + status-stempel
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>
2026-04-28 07:30:44 +02:00
Jörg Lohrer 050a38d51a docs: plaene ins archiv verschoben
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>
2026-04-28 07:30:31 +02:00
Jörg Lohrer 9017ed1926 docs: spec-header-konvention vereinheitlicht + cleanup-spec
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>
2026-04-28 07:30:24 +02:00
Jörg Lohrer 11e406e5de docs: prerender-snapshot spec-klarstellungen + implementation-plan
spec:
- deploy upload-reihenfolge: drei-phasen-flow (assets → html → delete)
  mit präziser beschreibung statt flag-kombi-raten
- migrations-schritt 4 + 5 entkoppelt: 4 liefert snapshot primär mit
  runtime-fallback für nostr-first-posts, 5 entfernt fallback erst
  nach stabilem cutover
- exclude-glob im delete-pass für extern verwaltete files

plan (20 tasks, tdd):
- snapshot/ als deno-modul mit config, relays, dedup, plausibility,
  cover, extract, write — voll unit-getestet
- renderMarkdown auf isomorphic-dompurify
- sveltekit-route mit prerender=true, entries, og/twitter/json-ld/
  hreflang im head, snapshot-primary + runtime-fallback
- deploy-script auf lftp drei-phasen
- dokumentation in HANDOFF und CLAUDE.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:20:25 +02:00
Jörg Lohrer fd093dff5e docs: markdown-rendering aus snapshot in svelte-build verschoben
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>
2026-04-21 17:08:10 +02:00
Jörg Lohrer 48d05f8d2d docs: spec-review-eingearbeitet (HTML-render, Felder, Migration)
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>
2026-04-21 17:05:43 +02:00
Jörg Lohrer 708c86aa29 docs: spec für prerender-snapshot (SEO + social-media-cards)
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>
2026-04-21 16:56:41 +02:00
Jörg Lohrer d12ed3c40e docs: status/handoff/readme/claude.md auf multilingual-stand
- 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>
2026-04-21 16:31:16 +02:00
Jörg Lohrer 9040e5ac02 feat(app): sprach-switcher direkt im post (📖 DE | EN)
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>
2026-04-21 16:09:33 +02:00
Jörg Lohrer 238b2a0938 feat(app): impressum-seitentitel lokalisiert (inhalt bleibt DE) 2026-04-21 14:15:04 +02:00
Jörg Lohrer 259d7949dd feat(app): post-route + komponenten lokalisiert (titel, datum, hinweise) 2026-04-21 14:13:59 +02:00
Jörg Lohrer 3411af610e feat(app): archiv-seite lokalisiert + nach locale gefiltert 2026-04-21 14:08:42 +02:00
Jörg Lohrer d7510953d2 feat(app): startseite lokalisiert + liste nach aktivem locale gefiltert 2026-04-21 14:07:17 +02:00
Jörg Lohrer d256670b56 feat(app): layout-header lokalisiert + sprach-switcher eingebunden 2026-04-21 14:00:26 +02:00
Jörg Lohrer 617b3dfccc feat(app): LanguageSwitcher-komponente mit de/en-buttons
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:33:40 +02:00
Jörg Lohrer 22997138f9 feat(app): i18n-init registriert messages und syncs mit activeLocale
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:33:19 +02:00
Jörg Lohrer 8f513495e3 feat(app): activeLocale-store mit persistence + initial-detection 2026-04-21 13:32:34 +02:00
Jörg Lohrer f799223836 chore(app): svelte-i18n + ui-messages-files (de/en)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:30:39 +02:00
Jörg Lohrer 5bab73def7 docs: plan 3/3 für multilinguale SPA (svelte-i18n + listen-filter)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:29:38 +02:00
Jörg Lohrer 0fca9cbfa2 fix(app): post-route lädt reaktiv via $effect statt onMount
bei navigation zwischen slugs innerhalb der gleichen [...slug]-route
bleibt die komponente montiert — onMount feuert dann nicht mehr, und
der neue post lud erst nach manuellem reload. $effect auf dtag löst
das und rendert die neue view sofort.

race-condition-guard: currentDtag wird pro effect-lauf festgefroren;
stale responses werden verworfen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:17:41 +02:00