Commit Graph

73 Commits

Author SHA1 Message Date
Jörg Lohrer 8eebd29266 publish(task 11): image-collector (ignoriert hugo-derivate)
collectImages(postDir) scannt ordner nach png/jpg/jpeg/gif/webp/svg,
ignoriert hugo-resize-derivate (*_hu_<hex>.*), liest bytes und gibt
sortierte ImageFile[]-liste zurück. mimeFromExt() mapped extension
auf mime-type. deno.jsonc test-task um --allow-write erweitert (Deno.
makeTempDir+writeFile in tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:31:32 +02:00
Jörg Lohrer 05ba4e4ef9 publish(task 10): nip-46 bunker-signer-wrapper mit timeout
createBunkerSigner(bunkerUrl) nutzt NostrConnectSigner.fromBunkerURI
aus applesauce-signers 2.x (früher als Nip46Signer im plan bezeichnet;
klassenname hat sich geändert). subscription- und publishMethod werden
global am class-constructor an einen shared RelayPool gekoppelt.
getPublicKey und signEvent bekommen je 30s-timeout mit sauberem
clearTimeout via withTimeout-helper. signer.ts ist vollständig, aber
ohne eigene unit-tests — integration folgt über check-subcommand
(task 16).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:30:15 +02:00
Jörg Lohrer 0a858371bf publish(task 9): blossom-server-liste-loader (kind:10063)
parseBlossomServers(ev) extrahiert "server"-tag-urls, normalisiert
trailing-slash. loadBlossomServers(bootstrapRelay, pubkey) fragt
kind:10063 via applesauce-relay. 3 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:28:44 +02:00
Jörg Lohrer 1ec48ad1a9 publish(task 8): outbox-relay-loader (kind:10002 parser + fetcher)
parseOutbox(ev) interpretiert nip-65 r-tags: ohne marker → read+write,
"read"/"write"-marker → nur jeweiliges set. ignoriert fremde tag-namen.
loadOutbox(bootstrapRelay, pubkey) fragt kind:10002-event via
applesauce-relay 2.x und parst das ergebnis. 3 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:27:47 +02:00
Jörg Lohrer ebe73cbf46 publish(task 7): relay-pool-wrapper (publish + checkExisting)
publishToRelays(urls, ev, opts) publisht signiertes event parallel zu
allen relays, mit retries + exponential backoff + timeout pro versuch.
retour: { ok: string[], failed: string[] }. default-pool via
applesauce-relay 2.x (new RelayPool()); publishFn via dependency-
injection für tests. checkExisting(slug, pubkey, urls) fragt je relay
nach kind:30023 mit #d-filter ab — true wenn irgendeiner matcht.
timer-leaks vermieden per clearTimeout in publishOne + im mock-test.
3 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:27:12 +02:00
Jörg Lohrer e4518fbf69 publish(task 6): kind:30023 event-builder mit tag-mapping
buildKind30023(args) baut unsigniertes kind:30023-event aus frontmatter
+ rewritten-body + cover-url. erzeugt pflicht-tags (d, title,
published_at) und bedingt optionale (summary aus description, image
aus coverUrl, t-tags aus tags[], client aus clientTag). plus
additionalTags-parameter für spätere task 15: license-tag und
imeta-tags (mit blossom-sha256) werden dort nach dem upload angehängt.
4 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:25:10 +02:00
Jörg Lohrer a6c5bd26e7 publish(task 5): markdown bild-url-rewriter (mapping-basiert, =WxH-strip)
rewriteImageUrls(md, mapping) ersetzt alle ![alt](filename)- und
[![alt](filename)](link)-konstrukte per dateinamen-lookup durch
blossom-urls aus dem mapping. absolute urls bleiben unverändert,
unbekannte dateinamen bleiben stehen (kein strip). =WxH-größenhinweise
werden entfernt. URL-dekodierung für leerzeichen-dateinamen (z.b.
'03-config generieren.png' im moodle-post). resolveCoverUrl()-helper
für den cover-lookup. 7 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:24:03 +02:00
Jörg Lohrer bc2679aeba publish(task 4): slug- und post-validation
validateSlug prüft regex ^[a-z0-9][a-z0-9-]*$ (lowercase, digits,
hyphen; kein führender hyphen). validatePost checkt title, slug
und date (muss Date-instanz mit gültigem timestamp sein). 7 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:23:18 +02:00
Jörg Lohrer 178016f0f4 publish(task 3): frontmatter-parser mit yaml + body-split
parseFrontmatter(md) trennt yaml-frontmatter vom markdown-body via
regex, parst yaml mit @std/yaml. Frontmatter-interface enthält
nostr-publish-konvention: optionale images-liste mit file/role/alt/
license/authors plus Author-interface (name/url/orcid). 4 tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:22:41 +02:00
Jörg Lohrer 1e4359aab6 publish(task 2): config-loader mit env-validation
loadConfig() liest 3 pflicht-keys (BUNKER_URL, AUTHOR_PUBKEY_HEX,
BOOTSTRAP_RELAY) und 3 optionale mit defaults (CONTENT_ROOT,
CLIENT_TAG=leer, MIN_RELAY_ACKS=2). pubkey-hex-format validiert
(64 lowercase hex), MIN_RELAY_ACKS als positive integer. 6 tests, alle
grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:21:39 +02:00
Jörg Lohrer 6b6502a22c publish(task 1): deno-grundgerüst (deno.jsonc, .env.example, readme)
deno 2.x projekt mit jsr/npm-imports für @std, nostr-tools, applesauce-signers,
applesauce-relay und rxjs. env-handling: primär ../.env.local (projekt-lokal),
alternativ publish/.env für fremd-repos (template in .env.example). tasks für
publish, check, validate-post, test, fmt, lint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 05:20:19 +02:00
Jörg Lohrer 32fe856232 docs: status + handoff für fortsetzung morgen aktualisiert
- aktueller stand: content-migration fertig, pipeline-plan geschrieben
- nächster schritt klar benannt: task 1 aus publish-pipeline-plan
- env-vorarbeiten dokumentiert (alle keys in .env.local)
- offene UNKNOWN-liste für spätere recherche

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:17:57 +02:00
Jörg Lohrer 931ef9f03f docs: publish-pipeline-vorbereitung + bild-metadaten-konvention
- spec publish-pipeline aktualisiert: blossom-only (rsync-legacy-pfad raus)
- plan publish-pipeline (24 tasks in 12 phasen), blaupausen-tauglich:
  alle projekt-konstanten via env (BUNKER_URL, AUTHOR_PUBKEY_HEX,
  BOOTSTRAP_RELAY, CONTENT_ROOT, CLIENT_TAG, MIN_RELAY_ACKS)
- spec bild-metadaten-konvention (phase 1, yaml-basiert)
- wiki-entwurf deutsch + englisch: inline-markdown-konvention zur
  bildattribution mit mapping zu nip-92 imeta-tags. konvention für
  menschen, parser passt sich an; bei ambiguität eskalation zur
  redaktion statt stillem raten
- redaktions-checkliste für bild-durchgang (abgearbeitet, bleibt
  zum abgleich bei späteren migrationen)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:14:02 +02:00
Jörg Lohrer c023b59769 content: strukturierte bild-metadaten für alle 18 posts
pro post ein images:-block im frontmatter mit file, role, alt, license,
authors — als vorbereitung für publish-pipeline (imeta-tags nip-92).
91 bilder insgesamt. fünf UNKNOWN-einträge im vr-post zur späteren
recherche markiert (wikipedia-screenshot, sketchfab-fotograf,
ready-player-me, eyemeasure-app).

bei erlebnispadagogik-post: tote amazon-hotlinks entfernt, literatur-
referenzen in saubere textliste umgebaut.

redaktionell geprüft; CC0 für eigene fotos und screenshots,
CC BY-SA 3.0 DE für saemann-midjourney-collage, CC BY 4.0 für
dezentrale-oep-oer-gemeinschaftsbeitrag, CC BY-NC-SA 3.0 für
raupen-flickr-bild, CC BY-NC 4.0 für sketchfab-kirche.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:13:47 +02:00
Jörg Lohrer 7ea29941a6 Merge branch 'main' into spa 2026-04-15 18:34:16 +02:00
Jörg Lohrer 0a3bf026ff docs: handoff-pakette für pause + spätere fortsetzung
- docs/STATUS.md: was ist getan, was läuft live, wo liegen die Fäden
- docs/HANDOFF.md: nächste Optionen (Publish-Pipeline, Menü, Cutover),
  Dev-Kommandos, bekannte Stolperfallen
- .claude/skills/joerglohrerde-workflow.md: repo-spezifischer Skill
  zum Session-Start — Konventionen, API-Eigenheiten, wiederkehrende
  Kommandos, wie mit Jörg arbeiten
- README.md als Navigation zu allen Dokumenten

Damit kann die nächste Session ohne Kontextverlust fortgeführt werden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:34:15 +02:00
Jörg Lohrer 51f0ae5067 spa(phase 6, tasks 33-35): robots, og-defaults, type-check, finaler deploy
- robots.txt: standard allow für alle Crawler.
- app.html <head>: og:title/type/url/description als Defaults für
  die Site. Per-Post OG-Tags erst mit Publish-Pipeline Phase 3
  (Meta-Stubs) möglich — aktuell out-of-scope.
- Final-Validierung:
  - svelte-check: 611 files, 0 errors, 0 warnings
  - Unit: 29/29 (markdown 14, naddr 4, legacy-url 11)
  - E2E (Playwright): 3/3
- Finaler Deploy nach svelte.joerg-lohrer.de.

35 Plan-Tasks + 2 Erweiterungen (Avatar/Name für Kommentatoren,
External-Client-Links) komplett.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:13:29 +02:00
Jörg Lohrer 9d41a68ef9 spa: edufeed-url ohne /a/-pfad
https://edufeed.org/<naddr> statt /a/<naddr>.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:10:46 +02:00
Jörg Lohrer 32a39144bb spa: external-links — edufeed zuerst, njump nur noch auf kommentator-profilen
- externalClientLinks-Reihenfolge: EduFeed, Habla, Yakihonne (njump
  raus). EduFeed als OER/OEP-Community-Home an erster Stelle.
- njump bleibt für Kommentar-Autor-Profile (Klick auf Avatar/Name
  unter einem Kommentar) — dort ist es der bessere Profil-Viewer.
- EduFeed-URL-Schema: https://edufeed.org/a/<naddr> (falls sich das
  als falsch erweist, in zweitem Commit korrigieren).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:09:33 +02:00
