Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ tailwind.log
tailwindcss-*.log
static/config.live.json
scripts/probe*
.vscode/settings.json
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Keine offenen Eintraege.

| Datum | Was | Details |
|---|---|---|
| 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) |
| 2026-02-21 | Automatische Gespraechs-Zusammenfassung (LLM + lokaler Fallback) | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
| 2026-02-21 | LLM Proxy 400-Fehler behoben (tool_choice, Umlaute, Retry) | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
| 2026-02-20 | Paste-System: Editable-Target Guard | [2026-Q1.md](docs/CHANGELOG/2026-Q1.md) |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Ein intelligentes Kanban-Board mit KI-Unterstützung und Nostr-Integration, geba
- 📱 **Reaktiv:** Svelte 5 Runes für optimale Performance
- 🔒 **Typsicher:** Vollständig in TypeScript implementiert
- 🎨 **Demo-Boards:** Sofortiger Start für anonyme Nutzer mit vorkonfigurierten Boards
- 🔗 **Kurzlinks:** Dezentraler URL-Shortener via Nostr — merkbare Board-URLs statt langer naddr-Strings

## 📚 Dokumentation

Expand Down
8 changes: 6 additions & 2 deletions docs/CHANGELOG/2026-Q1.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
4. Betrieb und Sicherheit gehaertet (OIDC Fixes, Docker Publishing, Event-Authorisierung).
5. LLM Proxy stabilisiert (tool_choice Fix, Umlaut-Bereinigung, Retry-Logik).
6. Automatische Gespraechs-Zusammenfassung fuer KI-Chat eingefuehrt.
7. Kurzlink-Feature: Dezentraler URL-Shortener via Nostr Kind 30491.

## Zeitstrahl in Clustern

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

## Wichtige Ergebnisbloecke (Versionen)

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

## PR-Highlights mit direktem Nutzen

| Datum | PR | Thema | Nutzen |
|---|---|---|---|
| 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 |
| 2026-02-21 | Direct | Automatische Gespraechs-Zusammenfassung | Chat-History wird nach 6 Nachrichten per LLM zusammengefasst — besserer Kontext bei gleichem Token-Budget |
| 2026-02-21 | Direct | LLM Proxy 400-Fehler behoben | `tool_choice` von `required` auf `auto`, Umlaut-Bereinigung, Retry-Logik |
| 2026-02-20 | Direct | Paste-System: Editable-Target Guard erweitert | Paste-Events werden nicht mehr in Input/Textarea/TipTap abgefangen |
Expand Down Expand Up @@ -126,6 +128,7 @@ Q1/2026 war kein einzelner Release, sondern ein schneller Stabilitaets- und Ausb
## Direkte Commits (ohne PR)

### 2026-02-21
- **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`.
- **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.
- **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`.

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

