Compare commits

...

2 Commits

Author SHA1 Message Date
Jörg Lohrer 40785df346 docs: cutover dokumentiert, option A (repo/nostr-konflikt) priorisiert
- README: aktueller Live-Stand (SPA auf joerg-lohrer.de), CC0-Lizenz
- STATUS: Cutover 2026-04-18 erfasst, Staging/Prod beide auf joerglohrer26/
- HANDOFF: Option A als priorisierter nächster Schritt — 9 verwaiste
  Nostr-Events mit problematischen d-tags (Emojis, Doppelpunkte, Umlaute,
  leerer d-tag) brauchen Markdown-Reimport + saubere Slugs + NIP-09-Cleanup

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:27:53 +02:00
Jörg Lohrer 54eb0b62cb feat: cc0-badge im footer + impressum auf cc0 umstellen
Footer zeigt jetzt CC0-Badge (Heart+Zero inline SVG, monochrom via
currentColor) statt "© Jörg Lohrer". Impressum entsprechend von
CC BY-SA auf CC0 umgestellt, mit freundlichem Hinweis, dass
Namensnennung erwünscht, aber nicht rechtlich erforderlich ist.

"Nostr-basiert" im Footer jetzt Link zum GitHub-Repo (Making-of).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:19:21 +02:00
6 changed files with 315 additions and 171 deletions

View File

@ -1,46 +1,97 @@
# joerg-lohrer.de
Persönliche Webseite. In Transition von einer Hugo-basierten, statischen Seite
hin zu einer SvelteKit-SPA, die Blog-Posts live aus signierten Nostr-Events
(NIP-23, `kind:30023`) rendert.
Persönliche Webseite. Nach einer Transition von einer Hugo-basierten,
statischen Seite läuft `joerg-lohrer.de` jetzt als SvelteKit-SPA, die
Blog-Posts live aus signierten Nostr-Events (NIP-23, `kind:30023`) rendert.
## Aktueller Stand
- **`https://joerg-lohrer.de/`** — Hugo-Seite, läuft noch.
- **`https://spa.joerg-lohrer.de/`** — Vanilla-HTML-Mini-Spike (Proof of Concept).
- **`https://svelte.joerg-lohrer.de/`** — produktive SvelteKit-SPA (Ziel).
- **`https://joerg-lohrer.de/`** — SvelteKit-SPA, Cutover am 2026-04-18 erfolgt.
- **`https://staging.joerg-lohrer.de/`** — Staging (gleicher Build, ein Schritt vor Prod).
- **`https://svelte.joerg-lohrer.de/`** — Entwicklungs-Deploy-Target der Pipeline.
- **`https://spa.joerg-lohrer.de/`** — Vanilla-HTML-Mini-Spike (Proof of Concept, historisch).
Detailliert in [`docs/STATUS.md`](docs/STATUS.md).
## Wie die Seite funktioniert
1. **Inhalte** liegen als Markdown in `content/posts/<slug>/index.md` mit
strukturierten Bild-Metadaten im Frontmatter (Alt-Text, Lizenz, Autor:innen).
2. **Publish-Pipeline** (`publish/`, Deno) lädt Bilder auf Blossom-Server
(content-addressed) und publiziert signierte `kind:30023`-Events via
NIP-46-Bunker (Amber) auf 5 Relays.
3. **SvelteKit-SPA** (`app/`) lädt diese Events zur Laufzeit und rendert
Post-Liste + Detailseiten. Keine Server-Komponente, Static-Hosting reicht.
4. **CI**: GitHub Actions triggert die Publish-Pipeline bei Push auf `main`
(via Forgejo→GitHub Push-Mirror).
Identität und Assets:
- **Pubkey:** `npub1f7jar3qnu269uyx5p0e4v24hqxjnxysxudvujza2ur5ehltvdeqsly2fx9`
- **NIP-05:** `joerglohrer@joerg-lohrer.de` (statisches `.well-known/nostr.json`)
- **Blossom-Server:** `blossom.edufeed.org`, `blossom.primal.net`
- **Relays:** `relay.damus.io`, `nos.lol`, `relay.primal.net`, `relay.tchncs.de`, `relay.edufeed.org`
## Navigation
- 📍 **Stand und Live-URLs:** [`docs/STATUS.md`](docs/STATUS.md)
- 🔜 **Wie es weitergeht:** [`docs/HANDOFF.md`](docs/HANDOFF.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)
- 📐 **Bild-Metadaten-Konvention:** [`docs/superpowers/specs/2026-04-16-image-metadata-convention.md`](docs/superpowers/specs/2026-04-16-image-metadata-convention.md)
- 🛠 **SvelteKit-SPA-Plan:** [`docs/superpowers/plans/2026-04-15-spa-sveltekit.md`](docs/superpowers/plans/2026-04-15-spa-sveltekit.md) (35 Tasks, abgeschlossen)
- 🛠 **Publish-Pipeline-Plan:** [`docs/superpowers/plans/2026-04-16-publish-pipeline.md`](docs/superpowers/plans/2026-04-16-publish-pipeline.md) (24 Tasks, abgeschlossen)
- 🤖 **Claude-Workflow-Skill:** [`.claude/skills/joerglohrerde-workflow.md`](.claude/skills/joerglohrerde-workflow.md)
## Branches
- **`main`** — kanonisch (Content, Specs, Pläne, Deploy-Scripts, Skill).
- **`spa`** — aktueller Arbeitszweig mit allen SvelteKit-Commits. Wird beim
Cutover nach `main` gemerged.
- **`main`** — kanonisch. Seit Cutover (2026-04-18) Produktions-Quelle.
- **`spa`** — historischer SvelteKit-Arbeitszweig, inzwischen gemerged.
- **`hugo-archive`** — eingefrorener Hugo-Zustand als Orphan-Branch.
Rollback über `git checkout hugo-archive && hugo build`.
Rollback-Option über `git checkout hugo-archive && hugo build`.
## Repo-Struktur
```
content/posts/ Markdown-Posts (Quelle für Nostr-Events)
app/ SvelteKit-SPA (Ziel-Implementation)
preview/spa-mini/ Vanilla-HTML-Mini-Spike (Referenz)
scripts/deploy-svelte.sh FTPS-Deploy nach svelte.joerg-lohrer.de
static/ Site-Assets (Favicons, Profilbild)
docs/ Specs, Pläne, Status, Handoff
.claude/ Claude-Code-Sessions (transparenz) + Skills
content/posts/ Markdown-Posts (Quelle für Nostr-Events, 18 Stück)
content/impressum.md Statisches Impressum (wird von SPA geladen)
app/ SvelteKit-SPA (Laufzeit-Renderer)
publish/ Deno-Publish-Pipeline (Blossom + Nostr)
preview/spa-mini/ Vanilla-HTML-Mini-Spike (historische Referenz)
scripts/deploy-svelte.sh FTPS-Deploy, Targets: svelte/staging/prod
static/ Site-Assets (Favicons, Profilbild, .well-known/)
docs/ Specs, Pläne, Status, Handoff, Wiki-Entwürfe
.github/workflows/ GitHub-Actions CI (Publish-Pipeline-Trigger)
.claude/ Claude-Code-Sessions (Transparenz) + Skills
```
## Entwicklung
```sh
# SPA lokal
cd app && npm run dev
# SPA testen
cd app && npm run test:unit
cd app && npm run test:e2e
cd app && npm run check
# Publish-Pipeline
cd publish && deno task check # pre-flight
cd publish && deno task publish --dry-run # Simulation
cd publish && deno task publish # diff-modus echt
cd publish && deno task publish --post <slug> # ein Post
cd publish && deno task test # Tests
# Deploy
DEPLOY_TARGET=staging ./scripts/deploy-svelte.sh
DEPLOY_TARGET=prod ./scripts/deploy-svelte.sh
```
## Lizenz
Siehe [LICENSE](LICENSE).
Inhalte: [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/deed.de)
(Namensnennung erwünscht, aber rechtlich nicht erforderlich), sofern nicht
anders vermerkt. Drittinhalte sind beim jeweiligen Bild mit Autor:innen und
Lizenz gekennzeichnet.
Code: siehe [LICENSE](LICENSE).