Jörg Lohrer 3ad1a72d84 spa: kommentar-author klickbar (njump) + external-client-links am post
Zwei Erweiterungen, die Community-Interaktion an Nostr-native Clients
auslagern statt in der SPA nachzubauen:

1. ReplyItem-Header ist jetzt ein <a href=https://njump.me/<npub>
   target=_blank>. Klick auf Avatar/Name öffnet das vollständige
   Profil des Kommentar-Authors mit allen Events.
2. Neue ExternalClientLinks.svelte zwischen Reactions und Composer:
   dezente Box mit "In Nostr-Client öffnen" — drei Links (Habla,
   Yakihonne, njump) über naddr, damit Leser Thread-Replies,
   Reactions, Teilen dort nutzen können, wo die volle Nostr-Social-
   Layer läuft.

Nostr-Helper erweitert:
- buildNpub(hex) — npub1…-Bech32-Encoding
- buildNjumpProfileUrl(hex) — njump.me/<npub>
- externalClientLinks({pubkey, kind, identifier}) — Liste der drei
  etablierten Langform-Viewer mit naddr1…-URLs.

npm run check: 0 errors, 611 files. Deploy live.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:04:23 +02:00
Jörg Lohrer eb400a8a6a spa: avatar + name für kommentar-authoren via kind:0-profil
- loadProfile(pubkey?) akzeptiert jetzt optional einen Pubkey, default
  weiterhin AUTHOR_PUBKEY_HEX.
