Compare commits
No commits in common. "fdab93e829bedcc5065c32e11102e6ecbab220ff" and "d12ed3c40e7425b3f267fb443485f10ea95436e1" have entirely different histories.
fdab93e829
...
d12ed3c40e
|
|
@ -1,9 +1,5 @@
|
||||||
# CLAUDE.md — Einstieg für Claude-Sessions
|
# CLAUDE.md — Einstieg für Claude-Sessions
|
||||||
|
|
||||||
> **Rolle dieses Dokuments:** Session-Einstieg — Lese-Reihenfolge,
|
|
||||||
> Tonfall, kritische Fallstricke. Logbuch in [`docs/STATUS.md`](docs/STATUS.md),
|
|
||||||
> Konventionen in [`docs/HANDOFF.md`](docs/HANDOFF.md).
|
|
||||||
|
|
||||||
Dieser Einstieg ist für Claude-Code-Sessions gedacht. Für den inhaltlichen
|
Dieser Einstieg ist für Claude-Code-Sessions gedacht. Für den inhaltlichen
|
||||||
Projektstand siehe [`docs/STATUS.md`](docs/STATUS.md) und
|
Projektstand siehe [`docs/STATUS.md`](docs/STATUS.md) und
|
||||||
[`docs/HANDOFF.md`](docs/HANDOFF.md).
|
[`docs/HANDOFF.md`](docs/HANDOFF.md).
|
||||||
|
|
@ -97,7 +93,7 @@ wird von der Pipeline ignoriert.
|
||||||
| `publish/src/` | Deno-Publish-Pipeline (Deno-Tasks in `publish/deno.jsonc`) |
|
| `publish/src/` | Deno-Publish-Pipeline (Deno-Tasks in `publish/deno.jsonc`) |
|
||||||
| `publish/tests/` | Deno-Tests für die Pipeline |
|
| `publish/tests/` | Deno-Tests für die Pipeline |
|
||||||
| `docs/superpowers/specs/` | Produktdesigns, Konventionen |
|
| `docs/superpowers/specs/` | Produktdesigns, Konventionen |
|
||||||
| `docs/superpowers/plans/archive/` | Umgesetzte Implementierungspläne (Geschichte) |
|
| `docs/superpowers/plans/` | Implementierungspläne (alle erledigt) |
|
||||||
| `scripts/deploy-svelte.sh` | FTPS-Deploy |
|
| `scripts/deploy-svelte.sh` | FTPS-Deploy |
|
||||||
|
|
||||||
## Quick-Links
|
## Quick-Links
|
||||||
|
|
@ -106,5 +102,4 @@ wird von der Pipeline ignoriert.
|
||||||
- [Produktspezifikation Publish-Pipeline](docs/superpowers/specs/2026-04-15-publish-pipeline-design.md)
|
- [Produktspezifikation Publish-Pipeline](docs/superpowers/specs/2026-04-15-publish-pipeline-design.md)
|
||||||
- [Bild-Metadaten-Konvention](docs/superpowers/specs/2026-04-16-image-metadata-convention.md)
|
- [Bild-Metadaten-Konvention](docs/superpowers/specs/2026-04-16-image-metadata-convention.md)
|
||||||
- [Multilingual-Design](docs/superpowers/specs/2026-04-21-multilingual-posts-design.md)
|
- [Multilingual-Design](docs/superpowers/specs/2026-04-21-multilingual-posts-design.md)
|
||||||
- [Prerender-Snapshot-Design](docs/superpowers/specs/2026-04-21-prerender-snapshot-design.md) (Entwurf, eingefroren)
|
|
||||||
- [Repo-Workflow-Skill](.claude/skills/joerglohrerde-workflow.md) (ausführlicher, mit Kommandos)
|
- [Repo-Workflow-Skill](.claude/skills/joerglohrerde-workflow.md) (ausführlicher, mit Kommandos)
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -1,10 +1,5 @@
|
||||||
# joerg-lohrer.de
|
# joerg-lohrer.de
|
||||||
|
|
||||||
> **Rolle dieses Dokuments:** Außensicht — was ist das Repo, wie funktioniert
|
|
||||||
> es grob, wo geht's weiter. Für interne Arbeit siehe
|
|
||||||
> [`docs/STATUS.md`](docs/STATUS.md) (Logbuch) und
|
|
||||||
> [`docs/HANDOFF.md`](docs/HANDOFF.md) (Konventionen, Workflows).
|
|
||||||
|
|
||||||
Persönliche Webseite. Nach einer Transition von einer Hugo-basierten,
|
Persönliche Webseite. Nach einer Transition von einer Hugo-basierten,
|
||||||
statischen Seite läuft `joerg-lohrer.de` jetzt als SvelteKit-SPA, die
|
statischen Seite läuft `joerg-lohrer.de` jetzt als SvelteKit-SPA, die
|
||||||
Blog-Posts live aus signierten Nostr-Events (NIP-23, `kind:30023`) rendert.
|
Blog-Posts live aus signierten Nostr-Events (NIP-23, `kind:30023`) rendert.
|
||||||
|
|
@ -50,8 +45,13 @@ Identität und Assets:
|
||||||
- 🤖 **Claude-Einstieg:** [`CLAUDE.md`](CLAUDE.md) (Agent-Konventionen, Deploy-Falle, Commit-Stil)
|
- 🤖 **Claude-Einstieg:** [`CLAUDE.md`](CLAUDE.md) (Agent-Konventionen, Deploy-Falle, Commit-Stil)
|
||||||
- 📐 **SPA-Spec:** [`docs/superpowers/specs/2026-04-15-nostr-page-design.md`](docs/superpowers/specs/2026-04-15-nostr-page-design.md)
|
- 📐 **SPA-Spec:** [`docs/superpowers/specs/2026-04-15-nostr-page-design.md`](docs/superpowers/specs/2026-04-15-nostr-page-design.md)
|
||||||
- 📐 **Publish-Pipeline-Spec:** [`docs/superpowers/specs/2026-04-15-publish-pipeline-design.md`](docs/superpowers/specs/2026-04-15-publish-pipeline-design.md)
|
- 📐 **Publish-Pipeline-Spec:** [`docs/superpowers/specs/2026-04-15-publish-pipeline-design.md`](docs/superpowers/specs/2026-04-15-publish-pipeline-design.md)
|
||||||
|
- 📐 **Bild-Metadaten-Konvention:** [`docs/superpowers/specs/2026-04-16-image-metadata-convention.md`](docs/superpowers/specs/2026-04-16-image-metadata-convention.md)
|
||||||
- 📐 **Multilinguale Posts:** [`docs/superpowers/specs/2026-04-21-multilingual-posts-design.md`](docs/superpowers/specs/2026-04-21-multilingual-posts-design.md)
|
- 📐 **Multilinguale Posts:** [`docs/superpowers/specs/2026-04-21-multilingual-posts-design.md`](docs/superpowers/specs/2026-04-21-multilingual-posts-design.md)
|
||||||
- 🗄 **Plan-Archiv:** [`docs/superpowers/plans/archive/`](docs/superpowers/plans/archive/) (alle umgesetzten Pläne als Geschichte)
|
- 🛠 **SvelteKit-SPA-Plan:** [`docs/superpowers/plans/2026-04-15-spa-sveltekit.md`](docs/superpowers/plans/2026-04-15-spa-sveltekit.md) (35 Tasks, erledigt)
|
||||||
|
- 🛠 **Publish-Pipeline-Plan:** [`docs/superpowers/plans/2026-04-16-publish-pipeline.md`](docs/superpowers/plans/2026-04-16-publish-pipeline.md) (24 Tasks, erledigt)
|
||||||
|
- 🛠 **Multilingual 1/3 — Pipeline:** [`docs/superpowers/plans/2026-04-21-multilingual-posts-pipeline.md`](docs/superpowers/plans/2026-04-21-multilingual-posts-pipeline.md) (10 Tasks, erledigt)
|
||||||
|
- 🛠 **Multilingual 2/3 — SPA-Auflösung:** [`docs/superpowers/plans/2026-04-21-multilingual-posts-spa.md`](docs/superpowers/plans/2026-04-21-multilingual-posts-spa.md) (8 Tasks, erledigt)
|
||||||
|
- 🛠 **Multilingual 3/3 — UI-i18n:** [`docs/superpowers/plans/2026-04-21-multilingual-posts-i18n.md`](docs/superpowers/plans/2026-04-21-multilingual-posts-i18n.md) (11 Tasks, erledigt)
|
||||||
- 🤖 **Claude-Workflow-Skill:** [`.claude/skills/joerglohrerde-workflow.md`](.claude/skills/joerglohrerde-workflow.md)
|
- 🤖 **Claude-Workflow-Skill:** [`.claude/skills/joerglohrerde-workflow.md`](.claude/skills/joerglohrerde-workflow.md)
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,8 @@
|
||||||
# Handoff — Nächste Session
|
# Handoff — Nächste Session
|
||||||
|
|
||||||
> **Rolle dieses Dokuments:** Konventions-Handbuch — wie hier gearbeitet
|
|
||||||
> wird (Posts, Übersetzungen, Deploys, Stolperfallen). Aktueller Stand
|
|
||||||
> steht in [`STATUS.md`](STATUS.md), Außensicht in [`../README.md`](../README.md).
|
|
||||||
|
|
||||||
Du (Claude, nächste Session) oder ich (Jörg, später) kommen hier zurück.
|
Du (Claude, nächste Session) oder ich (Jörg, später) kommen hier zurück.
|
||||||
Dieses Dokument sagt: was ist der Zustand, was wartet, wo liegen die Fäden.
|
Dieses Dokument sagt: was ist der Zustand, was wartet, wo liegen die Fäden.
|
||||||
|
|
||||||
## Single Source of Truth
|
|
||||||
|
|
||||||
Damit nichts an mehreren Stellen pflegen werden muss:
|
|
||||||
|
|
||||||
- **Live-URLs, Setup-Stand, Erledigt-Chronologie** → [`STATUS.md`](STATUS.md).
|
|
||||||
- **Frontmatter-Konventionen, Workflows, Stolperfallen** → dieses Dokument.
|
|
||||||
- **Spec-Liste** → `docs/superpowers/specs/`. README und CLAUDE.md
|
|
||||||
verlinken nur die für ihre Zielgruppe relevanten.
|
|
||||||
|
|
||||||
## Zustand (Details in `STATUS.md`)
|
## Zustand (Details in `STATUS.md`)
|
||||||
|
|
||||||
**Cutover + Reimport 2026-04-18, Mehrsprachigkeit live seit 2026-04-21.**
|
**Cutover + Reimport 2026-04-18, Mehrsprachigkeit live seit 2026-04-21.**
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
# Projekt-Status: joerg-lohrer.de → Nostr-basierte SPA
|
# Projekt-Status: joerg-lohrer.de → Nostr-basierte SPA
|
||||||
|
|
||||||
> **Rolle dieses Dokuments:** Logbuch — aktueller Stand und Erledigt-Chronologie.
|
|
||||||
> Konventionen und Workflows stehen in [`HANDOFF.md`](HANDOFF.md).
|
|
||||||
|
|
||||||
**Stand:** 2026-04-21 (Mehrsprachigkeit live)
|
**Stand:** 2026-04-21 (Mehrsprachigkeit live)
|
||||||
|
|
||||||
## Kurzfassung
|
## Kurzfassung
|
||||||
|
|
@ -73,9 +70,8 @@ joerglohrerde/
|
||||||
│ ├── wiki-draft-nostr-image-metadata.md
|
│ ├── wiki-draft-nostr-image-metadata.md
|
||||||
│ ├── github-ci-setup.md
|
│ ├── github-ci-setup.md
|
||||||
│ └── superpowers/
|
│ └── superpowers/
|
||||||
│ ├── specs/ # SPA, Publish-Pipeline, Bild-Metadaten, Multilingual, Prerender (Entwurf)
|
│ ├── specs/ # SPA, Publish-Pipeline, Bild-Metadaten, Multilingual
|
||||||
│ └── plans/
|
│ └── plans/ # Alle Pläne erledigt (SPA, Pipeline, 3× Multilingual)
|
||||||
│ └── archive/ # Umgesetzte Pläne (Geschichte) + eingefrorener Prerender-Plan
|
|
||||||
├── .github/workflows/ # publish.yml (Forgejo→GitHub Push-Mirror-Trigger)
|
├── .github/workflows/ # publish.yml (Forgejo→GitHub Push-Mirror-Trigger)
|
||||||
├── .claude/
|
├── .claude/
|
||||||
│ ├── skills/ # Repo-spezifischer Claude-Skill
|
│ ├── skills/ # Repo-spezifischer Claude-Skill
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
# GitHub-CI-Setup für die Publish-Pipeline
|
# GitHub-CI-Setup für die Publish-Pipeline
|
||||||
|
|
||||||
**Status:** Aktuell genutzt; mittelfristig zu ersetzen durch self-hosted CI (siehe `HANDOFF.md` → Option D).
|
|
||||||
|
|
||||||
**Kontext:** Das primäre Repo liegt in **Forgejo** (self-hosted). Für CI nutzen
|
**Kontext:** Das primäre Repo liegt in **Forgejo** (self-hosted). Für CI nutzen
|
||||||
wir GitHub als **Push-Mirror-Ziel**, weil Forgejo keine Woodpecker-Integration
|
wir GitHub als **Push-Mirror-Ziel**, weil Forgejo keine Woodpecker-Integration
|
||||||
hat. GitHub Actions triggert automatisch bei Push auf `main` mit Änderungen
|
hat. GitHub Actions triggert automatisch bei Push auf `main` mit Änderungen
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
# Redaktion: Bild-Metadaten-Durchgang
|
# Redaktion: Bild-Metadaten-Durchgang
|
||||||
|
|
||||||
**Status:** Schnappschuss vom 2026-04-18 — als Hilfsmittel für die Migrations-Sitzung erstellt, kein laufender Workflow. Bleibt als Referenz erhalten.
|
|
||||||
|
|
||||||
**Zweck:** 91 Bilder in 18 Posts visuell prüfen und Alt-Texte, Lizenzen, Autor:innen-Angaben gegen das echte Bild abgleichen.
|
**Zweck:** 91 Bilder in 18 Posts visuell prüfen und Alt-Texte, Lizenzen, Autor:innen-Angaben gegen das echte Bild abgleichen.
|
||||||
|
|
||||||
**Arbeitsweise:**
|
**Arbeitsweise:**
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,15 +0,0 @@
|
||||||
# Plan-Archiv
|
|
||||||
|
|
||||||
Diese Pläne sind abgearbeitet (5) oder zu Eis gelegt (1). Checkboxen
|
|
||||||
spiegeln nicht den realen Umsetzungsstand wider — sie wurden während
|
|
||||||
der Umsetzung nicht nachgepflegt. Verbindlicher Stand ist `docs/STATUS.md`
|
|
||||||
und die jeweilige Spec.
|
|
||||||
|
|
||||||
| Plan | Spec | Stand |
|
|
||||||
|---|---|---|
|
|
||||||
| 2026-04-15-spa-sveltekit | nostr-page-design | umgesetzt 2026-04-18 |
|
|
||||||
| 2026-04-16-publish-pipeline | publish-pipeline-design | umgesetzt 2026-04-18 |
|
|
||||||
| 2026-04-21-multilingual-posts-pipeline | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-multilingual-posts-spa | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-multilingual-posts-i18n | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-prerender-snapshot | prerender-snapshot-design | eingefroren |
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Nostr-Page auf Basis von Events — Design-Spec
|
# Nostr-Page auf Basis von Events — Design-Spec
|
||||||
|
|
||||||
**Datum:** 2026-04-15
|
**Datum:** 2026-04-15
|
||||||
**Status:** Umgesetzt (live seit 2026-04-18)
|
**Status:** Entwurf, ausstehende User-Freigabe
|
||||||
**Scope:** Ablösung der Hugo-Seite `joerg-lohrer.de` durch eine SvelteKit-SPA, die Blog-Posts live aus Nostr-Events rendert. Diese Spec beschreibt **nur die SPA und den Event-/URL-Kontrakt**. Publish-Pipeline (Markdown → Event → Relays + Assets-Upload) ist separate Spec.
|
**Scope:** Ablösung der Hugo-Seite `joerg-lohrer.de` durch eine SvelteKit-SPA, die Blog-Posts live aus Nostr-Events rendert. Diese Spec beschreibt **nur die SPA und den Event-/URL-Kontrakt**. Publish-Pipeline (Markdown → Event → Relays + Assets-Upload) ist separate Spec.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Publish-Pipeline für Nostr-Events — Design-Spec
|
# Publish-Pipeline für Nostr-Events — Design-Spec
|
||||||
|
|
||||||
**Datum:** 2026-04-15 (aktualisiert 2026-04-16: Blossom für alle Bilder, kein All-Inkl-rsync-Pfad mehr)
|
**Datum:** 2026-04-15 (aktualisiert 2026-04-16: Blossom für alle Bilder, kein All-Inkl-rsync-Pfad mehr)
|
||||||
**Status:** Umgesetzt (live seit 2026-04-18)
|
**Status:** Entwurf, ausstehende User-Freigabe
|
||||||
**Scope:** Toolchain, die Markdown-Posts aus `content/posts/*/index.md` in signierte Nostr-Events (`kind:30023`, NIP-23) umwandelt, zu Relays publiziert, und die zugehörigen Bilder zu Blossom hochlädt.
|
**Scope:** Toolchain, die Markdown-Posts aus `content/posts/*/index.md` in signierte Nostr-Events (`kind:30023`, NIP-23) umwandelt, zu Relays publiziert, und die zugehörigen Bilder zu Blossom hochlädt.
|
||||||
|
|
||||||
**Designentscheidung 2026-04-16:** Alle Bilder (auch die der 18 Altposts) werden zu Blossom hochgeladen. Kein rsync-Legacy-Pfad, kein `image_source`-Flag im Frontmatter. Die SPA rendert alle Posts über denselben Code-Pfad (Event-Text → Bild-URLs aus Blossom). Repo = Source-of-Truth für Content, Pipeline = Nostr-Export-Routine.
|
**Designentscheidung 2026-04-16:** Alle Bilder (auch die der 18 Altposts) werden zu Blossom hochgeladen. Kein rsync-Legacy-Pfad, kein `image_source`-Flag im Frontmatter. Die SPA rendert alle Posts über denselben Code-Pfad (Event-Text → Bild-URLs aus Blossom). Repo = Source-of-Truth für Content, Pipeline = Nostr-Export-Routine.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Konvention: Bild-Metadaten im Post-Frontmatter (Phase 1)
|
# Konvention: Bild-Metadaten im Post-Frontmatter (Phase 1)
|
||||||
|
|
||||||
**Datum:** 2026-04-16
|
**Datum:** 2026-04-16
|
||||||
**Status:** Umgesetzt — Phase 1 (live seit 2026-04-18). Phase 2 (Caption-Rendering, Reverse-Routine, License-Katalog, strikte Validierung) ist offen.
|
**Status:** Phase-1-Minimal — fokussiert auf sichere Attribution und `alt`-Vollständigkeit. Caption-Rendering, Reverse-Routine, License-Katalog und strikte Validierung sind explizit Phase 2.
|
||||||
**Scope:** YAML-Frontmatter-Schema für Bildmetadaten in Markdown-Posts. Wird von der Publish-Pipeline in `kind:30023`-Events (NIP-23) plus `imeta`-Tags (NIP-92) + `license`-Tag abgebildet.
|
**Scope:** YAML-Frontmatter-Schema für Bildmetadaten in Markdown-Posts. Wird von der Publish-Pipeline in `kind:30023`-Events (NIP-23) plus `imeta`-Tags (NIP-92) + `license`-Tag abgebildet.
|
||||||
|
|
||||||
## Ziele
|
## Ziele
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Multilinguale Posts — Design
|
# Multilinguale Posts — Design
|
||||||
|
|
||||||
**Datum:** 2026-04-21
|
**Datum:** 2026-04-21
|
||||||
**Status:** Umgesetzt (live seit 2026-04-21) — drei sequentielle Pläne (Pipeline, SPA-Resolving, UI-i18n).
|
**Status:** Umgesetzt. Live seit 2026-04-21 via drei Pläne (Pipeline, SPA-Resolving, UI-i18n).
|
||||||
**Scope:** Posts der SPA in mehreren Sprachen anbieten; UI-Chrome lokalisieren; Publish-Pipeline entsprechend anpassen.
|
**Scope:** Posts der SPA in mehreren Sprachen anbieten; UI-Chrome lokalisieren; Publish-Pipeline entsprechend anpassen.
|
||||||
|
|
||||||
## Umsetzungshinweis
|
## Umsetzungshinweis
|
||||||
|
|
|
||||||
|
|
@ -1,445 +0,0 @@
|
||||||
# Prerender-Snapshot für Nostr-Langform-Posts — Design
|
|
||||||
|
|
||||||
**Datum:** 2026-04-21
|
|
||||||
**Status:** Entwurf
|
|
||||||
**Scope:** SEO- und Social-Media-Tauglichkeit der Post-Detailseiten.
|
|
||||||
Post-URLs sollen beim ersten Request echtes HTML mit OG-Metadaten liefern —
|
|
||||||
ohne Runtime-Relay-Fetch, ohne Node-/Go-Server auf dem Hosting.
|
|
||||||
|
|
||||||
Plan-Pendant unter `docs/superpowers/plans/archive/2026-04-21-prerender-snapshot.md`
|
|
||||||
liegt unbearbeitet vor — die Umsetzung ist eingefroren bis zur Entscheidung,
|
|
||||||
ob das Vorhaben weiterverfolgt wird.
|
|
||||||
|
|
||||||
Schwester-Specs:
|
|
||||||
- [`2026-04-15-nostr-page-design.md`](2026-04-15-nostr-page-design.md) — SPA
|
|
||||||
- [`2026-04-15-publish-pipeline-design.md`](2026-04-15-publish-pipeline-design.md) — Publish
|
|
||||||
- [`2026-04-21-multilingual-posts-design.md`](2026-04-21-multilingual-posts-design.md) — Mehrsprachigkeit
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
Aktueller Zustand: SvelteKit-SPA auf All-Inkl-Shared-Hosting mit
|
|
||||||
`adapter-static` + `fallback: 'index.html'`. Post-Detailseiten unter
|
|
||||||
`https://joerg-lohrer.de/<d-tag>/` liefern beim Erstaufruf die generische
|
|
||||||
`index.html` mit Homepage-OG-Defaults. Post-Titel, Summary, Cover-Bild
|
|
||||||
werden erst nach JavaScript-Ausführung aus den Relays geladen.
|
|
||||||
|
|
||||||
Daraus entstehen drei Defizite:
|
|
||||||
|
|
||||||
- **Social-Media-Previews** (LinkedIn, Mastodon, Bluesky, Signal, iMessage)
|
|
||||||
zeigen nur die generischen Homepage-Tags, keine post-spezifischen.
|
|
||||||
- **Suchmaschinen** indexieren entweder nichts (kein crawler-readable
|
|
||||||
Content zur Request-Zeit) oder zeigen Treffer ohne Titel/Snippet.
|
|
||||||
- **Accessibility-/No-JS-Nutzer** sehen leere Detailseiten.
|
|
||||||
|
|
||||||
## Ziel
|
|
||||||
|
|
||||||
Bei jedem HTTP-GET auf `https://joerg-lohrer.de/<d-tag>/` liefert
|
|
||||||
All-Inkl eine statische `<d-tag>/index.html`, die enthält:
|
|
||||||
|
|
||||||
- korrekter `<title>` und `<meta name="description">`
|
|
||||||
- vollständige OG-Tags (`og:title`, `og:description`, `og:image`,
|
|
||||||
`og:locale`, `og:type=article`, `og:url`, `article:published_time`)
|
|
||||||
- Twitter-Cards (`summary_large_image`)
|
|
||||||
- `article`-JSON-LD-Schema für Google
|
|
||||||
- bidirektionale `<link rel="alternate" hreflang>` für Sprachvarianten
|
|
||||||
- vollständig gerenderten Post-Body (Markdown → HTML)
|
|
||||||
|
|
||||||
Die SPA hydriert über diesem HTML weiter und behält alle bisherigen
|
|
||||||
Laufzeit-Funktionen (Sprach-Switcher, Navigation, Reply-Loader).
|
|
||||||
|
|
||||||
## Nicht-Ziele
|
|
||||||
|
|
||||||
- **Kein generischer Nostr-Renderer.** Nur eigene `kind:30023`-Events mit
|
|
||||||
bekannter Pubkey. Fremde Events werden nie unter der eigenen Domain
|
|
||||||
gerendert (rechtliche Verantwortung nur für eigenen Content).
|
|
||||||
- **Kein Live-Proxy.** Relays werden zur Build-Zeit befragt, nicht pro
|
|
||||||
HTTP-Request.
|
|
||||||
- **Keine Edge-Function, kein VPS, kein PHP-Shim.** Lösung funktioniert
|
|
||||||
auf jedem Static-Hoster.
|
|
||||||
- **Kein Prerender für Listen-Seiten** (Homepage, Archiv, `/tag/<name>/`)
|
|
||||||
in dieser Iteration. Sie bleiben SPA-gerendert über den
|
|
||||||
`adapter-static`-`fallback: 'index.html'`-Mechanismus (Crawler auf
|
|
||||||
`/tag/nostr/` → bekommen `index.html`, Seite rendert nach Hydration).
|
|
||||||
Geteilt werden Artikel, nicht Listen.
|
|
||||||
- **Keine Änderung am Publish-Flow.** `publish`-Pipeline bleibt exakt
|
|
||||||
wie heute (Git-MD → Nostr-Event).
|
|
||||||
|
|
||||||
## Grundprinzipien
|
|
||||||
|
|
||||||
- **Repo ist Quelle der Wahrheit im Autorenprozess.** Das bleibt.
|
|
||||||
- **Relays sind Ort der Wahrheit zum Build-Zeitpunkt.** Der Snapshot fragt
|
|
||||||
die Relays, nicht das Repo. So werden auch Nostr-first publizierte
|
|
||||||
Posts (die nicht im Repo liegen) beim nächsten Build mitgerendert.
|
|
||||||
- **Blaupausen-Qualität.** Das Snapshot-Tool soll auch andere
|
|
||||||
Nostr-basierte Sites bedienen können. Keine harten Kopplungen an das
|
|
||||||
Blog-Setup.
|
|
||||||
- **Entkoppelte Stufen.** `publish`, `snapshot`, `build+deploy` sind
|
|
||||||
drei separate Kommandos. Sie können einzeln, hintereinander oder in
|
|
||||||
unterschiedlichen Kontexten ausgeführt werden.
|
|
||||||
|
|
||||||
## Architektur
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────┐
|
|
||||||
│ 1. publish (Deno) │ unverändert
|
|
||||||
│ Repo-MD → signed Event │ → Relays
|
|
||||||
│ + Blossom-Upload │ → GH-Actions-Trigger bei content/posts/**
|
|
||||||
└─────────────────────────────┘
|
|
||||||
|
|
||||||
┌─────────────────────────────┐
|
|
||||||
│ 2. snapshot (Deno, neu) │ neu
|
|
||||||
│ Relays → JSON-Artefakte │ schreibt snapshot/output/index.json
|
|
||||||
│ + NIP-09-Filter │ schreibt snapshot/output/posts/<slug>.json
|
|
||||||
│ + Plausibilitätschecks │
|
|
||||||
└─────────────────────────────┘
|
|
||||||
|
|
||||||
┌─────────────────────────────┐
|
|
||||||
│ 3. build+deploy (SvelteKit) │ erweitert
|
|
||||||
│ Prerender aus JSON │ → build/<slug>/index.html
|
|
||||||
│ + FTPS-Sync nach All-Inkl│ → lftp mirror --delete
|
|
||||||
└─────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stufe 1 — `publish`
|
|
||||||
|
|
||||||
Unverändert. Diese Spec modifiziert publish **nicht**.
|
|
||||||
|
|
||||||
### Stufe 2 — `snapshot`
|
|
||||||
|
|
||||||
Neues Deno-Modul. Verzeichnis: `snapshot/` als Geschwister zu `publish/`.
|
|
||||||
|
|
||||||
**Input:**
|
|
||||||
- `AUTHOR_PUBKEY_HEX` (env, 64 hex chars)
|
|
||||||
- `BOOTSTRAP_RELAY` (env, wss-URL)
|
|
||||||
- `--out <path>` (default: `./output/`, relativ zum `snapshot/`-Modulverzeichnis)
|
|
||||||
- `--min-events <n>` (Plausibilitätsschwelle, absolute Zahl; ohne Flag:
|
|
||||||
Last-known-good-Count aus Cache minus 2; ohne Cache: `1`)
|
|
||||||
- `--cache <path>` (default: `<out>/.last-snapshot.json`)
|
|
||||||
- `--allow-shrink` (Override des Drop-Checks, für Fälle in denen bewusst
|
|
||||||
massiv gelöscht wurde und kein `kind:5` als Signal existiert)
|
|
||||||
|
|
||||||
**Algorithmus:**
|
|
||||||
|
|
||||||
1. **Bootstrap.** `BOOTSTRAP_RELAY` anfragen, `kind:10002` des Autors
|
|
||||||
holen → Read-Relay-Liste extrahieren. Fallback: `FALLBACK_READ_RELAYS`
|
|
||||||
wenn `kind:10002` nicht ladbar.
|
|
||||||
2. **Event-Fetch.** Pro Read-Relay `kind:30023`, `authors:[pubkey]`
|
|
||||||
parallel abfragen. Timeout 10 s pro Relay.
|
|
||||||
3. **Dedup per d-tag.** Bei Duplikaten höchster `created_at` gewinnt.
|
|
||||||
4. **NIP-09-Filter.** `kind:5`-Events des Autors laden. Für jedes
|
|
||||||
`a`-Tag mit `30023:<pk>:<dtag>` den entsprechenden Eintrag verwerfen.
|
|
||||||
5. **Plausibilitätscheck:**
|
|
||||||
- mindestens `ceil(N × 0.6)` der N Read-Relays müssen geantwortet haben
|
|
||||||
(bei 5 Relays: 3, bei 3 Relays: 2) → sonst Hard-Fail
|
|
||||||
- Event-Count ≥ `--min-events` → sonst Hard-Fail. Beim allerersten
|
|
||||||
Lauf ohne Cache und ohne explizites Flag ist die Default-Schwelle `1`
|
|
||||||
(d.h. mindestens ein Event muss vorhanden sein) — der Drop-Check
|
|
||||||
greift erst beim zweiten Lauf.
|
|
||||||
- Event-Count-Drop > 20 % gegenüber Cache → Hard-Fail, **außer**:
|
|
||||||
- seit letztem Snapshot neue `kind:5`-Deletions von genau so vielen
|
|
||||||
Events wurden erkannt (Drop ist bewusst) → Check wird übersprungen
|
|
||||||
- `--allow-shrink` ist gesetzt → Check wird übersprungen
|
|
||||||
6. **Cover-Bild-Probe.** HEAD-Request auf `og:image`-Kandidat. Bei 200:
|
|
||||||
als `url` schreiben. Bei Fehler: Fallback-Blossom prüfen, als `url`
|
|
||||||
schreiben wenn verfügbar. Beide tot: primäre URL trotzdem schreiben +
|
|
||||||
Warnung loggen (Blossom ist content-addressed, URL wird später wieder
|
|
||||||
erreichbar sein).
|
|
||||||
7. **Kein Markdown-Rendering im Snapshot.** Body des Events wird als
|
|
||||||
rohes `content_markdown` ins JSON geschrieben. Das Rendering zu HTML
|
|
||||||
übernimmt der SvelteKit-Prerender-Schritt mit dem bereits existierenden
|
|
||||||
`$lib/render/markdown.ts`-Modul (marked + DOMPurify + highlight.js).
|
|
||||||
**Begründung:** Der SvelteKit-Build führt `renderMarkdown()` ohnehin
|
|
||||||
aus; eine Duplikation in Deno wäre doppelter Code-Pfad mit identischer
|
|
||||||
Policy. Für Blaupausen-Nutzung ist rohes Markdown zudem portabler —
|
|
||||||
jeder andere Renderer (Astro, Eleventy, …) bringt seinen eigenen
|
|
||||||
Markdown-Prozessor mit und würde fertiges HTML eher als Bürde
|
|
||||||
empfinden.
|
|
||||||
8. **Fallback-Politik für fehlende Felder:**
|
|
||||||
- fehlt `summary` im Event → aus `content_markdown` die ersten 200
|
|
||||||
Zeichen (Whitespace normalisiert, abgeschnitten an Wortgrenze,
|
|
||||||
Suffix `…`) extrahieren und als `summary` schreiben
|
|
||||||
- fehlt `image` im Event → `cover_image` ist `null`; der Prerender
|
|
||||||
nutzt ein Site-Default-OG-Bild (definiert in `app/static/`, z.B.
|
|
||||||
Profilbild oder Logo-Banner, als `og:image` bei null-Cover)
|
|
||||||
- fehlt `published_at`-Tag → `created_at` wird als
|
|
||||||
`published_at` übernommen
|
|
||||||
9. **JSON-Output schreiben.**
|
|
||||||
|
|
||||||
**Output-Format:**
|
|
||||||
|
|
||||||
`<out>/index.json` — Gesamtkatalog:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"generated_at": "2026-04-21T10:30:00Z",
|
|
||||||
"author_pubkey": "4fa5d1c4...",
|
|
||||||
"relays_queried": ["wss://relay.damus.io", "..."],
|
|
||||||
"relays_responded": ["wss://relay.damus.io", "..."],
|
|
||||||
"post_count": 27,
|
|
||||||
"posts": [
|
|
||||||
{
|
|
||||||
"slug": "bibel-selfies",
|
|
||||||
"lang": "de",
|
|
||||||
"created_at": 1713456789,
|
|
||||||
"title": "Bibel-Selfies"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`<out>/posts/<slug>.json` — pro Post:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"slug": "bibel-selfies",
|
|
||||||
"event_id": "abc123...",
|
|
||||||
"created_at": 1713456789,
|
|
||||||
"published_at": 1713456000,
|
|
||||||
"title": "Bibel-Selfies",
|
|
||||||
"summary": "Kurzbeschreibung für OG und Google.",
|
|
||||||
"lang": "de",
|
|
||||||
"cover_image": {
|
|
||||||
"url": "https://blossom.edufeed.org/<hash>.jpg",
|
|
||||||
"fallback_url": "https://blossom.primal.net/<hash>.jpg",
|
|
||||||
"width": 1600,
|
|
||||||
"height": 900,
|
|
||||||
"alt": "Alt-Text",
|
|
||||||
"mime": "image/jpeg"
|
|
||||||
},
|
|
||||||
"content_markdown": "…full markdown body, raw — Renderer sanitizes und rendert on demand…",
|
|
||||||
"tags": ["Nostr", "Bibel"],
|
|
||||||
"naddr": "naddr1...",
|
|
||||||
"habla_url": "https://habla.news/a/naddr1...",
|
|
||||||
"translations": [
|
|
||||||
{ "lang": "en", "slug": "bible-selfies", "title": "Bible-Selfies" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Semantik der `cover_image`-Felder:**
|
|
||||||
- `url` → primäre Bild-URL, wird vom Prerender als `og:image`-Wert in
|
|
||||||
den HTML-Head geschrieben. Crawler sehen nur diese URL.
|
|
||||||
- `fallback_url` → zweiter Blossom-Server mit demselben Hash (Blossom
|
|
||||||
ist content-addressed). Nicht Teil der OG-Tags. Nutzungsszenario:
|
|
||||||
Falls der Snapshot-HTML ein `<img>`-Element im Post-Body erzeugt, das
|
|
||||||
auf `url` zeigt, kann ein client-seitiger `onerror`-Handler bei
|
|
||||||
Ladefehler auf `fallback_url` umschalten. Ist das nicht gewünscht
|
|
||||||
(YAGNI), wird das Feld entfernt — Entscheidung in der Planungsphase.
|
|
||||||
|
|
||||||
**Semantik von `created_at` vs. `published_at`:**
|
|
||||||
- `published_at` → Redaktions-Zeitpunkt (menschlich), aus `published_at`-
|
|
||||||
Tag des Events. Ändert sich nicht bei Re-Publish. Wird als
|
|
||||||
`article:published_time` in OG-Tags gerendert. Hauptanzeige-Datum.
|
|
||||||
- `created_at` → technischer Event-Zeitstempel, ändert sich bei jedem
|
|
||||||
Update (z.B. bei Korrekturen). Kann als „zuletzt aktualisiert"
|
|
||||||
angezeigt werden. In OG nicht verwendet.
|
|
||||||
- Fehlt `published_at`-Tag im Event, wird `created_at` übernommen
|
|
||||||
(siehe Algorithmus, Schritt 8).
|
|
||||||
|
|
||||||
**Semantik der `translations[]`-Einträge:**
|
|
||||||
- Jeder Eintrag enthält `lang`, `slug` **und** `title` der fremdsprachlichen
|
|
||||||
Version. Prerender nutzt `lang`/`slug` für `hreflang`-Links, und
|
|
||||||
`title` für den SPA-Sprach-Switcher (📖 DE | EN). Damit entfällt ein
|
|
||||||
Runtime-Relay-Fetch beim Switcher.
|
|
||||||
|
|
||||||
**CLI:**
|
|
||||||
```sh
|
|
||||||
cd snapshot
|
|
||||||
deno task snapshot # default
|
|
||||||
deno task snapshot --out ./out # alternatives Ziel
|
|
||||||
deno task snapshot --min-events 20 # Schwelle
|
|
||||||
deno task snapshot --cache ./.last.json # Vergleich
|
|
||||||
deno task snapshot --allow-shrink # Drop-Check aus
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stufe 3 — `build+deploy`
|
|
||||||
|
|
||||||
Zwei Änderungen an der SvelteKit-SPA, eine Änderung am Deploy-Script.
|
|
||||||
|
|
||||||
**3.1 SvelteKit-Route `[...slug]/+page.ts`:**
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { EntryGenerator, PageLoad } from './$types';
|
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
import { resolve } from 'node:path';
|
|
||||||
|
|
||||||
export const prerender = true;
|
|
||||||
|
|
||||||
const SNAPSHOT_DIR = resolve('../snapshot/output');
|
|
||||||
|
|
||||||
export const entries: EntryGenerator = () => {
|
|
||||||
const catalog = JSON.parse(
|
|
||||||
readFileSync(`${SNAPSHOT_DIR}/index.json`, 'utf-8')
|
|
||||||
);
|
|
||||||
return catalog.posts.map((p: { slug: string }) => ({ slug: p.slug }));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ params }) => {
|
|
||||||
const postData = JSON.parse(
|
|
||||||
readFileSync(`${SNAPSHOT_DIR}/posts/${params.slug}.json`, 'utf-8')
|
|
||||||
);
|
|
||||||
return { dtag: params.slug, snapshot: postData };
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**3.2 SvelteKit-Route `[...slug]/+page.svelte`:**
|
|
||||||
|
|
||||||
Die Route rendert den Snapshot-Content statt Relay-Fetch. Im
|
|
||||||
`<svelte:head>` werden alle Meta-Tags eingesetzt:
|
|
||||||
|
|
||||||
- `<title>` aus `snapshot.title`
|
|
||||||
- `<meta name="description">` aus `snapshot.summary`
|
|
||||||
- `<meta property="og:*">` aus Snapshot-Feldern
|
|
||||||
- `<meta name="twitter:*">` (summary_large_image)
|
|
||||||
- `<link rel="canonical">` auf `${SITE_URL}/${slug}/`
|
|
||||||
- `<link rel="alternate" hreflang="...">` für jede Translation
|
|
||||||
- `<link rel="alternate" hreflang="x-default">` auf DE-Slug
|
|
||||||
- `<script type="application/ld+json">` mit `Article`-Schema
|
|
||||||
- `<html lang="...">` aus `snapshot.lang` (via Layout)
|
|
||||||
|
|
||||||
Post-Body wird aus `snapshot.content_markdown` per `renderMarkdown()`
|
|
||||||
zur Build-Zeit zu HTML gerendert und dann via `{@html …}` eingesetzt.
|
|
||||||
Die bestehende `$lib/render/markdown.ts` wird so angepasst, dass sie
|
|
||||||
im Node-Build-Kontext funktioniert (Umstellung auf
|
|
||||||
`isomorphic-dompurify` oder äquivalente Build-Zeit-DOM-Bereitstellung).
|
|
||||||
`ReplyList`/`ReplyComposer` bleiben clientseitig unverändert.
|
|
||||||
|
|
||||||
Der SPA-interne Sprach-Switcher liest `snapshot.translations[]` direkt
|
|
||||||
aus Page-Data — kein Relay-Fetch zur Laufzeit mehr nötig.
|
|
||||||
|
|
||||||
**3.3 Deploy-Script `scripts/deploy-svelte.sh`:**
|
|
||||||
|
|
||||||
FTPS-Upload wird auf `lftp mirror --delete` umgestellt, damit gelöschte
|
|
||||||
Posts (die nicht mehr im Build-Output stehen) auch auf dem Server
|
|
||||||
entfernt werden. Für die Site-Root wird `--exclude-glob` gesetzt, damit
|
|
||||||
nicht versehentlich Favicons/Hero-Bild gelöscht werden, die nicht Teil
|
|
||||||
des SvelteKit-Builds sind.
|
|
||||||
|
|
||||||
**Upload-Reihenfolge (kritisch wegen Hash-benannten JS-Bundles):**
|
|
||||||
|
|
||||||
1. Zuerst **Assets** hochladen (`_app/immutable/**`, Bilder, CSS) —
|
|
||||||
reine Upload-Phase ohne Server-seitiges Löschen. Neue Hash-Bundles
|
|
||||||
landen zusätzlich zu den alten auf dem Server.
|
|
||||||
2. Danach **HTML-Seiten** hochladen (`index.html`, `<slug>/index.html`,
|
|
||||||
`404.html`), ebenfalls ohne Löschen. Ab diesem Punkt zeigen die neuen
|
|
||||||
HTMLs auf ihre zugehörigen neuen Asset-Hashes — konsistent.
|
|
||||||
3. **Zum Schluss** ein separater **Delete-Pass**, der Server-Dateien
|
|
||||||
entfernt, die im aktuellen Build-Output nicht mehr existieren (alte
|
|
||||||
Hash-Bundles, gelöschte Post-HTMLs, veraltete Snapshot-JSONs). Nichts
|
|
||||||
wird in dieser Phase erneut hochgeladen. Konkrete `lftp`-Flag-Kombi
|
|
||||||
in der Planungsphase festzulegen — wichtig ist nur die
|
|
||||||
Phasen-Trennung: Upload zuerst, Delete zuletzt, kein paralleler
|
|
||||||
Mirror-Call.
|
|
||||||
|
|
||||||
Damit ist zu keinem Zeitpunkt ein inkonsistenter Zustand auf dem Server:
|
|
||||||
Neue HTMLs referenzieren stets bereits vorhandene Asset-Hashes; alte
|
|
||||||
Assets werden erst nach erfolgreichem Upload gelöscht.
|
|
||||||
|
|
||||||
Von `--delete` ausgeschlossen bleiben außerhalb des SvelteKit-Builds
|
|
||||||
verwaltete Dateien (Hero-Bild, Favicons im Root, `.well-known/`,
|
|
||||||
Webspace-Spezifika) via `--exclude-glob`.
|
|
||||||
|
|
||||||
Kein weiteres Verhalten ändert sich.
|
|
||||||
|
|
||||||
## Mehrsprachigkeit
|
|
||||||
|
|
||||||
Pro Sprache ein Event mit eigenem d-tag (z.B. `bibel-selfies` /
|
|
||||||
`bible-selfies`). Das bestehende bidirektionale `a`-Tag mit Marker
|
|
||||||
`translation` (siehe `2026-04-21-multilingual-posts-design.md`) wird vom
|
|
||||||
Snapshot als `translations[]`-Array im JSON serialisiert.
|
|
||||||
|
|
||||||
Der Prerender generiert pro d-tag eine eigene `<slug>/index.html`.
|
|
||||||
`hreflang`-Links im `<head>` verweisen bidirektional auf die Pendants.
|
|
||||||
`x-default` zeigt auf den DE-Slug (Autor arbeitet DE-first).
|
|
||||||
|
|
||||||
UI-Chrome-Locale via `activeLocale`-Store bleibt vom Prerender
|
|
||||||
unabhängig — die URL bestimmt Post-Sprache (hart), der Store bestimmt
|
|
||||||
nur die UI-Sprache (weich, umschaltbar).
|
|
||||||
|
|
||||||
## Fehlerszenarien
|
|
||||||
|
|
||||||
| Szenario | Verhalten |
|
|
||||||
|---|---|
|
|
||||||
| < 40 % Relays down | Snapshot mergt, was da ist, fährt fort |
|
|
||||||
| ≥ 40 % Relays down | Hard-Fail, Output nicht überschrieben |
|
|
||||||
| Event-Count-Drop > 20 % ohne korrespondierende `kind:5` | Hard-Fail (Override via `--allow-shrink`) |
|
|
||||||
| Event-Count-Drop > 20 % mit korrespondierenden `kind:5` | Check übersprungen, fährt fort |
|
|
||||||
| Blossom-Cover nicht erreichbar | Warnung loggen, URL trotzdem schreiben |
|
|
||||||
| Event ohne `summary` | `summary` aus Body-Anfang abgeleitet |
|
|
||||||
| Event ohne `image` | `cover_image: null`, Prerender nutzt Site-Default-OG-Bild |
|
|
||||||
| NIP-09-gelöschter Post | Aus Katalog weggelassen, Deploy-Sync löscht HTML |
|
|
||||||
| Repo-Post mit allen Relay-Events via NIP-09 gelöscht | Delete gewinnt: Post wird nicht gerendert, `<slug>/index.html` wird entfernt. Crawler erhalten 404. Gewolltes Verhalten — Relays sind Ort der Wahrheit. |
|
|
||||||
| Nostr-first-Post nicht im Repo | Wird trotzdem snapshot'd + gerendert |
|
|
||||||
| Alle Relays down | Hard-Fail, letzter Snapshot-Stand bleibt liegen |
|
|
||||||
|
|
||||||
## Rollback
|
|
||||||
|
|
||||||
**Snapshot-Ebene:** Der vorletzte Snapshot-Output bleibt als
|
|
||||||
`.last-snapshot.json` erhalten. Bei defektem Snapshot kann er manuell
|
|
||||||
wieder aktiviert werden.
|
|
||||||
|
|
||||||
**SPA-Ebene:** Reproduzierbar aus Git + Snapshot-JSONs.
|
|
||||||
|
|
||||||
**FTPS-Ebene:** Optional `tar.gz` des Webroots vor Upload (nicht Teil
|
|
||||||
der ersten Implementierung).
|
|
||||||
|
|
||||||
## Migrations-Weg
|
|
||||||
|
|
||||||
Inkrementell, jeder Schritt einzeln testbar und rollback-bar. Jeder
|
|
||||||
Schritt hat eine eigene Rollback-Strategie, sodass die Gesamtänderung
|
|
||||||
an keiner Stelle einen Big-Bang bildet:
|
|
||||||
|
|
||||||
1. **`renderMarkdown` Node-kompatibel machen.** DOM-Abhängigkeit auf
|
|
||||||
`isomorphic-dompurify` umstellen, sodass das Modul sowohl im Browser
|
|
||||||
als auch im SvelteKit-Build-Node-Kontext funktioniert.
|
|
||||||
Unit-Test-Verhalten gegen Regression sichern. Rollback: Commit revert.
|
|
||||||
2. **Snapshot-Modul ergänzen.** `snapshot/` mit Deno-Task, CLI, Tests.
|
|
||||||
Schreibt JSON mit `content_markdown`, keine HTML-Erzeugung. Keine
|
|
||||||
Änderung an SPA. Rollback: Verzeichnis löschen.
|
|
||||||
3. **Snapshot in CI einbauen.** GitHub-Actions-Schritt vor SvelteKit-Build.
|
|
||||||
Rollback: Workflow-Schritt entfernen.
|
|
||||||
4. **SvelteKit-Route auf Prerender umstellen, mit Laufzeit-Fallback.**
|
|
||||||
`[...slug]/+page.ts` bekommt `prerender = true` + `entries()` + Load
|
|
||||||
aus JSON. `+page.svelte` rendert `content_markdown` per
|
|
||||||
`renderMarkdown()` zur Build-Zeit. Der bisherige Runtime-Relay-Fetch
|
|
||||||
bleibt in diesem Schritt noch als Fallback bestehen — falls ein Slug
|
|
||||||
zur Build-Zeit nicht im Snapshot war (z.B. ganz frisch Nostr-first
|
|
||||||
publiziert), kann die SPA ihn über `adapter-static`-`fallback`
|
|
||||||
weiterhin rendern. Rollback: Commit revert, alte Runtime-Logik kommt
|
|
||||||
vollständig zurück.
|
|
||||||
5. **Runtime-Relay-Fetch der Detail-Seite entfernen.** Wenn Schritt 4
|
|
||||||
sich stabil zeigt, wird der Fallback-Code-Pfad abgebaut. Die
|
|
||||||
Detail-Seite lebt dann ausschließlich vom Snapshot. Neue Nostr-first-
|
|
||||||
Posts erscheinen erst nach dem nächsten Snapshot+Build-Lauf. Rollback:
|
|
||||||
Commit revert.
|
|
||||||
6. **Deploy-Script erweitern.** `lftp mirror --delete` mit
|
|
||||||
Upload-Reihenfolge. Rollback: Script revert — Site bleibt, nur
|
|
||||||
Obsolete-Cleanup fehlt.
|
|
||||||
|
|
||||||
## Blaupausen-Anforderungen
|
|
||||||
|
|
||||||
Damit das Tool als Vorlage für andere Nostr-Sites dient:
|
|
||||||
|
|
||||||
- **Konfiguration via env/CLI**, keine hart gecodeten Relay-Listen
|
|
||||||
- **JSON-Output als stabile Schnittstelle** — Renderer austauschbar
|
|
||||||
(SvelteKit, Astro, Eleventy, …)
|
|
||||||
- **Dokumentiertes Minimum-Viable-Use-Interface:**
|
|
||||||
```sh
|
|
||||||
export AUTHOR_PUBKEY_HEX="<64 hex>"
|
|
||||||
export BOOTSTRAP_RELAY="wss://..."
|
|
||||||
deno task snapshot --out ./my-site/snapshot-data
|
|
||||||
# eigener Site-Builder liest ./my-site/snapshot-data/index.json
|
|
||||||
```
|
|
||||||
- **Explizite Grenzen:** nur kind:30023, nur eigener Pubkey, kein
|
|
||||||
Live-Proxy — diese Beschränkungen sind Feature, nicht Bug.
|
|
||||||
|
|
||||||
## Offene Punkte
|
|
||||||
|
|
||||||
- Ob der SvelteKit-Prerender deterministisch identische HTML für
|
|
||||||
unveränderte Inputs produziert (für Diff-Builds / Cache-Invalidation).
|
|
||||||
Vermutlich ja, nachprüfen.
|
|
||||||
- Ob `fallback_url` im `cover_image` tatsächlich gebraucht wird. Wenn
|
|
||||||
der Snapshot-HTML keine `onerror`-Substitution implementiert, ist
|
|
||||||
das Feld toter Code. Entscheidung: mit `fallback_url` starten, bei
|
|
||||||
fehlender Nutzung in der SPA wieder entfernen (YAGNI).
|
|
||||||
- Site-Default-OG-Bild: welches konkret? Vermutlich Profilbild oder
|
|
||||||
Logo-Banner mit Überschrift „Jörg Lohrer". Entscheidung in
|
|
||||||
Planungsphase unter Abgleich mit vorhandenen `static/`-Assets.
|
|
||||||
|
|
@ -1,244 +0,0 @@
|
||||||
# Doku-Aufräumung — Design
|
|
||||||
|
|
||||||
**Datum:** 2026-04-25
|
|
||||||
**Status:** Entwurf
|
|
||||||
**Scope:** Strukturelle Bereinigung der Repo-Doku nach erreichten Kernzielen
|
|
||||||
(SPA + Pipeline + Multilingual live). Keine Code-Änderungen. Ziel ist ein
|
|
||||||
ehrlicher Doku-Stand mit klarer Rollenverteilung der Top-Level-Dokumente,
|
|
||||||
korrekten Status-Feldern in den Specs und einer sichtbaren Trennung zwischen
|
|
||||||
"lebender Doku" und "Geschichte".
|
|
||||||
|
|
||||||
## Auslöser
|
|
||||||
|
|
||||||
Die Inventur am 2026-04-25 hat drei strukturelle Probleme aufgedeckt:
|
|
||||||
|
|
||||||
1. **Specs lügen über ihren Status.** Die zwei ältesten Specs sagen
|
|
||||||
"Entwurf, ausstehende User-Freigabe", obwohl beide seit dem 18. April
|
|
||||||
live sind. Eine Spec hat ein abweichendes Header-Format (`**Stand:**`
|
|
||||||
statt `**Status:**`).
|
|
||||||
2. **Pläne sind Geschichte, stehen aber im aktiven Blickfeld.** Alle sechs
|
|
||||||
Pläne haben 0 abgehakte Checkboxen, obwohl die zugehörigen Features
|
|
||||||
nachweislich live sind. STATUS.md sagt "alle Pläne erledigt" —
|
|
||||||
das stimmt für die fünf Multilingual-/SPA-/Pipeline-Pläne, kollidiert
|
|
||||||
aber mit `2026-04-21-prerender-snapshot.md`, der nicht umgesetzt ist.
|
|
||||||
3. **Vier Top-Level-Dokumente überlappen.** README, STATUS.md, HANDOFF.md
|
|
||||||
und CLAUDE.md verlinken jeweils unterschiedliche Teilmengen der Specs;
|
|
||||||
keine klare Single-Source-of-Truth für Konventionen vs. Logbuch vs.
|
|
||||||
Außensicht vs. Session-Einstieg.
|
|
||||||
|
|
||||||
## Ziele
|
|
||||||
|
|
||||||
- Specs spiegeln den realen Implementierungsstand wider.
|
|
||||||
- Pläne sind als Historie sichtbar archiviert; was noch nicht umgesetzt
|
|
||||||
ist, ist als solches gekennzeichnet.
|
|
||||||
- Jedes Top-Level-Dokument hat genau eine Rolle, und die ist im Doc selbst
|
|
||||||
benannt.
|
|
||||||
- Wiki-Entwürfe haben einen expliziten Status: lebend, eingefroren oder
|
|
||||||
publiziert.
|
|
||||||
|
|
||||||
Ausdrücklich **nicht** Ziel:
|
|
||||||
- Inhaltliche Überarbeitung der Specs (keine Detail-Korrekturen am
|
|
||||||
Designgehalt, nur Status und Querverweise).
|
|
||||||
- Konsolidierung von STATUS.md + HANDOFF.md zu einem Dokument — die beiden
|
|
||||||
Rollen sollen erhalten bleiben, nur klarer abgegrenzt.
|
|
||||||
|
|
||||||
## Maßnahmen
|
|
||||||
|
|
||||||
### M1 — Specs: Status-Header vereinheitlichen und nachziehen
|
|
||||||
|
|
||||||
**Konvention** (gilt fortan für alle Specs):
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# <Titel>
|
|
||||||
|
|
||||||
**Datum:** YYYY-MM-DD
|
|
||||||
**Status:** <einer von: Entwurf | In Umsetzung | Umgesetzt (live seit YYYY-MM-DD) | Eingefroren | Verworfen>
|
|
||||||
**Scope:** <ein Satz>
|
|
||||||
```
|
|
||||||
|
|
||||||
Die einheitliche Status-Stufenfolge: `Entwurf` → `In Umsetzung` →
|
|
||||||
`Umgesetzt (live seit …)`. Alternative Endstände: `Eingefroren` (Idee gut,
|
|
||||||
aber nicht aktiv verfolgt) oder `Verworfen` (mit kurzem Grund). Bei
|
|
||||||
Status-Änderungen das Datum als Suffix in Klammern führen.
|
|
||||||
|
|
||||||
**Pro Spec konkret:**
|
|
||||||
|
|
||||||
- `2026-04-15-nostr-page-design.md` →
|
|
||||||
`Status: Umgesetzt (live seit 2026-04-18)`.
|
|
||||||
- `2026-04-15-publish-pipeline-design.md` →
|
|
||||||
`Status: Umgesetzt (live seit 2026-04-18)`. Die Inline-Notiz
|
|
||||||
"Designentscheidung 2026-04-16: Blossom für alle Bilder" bleibt im
|
|
||||||
Body, der Header wird klar.
|
|
||||||
- `2026-04-16-image-metadata-convention.md` →
|
|
||||||
`Status: Umgesetzt — Phase 1 (live seit 2026-04-18)`. Body bleibt
|
|
||||||
unverändert; die im Body bereits markierten Phase-2-Punkte
|
|
||||||
(Caption-Rendering, Reverse-Routine, License-Katalog, strikte
|
|
||||||
Validierung) bleiben offen.
|
|
||||||
- `2026-04-21-multilingual-posts-design.md` → bereits korrekt,
|
|
||||||
Format ggf. minimal nachziehen (`Umgesetzt (live seit 2026-04-21)`).
|
|
||||||
- `2026-04-21-prerender-snapshot-design.md` → `**Stand:**` durch
|
|
||||||
`**Status:** Entwurf` ersetzen, Datum-Zeile ergänzen. Hinweis im Body:
|
|
||||||
"Plan-Pendant unter `plans/archive/2026-04-21-prerender-snapshot.md`
|
|
||||||
liegt noch unbearbeitet vor."
|
|
||||||
|
|
||||||
### M2 — Pläne ins Archiv
|
|
||||||
|
|
||||||
**Aktion:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
mkdir -p docs/superpowers/plans/archive
|
|
||||||
git mv docs/superpowers/plans/2026-04-15-spa-sveltekit.md \
|
|
||||||
docs/superpowers/plans/2026-04-16-publish-pipeline.md \
|
|
||||||
docs/superpowers/plans/2026-04-21-multilingual-posts-pipeline.md \
|
|
||||||
docs/superpowers/plans/2026-04-21-multilingual-posts-spa.md \
|
|
||||||
docs/superpowers/plans/2026-04-21-multilingual-posts-i18n.md \
|
|
||||||
docs/superpowers/plans/2026-04-21-prerender-snapshot.md \
|
|
||||||
docs/superpowers/plans/archive/
|
|
||||||
```
|
|
||||||
|
|
||||||
**Begründung:** Alle sechs Pläne sind entweder umgesetzt (5) oder
|
|
||||||
eingefroren (1, prerender-snapshot). Keiner ist aktuell aktiv. Die
|
|
||||||
Trennung "Plans-Verzeichnis = aktive Pläne" / "Archive = Geschichte"
|
|
||||||
hält das aktive Blickfeld leer und ehrlich.
|
|
||||||
|
|
||||||
**Checkboxen werden NICHT retrospektiv nachgepflegt.** Die archivierten
|
|
||||||
Pläne bleiben so wie sie sind. Wer den Soll-Stand wissen will, liest
|
|
||||||
STATUS.md und die zugehörige Spec.
|
|
||||||
|
|
||||||
**`plans/archive/README.md` neu anlegen**, ein paar Zeilen, ungefähr:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Plan-Archiv
|
|
||||||
|
|
||||||
Diese Pläne sind abgearbeitet (5) oder zu Eis gelegt (1). Checkboxen
|
|
||||||
spiegeln nicht den realen Umsetzungsstand wider — sie wurden während
|
|
||||||
der Umsetzung nicht nachgepflegt. Verbindlicher Stand ist STATUS.md
|
|
||||||
und die jeweilige Spec.
|
|
||||||
|
|
||||||
| Plan | Spec | Stand |
|
|
||||||
|---|---|---|
|
|
||||||
| 2026-04-15-spa-sveltekit | nostr-page-design | umgesetzt 2026-04-18 |
|
|
||||||
| 2026-04-16-publish-pipeline | publish-pipeline-design | umgesetzt 2026-04-18 |
|
|
||||||
| 2026-04-21-multilingual-posts-pipeline | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-multilingual-posts-spa | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-multilingual-posts-i18n | multilingual-posts-design | umgesetzt 2026-04-21 |
|
|
||||||
| 2026-04-21-prerender-snapshot | prerender-snapshot-design | eingefroren |
|
|
||||||
```
|
|
||||||
|
|
||||||
`docs/superpowers/plans/` bleibt als Verzeichnis erhalten (mit `.gitkeep`),
|
|
||||||
damit die Konvention "hier liegen aktive Pläne" sofort wieder genutzt
|
|
||||||
werden kann, wenn ein neuer Plan entsteht.
|
|
||||||
|
|
||||||
### M3 — Top-Level-Dokumente: Rollen festlegen
|
|
||||||
|
|
||||||
Pro Dokument ein **Rollensatz** ganz oben (unter dem Titel), so dass beim
|
|
||||||
Reinklicken sofort klar ist, wofür es da ist.
|
|
||||||
|
|
||||||
| Datei | Rolle | Zielgruppe |
|
|
||||||
|---|---|---|
|
|
||||||
| `README.md` | Außensicht: was ist das Repo, wie funktioniert es grob, wo geht's weiter | Besucher:innen, GitHub-Crawler |
|
|
||||||
| `docs/STATUS.md` | Logbuch: aktueller Stand + Erledigt-Chronologie | Jörg + Claude beim Wiedereinstieg |
|
|
||||||
| `docs/HANDOFF.md` | Konventions-Handbuch: wie man hier arbeitet (Posts, Übersetzungen, Deploys, Stolperfallen) | Jörg + Claude im Alltag |
|
|
||||||
| `CLAUDE.md` | Session-Einstieg: Lese-Reihenfolge, Tonfall, kritische Fallstricke | Claude beim Session-Start |
|
|
||||||
|
|
||||||
**Konkrete Edits:**
|
|
||||||
|
|
||||||
- **README.md** — Lange Spec-/Plan-Linkliste auf 3 Specs eindampfen:
|
|
||||||
SPA, Publish-Pipeline, Multilingual (das sind die drei, die das
|
|
||||||
Außenbild der Seite erklären). Bild-Metadaten-Konvention und
|
|
||||||
prerender-Spec sind interne Detail-Themen und gehören nicht in die
|
|
||||||
README. Plan-Verlinkungen ganz raus, stattdessen ein Hinweis auf
|
|
||||||
`docs/superpowers/plans/archive/`. Erste Zeile nach dem Titel:
|
|
||||||
kurzer Rollensatz.
|
|
||||||
- **STATUS.md** — Erste Zeile: Rollensatz. "Erledigt"-Chronologie bleibt,
|
|
||||||
ist Logbuch. Widerspruch "alle Pläne erledigt" auflösen: Satz so
|
|
||||||
umformulieren, dass prerender-snapshot als eingefrorenes Vorhaben
|
|
||||||
separat aufgeführt ist.
|
|
||||||
- **HANDOFF.md** — Rollensatz oben. Workflow-Inhalte und Stolperfallen
|
|
||||||
bleiben.
|
|
||||||
- **CLAUDE.md** — Rollensatz oben. Quick-Links auf den finalen Spec-Stand
|
|
||||||
bringen (5 Specs inkl. prerender). "Kritische Fallstricke" bleibt
|
|
||||||
unverändert (alle 5 Punkte sind aktuell relevant).
|
|
||||||
|
|
||||||
**Single-Source-of-Truth-Regeln** (in HANDOFF.md kurz festhalten, damit
|
|
||||||
zukünftiges Edit-Verhalten klar ist):
|
|
||||||
|
|
||||||
- *Live-URLs / Setup-Stand* steht in **STATUS.md**. HANDOFF/README/CLAUDE
|
|
||||||
zitieren oder verlinken, duplizieren nicht.
|
|
||||||
- *Frontmatter-Konventionen, Workflows, Stolperfallen* stehen in
|
|
||||||
**HANDOFF.md**.
|
|
||||||
- *Spec-Liste* ist die Realität in `docs/superpowers/specs/` —
|
|
||||||
README/CLAUDE.md verlinken nur die für ihre Zielgruppe relevanten.
|
|
||||||
|
|
||||||
### M4 — Wiki-Entwürfe: Status klären
|
|
||||||
|
|
||||||
Drei Dateien, drei Entscheidungen:
|
|
||||||
|
|
||||||
- `docs/redaktion-bild-metadaten.md` — Frontmatter-Block oben ergänzen:
|
|
||||||
```markdown
|
|
||||||
**Status:** Schnappschuss vom 2026-04-18 — als Hilfsmittel für die
|
|
||||||
Migrations-Sitzung erstellt, kein laufender Workflow. Bleibt als
|
|
||||||
Referenz erhalten.
|
|
||||||
```
|
|
||||||
- `docs/wiki-entwurf-nostr-bild-metadaten.md` (DE) und
|
|
||||||
`docs/wiki-draft-nostr-image-metadata.md` (EN) — beide bekommen oben:
|
|
||||||
```markdown
|
|
||||||
**Status:** Eingefroren am 2026-04-25 — fertige Entwürfe für externe
|
|
||||||
Veröffentlichung (NIP-Proposal / nostrbook.dev). Solange nicht
|
|
||||||
publiziert, dient diese Repo-Kopie als Referenz und Geschichts-Dokument.
|
|
||||||
```
|
|
||||||
|
|
||||||
Bei späterer externer Veröffentlichung wird der Status auf
|
|
||||||
`Publiziert (URL)` aktualisiert.
|
|
||||||
|
|
||||||
### M5 — `github-ci-setup.md`
|
|
||||||
|
|
||||||
Ergänzung oben:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
**Status:** Aktuell genutzt; mittelfristig zu ersetzen durch self-hosted
|
|
||||||
CI (siehe HANDOFF.md → Option D).
|
|
||||||
```
|
|
||||||
|
|
||||||
Keine inhaltliche Änderung — die Setup-Schritte sind nach wie vor
|
|
||||||
korrekt für die heute laufende Pipeline.
|
|
||||||
|
|
||||||
## Reihenfolge der Umsetzung
|
|
||||||
|
|
||||||
1. M1 — Spec-Header durchziehen (am wichtigsten, alles andere baut darauf
|
|
||||||
auf).
|
|
||||||
2. M2 — Pläne archivieren + Archiv-README schreiben.
|
|
||||||
3. M3 — Top-Level-Dokumente (Rollensätze + Link-Bereinigung).
|
|
||||||
4. M4 + M5 — kleine Status-Stempel auf Wiki/CI-Doku.
|
|
||||||
5. Commit, ggf. in 2-3 logischen Schritten.
|
|
||||||
|
|
||||||
## Erfolgskriterien
|
|
||||||
|
|
||||||
- `grep -r "ausstehende User-Freigabe" docs/superpowers/specs/` liefert
|
|
||||||
nichts.
|
|
||||||
- `ls docs/superpowers/plans/` zeigt nur `archive/` (+ ggf. `.gitkeep`).
|
|
||||||
- Jede der vier Top-Level-Dateien beginnt mit einem Rollensatz.
|
|
||||||
- README enthält keine Verweise auf einzelne Pläne mehr (nur auf das
|
|
||||||
Archiv-Verzeichnis).
|
|
||||||
- Specs haben einheitliches `**Status:**`-Format.
|
|
||||||
|
|
||||||
## Was bewusst nicht gemacht wird
|
|
||||||
|
|
||||||
- **Keine inhaltliche Überarbeitung** der Specs. Falls beim Status-Update
|
|
||||||
inhaltliche Lügen auffallen (z. B. Spec beschreibt Feature anders als
|
|
||||||
es live ist), wird das in dieser Spec **nicht** mitgefixt — separat als
|
|
||||||
eigenes Vorhaben aufnehmen.
|
|
||||||
- **Keine Konsolidierung** von STATUS.md + HANDOFF.md. Beide bleiben
|
|
||||||
bestehen, nur Rollen werden klarer benannt.
|
|
||||||
- **Keine Frontmatter-Felder** (`status:`, `superseded-by:`) in den
|
|
||||||
Specs. Text-Header reicht für die aktuelle Repo-Größe; YAML-Metadaten
|
|
||||||
wären Overkill.
|
|
||||||
- **Keine retrospektiven Checkbox-Updates** in den archivierten Plänen.
|
|
||||||
|
|
||||||
## Folgevorhaben (außerhalb dieser Spec)
|
|
||||||
|
|
||||||
- **Paket B — SPA-Konsolidierung:** Tag-/Date-/Locale-Utilities
|
|
||||||
extrahieren, Stores auf Runes migrieren, `PostView.svelte` entzerren.
|
|
||||||
- **Paket C — Pipeline-Härtung:** `cli.ts` aufbrechen, Tests für
|
|
||||||
`signer.ts` + `check.ts`, CWD-Sensitivität fixen.
|
|
||||||
- Entscheidung über prerender-snapshot: weiterverfolgen oder verwerfen.
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Structured Image Metadata for Markdown-Sourced Nostr Long-Form Content
|
# Structured Image Metadata for Markdown-Sourced Nostr Long-Form Content
|
||||||
|
|
||||||
**Status:** Frozen 2026-04-25 — finalised draft for external publication (NIP proposal / nostrbook.dev). Until published, this in-repo copy serves as a reference and historical record. Practice convention, not (yet) a NIP.
|
**Status:** Working draft — a practice convention, not (yet) a NIP.
|
||||||
**Scope:** Authors who maintain Markdown long-form posts (`kind:30023`, NIP-23) in a git repository and publish them to Nostr via a build pipeline. The convention defines how image metadata (author, license, source, alt text, caption) lives in the repository, how it becomes `imeta` tags (NIP-92) in the event, and how to round-trip between the two.
|
**Scope:** Authors who maintain Markdown long-form posts (`kind:30023`, NIP-23) in a git repository and publish them to Nostr via a build pipeline. The convention defines how image metadata (author, license, source, alt text, caption) lives in the repository, how it becomes `imeta` tags (NIP-92) in the event, and how to round-trip between the two.
|
||||||
**Goal:** Zero data loss between repository and event. Human-readable in raw Markdown. Machine-readable in the published event. Safe defaults against accidental misattribution.
|
**Goal:** Zero data loss between repository and event. Human-readable in raw Markdown. Machine-readable in the published event. Safe defaults against accidental misattribution.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Strukturierte Bild-Metadaten für Markdown-basierte Nostr-Langform-Beiträge
|
# Strukturierte Bild-Metadaten für Markdown-basierte Nostr-Langform-Beiträge
|
||||||
|
|
||||||
**Status:** Eingefroren am 2026-04-25 — fertiger Entwurf für externe Veröffentlichung (NIP-Proposal / nostrbook.dev). Solange nicht publiziert, dient diese Repo-Kopie als Referenz und Geschichts-Dokument. Praxis-Konvention, (noch) kein NIP.
|
**Status:** Arbeitsentwurf — eine Praxis-Konvention, (noch) kein NIP.
|
||||||
**Scope:** Eine Inline-Markdown-Konvention zur Bildattribution (Urheber, Lizenz, Quelle, Bearbeitung), die in jedem Markdown-Editor direkt nutzbar ist und sich verlustfrei auf NIP-92-`imeta`-Tags in `kind:30023`-Events abbilden lässt.
|
**Scope:** Eine Inline-Markdown-Konvention zur Bildattribution (Urheber, Lizenz, Quelle, Bearbeitung), die in jedem Markdown-Editor direkt nutzbar ist und sich verlustfrei auf NIP-92-`imeta`-Tags in `kind:30023`-Events abbilden lässt.
|
||||||
**Ziel:** Ein einheitliches, menschlich lesbares und maschinell parsbares Attributions-Format für Bilder in Nostr-Langform-Beiträgen. TULLU-BA-konform. Zero-Tool: funktioniert ohne Build-Pipeline. Zero-Loss: bidirektional konvertierbar zu `imeta`-Tags, sobald Publishing dazukommt.
|
**Ziel:** Ein einheitliches, menschlich lesbares und maschinell parsbares Attributions-Format für Bilder in Nostr-Langform-Beiträgen. TULLU-BA-konform. Zero-Tool: funktioniert ohne Build-Pipeline. Zero-Loss: bidirektional konvertierbar zu `imeta`-Tags, sobald Publishing dazukommt.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue