# 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 lexicons/app/bsky/ like, repost, follow, actor.profile 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 ```bash # 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`](docs/tauri-release.md)) | | 8 Social-Graph + Benachrichtigungen | ✅ done — `notifications`-Tabelle, Schreibpfad im Jetstream-Indexer (idempotent, keine Selbst-Notifications), `/api/notifications[/count|/seen]`, `/api/followers`, `/api/following`, eigene `/api/thread`-Route; im Client Notifications-View mit Unread-Badge und klickbare Follower-/Following-Listen im Profil | | 9 Auth + Performance | ✅ done — AppView prüft Bearer-Tokens (ES256, Schlüssel aus dem neuen `/.well-known/did.json` der PDS, fail closed); Timeline und Notifications nur noch für die eigene DID; CORS-Allowlist statt `Any`; Indizes für Handle-Lookup und Cold-Start-Feed | | 10 Lokaler Firehose | ✅ done — `com.atproto.sync.subscribeRepos` auf der PDS (Event in derselben Transaktion wie der Commit, `seq`-Cursor mit lückenfreiem Replay, WebSocket-Frames in atproto-Form); die AppView konsumiert ihn mit persistiertem Cursor. Ein verlorener Push ist damit nicht mehr endgültig. | ## Tests ```bash 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 Tests sind *fail-open*: **ohne `DATABASE_URL_APPVIEW` in der Umgebung überspringen sie sich selbst** und melden das nur auf stderr. `cargo test --workspace` in einer nackten Shell meldet dann grün, ohne sie ausgeführt zu haben — für einen aussagekräftigen Lauf `set -a; . ./.env; set +a` voranstellen und PDS + AppView laufen lassen. * Läuft die Auth (Default), holen sich die Integrationstests echte Tokens von der PDS bzw. signieren sie aus `PDS_JWT_SECRET`; ohne erreichbare PDS überspringen sie. `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`](docs/architecture.md) | Crate-Verantwortlichkeiten, Datenfluss PDS → Jetstream → AppView → Client, Tabellenübersicht | | [`docs/deployment.md`](docs/deployment.md) | Betrieb: docker compose, Migrationen, alle Env-Variablen, Release-Build, systemd-Units, Reverse-Proxy, Health-Checks | | [`docs/tauri-release.md`](docs/tauri-release.md) | Signing-Keys, Updater-Config, `latest.json`, Build pro Plattform, Artefaktpfade | ## Bekannte Lücken * Der Firehose ist **lokal**: er verbindet die eigenen zwei Dienste. Ein fremder Relay erfährt von dieser PDS weiterhin nichts. * Die Frame-Hülle ist spec-konformes DAG-CBOR, die Blöcke darin nicht: CIDs innerhalb von Commit-Blöcken sind Strings statt Tag-42-Links. Ein fremder atproto-Consumer liest die Frames, scheitert aber an den Blockinhalten. Das zu ändern hieße, jede CID im System zu ändern — inklusive der `did:plc:`-Ableitung. * `firehose_events` wird nie beschnitten. * 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 (siehe `docs/tauri-release.md`). * Reply-Notifications gehen verloren, wenn die Antwort vor ihrem Parent indiziert wird (kein Nachlauf) — bei Jetstream möglich. * `at-blob` spricht MinIO ohne Signature V4 — echtes AWS S3 funktioniert damit nicht. ## 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`).