- Neuer profileCache.ts: sessionsweiter Cache, Promise-Memoization —
  paralleles Nachladen derselben Pubkey teilt dieselbe Request.
- ReplyItem lädt das kind:0-Profil des Kommentar-Authors on mount,
  zeigt Avatar (32px rund) + display_name/name. Fallback bei fehlendem
  Profil: Pubkey-Hex-Prefix (wie bisher).
- Home-Page nutzt getProfile(AUTHOR_PUBKEY_HEX) statt loadProfile()
  direkt — gleicher Cache, kein doppeltes Fetchen.

npm run check: 0 errors. E2E 3/3 grün. Deploy live.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:58:44 +02:00
Jörg Lohrer 22935d6737 spa(chore): test-results/ aus git und in .gitignore
Playwright schreibt Run-Artefakte in test-results/ — gehören nicht
ins Repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:53:33 +02:00
Jörg Lohrer 3b0f059cea spa(phase 5, tasks 26-32): reactions, replies, nip-07 kommentare, e2e
Neue Komponenten unter $lib/components/:
- Reactions.svelte: lädt kind:7-Aggregation via loadReactions, rendert
  Chips mit Emoji + Count. +/- werden zu 👍/👎 gemappt.
- ReplyItem.svelte: einzelner Kommentar mit Author-Npub-Prefix + Datum.
- ReplyList.svelte: lädt kind:1-Replies, merged mit optimistic-Props
  (dedup per id), sortiert chronologisch.