| Datum | PR | Branch | Merge Commit |
|---|---|---|---|
| 2026-02-21 | [#122](https://github.com/edufeed-org/kanban-editor/pull/122) | feature/urlshortener | — |
| 2026-02-20 | [#121](https://github.com/edufeed-org/kanban-editor/pull/121) | edufeed-org/security-fix | cec26f4 |
| 2026-02-20 | [#120](https://github.com/edufeed-org/kanban-editor/pull/120) | JannikStreek/feat/oer-finder-plugin-local-search | ec92467 |
| 2026-02-19 | [#119](https://github.com/edufeed-org/kanban-editor/pull/119) | edufeed-org/fix/editshared | 34944fa |
Expand Down
251 changes: 251 additions & 0 deletions docs/FEATURE/SHORTLINK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# 🔗 Kurzlink-Feature (URL Shortener via Nostr)

**Version:** 1.0
**Status:** ✅ COMPLETE (26 Unit-Tests)
**Datum:** 21. Februar 2026
**Branch:** `feature/urlshortener`

---

## 📋 Übersicht & Motivation

### Das Problem

Die vollständigen Board-URLs enthalten einen langen `naddr`-String (Nostr Addressable Identifier), z.B.:

```
https://kanban.edufeed.org/cardsboard/naddr1qqpkucttqy28wumn8ghj7un9d3shjtn...
```

Solche URLs sind:
- ❌ Schwer mündlich zu kommunizieren
- ❌ Nicht merkbar
- ❌ QR-Codes werden unnötig groß und schwer scanbar
- ❌ Ungeeignet für Social-Media-Posts

### Die Lösung

Dezentrale Kurzlinks über **Nostr Addressable Events (Kind 30491)** nach NIP-33.
Ein kurzer Slug wird auf den vollen `naddr`-String gemappt und auf Nostr-Relays publiziert.

```
Vorher: https://kanban.edufeed.org/cardsboard/naddr1qqpkucttqy28wumn8ghj7...
Nachher: https://kanban.edufeed.org/b/mein-projekt
```

**Vorteile:**
- 🚀 Kurze, merkbare URLs
- 🌐 Dezentral — kein URL-Shortener-Service nötig
- 🔁 Deterministisch — Slug wird aus Board-Name generiert
- ✏️ Editierbar — Nutzer kann Slug vor Publizierung anpassen
- 📱 Kompakte QR-Codes

---

## 🚀 Quick Start (Benutzer-Anleitung)

### Kurzlink erstellen

1. Board öffnen → **Share-Button** (Toolbar oben rechts) klicken
2. Im Dialog ist der **Kurzlink-Tab** bereits aktiv
3. Ein Slug wird automatisch aus dem Board-Namen generiert (z.B. `mein-projekt`)
4. Optional: Slug im Eingabefeld bearbeiten
5. Auf **Kopieren**, **Öffnen** oder **QR-Code** klicken
- Der Kurzlink wird beim ersten Klick automatisch auf Nostr publiziert
- Danach wird die gewählte Aktion ausgeführt
6. Grüne ✓-Markierung zeigt: Slug ist publiziert

### Kurzlink verwenden

Jeder mit dem Link (z.B. `https://kanban.edufeed.org/b/mein-projekt`) wird automatisch zum Board weitergeleitet.

---

## 🔧 Technische Architektur

### Nostr Event-Struktur (Kind 30491)

| Feld | Wert | Beschreibung |
|------|------|--------------|
| `kind` | `30491` | Addressable Event (NIP-33) |
| `d`-Tag | Slug | z.B. `"mein-projekt"` — macht Event per Slug adressierbar |
| `r`-Tag | naddr-String | Maschinenlesbarer Board-Link |
| `a`-Tag | `30301:<pubkey>:<boardId>` | Querverweis zum Board-Event |
| `title`-Tag | Board-Titel | Für Discovery/Suche |
| `content` | naddr-String | Human-Readable Fallback |

**Beispiel-Event:**
```json
{
"kind": 30491,
"tags": [
["d", "mein-projekt"],
["r", "naddr1qqpkucttqy28wumn8ghj7..."],
["a", "30301:abc123:board-d-tag"],
["title", "Mein Projekt"]
],
"content": "naddr1qqpkucttqy28wumn8ghj7..."
}
```

### Datenfluss

```
ShareDialog (UI)
├─ slugifyBoardName() → Auto-Slug aus Board-Name
├─ [User bearbeitet Slug optional]
└─ ensurePublished() → Beim Klick auf Copy/Open/QR
├─ boardStore.publishShortlink(slug)
│ ├─ nip19.naddrEncode() → naddr generieren
│ ├─ createShortlinkEvent() → Kind 30491 Event
│ └─ event.publish() → Auf öffentliche Relays
└─ Aktion ausführen (Copy/Open/QR)
```

### Resolver-Route (`/b/[slug]`)

```
Browser: /b/mein-projekt
├─ +page.ts → slug aus URL extrahieren, prerender=false
└─ +page.svelte (onMount)
├─ NDK-Bereitschaft abwarten (max. 5s)
├─ resolveShortlinkBySlug(slug, ndk)
│ ├─ Kind 30491, #d=[slug] auf Nostr suchen
│ └─ Last-Write-Wins bei mehreren Ergebnissen
└─ goto('/cardsboard/<naddr>', { replaceState: true })
```

**Status-States der Resolver-Seite:**
- `loading` — Slug wird auf Nostr gesucht (mit Fortschrittsanzeige)
- `not-found` — Kein Event mit diesem Slug gefunden
- `error` — NDK nicht bereit oder anderer Fehler

---

## 📁 Betroffene Dateien

| Datei | Änderung |
|-------|----------|
| `src/lib/utils/nostrEvents.ts` | 5 neue Funktionen + `EVENT_KINDS.SHORTLINK = 30491` |
| `src/lib/stores/kanbanStore.svelte.ts` | `publishShortlink(slug)` Methode |
| `src/lib/components/board/ShareDialog.svelte` | Kurzlink-Tab mit Auto-Publish UX |
| `src/routes/b/[slug]/+page.ts` | SvelteKit Load-Funktion |
| `src/routes/b/[slug]/+page.svelte` | Resolver-Seite (Loading/Error/NotFound) |
| `src/lib/utils/nostrEvents.spec.ts` | 17 neue Unit-Tests |

---

## 📚 API-Referenz

### `slugifyBoardName(boardName: string): string`

Generiert einen URL-freundlichen Slug aus einem Board-Namen.

- Umlaute → ASCII (ä→ae, ö→oe, ü→ue, ß→ss) — **vor** NFD-Normalisierung
- Diakritische Zeichen entfernen
- Nicht-alphanumerische Zeichen → Bindestrich
- Max. 48 Zeichen

```typescript
slugifyBoardName('Mein Tolles Board') // → "mein-tolles-board"
slugifyBoardName('Übung für Schüler') // → "uebung-fuer-schueler"
slugifyBoardName('Café & Résumé') // → "cafe-resume"
```

### `createShortlinkEvent(slug, naddr, boardId, authorPubkey, boardTitle?, ndk): NDKEvent`

Erstellt ein unsigniertes Kind 30491 Event.

| Parameter | Typ | Beschreibung |
|-----------|-----|--------------|
| `slug` | `string` | Das Kürzel (wird zum d-Tag) |
| `naddr` | `string` | Vollständiger naddr-String |
| `boardId` | `string` | Board d-Tag |
| `authorPubkey` | `string` | Hex-Pubkey des Board-Autors |
| `boardTitle` | `string \| undefined` | Optionaler Board-Titel |
| `ndk` | `NDK` | NDK-Instanz |

### `resolveShortlink(slug, authorPubkey, ndk): Promise<string | null>`

Löst einen Slug auf, wenn der Author bekannt ist (schneller, gezielter Filter).

### `resolveShortlinkBySlug(slug, ndk): Promise<{ naddr, authorPubkey } | null>`

Löst einen Slug auf **ohne** Author-Kenntnis. Sucht über alle Autoren, nimmt das neueste Event (Last-Write-Wins).

### `boardStore.publishShortlink(slug): Promise<boolean>`

Publiziert ein Shortlink-Event für das aktuelle Board.
- Generiert naddr via `nip19.naddrEncode()`
- Publiziert auf öffentliche Relays
- Gibt `true` bei Erfolg zurück

---

## 🧪 Testing

### Unit-Tests (17 Tests in `nostrEvents.spec.ts`)

```
✅ slugifyBoardName
- Lowercase + Bindestriche
- Umlaute (ä→ae, ö→oe, ü→ue, ß→ss)
- Diakritische Zeichen (Café → cafe)
- Max. 48 Zeichen
- Leerer String
- Sonderzeichen

✅ createShortlinkEvent
- Kind 30491, d/r/a-Tags
- Without title → kein title-Tag
- Content = naddr

✅ resolveShortlink (4 Tests)
- Erfolgreiche Auflösung via r-Tag
- Fallback auf Content wenn kein r-Tag
- Nicht gefunden → null
- Leerer Content ohne r-Tag → null

✅ resolveShortlinkBySlug (5 Tests)
- Erfolgreiche Auflösung ohne Author-Kenntnis
- Nicht gefunden (leere Menge) → null
- Last-Write-Wins bei 3 konkurrierenden Events
- Fallback auf Content ohne r-Tag
- fetchEvents → null → null
```

---

## ⚠️ Fehlerbehebung

### Kurzlink wird nicht gefunden

**Mögliche Ursachen:**
- Board-Autor ist nicht angemeldet (Shortlink braucht signiertes Event)
- Relays sind nicht erreichbar
- Slug wurde noch nicht publiziert (grüne ✓-Markierung fehlt)

### QR-Code wird nicht generiert

- QR-Button klicken → Shortlink wird automatisch publiziert → QR wird erzeugt
- Wenn Slug geändert wird, muss QR erneut generiert werden

### Slug-Kollision

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).

---

## 🔗 Referenzen

- [Nostr NIP-33: Addressable Events](https://github.com/nostr-protocol/nips/blob/master/33.md)
- [NIP-19: naddr Encoding](https://github.com/nostr-protocol/nips/blob/master/19.md)
- [Share-Link Feature (Token-basiert)](./SHARELINK.md) — Verwandtes Feature für Board-Sharing via komprimiertem Token
- [Kanban-NIP Event Schema](../../Kanban-NIP.md) — Kind 30301/30302 Board/Card Events
3 changes: 2 additions & 1 deletion docs/_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,11 @@ docs/
| [`GUIDE.md`](./TESTS/GUIDE.md) | Ausführliches Test-Guide | ✅ |
| [`STATUS.md`](./TESTS/STATUS.md) | Test Suite Status & Überblick | ✅ |

### FEATURE/ (13 Dateien)
### FEATURE/ (14 Dateien)

| Datei | Zweck | Status |
|-------|-------|--------|
| [`SHORTLINK.md`](./FEATURE/SHORTLINK.md) | 🆕 **NEU (21.02.26)**: Dezentraler URL-Shortener via Nostr Kind 30491 | ✅ Neu (21.02.26) |
| [`LANDINGPAGE.md`](./FEATURE/LANDINGPAGE.md) | Landingpage für das Kanban-Board (CTA, Links, Lehrkräfte-Fokus) | ✅ Neu (03.02.) |
| [`TOOL-BASED-AI.md`](./AGENT/TOOL-BASED-AI.md) | 🆕 **NEU (21.01.26)**: MCP-Style Tool-Based KI
| [`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 |
Expand Down
Loading