View File

@ -0,0 +1,47 @@
<script lang="ts">
// CC-Zero-Badge: kombination aus CC-Heart + Zero-Logo, monochrom via
// currentColor. Icons aus dem offiziellen CC-Press-Kit
// (creativecommons.org/mission/branding/). Inline hier, weil statische
// svg-imports mit ?raw in vite problematisch sind.
</script>
<span class="cc-badge" aria-hidden="true">
<!-- CC-Heart (vereinfachtes herz aus dem offiziellen logo) -->
<svg viewBox="0 0 46296 40689" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="M23204.91 7530.98c2944.63,-3188.84 6384.04,-4639.01 10366.38,-4077.21 4110.34,579.88 7609.97,3518.41 8854.17,7479.01 957.39,3047.58 559.96,6460.83 -722.09,9573.35 -1993.98,4840.97 -7886.31,11722.09 -18555.24,16532.85 -10668.92,-4810.76 -16561.25,-11691.88 -18555.24,-16532.85 -1282.05,-3112.52 -1679.47,-6525.77 -722.09,-9573.35 1244.19,-3960.6 4743.83,-6899.13 8854.17,-7479.01 3982.46,-561.82 7421.94,888.46 10366.64,4077.48 5.4,5.84 56.52,61.37 56.53,61.36 0.04,0.04 51.9,-56.36 56.79,-61.63zm-56.79 -4522.44c-6431.69,-5048.01 -16512.25,-3730.83 -21147.65,3855.94 -1539.08,2519.03 -2117.14,5447.75 -1981.3,8355.45 235.64,5043.59 2412.75,9452.27 5610.61,13256.78 4306.02,5122.9 10531.26,9148.59 17382.21,12152.72 9.53,4.18 88.63,38.56 136.13,59.69 41.66,-17.53 114.6,-50.41 137.01,-60.3 6815.65,-3004.07 13075.56,-7030.12 17381.33,-12152.12 3198.08,-3804.33 5374.97,-8213.2 5610.61,-13256.78 135.85,-2907.7 -442.2,-5836.43 -1981.3,-8355.45 -4635.4,-7586.77 -14715.95,-8903.95 -21147.65,-3855.94z"
/>
<path
fill="currentColor"
d="M22983.64 21630.19l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.85,-2300.18 -2239.67,-3843.76 -87.17,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.75,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.13,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.43,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12zm13802.46 0l-2928.01 -1451.38c-1017.73,1483.99 -1758.21,2488.33 -3897.08,1902.25 -1678.91,-460.05 -2175.86,-2300.18 -2239.67,-3843.76 -87.18,-2108.39 649.94,-4543.46 3168.15,-4413.24 1609.13,83.19 2294.74,1032.23 2661.15,1885.36l3196.99 -1638.9c-1574.75,-3004.31 -5265.14,-4026.05 -8393.32,-3188.81 -3328.66,890.9 -5014.61,3952.95 -4955.5,7255.23 60.42,3375.58 1680.8,6291.51 5161.55,7052.54 1697.16,371.06 3545.13,284.81 5116.74,-503.18 1216.27,-609.83 2567.56,-1786.86 3109,-3056.12z"
/>
</svg>
<!-- CC-Zero (kreis + 0 aus dem cc-0-logo) -->
<svg viewBox="-0.5 0.5 64 64" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
d="M31.5,14.08c-10.565,0-13.222,9.969-13.222,18.42c0,8.452,2.656,18.42,13.222,18.42c10.564,0,13.221-9.968,13.221-18.42C44.721,24.049,42.064,14.08,31.5,14.08z M31.5,21.026c0.429,0,0.82,0.066,1.188,0.157c0.761,0.656,1.133,1.561,0.403,2.823l-7.036,12.93c-0.216-1.636-0.247-3.24-0.247-4.437C25.808,28.777,26.066,21.026,31.5,21.026z M36.766,26.987c0.373,1.984,0.426,4.056,0.426,5.513c0,3.723-0.258,11.475-5.69,11.475c-0.428,0-0.822-0.045-1.188-0.136c-0.07-0.021-0.134-0.043-0.202-0.067c-0.112-0.032-0.23-0.068-0.336-0.11c-1.21-0.515-1.972-1.446-0.874-3.093L36.766,26.987z"
/>
<path
fill="currentColor"
d="M31.433,0.5c-8.877,0-16.359,3.09-22.454,9.3c-3.087,3.087-5.443,6.607-7.082,10.532C0.297,24.219-0.5,28.271-0.5,32.5c0,4.268,0.797,8.32,2.397,12.168c1.6,3.85,3.921,7.312,6.969,10.396c3.085,3.049,6.549,5.399,10.398,7.037c3.886,1.602,7.939,2.398,12.169,2.398c4.229,0,8.34-0.826,12.303-2.465c3.962-1.639,7.496-3.994,10.621-7.081c3.011-2.933,5.289-6.297,6.812-10.106C62.73,41,63.5,36.883,63.5,32.5c0-4.343-0.77-8.454-2.33-12.303c-1.562-3.885-3.848-7.32-6.857-10.33C48.025,3.619,40.385,0.5,31.433,0.5z M31.567,6.259c7.238,0,13.412,2.566,18.554,7.709c2.477,2.477,4.375,5.31,5.67,8.471c1.296,3.162,1.949,6.518,1.949,10.061c0,7.354-2.516,13.454-7.506,18.33c-2.592,2.516-5.502,4.447-8.74,5.781c-3.2,1.334-6.498,1.994-9.927,1.994c-3.468,0-6.788-0.653-9.949-1.948c-3.163-1.334-6.001-3.238-8.516-5.716c-2.515-2.514-4.455-5.353-5.826-8.516c-1.333-3.199-2.017-6.498-2.017-9.927c0-3.467,0.684-6.787,2.017-9.949c1.371-3.2,3.312-6.074,5.826-8.628C18.092,8.818,24.252,6.259,31.567,6.259z"
/>
</svg>
</span>
<style>
.cc-badge {
display: inline-flex;
align-items: center;
gap: 0.15em;
color: var(--accent);
vertical-align: -0.2em;
}
.cc-badge svg {
width: 1.1em;
height: 1.1em;
display: block;
}
</style>