- ReplyComposer.svelte: Textarea + Senden-Button. Nutzt NIP-07-Wrapper
  (getPublicKey, signEvent), baut kind:1-Event mit a/e/p-Tags, pusht
  via pool.publish() zu allen Read-Relays. Fehlertolerant: zeigt
  Hinweis, wenn NIP-07-Extension fehlt.

Integration in PostView: Reactions, Composer, ReplyList unterhalb des
Artikel-Bodys. Optimistisches Reply-Pattern: Composer.onPublished
pushed signed event in PostView-local $state, ReplyList merged mit
fetched events.

Playwright-E2E:
- playwright.config.ts mit Dev-Server-Auto-Start
- home.test.ts: Profil + Beitragsliste sichtbar
- post.test.ts: Titel + Body + Legacy-URL-Redirect

Alle 3 E2E-Tests grün. npm run check: 600 files, 0 errors.
Deploy live auf svelte.joerg-lohrer.de (Phase 5 inklusive).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:51:57 +02:00
Jörg Lohrer c089d9e429 spa(phase 4, tasks 23-25): tag-navigation
- loadPostsByTag(tagName): client-seitige Filterung der Post-Liste
  (case-insensitive). #t-Filter wird nicht von allen Relays zuverlässig
  unterstützt — wir laden alles und filtern lokal.
- /tag/[name]/+page.ts+svelte: neue Tag-Route, Breadcrumb zurück zur
  Übersicht, #tagName als H1, dieselbe PostCard-Darstellung wie Home.
- Tag-Chips in PostView sind bereits klickbar (aus Task 21).

npm run check: 0 errors. Deploy live auf svelte.joerg-lohrer.de.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:44:02 +02:00
Jörg Lohrer feb336fc5b spa(phase 3, tasks 15-22): routing, komponenten, home, postview
Phase 3 komplett:
- Task 15: LoadingOrError-Komponente (loading/error-states, Habla-Fallback)
- Task 16: app.html mit CSS-Variablen (light/dark), Base-Typography
- Task 17: +layout.svelte mit Container + bootstrapReadRelays onMount
- Task 18: ProfileCard-Komponente (Avatar, Name, About, NIP-05, Website)
- Task 19: PostCard-Komponente (Thumbnail + Titel/Summary/Datum), responsive
- Task 20: +page.svelte als Home (Profil + Liste, Promise.all für beides)
- Task 21: PostView-Komponente (Titel, Meta, Cover, Summary, Markdown-Body)
- Task 22: [...slug]/+page.ts+svelte — Catch-all-Route mit Legacy-301-Redirect

Alle $props()-abhängigen Werte via $derived() (Svelte-5-Runes-Konformität).

npm run check: 0 errors, 0 warnings, 592 files. npm run build grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:39:24 +02:00
Jörg Lohrer dcef74e75c spa(task 14): nip-07-signer-wrapper
window.nostr-Proxy für Alby/nos2x/Flamingo-Extensions. Fehlertolerant:
bei fehlender Extension ODER User-Ablehnung returnen die Helper null,
damit UI klar "bitte Extension installieren"-Hinweise zeigen kann
statt zu crashen.

