Skip to content

Commit 010e086

Browse files
authored
Merge pull request #122 from edufeed-org/feature/urlshortener
Kurzlink-Feature via Kind 30491 #110
2 parents e3e1865 + ffdb1ef commit 010e086

File tree

12 files changed

+1064
-121
lines changed

12 files changed

+1064
-121
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ tailwind.log
5555
tailwindcss-*.log
5656
static/config.live.json
5757
scripts/probe*
58+
.vscode/settings.json

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Keine offenen Eintraege.
1414

1515
| Datum | Was | Details |
1616
|---|---|---|
17+
| 2026-02-21 | Kurzlink-Feature (PR #122): Dezentraler URL-Shortener via Nostr Kind 30491 (ShareDialog, /b/[slug] Route) | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
1718
| 2026-02-21 | Automatische Gespraechs-Zusammenfassung (LLM + lokaler Fallback) | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
1819
| 2026-02-21 | LLM Proxy 400-Fehler behoben (tool_choice, Umlaute, Retry) | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
1920
| 2026-02-20 | Paste-System: Editable-Target Guard | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Ein intelligentes Kanban-Board mit KI-Unterstützung und Nostr-Integration, geba
3838
- 📱 **Reaktiv:** Svelte 5 Runes für optimale Performance
3939
- 🔒 **Typsicher:** Vollständig in TypeScript implementiert
4040
- 🎨 **Demo-Boards:** Sofortiger Start für anonyme Nutzer mit vorkonfigurierten Boards
41+
- 🔗 **Kurzlinks:** Dezentraler URL-Shortener via Nostr — merkbare Board-URLs statt langer naddr-Strings
4142

4243
## 📚 Dokumentation
4344

docs/CHANGELOG/2026-Q1.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
1313
4. Betrieb und Sicherheit gehaertet (OIDC Fixes, Docker Publishing, Event-Authorisierung).
1414
5. LLM Proxy stabilisiert (tool_choice Fix, Umlaut-Bereinigung, Retry-Logik).
1515
6. Automatische Gespraechs-Zusammenfassung fuer KI-Chat eingefuehrt.
16+
7. Kurzlink-Feature: Dezentraler URL-Shortener via Nostr Kind 30491.
1617

1718
## Zeitstrahl in Clustern
1819

@@ -22,7 +23,7 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
2223
| 2026-01-25 bis 2026-01-31 | Paste/Import, OER- und AI-MCP-Verbesserungen, Inline Editing | Schnellere Inhaltserfassung und effizientere Bearbeitung |
2324
| 2026-02-01 bis 2026-02-05 | Communikey/Sharing, Mobile-Polish, Sync-Hotfix-Welle | Stabileres kollaboratives Arbeiten, bessere Mobile-Erfahrung |
2425
| 2026-02-06 bis 2026-02-20 | OIDC-Fixes, Docker CI, Fremd-Board/Permalink, Shared-Sync, Security Guards | Robustere Sessions, bessere Deploybarkeit, hoehere Datensicherheit |
25-
| 2026-02-21 | LLM Proxy Fix, Auto-Summarization, Paste Guard | Stabilere und kontextreichere KI-Interaktion |
26+
| 2026-02-21 | LLM Proxy Fix, Auto-Summarization, Paste Guard, Kurzlink-Feature | Stabilere und kontextreichere KI-Interaktion, dezentraler URL-Shortener |
2627

2728
## Wichtige Ergebnisbloecke (Versionen)
2829

@@ -34,12 +35,13 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
3435
| Shared-Board Einstieg | 4.7.29 bis 4.7.26 (01.02.2026) | Follow/Visibility/Share-Dialog stabiler |
3536
| Content-Pipelines | 4.7.25 bis 4.7.19 (26.01-31.01.2026) | Paste, OER-Suche und AI-Workflows besser integriert |
3637
| Nach 4.7.96 (Git/PR) | 06.02-20.02.2026 | Security, OIDC, Shared-Sync, Permalink/Follow weiter gehaertet |
37-
| Direct Pushes | 20.02-21.02.2026 | LLM Proxy Fix, Auto-Summarization, Paste Guard |
38+
| Direct Pushes + PR | 20.02-21.02.2026 | LLM Proxy Fix, Auto-Summarization, Paste Guard, Kurzlink (PR #122) |
3839

3940
## PR-Highlights mit direktem Nutzen
4041

4142
| Datum | PR | Thema | Nutzen |
4243
|---|---|---|---|
44+
| 2026-02-21 | [#122](https://github.com/edufeed-org/kanban-editor/pull/122) | Kurzlink-Feature (URL Shortener) | Boards via kurzer URL teilbar (`/b/slug`), QR-Codes deutlich kompakter, dezentral via Nostr Kind 30491 |
4345
| 2026-02-21 | Direct | Automatische Gespraechs-Zusammenfassung | Chat-History wird nach 6 Nachrichten per LLM zusammengefasst — besserer Kontext bei gleichem Token-Budget |
4446
| 2026-02-21 | Direct | LLM Proxy 400-Fehler behoben | `tool_choice` von `required` auf `auto`, Umlaut-Bereinigung, Retry-Logik |
4547
| 2026-02-20 | Direct | Paste-System: Editable-Target Guard erweitert | Paste-Events werden nicht mehr in Input/Textarea/TipTap abgefangen |
@@ -126,6 +128,7 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
126128
## Direkte Commits (ohne PR)
127129

128130
### 2026-02-21
131+
- **Kurzlink-Feature (PR [#122](https://github.com/edufeed-org/kanban-editor/pull/122), URL Shortener via Nostr Kind 30491):** Boards koennen ueber kurze, merkbare URLs geteilt werden (z.B. `/b/mein-projekt` statt langer naddr-String). Slug wird automatisch aus Board-Name generiert (mit Umlaut-Behandlung), ist manuell editierbar. Auto-Publish beim ersten Klick auf Copy/Open/QR. Resolver-Route `/b/[slug]` laedt Shortlink-Event von Nostr und leitet zum Board weiter. 26 Unit-Tests (inkl. Resolve-Funktionen mit NDK-Mocking). Betroffene Dateien: `nostrEvents.ts`, `kanbanStore.svelte.ts`, `ShareDialog.svelte`, `src/routes/b/[slug]/`. Feature-Doku: `docs/FEATURE/SHORTLINK.md`.
129132
- **Automatische Gespraechs-Zusammenfassung:** Chat-History wird nach 6 Nachrichten (3 Runden) automatisch per LLM zusammengefasst. Statt harter 3-Nachrichten-Grenze erhaelt das LLM [Zusammenfassung] + aktuelle Nachrichten — besserer Kontext bei gleichem Token-Budget. Lokaler Fallback bei LLM-Fehler. Nutzt `ConversationSummary` aus ChatModel.ts. Siehe `docs/ARCHITECTURE/AGENT/TOOL-BASED-AI.md` Section VIII.
130133
- **LLM Proxy 400-Fehler behoben:** `tool_choice` von `required` auf `auto` umgestellt — eliminiert systematische 400-Fehler bei 16 Tools + Chat-History. Synthetischer `respond`-Fallback fuer reine Text-Antworten. Umlaute in Tool-Definitionen/System-Prompt durch ASCII ersetzt. Retry-Logik und Budget-Fallback-Loop. Siehe `docs/ARCHITECTURE/AGENT/LLM-PROXY-INVESTIGATION.md`.
131134

@@ -159,6 +162,7 @@ git log <merge>^1..<merge>^2 --pretty=format:"%h|%ad|%s" --date=short
159162

160163
| Datum | PR | Branch | Merge Commit |
161164
|---|---|---|---|
165+
| 2026-02-21 | [#122](https://github.com/edufeed-org/kanban-editor/pull/122) | feature/urlshortener ||
162166
| 2026-02-20 | [#121](https://github.com/edufeed-org/kanban-editor/pull/121) | edufeed-org/security-fix | cec26f4 |
163167
| 2026-02-20 | [#120](https://github.com/edufeed-org/kanban-editor/pull/120) | JannikStreek/feat/oer-finder-plugin-local-search | ec92467 |
164168
| 2026-02-19 | [#119](https://github.com/edufeed-org/kanban-editor/pull/119) | edufeed-org/fix/editshared | 34944fa |

docs/FEATURE/SHORTLINK.md

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
# 🔗 Kurzlink-Feature (URL Shortener via Nostr)
2+
3+
**Version:** 1.0
4+
**Status:** ✅ COMPLETE (26 Unit-Tests)
5+
**Datum:** 21. Februar 2026
6+
**Branch:** `feature/urlshortener`
7+
8+
---
9+
10+
## 📋 Übersicht & Motivation
11+
12+
### Das Problem
13+
14+
Die vollständigen Board-URLs enthalten einen langen `naddr`-String (Nostr Addressable Identifier), z.B.:
15+
16+
```
17+
https://kanban.edufeed.org/cardsboard/naddr1qqpkucttqy28wumn8ghj7un9d3shjtn...
18+
```
19+
20+
Solche URLs sind:
21+
- ❌ Schwer mündlich zu kommunizieren
22+
- ❌ Nicht merkbar
23+
- ❌ QR-Codes werden unnötig groß und schwer scanbar
24+
- ❌ Ungeeignet für Social-Media-Posts
25+
26+
### Die Lösung
27+
28+
Dezentrale Kurzlinks über **Nostr Addressable Events (Kind 30491)** nach NIP-33.
29+
Ein kurzer Slug wird auf den vollen `naddr`-String gemappt und auf Nostr-Relays publiziert.
30+
31+
```
32+
Vorher: https://kanban.edufeed.org/cardsboard/naddr1qqpkucttqy28wumn8ghj7...
33+
Nachher: https://kanban.edufeed.org/b/mein-projekt
34+
```
35+
36+
**Vorteile:**
37+
- 🚀 Kurze, merkbare URLs
38+
- 🌐 Dezentral — kein URL-Shortener-Service nötig
39+
- 🔁 Deterministisch — Slug wird aus Board-Name generiert
40+
- ✏️ Editierbar — Nutzer kann Slug vor Publizierung anpassen
41+
- 📱 Kompakte QR-Codes
42+
43+
---
44+
45+
## 🚀 Quick Start (Benutzer-Anleitung)
46+
47+
### Kurzlink erstellen
48+
49+
1. Board öffnen → **Share-Button** (Toolbar oben rechts) klicken
50+
2. Im Dialog ist der **Kurzlink-Tab** bereits aktiv
51+
3. Ein Slug wird automatisch aus dem Board-Namen generiert (z.B. `mein-projekt`)
52+
4. Optional: Slug im Eingabefeld bearbeiten
53+
5. Auf **Kopieren**, **Öffnen** oder **QR-Code** klicken
54+
- Der Kurzlink wird beim ersten Klick automatisch auf Nostr publiziert
55+
- Danach wird die gewählte Aktion ausgeführt
56+
6. Grüne ✓-Markierung zeigt: Slug ist publiziert
57+
58+
### Kurzlink verwenden
59+
60+
Jeder mit dem Link (z.B. `https://kanban.edufeed.org/b/mein-projekt`) wird automatisch zum Board weitergeleitet.
61+
62+
---
63+
64+
## 🔧 Technische Architektur
65+
66+
### Nostr Event-Struktur (Kind 30491)
67+
68+
| Feld | Wert | Beschreibung |
69+
|------|------|--------------|
70+
| `kind` | `30491` | Addressable Event (NIP-33) |
71+
| `d`-Tag | Slug | z.B. `"mein-projekt"` — macht Event per Slug adressierbar |
72+
| `r`-Tag | naddr-String | Maschinenlesbarer Board-Link |
73+
| `a`-Tag | `30301:<pubkey>:<boardId>` | Querverweis zum Board-Event |
74+
| `title`-Tag | Board-Titel | Für Discovery/Suche |
75+
| `content` | naddr-String | Human-Readable Fallback |
76+
77+
**Beispiel-Event:**
78+
```json
79+
{
80+
"kind": 30491,
81+
"tags": [
82+
["d", "mein-projekt"],
83+
["r", "naddr1qqpkucttqy28wumn8ghj7..."],
84+
["a", "30301:abc123:board-d-tag"],
85+
["title", "Mein Projekt"]
86+
],
87+
"content": "naddr1qqpkucttqy28wumn8ghj7..."
88+
}
89+
```
90+
91+
### Datenfluss
92+
93+
```
94+
ShareDialog (UI)
95+
96+
├─ slugifyBoardName() → Auto-Slug aus Board-Name
97+
98+
├─ [User bearbeitet Slug optional]
99+
100+
└─ ensurePublished() → Beim Klick auf Copy/Open/QR
101+
102+
├─ boardStore.publishShortlink(slug)
103+
│ ├─ nip19.naddrEncode() → naddr generieren
104+
│ ├─ createShortlinkEvent() → Kind 30491 Event
105+
│ └─ event.publish() → Auf öffentliche Relays
106+
107+
└─ Aktion ausführen (Copy/Open/QR)
108+
```
109+
110+
### Resolver-Route (`/b/[slug]`)
111+
112+
```
113+
Browser: /b/mein-projekt
114+
115+
├─ +page.ts → slug aus URL extrahieren, prerender=false
116+
117+
└─ +page.svelte (onMount)
118+
├─ NDK-Bereitschaft abwarten (max. 5s)
119+
├─ resolveShortlinkBySlug(slug, ndk)
120+
│ ├─ Kind 30491, #d=[slug] auf Nostr suchen
121+
│ └─ Last-Write-Wins bei mehreren Ergebnissen
122+
└─ goto('/cardsboard/<naddr>', { replaceState: true })
123+
```
124+
125+
**Status-States der Resolver-Seite:**
126+
- `loading` — Slug wird auf Nostr gesucht (mit Fortschrittsanzeige)
127+
- `not-found` — Kein Event mit diesem Slug gefunden
128+
- `error` — NDK nicht bereit oder anderer Fehler
129+
130+
---
131+
132+
## 📁 Betroffene Dateien
133+
134+
| Datei | Änderung |
135+
|-------|----------|
136+
| `src/lib/utils/nostrEvents.ts` | 5 neue Funktionen + `EVENT_KINDS.SHORTLINK = 30491` |
137+
| `src/lib/stores/kanbanStore.svelte.ts` | `publishShortlink(slug)` Methode |
138+
| `src/lib/components/board/ShareDialog.svelte` | Kurzlink-Tab mit Auto-Publish UX |
139+
| `src/routes/b/[slug]/+page.ts` | SvelteKit Load-Funktion |
140+
| `src/routes/b/[slug]/+page.svelte` | Resolver-Seite (Loading/Error/NotFound) |
141+
| `src/lib/utils/nostrEvents.spec.ts` | 17 neue Unit-Tests |
142+
143+
---
144+
145+
## 📚 API-Referenz
146+
147+
### `slugifyBoardName(boardName: string): string`
148+
149+
Generiert einen URL-freundlichen Slug aus einem Board-Namen.
150+
151+
- Umlaute → ASCII (ä→ae, ö→oe, ü→ue, ß→ss) — **vor** NFD-Normalisierung
152+
- Diakritische Zeichen entfernen
153+
- Nicht-alphanumerische Zeichen → Bindestrich
154+
- Max. 48 Zeichen
155+
156+
```typescript
157+
slugifyBoardName('Mein Tolles Board') // → "mein-tolles-board"
158+
slugifyBoardName('Übung für Schüler') // → "uebung-fuer-schueler"
159+
slugifyBoardName('Café & Résumé') // → "cafe-resume"
160+
```
161+
162+
### `createShortlinkEvent(slug, naddr, boardId, authorPubkey, boardTitle?, ndk): NDKEvent`
163+
164+
Erstellt ein unsigniertes Kind 30491 Event.
165+
166+
| Parameter | Typ | Beschreibung |
167+
|-----------|-----|--------------|
168+
| `slug` | `string` | Das Kürzel (wird zum d-Tag) |
169+
| `naddr` | `string` | Vollständiger naddr-String |
170+
| `boardId` | `string` | Board d-Tag |
171+
| `authorPubkey` | `string` | Hex-Pubkey des Board-Autors |
172+
| `boardTitle` | `string \| undefined` | Optionaler Board-Titel |
173+
| `ndk` | `NDK` | NDK-Instanz |
174+
175+
### `resolveShortlink(slug, authorPubkey, ndk): Promise<string | null>`
176+
177+
Löst einen Slug auf, wenn der Author bekannt ist (schneller, gezielter Filter).
178+
179+
### `resolveShortlinkBySlug(slug, ndk): Promise<{ naddr, authorPubkey } | null>`
180+
181+
Löst einen Slug auf **ohne** Author-Kenntnis. Sucht über alle Autoren, nimmt das neueste Event (Last-Write-Wins).
182+
183+
### `boardStore.publishShortlink(slug): Promise<boolean>`
184+
185+
Publiziert ein Shortlink-Event für das aktuelle Board.
186+
- Generiert naddr via `nip19.naddrEncode()`
187+
- Publiziert auf öffentliche Relays
188+
- Gibt `true` bei Erfolg zurück
189+
190+
---
191+
192+
## 🧪 Testing
193+
194+
### Unit-Tests (17 Tests in `nostrEvents.spec.ts`)
195+
196+
```
197+
✅ slugifyBoardName
198+
- Lowercase + Bindestriche
199+
- Umlaute (ä→ae, ö→oe, ü→ue, ß→ss)
200+
- Diakritische Zeichen (Café → cafe)
201+
- Max. 48 Zeichen
202+
- Leerer String
203+
- Sonderzeichen
204+
205+
✅ createShortlinkEvent
206+
- Kind 30491, d/r/a-Tags
207+
- Without title → kein title-Tag
208+
- Content = naddr
209+
210+
✅ resolveShortlink (4 Tests)
211+
- Erfolgreiche Auflösung via r-Tag
212+
- Fallback auf Content wenn kein r-Tag
213+
- Nicht gefunden → null
214+
- Leerer Content ohne r-Tag → null
215+
216+
✅ resolveShortlinkBySlug (5 Tests)
217+
- Erfolgreiche Auflösung ohne Author-Kenntnis
218+
- Nicht gefunden (leere Menge) → null
219+
- Last-Write-Wins bei 3 konkurrierenden Events
220+
- Fallback auf Content ohne r-Tag
221+
- fetchEvents → null → null
222+
```
223+
224+
---
225+
226+
## ⚠️ Fehlerbehebung
227+
228+
### Kurzlink wird nicht gefunden
229+
230+
**Mögliche Ursachen:**
231+
- Board-Autor ist nicht angemeldet (Shortlink braucht signiertes Event)
232+
- Relays sind nicht erreichbar
233+
- Slug wurde noch nicht publiziert (grüne ✓-Markierung fehlt)
234+
235+
### QR-Code wird nicht generiert
236+
237+
- QR-Button klicken → Shortlink wird automatisch publiziert → QR wird erzeugt
238+
- Wenn Slug geändert wird, muss QR erneut generiert werden
239+
240+
### Slug-Kollision
241+
242+
Addressable Events (NIP-33) sind pro Author + d-Tag unique. Verschiedene Autoren können denselben Slug haben. `resolveShortlinkBySlug` nimmt das neueste Event (Last-Write-Wins).
243+
244+
---
245+
246+
## 🔗 Referenzen
247+
248+
- [Nostr NIP-33: Addressable Events](https://github.com/nostr-protocol/nips/blob/master/33.md)
249+
- [NIP-19: naddr Encoding](https://github.com/nostr-protocol/nips/blob/master/19.md)
250+
- [Share-Link Feature (Token-basiert)](./SHARELINK.md) — Verwandtes Feature für Board-Sharing via komprimiertem Token
251+
- [Kanban-NIP Event Schema](../../Kanban-NIP.md) — Kind 30301/30302 Board/Card Events

docs/_INDEX.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,11 @@ docs/
409409
| [`GUIDE.md`](./TESTS/GUIDE.md) | Ausführliches Test-Guide | ✅ |
410410
| [`STATUS.md`](./TESTS/STATUS.md) | Test Suite Status & Überblick | ✅ |
411411

412-
### FEATURE/ (13 Dateien)
412+
### FEATURE/ (14 Dateien)
413413

414414
| Datei | Zweck | Status |
415415
|-------|-------|--------|
416+
| [`SHORTLINK.md`](./FEATURE/SHORTLINK.md) | 🆕 **NEU (21.02.26)**: Dezentraler URL-Shortener via Nostr Kind 30491 | ✅ Neu (21.02.26) |
416417
| [`LANDINGPAGE.md`](./FEATURE/LANDINGPAGE.md) | Landingpage für das Kanban-Board (CTA, Links, Lehrkräfte-Fokus) | ✅ Neu (03.02.) |
417418
| [`TOOL-BASED-AI.md`](./AGENT/TOOL-BASED-AI.md) | 🆕 **NEU (21.01.26)**: MCP-Style Tool-Based KI
418419
| [`OER-FINDER-CHAT-BOT-INTEGRATION.md`](./AGENT/OER-FINDER-CHAT-BOT-INTEGRATION.md) | OER-Finder Integration für Chatbot fügt OER-Content hinzu ✅ Neu |

0 commit comments

Comments
 (0)