View File

@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { page } from '$app/state';
import { bootstrapReadRelays } from '$lib/stores/readRelays';
import CcZeroBadge from '$lib/components/CcZeroBadge.svelte';
let { children } = $props();
@ -37,11 +38,28 @@
<footer class="site-footer">
<div class="footer-inner">
<span class="footer-copy">© Jörg Lohrer</span>
<span class="footer-license">
<a
href="https://creativecommons.org/publicdomain/zero/1.0/deed.de"
target="_blank"
rel="license noopener"
aria-label="CC0 1.0 Universal Public Domain Dedication"
title="CC0 1.0 Universal"
>
<CcZeroBadge />
<span class="cc-label">CC0</span>
</a>
Jörg Lohrer
</span>
<span class="footer-sep">·</span>
<a href="/impressum/">Impressum</a>
<span class="footer-sep">·</span>
<span class="footer-meta">Nostr-basiert</span>
<a
href="https://github.com/joerglohrer/joerglohrerde"
target="_blank"
rel="noopener"
title="Quellcode, Making-of und Nostr-Publish-Pipeline"
>Nostr-basiert Making-of im Repo</a>
</div>
</footer>
@ -129,7 +147,17 @@
.footer-sep {
opacity: 0.5;
}
.footer-meta {
opacity: 0.7;
.footer-license a {
color: var(--accent);
display: inline-flex;
align-items: center;
gap: 0.25em;
text-decoration: none;
}
.footer-license a:hover .cc-label {
text-decoration: underline;
}
.cc-label {
font-weight: 600;
}
</style>

View File

@ -17,7 +17,7 @@ Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Rich
Mein Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen Einfluss habe. Deshalb kann ich für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen.
### Urheberrecht
Die durch den Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen jedoch nicht der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite sind sowohl für den privaten, als auch für den kommerziellen Gebrauch unter Namensnennung und der Creative Commons Lizenz [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/deed.de) gestattet. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Solltest Du trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitten ich um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Inhalte umgehend entfernen.
Die durch den Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Soweit nicht anders angegeben, stelle ich eigene Inhalte und Werke unter der Creative-Commons-Lizenz [CC0 1.0 Universal (Public Domain Dedication)](https://creativecommons.org/publicdomain/zero/1.0/deed.de) zur Verfügung — sie dürfen ohne Rückfrage für jeden Zweck, auch kommerziell, kopiert, bearbeitet, verbreitet und weiterverwendet werden. Eine Namensnennung ist rechtlich nicht erforderlich, aber ich freue mich natürlich, wenn Du mich als Quelle nennst. Wo eine abweichende Lizenz gilt, ist sie beim jeweiligen Inhalt vermerkt. Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden, werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte Dritter als solche gekennzeichnet. Solltest Du trotzdem auf eine Urheberrechtsverletzung aufmerksam werden, bitte ich um einen entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Inhalte umgehend entfernen.
### Datenschutz
Die Nutzung meiner Webseite ist in der Regel ohne Angabe personenbezogener Daten möglich. Soweit auf meiner Seiten personenbezogene Daten (beispielsweise Name, Anschrift oder eMail-Adressen) erhoben werden, erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten werden ohne Deine ausdrückliche Zustimmung nicht an Dritte weitergegeben. Ich weise darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich. Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten durch Dritte zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit ausdrücklich widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-Mails, vor.

View File

@ -5,87 +5,87 @@ Dieses Dokument sagt: was ist der Zustand, was wartet, wo liegen die Fäden.
## Zustand (Details in `STATUS.md`)
**Die Nostr-Publish-Pipeline ist live.** Alle 18 Posts sind publiziert als
`kind:30023`-Events auf 5 Relays, 91 Bilder auf 2 Blossom-Servern. Die
SvelteKit-SPA unter `svelte.joerg-lohrer.de` rendert alles ordentlich.
**Cutover am 2026-04-18 abgeschlossen.** `joerg-lohrer.de` läuft als
SvelteKit-SPA, rendert 18 Nostr-Langform-Posts live aus 5 Relays, Bilder
auf 2 Blossom-Servern. Hugo-Altbestand liegt als `hugo-archive`-Branch
eingefroren.
**Das inhaltliche Kernziel des Gesamtprojekts ist damit erreicht.**
Der Rest sind Feinschliff-Aufgaben.
Der Rest sind Feinschliff- und Cutover-Aufgaben.
## Was als Nächstes ansteht (priorisiert)
## Was als Nächstes ansteht
### Option A — Repo/Nostr-Konflikt-Management (priorisiert)
### Option 1 — CI-Push-Auto-Trigger verifizieren (optional)
**Warum jetzt:** Es gibt **9 Langform-Events auf Nostr, die keine
Markdown-Entsprechung im Repo haben** — alle via Client (Habla / Yakihonne)
direkt auf Nostr erstellt, zum Teil mit problematischen d-tags (Emojis,
Doppelpunkte, Umlaute, Trailing-Dashes, oder leer).
**Status:** Workflow-Manual-Trigger ist grün (Run #1 am 18.04.2026).
Automatischer Auto-Trigger bei Content-Push noch nicht ausprobiert —
kann jederzeit beiläufig mitgenommen werden:
**Liste der verwaisten Nostr-Events** (d-tag → event-id-Prefix):
1. Minimaler Edit in einem Post (z. B. Typo) → commit → push `main`
2. Forgejo synct automatisch zu GitHub → Workflow triggert → 1 Post als
`update` publiziert
3. Log-Artefakt in GitHub Actions prüfen
| d-tag | event-id | Probleme |
|---|---|---|
| `richter-oder-rcher-banksy-als-moderner-prophet-vor-dem-high-court` | `bb2c2cea…` | Umlaut-Abdecker (`Rächer` → `rcher`) |
| `die-kraft-der-gemeinschaft-wahre-strke-liegt-nicht-in-strukturen-sondern-in-prozessen` | `27d7fbee…` | Umlaut-Abdecker (`Stärke` → `strke`) |
| `nostr-und-open-educational-practices-oep-` | `0baa3615…` | Trailing-Dash, unschön |
| `religionsbezogene-bildung-mit-rollenkarten:-ki-bilder-als-impulsgeber` | `3ac719ca…` | `:` ungültig für URL-Slug |
| `📢-empowering-learners-for-the-age-of-ai--der-neue-review-draft-des-ai-literacy-frameworks-für-schule-ist-da!` | `3c005996…` | Emoji + `!` + Umlaute + extrem lang |
| `🟠-prompts-für-die-religionsbezogene-bildung-posten-und-diskutieren-auf-nostr` | `f726fcd5…` | Emoji + Umlaute |
| `ki-mitmachen` | `a1368d2e…` | sauber, aber fehlt im Repo |
| `bibel-selfies` | `00cbe5f3…` | Langform-Version; Duplikat mit Unix-Timestamp wurde bereits gelöscht |
| `""` (leer!) | `d75857dc…` | leerer d-tag — SPA-kritisch, Event hat keinen Slug |
Kein Ziel mehr, nur Bestätigung. Pipeline ist funktional vollständig.
**Zielbild:** Repo ist die Quelle der Wahrheit. Jeder Post existiert als
Markdown mit sauberem Slug. d-tags sind URL-freundlich (ASCII, keine
Sonderzeichen außer `-`).
### Option 2 — Cutover auf `joerg-lohrer.de`
**Empfohlener Flow (Reihenfolge nicht tauschen!):**
**Voraussetzung:** Option 1 optional, aber nicht blockierend. Die Pipeline
läuft ja schon, ob manuell oder via CI ist für den Cutover egal.
1. **Content exportieren.** Pro Event: `nak req -i <event-id> <relay>`
JSON mit Content + Tags. Manuell in neue Markdown-Datei umwandeln
(`content/posts/<YYYY-MM-DD>-<saubererslug>/index.md`). Titel,
published_at, ggf. summary und bestehende image-Tags übernehmen.
Bilder nach `images/` kopieren (falls noch erreichbar), sonst
Blossom-URLs im Markdown belassen und beim Publish neu hashen.
2. **Slugs bereinigen.** Neue, saubere, ASCII-only d-tags wählen. Doku
in `docs/redaktion-bild-metadaten.md` oder einem neuen
`docs/nostr-reimport-mapping.md` festhalten (alter d-tag → neuer slug).
3. **Neu publizieren.** `deno task publish --post <slug>` pro Datei.
Pipeline hasht Bilder zu Blossom, signiert mit stabiler Identität.
4. **Alte Events löschen** via NIP-09 (`kind:5`). Heute noch manuell per
`nak event -k 5 -t e=<old-event-id>`, siehe Option D. Oder: erst
Option D bauen, dann diesen Schritt per Pipeline-Subcommand.
5. **Verifikation.** Post-Count pro Relay checken, SPA-Post-Liste
visuell prüfen.
**Schritte:**
1. In All-Inkl KAS die Domain `joerg-lohrer.de` auf den SvelteKit-Webroot
umhängen (aktuell: `svelte.joerg-lohrer.de``/www/htdocs/v109928/joerglohrer28/`
oder welcher Ordner auch immer).
2. SvelteKit-SPA deployen, sofern sie nicht schon dort liegt.
3. Live-Check: `curl -sI https://joerg-lohrer.de/` → sollte die neue SPA
liefern, nicht mehr Hugo.
**Edge Cases:**
- Das leere-d-tag-Event (`d75857dc…`): wenn es noch sinnvoller Content
ist, als neuer Post re-importieren. Sonst einfach löschen.
- Bilder in alten Events zeigen auf externe Server (nicht Blossom). Beim
Re-Publish lädt die Pipeline sie herunter und hasht sie neu. Wenn die
Quelle tot ist, muss das Bild manuell beschafft oder der Post mit
Platzhalter markiert werden.
- `relay.damus.io` liefert mehr Events als andere Relays — bei
Nicht-Auffindbarkeit auf anderen Relays trotzdem löschen, damus.io
respektiert den NIP-09.
Hugo-Altbestand bleibt als Archiv im `hugo-archive`-Branch.
### Option 3 — Startseite + Menü-Navigation + Impressum in der SPA
**Unabhängig von Cutover**, aber Voraussetzung für diesen: ohne Design wäre
die Hauptdomain eine rohe Post-Liste.
- **Startseite** bekommt ein eigenes Design (nicht nur Post-Liste dump)
- **Menü-Navigation** in `app/src/routes/+layout.svelte` (Home, Archiv,
Impressum, Mastodon-Link)
- **Impressum** als Static-Page (SvelteKit-Route `/impressum/`), **nicht**
als Nostr-Event — soll nicht als Blog-Beitrag in Listen erscheinen.
Text ist bereits im `content/`-Ordner (Repo-Quelle); die einzige
rechtlich relevante HTML-Datei, die auf dem Server liegt.
### Option 4 — SPA respektiert `kind:5`-Deletion-Events
### Option B — SPA respektiert NIP-09-Deletion-Events
**Status:** aktuell filtert die SPA nicht nach NIP-09. Wenn ein Event per
`kind:5`-Referenz gelöscht wurde (z. B. `7f5d08b8…` deletet `89609df5…`
für `d=1744905463975` am 18.04.), zeigen Relays es meist nicht mehr aus —
`kind:5`-Referenz gelöscht wurde, zeigen Relays es meist nicht mehr aus —
aber die SPA würde es trotzdem rendern, falls ein Relay es doch liefert.
**Zu tun:** im `kind:30023`-Loader (`app/src/lib/nostr/...`) einen
**Zu tun:** im `kind:30023`-Loader (`app/src/lib/nostr/…`) einen
Cross-Check auf `kind:5`-Events einbauen. Events, deren Addressable-Pointer
(`30023:pubkey:d-tag`) in einem `kind:5` referenziert ist, werden
gefiltert. Defensive Maßnahme für zukünftige Duplikate / Soft-Deletes.
### Option 5 — Repo/Nostr-Konflikt-Management
### Option C — Postfach `webmaster@joerg-lohrer.de`
**Warum:** aktuell ist die Pipeline eine einseitige Straße — Repo → Nostr.
Wenn du via Client (Habla, Yakihonne, Amber) auf Nostr editierst,
überschreibt der nächste Pipeline-Lauf deine Client-Edits mit dem (alten)
Repo-State. Das ist ein echter Datenverlust-Risikofaktor.
User-Task: im All-Inkl KAS als Weiterleitung anlegen. Der Link im
Footer und in den Social-Icons zeigt bereits darauf.
**Zu tun:**
- **Defensiv (`created_at`-Check):** Pipeline liest vor Publish das
aktuelle Event vom Relay und vergleicht `created_at`. Wenn das
Remote-Event neuer ist: Abbruch mit Warnung.
- **Reverse-Sync (`pull-from-nostr`-Subcommand):** liest Events, vergleicht
mit Repo, zeigt Diffs. Manuelle Konfliktauflösung.
Keine Eile, solange du nicht parallel editierst. Erst relevant, wenn du
dich an Habla & Co. gewöhnst.
### Option 6 — NIP-09-Delete als Pipeline-Subcommand
### Option D — NIP-09-Delete als Pipeline-Subcommand
**Status:** heute einmalig per `nak event -k 5 …` mit neu erzeugter Bunker-
URL erledigt (Duplikat `1744905463975`). Das war ein Workaround um das
@ -100,9 +100,10 @@ deno task publish-delete --slug <slug>
deno task publish-delete --event-id <hex>
```
Jetzt nicht dringend — nur bauen, wenn der Fall öfter eintritt.
**Sinnvollerweise mit Option A kombinieren** — in der Re-Import-Kampagne
werden 9 NIP-09 hintereinander gebraucht.
### Option 7 — Pipeline weg von GitHub (self-hosted CI)
### Option E — Pipeline weg von GitHub (self-hosted CI)
**Wann:** Wenn der Optiplex-Server steht und ein zentraler Ort für Dienste
existiert.
@ -116,6 +117,17 @@ existiert.
Der Pipeline-Code selbst (`publish/src/**`) ist CI-agnostisch — nur die
Trigger-Konfiguration ändert sich.
### Option F — Design-Refinements
**Wann:** irgendwann, wenn Lust drauf ist.
- Parallax-Effekte, Animations
- Dark-Mode-Feinschliff (aktuell `prefers-color-scheme`, könnte Toggle bekommen)
- Typografie-Experimente (Variable Fonts?)
- Bildergalerie-Komponente für Posts mit vielen Bildern
Alles nicht-blockierend, die SPA funktioniert solide.
## Schnell-Orientierung für die nächste Claude-Session
Lies in dieser Reihenfolge:
@ -134,7 +146,8 @@ cd app && npm run check
cd app && npm run dev
# SPA-Build + Deploy
cd app && npm run build && cd .. && ./scripts/deploy-svelte.sh
DEPLOY_TARGET=staging ./scripts/deploy-svelte.sh
DEPLOY_TARGET=prod ./scripts/deploy-svelte.sh
# Publish-Pipeline
cd publish && deno task check # pre-flight
@ -142,7 +155,7 @@ cd publish && deno task publish --dry-run # diff-modus simulation
cd publish && deno task publish # diff-modus echt
cd publish && deno task publish --force-all # alle posts
cd publish && deno task publish --post <slug> # einen post
cd publish && deno task test # 59 tests
cd publish && deno task test # tests
```
## Bekannte Stolperfallen
@ -161,6 +174,9 @@ cd publish && deno task test # 59 tests
- **Hugo-quotierte Dates:** `date: "2023-02-26"` ist ein YAML-String, nicht
ein Date-Objekt. `validatePost` coerced das automatisch; in neuen Posts
am besten ohne Quotes schreiben.
- **Deploy-Targets:** `svelte` → Entwicklung, `staging` → Pre-Prod,
`prod``joerglohrer26/` (Produktion seit Cutover). Script parst
`.env.local` per awk (wegen Sonderzeichen in FTP-Passwörtern).
## Offene UNKNOWN-Einträge zur späteren Recherche
@ -177,10 +193,14 @@ Pipeline loggt Warnungen, publisht aber trotzdem. Recherche-Notizen in
## Session-Kontext
Hilfreich beim Wiedereinstieg mit Claude:
- Branch-Check: `git log --oneline -10 spa main`
- Live-Check SPA: `curl -sI https://svelte.joerg-lohrer.de/`
- Event-Count: `nak req -k 30023 -a 4fa5d1c413e2b45e10d40bf3562ab701a5331206e359c90baae0e99bfd6c6e41 wss://relay.primal.net 2>/dev/null | jq -s 'length'` → 18
- Pipeline-Tests: `cd publish && deno task test` → 59 grün
- Branch-Check: `git log --oneline -10 main`
- Live-Check: `curl -sI https://joerg-lohrer.de/`
- Event-Count Repo vs. Relays:
```sh
ls content/posts/ | wc -l
nak req -k 30023 -a 4fa5d1c413e2b45e10d40bf3562ab701a5331206e359c90baae0e99bfd6c6e41 wss://relay.edufeed.org 2>/dev/null | jq -s 'length'
```
- Pipeline-Tests: `cd publish && deno task test`
## Community-Wiki-Entwürfe

View File

@ -1,63 +1,63 @@
# Projekt-Status: joerg-lohrer.de → Nostr-basierte SPA
**Stand:** 2026-04-18
**Stand:** 2026-04-18 (Cutover abgeschlossen)
## Kurzfassung
Jörg Lohrers persönliche Webseite wird von einem Hugo-basierten statischen
Site-Generator zu einer dezentralen Nostr-basierten SPA überführt. Posts
existieren als signierte Events (NIP-23, `kind:30023`) auf Public-Relays und
werden zur Laufzeit im Browser gerendert.
`joerg-lohrer.de` läuft als SvelteKit-SPA, die Blog-Posts live aus
signierten Nostr-Events (NIP-23, `kind:30023`) auf 5 Public-Relays rendert.
Bilder liegen content-addressed auf 2 Blossom-Servern. Die Hugo-basierte
Altseite ist als `hugo-archive`-Branch eingefroren.
## Vier parallele Webseiten
**Das inhaltliche Kernziel des Gesamtprojekts ist erreicht.**
## Live-URLs
| URL | Status | Rolle |
|---|---|---|
| `https://joerg-lohrer.de/` | live, unverändert | **Hugo-Altbestand** (bleibt bis Cutover) |
| `https://spa.joerg-lohrer.de/` | live | **Vanilla-HTML-Mini-Spike** (Proof of Concept) |
| `https://svelte.joerg-lohrer.de/` | live | **SvelteKit-SPA** (35-Task-Plan komplett) |
| `https://staging.joerg-lohrer.de/` | live, leer | **Staging** (Webroot `joerglohrer26/` für Pipeline-Entwicklung; FTP-Creds in `.env.local`) |
Die SvelteKit-SPA unter `svelte.joerg-lohrer.de` ist die Ziel-Implementierung.
`spa.joerg-lohrer.de` bleibt als schlanke Referenz erhalten. Hugo läuft weiter,
bis die Publish-Pipeline steht und der Cutover auf die Hauptdomain erfolgt.
| `https://joerg-lohrer.de/` | live | **Produktion**, SvelteKit-SPA (Cutover 2026-04-18) |
| `https://staging.joerg-lohrer.de/` | live | **Staging**, letzter Pre-Prod-Build |
| `https://svelte.joerg-lohrer.de/` | live | **Entwicklung**, Deploy-Target der Pipeline |
| `https://spa.joerg-lohrer.de/` | live | **Historisch**, Vanilla-HTML-Mini-Spike |
## Was auf Nostr liegt
- **Autoren-Pubkey:** `npub1f7jar3qnu269uyx5p0e4v24hqxjnxysxudvujza2ur5ehltvdeqsly2fx9`
(hex: `4fa5d1c413e2b45e10d40bf3562ab701a5331206e359c90baae0e99bfd6c6e41`)
- **Publizierte Events:** **18 Langform-Posts** (`kind:30023`) — alle Altposts
via Publish-Pipeline migriert (Commit `0c6fdd1`, Log in
`docs/publish-logs/2026-04-18-force-all-migration.json`).
- **NIP-05:** `joerglohrer@joerg-lohrer.de` (via `/.well-known/nostr.json`)
- **Publizierte Events:** **18 Langform-Posts** (`kind:30023`) aus dem Repo,
plus **9 nicht-Repo-Events** (via Client wie Habla/Yakihonne erstellt,
siehe HANDOFF → Option 5 für Konflikt-Management-Plan).
- **Relay-Liste** (`kind:10002`): `relay.damus.io`, `nos.lol`,
`relay.primal.net`, `relay.tchncs.de`, `relay.edufeed.org`
- **Blossom-Server** (`kind:10063`): `blossom.edufeed.org`, `blossom.primal.net`
**91 Bilder** auf beiden Blossom-Servern. Alle Events enthalten hash-basierte
Blossom-URLs. SPA rendert alle Posts einheitlich — kein Legacy-Pfad, keine
rsync-Artefakte.
- **91 Bilder** auf beiden Blossom-Servern, alle Events enthalten
hash-basierte Blossom-URLs.
## Repo-Struktur
```
joerglohrerde/
├── content/posts/ # 18 Markdown-Posts, alle mit structured images: im Frontmatter
├── app/ # SvelteKit-SPA (Ziel-Implementation)
├── preview/spa-mini/ # Vanilla-HTML-Mini-Spike (Referenz)
├── publish/ # NOCH NICHT ANGELEGT — Publish-Pipeline (Task 1 aus Plan)
├── content/posts/ # 18 Markdown-Posts, alle mit strukturierten images:
├── content/impressum.md # Statisches Impressum (wird von SPA geladen)
├── app/ # SvelteKit-SPA (Laufzeit-Renderer)
├── publish/ # Deno-Publish-Pipeline (Blossom + Nostr)
├── preview/spa-mini/ # Vanilla-HTML-Mini-Spike (historisch)
├── scripts/
│ └── deploy-svelte.sh # FTPS-Deploy nach svelte.joerg-lohrer.de
│ └── deploy-svelte.sh # FTPS-Deploy, Targets: svelte/staging/prod
├── docs/
│ ├── STATUS.md # Dieses Dokument
│ ├── HANDOFF.md # Wie man hier weitermacht
│ ├── redaktion-bild-metadaten.md # Checkliste, Bild-Durchgang (abgearbeitet)
│ ├── wiki-entwurf-nostr-bild-metadaten.md # Wiki-Konvention deutsch
│ ├── wiki-draft-nostr-image-metadata.md # Wiki-Konvention englisch
│ ├── redaktion-bild-metadaten.md
│ ├── wiki-entwurf-nostr-bild-metadaten.md
│ ├── wiki-draft-nostr-image-metadata.md
│ ├── github-ci-setup.md
│ └── superpowers/
│ ├── specs/ # SPA + Publish-Pipeline + Bild-Metadaten-Konvention
│ └── plans/
│ ├── 2026-04-15-spa-sveltekit.md # erledigt
│ └── 2026-04-16-publish-pipeline.md # ⬅ als nächstes
│ └── 2026-04-16-publish-pipeline.md # erledigt
├── .github/workflows/ # publish.yml (Forgejo→GitHub Push-Mirror-Trigger)
├── .claude/
│ ├── skills/ # Repo-spezifischer Claude-Skill
│ └── settings.local.json # Claude-Session-State (gitignored)
@ -66,80 +66,78 @@ joerglohrerde/
## Branch-Layout (Git)
- **`main`** — kanonischer Zweig.
- **`spa`** — aktueller Arbeits-Branch, vor `main`. SvelteKit-SPA live,
Content-Migration (Bild-Metadaten) abgeschlossen, Publish-Pipeline geplant.
- **`main`** — kanonischer Zweig, Produktions-Quelle seit Cutover.
- **`spa`** — historischer SvelteKit-Arbeitszweig, gemerged.
- **`hugo-archive`** — Orphan-Branch mit Hugo-Zustand, eingefroren.
## Setup-Zustand
Einmalig manuell erledigt:
- ✅ Amber-Bunker-URL in `.env.local` als `BUNKER_URL`
- ✅ FTP-Creds für alle Subdomains (SPA, SVELTE, STAGING) in `.env.local`
- ✅ `AUTHOR_PUBKEY_HEX` und `BOOTSTRAP_RELAY=wss://relay.primal.net` in `.env.local`
Einmalig manuell erledigt (gitignored in `.env.local`):
- ✅ Amber-Bunker-URL als `BUNKER_URL`
- ✅ FTP-Creds für alle Targets (SVELTE/STAGING/PROD)
- ✅ `AUTHOR_PUBKEY_HEX` und `BOOTSTRAP_RELAY=wss://relay.primal.net`
- ✅ `CLIENT_SECRET_HEX` (identisch mit GitHub-Secret für stabile App-ID in Amber)
- ✅ `kind:10002`-Event publiziert (Relay-Liste)
- ✅ `kind:10063`-Event publiziert (Blossom-Server)
- ✅ Subdomains mit TLS + HSTS
- ✅ Staging-Subdomain `staging.joerg-lohrer.de` → Webroot `joerglohrer26/`
- ✅ Staging → Webroot `joerglohrer26/`
- ✅ Prod → Webroot `joerglohrer26/` (Cutover 2026-04-18)
- ✅ NIP-05-JSON mit CORS-Header via `.htaccess`
Alles in `.env.local` — gitignored, nicht committet.
## Offene Punkte (Details in HANDOFF.md)
## Offene Punkte
Nach Priorität:
1. **Repo/Nostr-Konflikt-Management** — 9 verwaiste Nostr-Events haben
keine Markdown-Entsprechung. Geplanter Flow: Markdown nachziehen →
d-tags bereinigen → neu publizieren → alte löschen (NIP-09).
2. **Postfach `webmaster@joerg-lohrer.de`** als Weiterleitung in KAS anlegen.
3. **SPA respektiert NIP-09-Deletion-Events** (defensiver kind:5-Filter).
4. **NIP-09-Delete als Pipeline-Subcommand** (heute noch `nak event -k 5`).
5. **Self-hosted CI** (Woodpecker / Cron auf Optiplex), weg von GitHub.
6. **5 UNKNOWN-Einträge** im VR-Post zur späteren Recherche.
- **End-to-End-Test CI mit echtem Content-Push** (Workflow-Manual-Trigger
ist grün; automatischer Auto-Trigger via Push-Mirror noch nicht real
ausprobiert).
- **Menü-Navigation** in der SPA (Home / Archiv / Impressum / Kontakt)
- **Impressum-Seite** (braucht rechtlichen Text)
- **Cutover auf `joerg-lohrer.de`** (Pipeline läuft, Voraussetzung erfüllt;
Hauptdomain kann auf SvelteKit-SPA umgestellt werden)
- **5 UNKNOWN-Einträge** im `virtual-reality`-Post zur späteren Recherche
(Wikipedia-Screenshot, Sketchfab-Fotograf, Ready-Player-Me, EyeMeasure-App)
- **CI-Migration** (später): weg von GitHub-Actions zu Woodpecker oder
Cron auf Optiplex — siehe `docs/github-ci-setup.md`.
## Erledigt seit 2026-04-15
## Erledigt (chronologisch seit 2026-04-15)
- ✅ Content-Migration: alle 18 Posts haben strukturierte `images:`-Liste
im Frontmatter (91 Bilder, mit Alt-Text, Lizenz, Autor:innen, ggf. Caption
und Modifications). Commit `c023b59`.
- ✅ Erlebnispädagogik-Post: tote Amazon-Hotlinks entfernt.
im Frontmatter (91 Bilder, mit Alt-Text, Lizenz, Autor:innen, ggf.
Caption und Modifications).
- ✅ Spec, Plan und Bild-Metadaten-Konvention geschrieben.
- ✅ Community-Wiki-Entwürfe (DE + EN) für Nostr-Bildattribution.
- ✅ **Publish-Pipeline komplett implementiert**, 22 Tasks aus dem Plan:
- 18 Code-Tasks (Phase 16), 59 Unit-Tests grün
- Stabile NIP-46-Anbindung via `CLIENT_SECRET_HEX` für wiederverwendbare
App-Identität in Amber
- `validatePost` akzeptiert auch string-dates (für Hugo-Kompatibilität)
- ✅ **Alle 18 Altposts publiziert** als `kind:30023`-Events (Commit `0c6fdd1`,
Log in `docs/publish-logs/2026-04-18-force-all-migration.json`).
- ✅ **Publish-Pipeline komplett implementiert** (24 Tasks, 59 Tests grün).
- ✅ **Alle 18 Altposts publiziert** als `kind:30023`-Events.
- ✅ **91 Bilder** auf beiden Blossom-Servern.
- ✅ SPA rendert alle Posts mit Bildern von Blossom (visuell verifiziert).
- ✅ **GitHub-Actions-Workflow** angelegt (`.github/workflows/publish.yml`).
- ✅ Forgejo → GitHub Push-Mirror eingerichtet, GitHub-Secrets gesetzt.
- ✅ **`spa``main` gemergt**, GitHub-Actions-Workflow manuell verifiziert
(Run #1: signer ok, outbox ok, blossom-liste ok, mode=diff posts=0).
**Alle 24 Tasks des Publish-Pipeline-Plans abgeschlossen.**
- ✅ **Staging-Deploy-Infrastruktur:** `scripts/deploy-svelte.sh` mit
drei Targets (`svelte`/`staging`/`prod`), dynamisches `og:url` und
`canonical` via `__SITE_URL__`-Platzhalter. Staging-Subdomain
`staging.joerg-lohrer.de` bedient jetzt `joerglohrer26/` (Cutover-Ziel).
- ✅ **Duplikat-Event `1744905463975` via NIP-09 gelöscht** (Delete-Event
`7f5d08b8…`, auf alle 5 Relays). Nach Migration nicht mehr auffindbar.
- ✅ **GitHub-Actions-Workflow** + Forgejo→GitHub Push-Mirror + Secrets.
- ✅ **Duplikat-Event via NIP-09 gelöscht** (`bibel-selfies` Unix-Timestamp-dup).
- ✅ **Staging-Deploy-Infrastruktur** mit `__SITE_URL__`-Templating.
- ✅ **Homepage** mit Hero, Profilbild, Social-Icons (Nostr/Mastodon/
Bluesky/LinkedIn/ORCID/Mail), Latest-Posts.
- ✅ **Archiv-Seite**, **Impressum-Seite**, Menü-Navigation im Layout.
- ✅ **CC0-Footer-Badge** (Heart+Zero inline SVG, monochrom).
- ✅ **Impressum auf CC0 umgestellt** (mit freundlichem Namensnennungs-Hinweis).
- ✅ **Cutover 2026-04-18**`joerg-lohrer.de` von Hugo (`joerglohrer24/`)
auf SvelteKit-SPA (`joerglohrer26/`) umgehängt.
## Live-Verifikation
```sh
curl -sI https://svelte.joerg-lohrer.de/ | head -3
curl -sI https://joerg-lohrer.de/ | head -3
curl -sI https://staging.joerg-lohrer.de/ | head -3
curl -s https://joerg-lohrer.de/.well-known/nostr.json | jq .
```
## Kontakt zur Implementierung
## Pipeline-Quick-Check
```sh
# Event-Count pro Relay
for r in wss://relay.damus.io wss://nos.lol wss://relay.primal.net wss://relay.tchncs.de wss://relay.edufeed.org; do
echo -n "$r: "; nak req -k 30023 -a 4fa5d1c413e2b45e10d40bf3562ab701a5331206e359c90baae0e99bfd6c6e41 $r 2>/dev/null | wc -l
done
```
## Design-Referenzen
Alle Design-Entscheidungen in:
- `docs/superpowers/specs/2026-04-15-nostr-page-design.md` (SPA)
- `docs/superpowers/specs/2026-04-15-publish-pipeline-design.md` (Publish, Blossom-only)
- `docs/superpowers/specs/2026-04-16-image-metadata-convention.md` (Bild-Metadaten-YAML)
- `docs/superpowers/plans/2026-04-16-publish-pipeline.md` (24 Tasks, als nächstes)
Für die nächste Session: **`docs/HANDOFF.md`** lesen.