UnsignedEvent/SignedEvent als explizite Types — werden ab Task 29
(ReplyComposer) für NIP-07-signierte kind:1-Kommentare genutzt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:22:54 +02:00
Jörg Lohrer f470732c2c spa(task 13): reactions-loader mit aggregation
loadReactions(dtag) sammelt kind:7-Events mit #a-Filter auf den
Post, gruppiert nach content (emoji oder +/-), zählt und sortiert
nach Häufigkeit. Leerer content wird als + interpretiert (NIP-25-
Konvention für Like-Default).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:21:44 +02:00
Jörg Lohrer bab2895848 spa(task 12): replies-loader für kind:1 mit a-tag-filter
Fügt `loadReplies(dtag)` an loaders.ts an. Filter `#a` auf das
addressable-Event-Format "30023:<pubkey>:<dtag>" findet alle kind:1
Replies auf den Post. Sortiert aufsteigend (älteste zuerst).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 17:19:21 +02:00
Jörg Lohrer 09f2ce8b49 spa: loader für postlist, post, profile 2026-04-15 16:40:21 +02:00
Jörg Lohrer 078423a1b2 spa: read-relays-store mit bootstrap aus kind:10002 2026-04-15 16:37:41 +02:00
Jörg Lohrer 0bf9bf3bf2 spa: outbox-relay-loader für kind:10002 mit fallback 2026-04-15 16:33:27 +02:00
Jörg Lohrer 6f9f53c561 spa: relaypool-singleton via applesauce-relay 2026-04-15 16:10:06 +02:00
Jörg Lohrer ec9d361a13 spa(task 7 polish): scoped marked-instance, ssr-guard, erweiterte xss-tests
- Eigene `new Marked({...})`-Instanz statt globaler `marked.use()`-Mutation
  — schützt andere Module vor Konfigurationsleckage, schärft Spec §3
  ("lokale Ersetzbarkeit").
- SSR-Guard: `renderMarkdown` wirft in Non-DOM-Umgebungen eine
  Fehlermeldung statt stumm unsicher durchzulaufen. SPA hat `ssr=false`,
  Vitest läuft in jsdom — Guard ist Early-Fail für versehentliche
  Node-Aufrufe.
- `ADD_ATTR: ['target', 'rel']` entfernt — war ein No-Op, weil Marked
  diese Attribute nicht einfügt. Link-Attribut-Hardening kommt später,
  wenn externe Links tatsächlich `target="_blank"` bekommen sollen.
- Code-Block-Test prüft zusätzlich `class="hljs"` (Regression-Anker
  für Custom-Renderer).
- Erweiterte XSS-Matrix: onerror, onclick, iframe, data:text/html,
  vbscript:, svg+script — relevant für spätere Reply-Darstellung.

14/14 Tests grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:06:51 +02:00
Jörg Lohrer 2bcb2451b4 spa: markdown-renderer mit sanitize (tdd) 2026-04-15 16:03:04 +02:00
Jörg Lohrer 8af049a9ff spa: deploy-script und htaccess für svelte.joerg-lohrer.de
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:59:46 +02:00
Jörg Lohrer 1fb77669e6 spa(task 5 polish): jsdoc auf naddr-helpers, coverage-lücken geschlossen
- JSDoc zu NaddrArgs, buildNaddr, buildHablaLink (Stil konsistent mit config.ts).
- Neue Tests: ohne relays (Default-`?? []`-Pfad), unterschiedliche Inputs
  erzeugen unterschiedliche Links (Guard gegen konstanten Rückgabewert).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:21:36 +02:00
Jörg Lohrer c539c4fee3 spa: naddr/habla-link-helper (tdd) 2026-04-15 15:18:41 +02:00
Jörg Lohrer 36dd76a88f spa(task 4 polish): decodeURIComponent crash-safe, edge-case-tests
- decodeURIComponent in try/catch (malformed URI encoding crasht
  den SPA-Boot-Path nicht mehr, returned stattdessen null).
- JSDoc präzisiert: erwartet nur Pfad ohne Query/Fragment.
- Neue Tests: malformed %E0 → null, leerer dtag → null,
  round-trip Legacy → canonical.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:17:38 +02:00
Jörg Lohrer 47decd9b70 spa: url-parser für legacy-hugo-urls (tdd) 2026-04-15 15:14:35 +02:00
Jörg Lohrer bf3d82d266 spa(task 3 polish): config-konstanten immutable, klarere timeout-doku
- FALLBACK_READ_RELAYS als `as const` tuple (kein mutables Array).
- BOOTSTRAP_RELAY als erster Eintrag referenziert statt dupliziert.
- Präzisere JSDoc zu HABLA_BASE (klarmacht, dass /a/ baked-in ist).
- Timeout-Kommentare trennen soft (per-Relay) vs. hard (Page-Budget).

