Die AppView soll die Access-Tokens der PDS prüfen können, ohne dass PDS_JWT_SECRET den PDS-Prozess verlässt. Verifiziert wird ES256 mit dem *öffentlichen* Teil des P-256-Schlüssels — den veröffentlicht die PDS jetzt als verificationMethod (Multikey) in ihrem DID-Dokument. Damit fällt auch die hartkodierte Service-DID: describeServer gab stur did:web:pds.maarcadetweet.local zurück, unabhängig von PDS_PUBLIC_URL. Beide Endpoints leiten sie jetzt aus einer Quelle ab (AppConfig::pds_did(), did:web-Regel mit %3A-kodiertem Port). Der `iss` des Access-Tokens baute die DID zuvor ohne Port-Kodierung zusammen — also in einer Form, der kein did:web-Resolver folgen könnte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
maarcadetweet
AT-Protocol-PDS in Rust + AppView + Tauri/Svelte-Desktop-Client.
Posts sind auf 160 Zeichen limitiert (oldschool Twitter), erzwungen durch eigenes Lexicon app.twi.post.
Architektur
crates/
├── at-lexicon/ Lexicon-Schemas + 160-Char-Validierung
├── at-crypto/ k256, p256, CID, multibase, JWT, PLC-Ops, Repo-Signing
├── at-identity/ DID, PLC, Handle-Resolution
├── at-mst/ Merkle-Search-Tree
├── at-repo/ Repos, Commits, Blöcke, TID-Revs
├── at-blob/ S3-kompatibler Blob-Store (MinIO) + In-Memory-Store
├── at-firehose/ Jetstream-Consumer (WebSocket)
├── at-shared/ Config, Errors, DID, Cursor
├── pds-server/ axum HTTP PDS (bin)
└── appview/ Jetstream-Indexer + REST-API (bin)
crates/tauri-app/ Tauri 2 + Svelte 5 + Vite + TS Desktop-Client
├── src/ Svelte-Components (Terminal, StatusBar, NavRail, PostCard, ComposeBox,
│ LoginScreen, ProfileView, NotificationsView)
├── src/lib/styles/ tokens.css (1:1 vom maarcade-Design)
└── src-tauri/ Rust-IPC-Layer
lexicons/app/twi/post.json Custom Lexicon mit maxLength: 160
migrations/pds/ PDS-DB-Schema (users, repos, blobs, sessions, plc_ops)
migrations/appview/ AppView-DB-Schema (posts, likes, follows, notifications, profiles, jetstream_cursor)
docs/ Deployment, Architektur, Tauri-Release (siehe unten)
Setup
# 1) Datenbanken + MinIO starten
docker compose up -d
# 2) Umgebungsvariablen — beide Binaries laden .env selbst (dotenvy).
# PDS_JWT_SECRET muss Hex sein: openssl rand -hex 32
cp .env.example .env
# 3) Workspace kompilieren + Tests
cargo test --workspace
cargo check --workspace
# 4) Tauri-Frontend (Vite dev)
cd crates/tauri-app
npm install
npm run dev
# → http://127.0.0.1:1420
# 5) PDS / AppView (eigene Terminals)
cargo run -p pds-server
cargo run -p appview
Status
| Phase | Stand |
|---|---|
| 0 Foundation, Workspace, Migrations, Lexicon, Crypto | ✅ done |
| 1 Identity (PLC-Ops vollständig signieren) | ✅ done — did:plc: deterministisch aus signed op CID |
| 2 MST + Repo (Spec-konforme CBOR-Encoding) | ✅ done — encode_key = base64url(sha256(raw_key)) per atproto-Spec, split_around/wrap_with_split threaden den recursive right_sub korrekt als k_tree weiter. 27 MST + 13 Repo + 4 Commit Tests grün. |
| 3 PDS-Server (com.atproto.* XRPC) | ✅ done — createAccount/Session/Refresh, createRecord/deleteRecord, like/repost, follow |
| 4 AppView-Foundation (Jetstream-Index) | ✅ done — Jetstream-Indexer + identity-Event-Backfill + PLC-handle-sync-Worker |
| 5 AppView-REST-API | ✅ done — timeline, profile (by-did + by-handle), search, post-by-uri, thread, notifications, followers/following |
| 6 Tauri-UI-Logik an Backend koppeln | ✅ done — LoginScreen, NavRail, PostCard, ComposeBox, Profile/Compose/Search/Settings-Views |
| 7 Polish (Tray, Notifications, Auto-Update) | ✅ done — Tray-Icon custom (tauri::include_image!), Notification-Click navigiert via app://notification-Event + openThread-Helper zu Thread-Detail, Auto-Update in Dev inert (Production-Weg: docs/tauri-release.md) |
| 8 Social-Graph + Benachrichtigungen | ✅ done — notifications-Tabelle, Schreibpfad im Jetstream-Indexer (idempotent, keine Selbst-Notifications), `/api/notifications[/count |
Tests
cargo test --workspace # Rust: PDS, AppView, at-* Crates
cd crates/tauri-app && npx vitest run # Svelte/TS
cd crates/tauri-app/src-tauri && cargo test # Tauri-IPC (eigener Workspace!)
Stand zuletzt gegen den lokalen Dev-Stack (docker compose + laufender PDS + AppView): Rust-Workspace grün (u.a. 27 MST, 24 PDS-Integration, 49 AppView-Lib, 14 AppView-Integration), Frontend grün. Zwei Vorbehalte:
- Die DB-gestützten Integrationstests sind fail-open — ohne erreichbare Postgres/PDS überspringen sie sich selbst und melden das nur auf stderr. Ein grüner Lauf ohne laufenden Stack sagt also weniger, als er aussieht.
- Einige Tests hängen am Zustand der Dev-Datenbank; auf einer frischen DB können
handle_sync-Tests abweichen. Wer sie ernst nimmt, prüft sie gegen eine definierte DB.
crates/tauri-app/src-tauri hat ein eigenes [workspace] und ist nicht Teil des
Root-Workspace; cargo test --workspace von oben erfasst den IPC-Layer nicht.
Dokumentation
| Datei | Inhalt |
|---|---|
docs/architecture.md |
Crate-Verantwortlichkeiten, Datenfluss PDS → Jetstream → AppView → Client, Tabellenübersicht |
docs/deployment.md |
Betrieb: docker compose, Migrationen, alle Env-Variablen, Release-Build, systemd-Units, Reverse-Proxy, Health-Checks |
docs/tauri-release.md |
Signing-Keys, Updater-Config, latest.json, Build pro Plattform, Artefaktpfade |
Bekannte Lücken
- Die eigene PDS speist keinen Firehose (
com.atproto.sync.subscribeReposfehlt) — eigene Records erreichen die AppView nur über den Best-Effort-PushPOST /internal/ingest-commit. - Die AppView-Leseschnittstelle hat keine Auth und CORS
Any; bei/api/notificationssind das erstmals halbwegs private Daten. - Notifications werden nie gelöscht: Unlike/Unfollow lässt die Zeile stehen, und der Dedupe-Key macht sie „einmal pro (Empfänger, Autor, Art, Subject) für immer".
- Auto-Update ist nur dokumentiert, nicht verdrahtet: niemand ruft
check()auf, das Updater-Plugin ist nicht installiert (siehedocs/tauri-release.md). - Reply-Notifications gehen verloren, wenn die Antwort vor ihrem Parent indiziert wird (kein Nachlauf) — bei Jetstream möglich.
at-blobspricht MinIO ohne Signature V4 — echtes AWS S3 funktioniert damit nicht.- Die PDS liefert kein
.well-known/did.json;describeServergibt die DID hart alsdid:web:pds.maarcadetweet.localzurück.
Design
Orange Akzent, IBM Plex Mono, schwarzer Hintergrund mit 3%-Grid, Terminal-Fenster-Component mit blinkendem Cursor. Tokens sind 1:1 von maarcade-shell/landing/assets/css/tokens.css abgeleitet, plus zwei neue Repos-Tokens (--cid-fg, --rev-fg).