Code-Quality-Nitpicks aus Task 3 Review adressiert. npm run check grün.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:12:30 +02:00
Jörg Lohrer b5fbfb0e85 spa: nostr-konfigurations-modul mit pubkey, bootstrap-relay, fallbacks 2026-04-15 15:10:17 +02:00
Jörg Lohrer bc02a80e10 spa(task 2): runtime- und dev-dependencies installiert
Runtime: applesauce-core/relay/loaders/signers, nostr-tools, marked,
dompurify, highlight.js, rxjs.

Dev: vitest, @playwright/test, @testing-library/svelte, jsdom,
@types/dompurify.

vite.config.ts um vitest-Konfiguration erweitert (jsdom, globals,
tests/unit/**). package.json um test:unit, test:e2e, deploy:svelte
npm-Scripts ergänzt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:09:10 +02:00
Jörg Lohrer 5b9773ccd3 spa(task 1): sveltekit-skeleton mit adapter-static initialisiert
- sv create minimal template, TypeScript, ohne addons
- adapter-static statt adapter-auto (fallback: index.html)
- ssr=false, prerender=false, trailingSlash=always im layout.ts
- build produziert statisches build/ (getestet)
- .gitignore um package-lock.json und *.log ergänzt

Svelte 5 mit Runes, SvelteKit 2.57, TypeScript 6, Vite 8.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:03:15 +02:00
Jörg Lohrer 64640a5eed Merge branch 'main' into spa 2026-04-15 14:57:27 +02:00
Jörg Lohrer 937e356f4c plan: sveltekit-spa implementierung (35 tasks, 6 phasen)
Deckt die vollständige SPA-Spec ab: Setup + adapter-static, Daten-
ebene (Relay-Pool, Loader, Profil, Markdown, Signer), Routing mit
Legacy-URL-Normalisierung, Tag-Filter, Reactions, NIP-07-Kommentare.
TDD für pure Transformationen (URL-Parser, naddr, Markdown), E2E mit
Playwright für Happy-Paths.

Deploy-Ziel: svelte.joerg-lohrer.de. spa.joerg-lohrer.de bleibt als
Vanilla-Mini-Spike-MVP erhalten (Referenz-Verhalten).

Publish-Pipeline hat eigene Spec und bekommt separaten Plan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:57:26 +02:00
Jörg Lohrer 3bcc4a7170 Merge branch 'main' into spa 2026-04-15 14:46:34 +02:00
Jörg Lohrer 841b183b3c spec(spa): kurze form /<dtag>/ als kanonische url
Revidiert das URL-Schema: die lange Hugo-Form /YYYY/MM/DD/<dtag>.html/
wird als Legacy behandelt, die kurze Form /<dtag>/ als kanonisch.

Gründe: schöner zu teilen, skaliert über die 18 Altposts hinaus,
Datumskomponente fügt keinen Informationswert (Datum steht im Event
als published_at).

Legacy-URLs werden clientseitig via history.replaceState ohne Reload
auf die kurze Form normalisiert — Backlinks bleiben funktional,
Bookmarks konvergieren zur kurzen Form. Keine .htaccess-Redirects.

Im Mini-Spike bereits umgesetzt und live auf spa.joerg-lohrer.de.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:46:33 +02:00
Jörg Lohrer 1147980f2a spike(spa-mini): legacy-hugo-urls auf kurze form normalisieren
/YYYY/MM/DD/<dtag>.html/ wird erkannt, via history.replaceState auf
die kanonische Form /<dtag>/ umgeschrieben, dann der Post geladen.
Externe Backlinks auf alte Hugo-URLs landen damit ohne Reload-Flash
auf der neuen kurzen Adresse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:44:25 +02:00
Jörg Lohrer fc6e0fecdb spike(spa-mini): profilkachel auf der startseite
Lädt kind:0-Metadata-Event des Autors parallel zur Beitragsliste und
zeigt Avatar, Anzeigename, About-Text, NIP-05 und Website oben auf
der Übersichtsseite. Einzelpost-Seiten bleiben fokussiert, ohne
Profil-Header.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:37:32 +02:00
Jörg Lohrer 2e18e68907 Merge branch 'main' into spa 2026-04-15 14:31:21 +02:00