diff --git a/docs/SPEC-ios-app-review-v1.md b/docs/SPEC-ios-app-review-v1.md new file mode 100644 index 0000000..f55f9cd --- /dev/null +++ b/docs/SPEC-ios-app-review-v1.md @@ -0,0 +1,749 @@ +# pi-remote — iOS Native App Spec + +> **Review-Modus:** Jedes Feature unten hat eine ID und kann einzeln gestrichen +> werden. Sektionen "Vision", "Principles", "Architecture" sind das Rückgrat — +> wenn die fallen, kollabiert der Rest. Alles unter "Server Features" und "iOS +> Features" ist verhandelbar. Features tragen einen Priority-Hinweis +> (`MUST` / `SHOULD` / `NICE`) als Vorschlag, nicht als Festlegung. + +--- + +## 1. Vision + +Eine iOS-App, die eine laufende `pi`-Session **byte-genau spiegelt**, wie sie +im SSH-Terminal aussieht — und durch native iOS-Mittel (Touch, Sprache, +Notifications, System-Integration) die Bedienung dieser Terminal-Umgebung +unterwegs angenehm macht. + +**Kein Hybrid-Rendering.** Was im Terminal ANSI ist, bleibt in der App ANSI. +Die App fügt eine *Augmentation-Schicht* hinzu, kein paralleles UI. + +**Kernszenario:** Eine Pi-Session läuft tagelang. Der Nutzer "besucht" sie +unregelmäßig — vom Mac via SSH, vom iPhone via App. Beide sehen jederzeit +denselben State. Reattach ist instant. + +joe: anderer use case ist das spawnen und wechseln aktiver sessions. es muss +super easy sein, sessions zu spawnen und zu switchen. das session handling auf server seite sollte robust sein. + +claude: Großer Punkt — das verändert die Gewichtung deutlich. Konkret: +- S-09 (Multi-Session) wird von SHOULD zu **MUST**, ebenso iOS-E-01. +- Wir brauchen explizite Server-Endpoints: `POST /sessions` (spawn), `DELETE /sessions/` (kill), `PATCH /sessions/` (rename), `GET /sessions` (list inkl. Status). +- Robustheit heißt für mich konkret: jede Session überlebt Sidecar-Restart (tmux-Sessions bleiben sowieso, wir reattach beim Boot), getrennte Ringbuffer/Log-Files pro Session, Crash einer Session zieht die anderen nicht runter. +- Spawn-Flow: User tippt in der App "+" → optional Name oder leer (dann Auto-Name, siehe S-09-Kommentar) → Server macht `tmux new-session -d -s 'pi'` → Stream direkt verfügbar. +- Switching-UX siehe iOS-E-01-Reply. + +Ich trage das in v2 der Spec ein und ziehe S-09 + iOS-E-01 hoch. + +--- + +## 2. Principles (locked) + +- **P-1** — Der Terminal-Stream ist die einzige Wahrheit. Strukturierte Events + dienen ausschließlich Statusanzeigen und Notifications, nie dem Rendering + des Inhalts. +- **P-2** — SSH-Erfahrung bleibt unverändert. Der Mac-Workflow ändert sich + durch dieses Projekt nicht spürbar. +- **P-3** — Reconnect ist die wichtigste Operation. Sie muss < 1s nach + App-Wake sichtbares Ergebnis liefern. joe: warum is das so wichtig? + + claude: Weil iOS deine App alle paar Minuten suspended und du sie dutzendfach + am Tag öffnest. Wenn jedes Öffnen 3-5s mit einem Spinner verbringt, fühlt + sich die App "kaputt" an, selbst wenn technisch nichts kaputt ist. Bei < 1s + ist es "die App war nie weg". Das ist das Gefühl, das du willst, wenn du + in einer langen Session immer wieder reinguckst. + + Wenn du das anders gewichtest, können wir es zu "< 2s" lockern — aber + irgendein hartes Ziel sollten wir haben, sonst optimieren wir es nicht. + Soll das raus / weicher werden? +- **P-4** — Touch-UX wird *zur Terminal-Bedienung* gebaut, nicht *anstelle* + von Terminal-Bedienung. +- **P-5** — Solo-Use. Multi-User, Sharing, Org-Features sind out of scope. + +--- + +## 3. Architecture (locked) + +``` +┌──────────────────────────────────────────────────────────┐ +│ Server │ +│ │ +│ pi (Ink TUI) ◄─► tmux session ◄─► SSH-Client (Mac) │ +│ │ │ +│ │ pipe-pane (raw bytes) │ +│ │ send-keys │ +│ ▼ │ +│ pi-remote sidecar │ +│ │ │ +│ │ WebSocket │ +│ │ ├─ raw ANSI stream │ +│ │ ├─ control (send-keys) │ +│ │ └─ side-channel (state, meta) │ +└─────────────────────────┼────────────────────────────────┘ + │ + ▼ + ┌──────────────────────────┐ + │ iOS App │ + │ ┌────────────────────┐ │ + │ │ SwiftTerm renderer │ │ + │ └────────────────────┘ │ + │ ┌────────────────────┐ │ + │ │ Augmentation layer │ │ + │ │ (slash, snippets, │ │ + │ │ status, voice, │ │ + │ │ markers, ...) │ │ + │ └────────────────────┘ │ + └──────────────────────────┘ +``` + +**Komponenten:** + +- **tmux** — Session-Persistenz, Multi-Client-Attach, Pane-Pipe, Send-Keys. + Nicht selbst gebaut. +- **Sidecar** (`pi-remote-control` erweitert) — Node-Prozess. Spawn/Reattach + tmux, exponiert WebSocket-API, hält Ringbuffer für Replay, leitet + ExtensionAPI-Metadaten als Side-Channel weiter. +- **iOS-App** — SwiftUI-Shell, SwiftTerm als Renderer-View, eigener + Augmentation-Layer. + +**Datenfluss Stream:** `pi` → tmux pipe-pane → sidecar ringbuffer → WS → iOS +SwiftTerm. + +**Datenfluss Input:** iOS → WS → sidecar → `tmux send-keys` → tmux pane → pi +stdin. Der Mac-SSH-Client sieht denselben Input. + +--- + +## 4. Server Features (Sidecar) + +### S-01 — tmux launcher / attach +**MUST.** Sidecar startet eine tmux-Session (`pi-main` oder pro Projekt +benannt), spawned `pi` darin. Falls Session existiert → reattach. Existiert +als CLI-Befehl `pi-remote attach`. + +*Rationale:* P-2 — User-Workflow bleibt fast identisch. +*Dependencies:* — + +### S-02 — Raw ANSI WebSocket-Endpoint +**MUST.** Endpoint `/stream` liefert die Bytes von `tmux pipe-pane` als +binäre WS-Frames. Eine Verbindung = ein Pane. + +joe: warum können wir sicher sein, dass der stream der kommt von der ios app bzw der library so angenommen werden? sind hier fallstricke, die wir noch nicht sehen? ist der binary stream das beste für den use case? bringt uns compression hier was? + +claude: Drei Teilfragen, separat beantwortet. + +**(1) Kann SwiftTerm den Stream verarbeiten?** +Ja, mit Caveat. SwiftTerm ist eine vollständige VT100/xterm-Implementierung — alles was tmux/Ink rauswirft (CSI/SGR/cursor-positioning/alternate-screen-buffer/title) wird verarbeitet. **Fallstricke** die ich sehe: +- *Alternate Screen Buffer*: pi/Ink nutzt `\e[?1049h` für Full-Screen-Modus. SwiftTerm handhabt das, aber wir müssen den Scrollback-Cache (iOS-A-04) korrekt davon trennen (alternate-screen-Inhalt nicht im scrollback persistieren). +- *Mouse-Mode*: pi nutzt vermutlich kein Mouse-Tracking, aber falls eine Extension das tut, schicken wir die ANSI-Sequenzen blind an SwiftTerm — könnte auf Touch komisch sein. Lösung: Mouse-Tracking-Sequenzen optional ignorieren. +- *Terminal-Size-Mismatch*: tmux ist auf eine feste Größe konfiguriert. Wenn iPhone-Portrait nur 40 Spalten zeigt, sieht die Ausgabe gequetscht aus. Lösung: tmux per Session auf eine sinnvolle Größe (z.B. 120×40) fixen, Client rendert mit Pinch-Zoom passend. +- *Truecolor / 24bit*: SwiftTerm + Ink unterstützen beide, sollte sauber sein. +- *Title sequences*: pi setzt evtl. den Terminal-Title — wir können das als Side-Channel-Hinweis nutzen oder ignorieren. joe: brauchen wir nicht + +**(2) Binär vs. Text-Frame?** +Binär. Begründung: ANSI ist 8-bit-clean (256-color codes, UTF-8 in vielen Bytes) — als Text-Frame müssten wir Base64'en (33% Overhead) oder JSON-escapen (variabel, oft schlimmer). Binär ist auch das, was WebSocket-Stacks für `ArrayBuffer`/`Data` direkt vertragen. Side-Channel-Events bleiben separate Text-Frames (JSON). Multiplexing per Frame-Typ. + +**(3) Compression?** +Ja, lohnt sich. `permessage-deflate` (WebSocket-Standard-Extension) ist genau dafür gemacht. ANSI-Streams sind hochgradig redundant (gleiche SGR-Codes, gleiche Sequenzen), typische Kompressionsrate 3-5×. Für Mobile-Daten und Reconnect-Snapshots (die schnell mehrere MB groß werden) macht das einen spürbaren Unterschied. Auf Server-Seite billig in `ws` (Node-Library), auf iOS-Seite muss der WS-Client `permessage-deflate` unterstützen — `Starscream` (Swift WS-Lib) tut das. + +**Empfehlung:** binär + `permessage-deflate` an, alternate-screen sauber tracken, Terminal-Size auf 120×40 fixieren als Default. Soll ich das in der Spec konkret festschreiben? + +*Rationale:* P-1 — Direkter Stream als einzige Render-Quelle. +*Dependencies:* S-01 + +### S-03 — Send-Keys-Endpoint +**MUST.** WS-Nachrichten vom Client mit Keystroke-Payload werden in +`tmux send-keys -l` (literal) oder Key-Namen übersetzt und injiziert. + +*Rationale:* Input-Pfad ohne den der Stream nutzlos ist. +*Dependencies:* S-01 + +### S-04 — Sequence-Cursor & Delta-Replay +**MUST.** Sidecar nummeriert ausgehende Bytes (oder Chunks). Client sendet +bei Reconnect `lastSeq`, Server liefert ab dort weiter. + +*Rationale:* P-3 — Reconnect-Performance. +*Dependencies:* S-02 + +### S-05 — Snapshot-Endpoint +**MUST.** Wenn `lastSeq` außerhalb des Ringbuffers liegt: Server liefert +`tmux capture-pane -p -e -S -10000` als Snapshot + neuen Start-Seq. + +*Rationale:* Fallback wenn App lange weg war. +*Dependencies:* S-01, S-04 + +### S-06 — Ringbuffer auf Disk +**SHOULD.** Sidecar persistiert die letzten N MB ANSI-Bytes pro Pane auf +Disk (z.B. `/var/lib/pi-remote/buffer/.log`). Erlaubt Replay +weiter zurück als tmux-Scrollback. + +joe: fokus hier auch auf das log file management. ich würde vermeiden wollen das die platte irgendwann vollläuft. + +claude: Berechtigt. Konkretisierungsvorschlag für die Spec: +- **Pro Session**: Ringbuffer-Datei `/var/lib/pi-remote/buffer/.log`, hartes Limit (default 100MB), Rotation in 10MB-Chunks, älteste Chunks werden gelöscht sobald Limit überschritten. joe: keine rotation. eine file pro session. wenn die 100mb überlaufen, dann is das halt so. +- **Global**: Hartes Total-Cap (default 1GB) über alle Sessions; wenn überschritten, werden die ältesten Chunks der größten Session gekürzt. +- **Idle-Cleanup**: Sessions die seit > 30 Tagen keinen Output erzeugt UND keinen Client gesehen haben → Buffer wird komprimiert/archiviert oder gelöscht (konfigurierbar). +- **Disk-Watchdog**: Sidecar checkt periodisch verfügbaren Platz; wenn < 1GB frei, schaltet er Buffer-Schreiben ab und meldet Health-Status "degraded" (S-13). +- Konfigurierbar via `~/.config/pi-remote/config.toml`. + +Fertig in die Spec übernehmen? joe: ja + +*Rationale:* "Sessions die tagelang laufen" können sehr großen Output +ansammeln; tmux-history wird truncated. +*Dependencies:* S-02 + +### S-07 — State Side-Channel +**SHOULD.** Sidecar abonniert pi-ExtensionAPI-Events (`thinking`, +`tool-start`, `tool-end`, `idle`, `awaiting-input`) und sendet sie über +denselben WS als JSON-Control-Frames. + +*Rationale:* Quelle für Status-Bar und Notifications. Nicht für Rendering. +*Dependencies:* S-01 + +### S-08 — Slash-Command-Registry +**SHOULD.** Endpoint `/commands` liefert JSON-Liste der verfügbaren +Slash-Commands (Name, Beschreibung, Argument-Schema) aus pi's Registry. +Dynamisch — Extensions die Commands hinzufügen erscheinen automatisch. + +*Rationale:* Speist die iOS-Command-Palette (iOS-D-01). +*Dependencies:* S-01 + +### S-09 — Multi-Session-Management +**SHOULD.** Sidecar kann mehrere tmux-Sessions parallel verwalten +(z.B. eine pro Projekt). Endpoint `/sessions` listet sie, Client wählt +beim Connect. + +joe: client (also ios ux) sollte am besten alle sessions vom looka nd feel so anzeigen als wären sie direkt connected/zugreifbar. können wir mit tmux da auch irgendwie session namen oder descriptions vergeben? optional dynamisch von pi benannt (mini prompt, wenig tokens)? + +claude: Drei separate Themen, alle ja: + +**(a) Sessions "als wären sie connected":** +Die App connectet im Hintergrund parallel zu allen bekannten Sessions, hält für jede einen rolling Mini-Buffer (z.B. letzte 100 Zeilen) im RAM, plus die letzte gerenderte Frame-Snapshot. Switch = Render-View wechselt instant, im Hintergrund läuft Sync für volle Historie wenn nötig. → siehe auch iOS-E-01-Reply. + +**(b) Session-Namen & Descriptions in tmux:** +- Name: tmux-Sessions HABEN bereits Namen (`tmux new -s `). Wir nutzen den als stable ID. +- Description: tmux unterstützt User-Options, `tmux set-option -t @description "…"`. Lesbar via `tmux show-options -t -v @description`. Beliebige Metadaten so anhängbar (z.B. `@project`, `@created`). +- Wir können das mit unserem eigenen JSON-State-File im Sidecar kombinieren wenn wir mehr brauchen. joe: keinen weiteren state selbst handlen. tmux oder gar nicht + +**(c) Auto-Naming via Pi (mini-prompt):** +Gute Idee. Konkret: nach ~3 User-Messages in einer namenlosen Session feuert der Sidecar einen Cheap-LLM-Call (haiku-tier, ~50 Tokens Output) gegen den bisherigen Verlauf: "Gib einen 2-4-Wort-Titel für diese Konversation." Ergebnis landet als `@description`. Manuelles Umbenennen aus der App jederzeit möglich und überschreibt das. +Open Question: Welcher Mini-LLM, welche Credentials? Wahrscheinlich derselbe Provider wie pi selbst — wir borgen pi's Konfiguration aus der Extension. Soll ich das als sub-feature S-09a notieren? joe: wir nutzen pi direkt. die cli bietet one shot an. als model nutzen wir haiku. anthropic auth ist in pi schon configuriert. + +*Rationale:* Speist Session-Switcher (iOS-E-01). +*Dependencies:* S-01 + +### S-10 — Bearer-Token-Auth +**MUST.** WS-Verbindungen erfordern Bearer-Token aus Sidecar-Config. +Per-Device-Tokens möglich. + +joe: auth mit mbile sollte einfach gehen, qr code im terminal oder so. + +claude: Ja, sauberer Pfad. Konkret: +- Sidecar hat einen CLI-Befehl `pi-remote pair`. Generiert ein Pairing-Token (kurzlebig, z.B. 5min gültig). +- Druckt einen QR-Code im Terminal (Unicode block chars, funktioniert per SSH). +- QR enthält: WS-URL + Pairing-Token + (siehe S-11-Reply) TLS-Fingerprint. +- iOS-App scannt → tauscht Pairing-Token gegen permanenten Bearer-Token, speichert im Keychain (iOS-G-01). +- Token kann per Device benannt sein ("jay's iPhone"), revocable via CLI (`pi-remote auth list/revoke`). + +QR-Library Server-seitig: `qrcode-terminal` (Node) — winzig, kein Aufwand. + +*Rationale:* Sidecar ist ggf. öffentlich erreichbar. +*Dependencies:* — + +### S-11 — TLS-Termination (Hinweis) +**MUST in Production.** WS via `wss://` hinter Reverse-Proxy oder +nativ. Kein Plaintext über öffentliche Netze. + +joe: einfaches handling von self signed certificates. pinning via onboarding qr code oder so. +kein lust auf pki xD + +claude: Genau der Plan. Trust-on-First-Use via QR: +- Sidecar generiert beim ersten Start ein selbstsigniertes Cert (ED25519 oder RSA-2048), persistent auf Disk. +- `pi-remote pair` QR enthält neben Token auch den SHA-256-Fingerprint des Server-Certs. +- iOS-App pinnt den Fingerprint beim Pairing fest. Jede künftige Connection verifiziert dagegen. +- Cert läuft nie ab (oder 10 Jahre — egal, wir validieren ja nur den Fingerprint, nicht die CA-Chain). +- Kein Let's Encrypt, kein DNS, kein Reverse-Proxy nötig — solange du eine IP/Hostname hast die das iPhone erreicht. +- Falls Cert rotiert wird (z.B. neuer Sidecar-Host): re-pairing via neuem QR. + +Kein PKI, kein CA, kein Browser-Warning-Hell. Klar? + +### S-12 — Marker-Detection / Prompt-Grenzen +**NICE.** Sidecar versucht heuristisch oder via Extension-Hook, +Prompt-Grenzen im Stream zu erkennen und als Marker im Side-Channel zu +publizieren (`{type: "marker", seq: 12345, label: "user-prompt-start"}`). + +joe: wie robust ist das. gibt pi hier consistente marker? + +claude: Ehrlich: ich weiß es nicht sicher. Vermutung: pi/Ink gibt **keine** expliziten ANSI-Marker-Sequenzen aus, die wir parsen könnten — Ink redrawt komplette Frames, das ist alles ein einziger Stream-of-Updates. Heuristisch ginge nur "Zeile beginnt mit `> ` oder `❯`" o.ä., das ist brüchig. + +DU hast in Q-4 schon gesagt "gar nicht rein als feature". Damit fällt S-12 + iOS-E-03 + iOS-B-04 zusammen. Cleaner so. → Ich streiche S-12 in v2. + +*Rationale:* Speist Jump-to-Marker (iOS-E-03). +*Dependencies:* S-07 + +### S-13 — Health & Metrics +**NICE.** `/health` Endpoint mit Session-Count, Buffer-Size, Connected +Clients. Für Monitoring. + +*Rationale:* Selbst-Debugging im Long-Running-Setup. +*Dependencies:* — + +--- + +## 5. iOS Client Features + +### Gruppe A — Rendering & Stream + +#### iOS-A-01 — SwiftTerm-Renderer +**MUST.** Vollwertiger ANSI-Terminal-View, der den raw Stream rendert. +Konfigurierbar: Font (monospace), Größe, Farbschema. + +*Rationale:* P-1 — der Stream wird gerendert, nicht interpretiert. +*Dependencies:* S-02 + +#### iOS-A-02 — Sequence-Cursor & Reconnect +**MUST.** Client speichert lokal die letzte `seq` pro Session. +Bei WS-Reconnect: schickt `lastSeq`, verarbeitet Delta oder Snapshot. + +*Rationale:* P-3. +*Dependencies:* S-04, S-05 + +#### iOS-A-03 — Stale-Frame-Display +**SHOULD.** Während Sync nach App-Wake: letzten gerenderten Frame +einfrieren, "syncing…" als subtile Overlay-Pill anzeigen. Kein leerer +Screen. + +*Rationale:* Wahrnehmung: App fühlt sich immer "live" an. +*Dependencies:* iOS-A-02 + +#### iOS-A-04 — Local Scrollback-Cache +**SHOULD.** App puffert empfangene Bytes lokal (rolling, z.B. 5MB) für +Offline-Scrolling und Suche, auch wenn WS offline. + +*Rationale:* Speist iOS-E-02 (Scrollback-Search). Reduziert Server-Hits. +*Dependencies:* iOS-A-01 + +#### iOS-A-05 — Theme-Editor +**NICE.** Color-Schemes (Solarized, Dracula, custom), Font-Size, +Line-Height. iCloud-Sync der Preferences. + +joe: gerne eine klassische liste bekannter defaults mitliefern (inkl. monokai o.vgl.) + +claude: 👍 — ich schreibe in v2 eine konkrete Liste ein: **Solarized Light/Dark, Monokai, Dracula, Nord, Gruvbox Dark/Light, Tomorrow Night, GitHub Light/Dark, System (folgt iOS Dark Mode), Default**. Plus "Custom" für eigene Farbtabellen. iCloud-Sync nur für Custom-Themes. +joe: top. ich will auch unbedingt mind. "Hack" und Jetbrains Mono als font. + +*Rationale:* Lange Lesephasen → Augenkomfort. +*Dependencies:* iOS-A-01 + +### Gruppe B — Input & Modifier + +#### iOS-B-01 — Software-Keyboard + Text-Input +**MUST.** Standard-iOS-Keyboard. Text wird beim Tippen oder beim +Enter-Tap an `send-keys` geschickt. + +joe: leeres enter sendet new line im editor + +claude: Hmm, ich muss das nochmal verstehen — meinst du: + +**(a)** Eingabefeld der App ist ein **mehrzeiliges Compose-Feld** (du tippst kompletten Prompt mit Newlines, drückst dann einen separaten "Send"-Button). Enter im Feld = Newline einfügen. +Vorteil: bequem für längere Prompts, kein Risiko versehentlich abzuschicken. +Nachteil: nicht 1:1 wie im Terminal — pi sieht den Prompt erst beim Send als ein Block. + +**(b)** Eingabe ist **direkt-passthrough** (jeder Tastendruck geht sofort als `send-keys`). Enter sendet `\r` an pi → pi terminiert Eingabe und schickt sie an den Agent. Wenn du in pi's eigenem multi-line-Modus bist (Shift-Enter o.ä.), entsprechend. + +Ich hatte (b) im Kopf, weil P-1/P-4 (Terminal-Bedienung). Aber dein Kommentar klingt nach (a) mit einer Hybrid-Regel: "leeres Enter = newline einfügen" (also Modus a, nur smart). + +Welche Variante meinst du? Mein Vorschlag: **(b) als Default, (a) optional togglebar** — weil wir sonst pi's eigenes Input-Handling (z.B. multi-line-Mode in pi) duplizieren und Bugs kriegen. + +joe: du hast recht. es ist b. aber ich will den shift+enter easily available haben. termius hat shortcuts die man auf volume down und up legen kann. oder doch besser in der status leiste? + +*Rationale:* Die Basis. +*Dependencies:* S-03 + +#### iOS-B-02 — Modifier-Bar +**MUST.** Akzessoriumsleiste über der Tastatur mit: `ESC`, `Tab`, +`Ctrl`, `Alt`, `↑↓←→`, `Enter`. Sticky-Toggle für Modifier (ein Tap = +hält bis zur nächsten Eingabe). + +joe: welche modifier machen hier sinn? wir sind ja pi agent centric. gibts noch alternativen. der platz ist ja begrenzt. + +claude: Guter Punkt — generischer Terminal-Approach passt nicht zu pi-centric. Konkret was pi tatsächlich braucht: + +**Aus pi-Usage tatsächlich essenziell:** +- `Esc` — pi abbrechen / aus Modi raus +- `↑/↓` — History scrollen, in Auswahllisten navigieren +- `Tab` — Autocomplete in pi-Eingabe (Dateien, Commands) +- `Enter` — senden +- `Ctrl-C` — laufendes Tool abbrechen / pi-Action stoppen joe: macht das nicht Esc eh schon? + +**Nice-to-have:** +- `Ctrl-D` — pi beenden (vielleicht versteckt, zu gefährlich für leichte Touch) +- `Ctrl-L` — clear + +**Vorschlag finale Modifier-Bar (pi-tuned):** +``` +[Esc] [Tab] [↑] [↓] [Ctrl-C] [/] [🎙] joe: lieber ctrl als ctrl+c. und / kann raus. +``` +- `/` ist Shortcut: öffnet Slash-Command-Palette (iOS-C-04) ODER tippt einfach `/` ein +- `🎙` ist Voice-Input (iOS-C-06) +- Pfeile nur ↑/↓ weil ←/→ via Touch-Cursor selten (und Touch-Cursor flog ja raus) joe: link rechts pfeile brauche ich eben weil touch cursor rausflog) +- Ctrl-C als eigener Button weil häufigste Aktion +- Kein Alt/Meta, kein Ctrl-als-Modifier-Toggle — wir mappen 2-3 wichtige Ctrl-Combos auf eigene Buttons, fertig joe: s.o. + +Wenn man im pi-internen Editor (für längere Prompts) ist, könnten ←/→ einblendbar sein context-aware. Aber MVP ohne. + +Passt das? Was fehlt dir noch in deinem täglichen pi-Flow? + +*Rationale:* Ohne ESC und Pfeile ist iOS-Terminal-Nutzung untauglich. +*Dependencies:* iOS-B-01 + +#### iOS-B-03 — Long-Press-Repeat +**SHOULD.** Long-Press auf Pfeiltasten / Backspace → repeat mit +Beschleunigung. + +*Rationale:* Lange Navigation in Prompts. +*Dependencies:* iOS-B-02 + +#### iOS-B-04 — Tap-to-Cursor +**SHOULD.** Tippen auf eine Position in der aktuellen pi-Eingabezeile → +App schickt entsprechend viele `←`/`→` um den Cursor dahin zu bewegen. + +*Rationale:* Cursorbewegung per Touch — einer der "killer" Hebel den ein +dummes Terminal nicht hat. +*Dependencies:* iOS-A-01, iOS-B-02. Heuristik: aktuelle Zeile = letzte +Zeile vor Prompt-Marker; ohne S-12 nur best-effort. + +joe: brauchen wir nicht fliegt raus. + +claude: ✂️ raus. Konsistent mit dem Marker-Drop. + +#### iOS-B-05 — Selection & Copy +**MUST.** Doppel-Tap → Wort. Tripel-Tap → Zeile. Lange-Press + Drag → +Range. Native iOS-Copy-Menu. + +*Rationale:* Output ist oft das Wertvolle (Code, IDs, Pfade). +*Dependencies:* iOS-A-01 + +#### iOS-B-06 — Pinch-Zoom Font +**SHOULD.** Pinch in der Terminal-View → Font-Size live. +*Rationale:* Lesbarkeit. +*Dependencies:* iOS-A-01 + +joe: love it + +claude: 🤝 + +#### iOS-B-07 — Hardware-Keyboard-Support +**SHOULD.** Externe iPad-Keyboards: Caps→Esc-Remap (optional), +Modifier-Pass-Through (Cmd, Option, Ctrl), Shortcuts wie Cmd-K → klar +definierte App-Aktionen. + +*Rationale:* iPad mit Magic Keyboard = ernsthafte Pi-Workstation. +*Dependencies:* iOS-B-01 + +joe: love it + +claude: 🤝 — wird in v2 noch konkretisiert welche Cmd-K Shortcuts sinnvoll sind (Cmd-K Clear, Cmd-T new session, Cmd-1..9 session switch, Cmd-F search). + +#### iOS-B-08 — Reachability / One-Hand-Mode +**NICE.** iPhone-Querformat: Modifier-Bar + Action-Buttons auf einer +Seite gespiegelt, daumengerecht. + +*Rationale:* Einhandbedienung. +*Dependencies:* iOS-B-02 + +#### iOS-B-09 — Smart Paste +**SHOULD.** Clipboard-Indicator in der Modifier-Bar. Tap → Preview +(insb. multi-line), Confirm → paste. Bei Pi-detection (z.B. bracketed +paste mode an) korrekt einrahmen. + +joe: check ich nicht. erkläre nochmal + +claude: Sorry, zu kryptisch. Zwei Sachen die zusammenhängen: + +**"Smart Paste" = Confirm-vor-Paste** +Problem: du kopierst auf dem iPhone aus Safari einen Stacktrace, wechselst in die pi-App, tappst aus Versehen auf "Paste" → riesiger Blob wird an pi geschickt, möglicherweise mit ANSI-Escape-Codes oder Geheimnissen drin. Schwer zu undo. +Lösung: Clipboard-Indicator in der Modifier-Bar zeigt einen kleinen Vorschau-Chip ("📋 12 lines, 847 chars"). Tap → Sheet mit vollem Preview + "Insert" / "Cancel". Erst dann geht's an pi. + +**"Bracketed Paste Mode" = pi unterscheidet getippte vs. gepastete Eingabe** +Technischer Hintergrund: moderne Terminals wrappen gepastete Eingabe in spezielle ESC-Sequenzen (`\e[200~` vor Paste, `\e[201~` danach). pi/Ink kann dann erkennen "das war Paste, nicht getippt" und z.B. den ganzen Block als einen Prompt nehmen statt nach jedem Newline zu submitten. Wenn pi diesen Modus aktiviert (`\e[?2004h` im Stream), muss unsere App das mitkriegen und beim Paste die richtigen Sequenzen drumherum schicken. + +Die beiden Sachen sind orthogonal — Confirm-vor-Paste ist UX, Bracketed-Paste ist Protocol-Correctness. Ich hatte sie in einem Bullet gemischt, das war unklar. In v2 trenne ich sie in **iOS-B-09a (Paste-Confirm)** und **iOS-B-09b (Bracketed-Paste-Compliance)**. + +Macht das mehr Sinn? Beide drin lassen, oder nur eines? +joe: passt. + +*Rationale:* Sicherer als blindes Paste. +*Dependencies:* iOS-B-01 + +### Gruppe C — Pi-aware Augmentation + +#### iOS-C-01 — Status-Bar +**MUST.** Top-Bar zeigt Connection-Status, Session-Name und +abgeleiteten Pi-State (`● thinking` / `⏵ tool: Edit` / `▶ waiting` / +`⏸ idle`) basierend auf Side-Channel-Events. + +*Rationale:* Schneller Blick: was tut Pi gerade? +*Dependencies:* S-07 + +#### iOS-C-02 — Push-Notification bei State-Change +**MUST.** Wenn App im Background und Pi wechselt zu +`awaiting-input` → Push-Notification "Pi ist fertig". Tap → +App in der richtigen Session. + +*Rationale:* Killer-Feature für "Session läuft lange, kommt wieder +wenn fertig". +*Dependencies:* S-07, APNs-Setup + +#### iOS-C-03 — Haptic Feedback bei State-Wechseln +**NICE.** Subtile Vibration bei `thinking → idle` Übergang. +*Rationale:* Fühlt sich "lebendig" an. +*Dependencies:* S-07 + +#### iOS-C-04 — Slash-Command-Palette +**SHOULD.** Swipe-up-Sheet mit nativer Liste aller Slash-Commands +(aus S-08), fuzzy-search, Tap → injiziert via `send-keys`. Bei +Commands mit Argumenten → Formular-View für die Eingabe. + +*Rationale:* Slash-Commands sind im Terminal mühsam zu tippen. +*Dependencies:* S-03, S-08 + +#### iOS-C-05 — Snippet-Library +**SHOULD.** Vorgefertigte Prompts mit Variablen ("review {{branch}} +for security"). iCloud-Sync. Native Edit-UI. + +*Rationale:* Wiederkehrende Prompts. +*Dependencies:* iOS-B-01 + +joe: will ich mit pi internen tools lösen, kann raus + +claude: ✂️ raus. Macht Sinn — Snippets gehören thematisch zu pi, nicht zum Remote-Client. + +#### iOS-C-06 — Voice-to-Prompt +**NICE.** Mikro-Button → Speech-Recognition (lokal, iOS native) → +editable Preview → Send. + +*Rationale:* Unterwegs schneller als tippen. +*Dependencies:* iOS-B-01, Mic-Permission + +### Gruppe D — Session & Navigation + +#### iOS-E-01 — Session-Switcher +**SHOULD.** Native iOS-Liste der verfügbaren tmux-Sessions (aus S-09). +Wechsel = neuer WS-Stream, vorhergehender wird im Hintergrund weiter +gepuffert. + +joe: kann man hier schon auf basis einer bestimmten aktion des users preloaden? wäre nice, wenn es sich so anfühlt als wäre die session "schon immer da gewesen" :D + +claude: Yes! Konkret zwei Strategien kombinierbar: + +**(1) Background-Pre-Connect aller Sessions** +App connectet im Hintergrund zu allen bekannten Sessions parallel, hält für jede einen kleinen In-Memory-Stream-Buffer. Switch = Renderer wechselt instant, ist schon synchronisiert. Kosten: paar Sockets + paar MB RAM für Buffer pro Session. Akzeptabel solange du < ~10 aktive Sessions hast. + +**(2) Optimistic-Switch mit Stale-Frame** +Swipe zu Session B → sofort den letzten gerenderten Frame von B zeigen (lokal gecached), gleichzeitig live-Stream nachholen. Du siehst niemals einen leeren Screen, nur eine kurze "sync"-Pille. + +**(3) Bonus: Predictive Pre-Render bei Switcher-Open** +Wenn User die Switcher-Liste öffnet → App pingt alle Sessions, holt Frame-Snapshots, zeigt Live-Thumbnails (kleine Mini-Terminal-Vorschauen in der Liste). Switch ist dann visuell "ich sehe schon was drin ist". + +Kosten: trivial server-seitig (capture-pane), client-seitig moderate Memory. + +Ich trage (1) + (2) + (3) in v2 als iOS-E-01a/b/c ein. Soll (3) rein? Wäre wirklich "feels-magic"-Material. + +*Rationale:* Pro Projekt eine Pi-Session. +*Dependencies:* S-09 + +#### iOS-E-02 — Scrollback-Search +**SHOULD.** Such-Sheet sucht im lokalen Scrollback-Cache (iOS-A-04) +mit Highlight + Jump-to-Match. + +*Rationale:* "Wo war das eine Snippet?" — Standard-Bedarf. +*Dependencies:* iOS-A-04 + +#### iOS-E-03 — Jump-to-Marker +**NICE.** Vertikales Swipe-Gesture springt zwischen Prompt-Grenzen. +Visualisiert als kleine Edge-Indicator-Linien. + +*Rationale:* "Vor 3 Antworten gab's diese Idee" — Standard-Bedarf. +*Dependencies:* S-12, iOS-A-01 + +joe: lassen wir raus. dazu aber ein anderer gedanke. wie viel info können wir reliable von pi über die session und die tree view bekommen? das feature vom pi ist so zentral, dass es nativen ios support verdient + +claude: ✂️ iOS-E-03 raus. Aber der **andere Gedanke** ist groß — wert eines eigenen Threads. + +**Zur Frage: Tree-View nativ in iOS rendern.** + +Das wäre die einzige Stelle, an der wir P-1 ("Stream ist die einzige Wahrheit") aufweichen. Argument dafür: Tree-State ist **Meta-State der Session**, nicht **Inhalt der Session**. Das ist eine andere Kategorie als "Markdown rendern statt ANSI". Konsistent argumentierbar. + +**Was wir bräuchten (Server-seitig):** +Ein neuer Side-Channel-Endpoint, der pi's Tree als strukturiertes JSON liefert + Live-Updates bei Branch/Fork/Compact. Format ungefähr: +```json +{ + "nodes": [ + {"id": "abc", "parent": null, "summary": "main", "messageCount": 42, "createdAt": "..."}, + {"id": "def", "parent": "abc", "summary": "explored iOS", "messageCount": 18, ...} + ], + "current": "def" +} +``` + +**Was wir brauchen (Client-seitig):** +Ein natives SwiftUI-Tree-View-Sheet. Tap auf Knoten → entweder direkter Switch via `/checkout ` an pi gesendet, oder Preview-Modus mit Stream-Snapshot des Knotens. + +**Großes Aber:** +Das hängt komplett davon ab, was die ExtensionAPI hergibt. Das ist genau das Thema von Q-2/Q-3 (was kann die ExtensionAPI?). Wir hatten in der vorherigen Session festgestellt: `pi.sendUserMessage()` umgeht Slash-Command-Processing, `session.prompt()` ist nicht exposed, der Tree wird intern in der Runtime gehalten — Zugriff auf Tree-State ist nicht öffentlich. + +**Vorschlag:** Ich trage in v2 einen neuen Feature-Block **Gruppe T — Tree Navigation** ein, mit dem expliziten Hinweis "depends on ExtensionAPI investigation". Konkret: +- **S-14 (NEU)** — Tree-State-Endpoint + Live-Updates (server-seitig, bedingt machbar) +- **iOS-T-01 (NEU)** — Native Tree-View mit Checkout-Action +- **iOS-T-02 (NEU)** — Branch/Fork-Action aus der App heraus (`/fork`, `/new`, `/compact` via slash-command-injection) + +Ohne ExtensionAPI-Verbesserung sind iOS-T-01/T-02 nur via Slash-Command-Injection halbgar machbar (wir sehen den Tree nicht, können aber Commands schicken). Mit ExtensionAPI-Erweiterung wird's mächtig. + +Soll ich diese Gruppe T in v2 mit aufnehmen + als ersten Spike-Punkt "ExtensionAPI-Audit" definieren? + +#### iOS-E-04 — Bookmarks +**NICE.** Long-Press auf Stelle im Scrollback → benannte Marke. +Bookmarks-Sheet listet sie, Tap → Jump. + +*Rationale:* Manuelle Marker für wichtige Stellen. +*Dependencies:* iOS-A-04 + +joe: brauchen wir nicht. raus + +claude: ✂️ raus. + +### Gruppe E — Background & Lifecycle + +#### iOS-F-01 — WS-Keepalive +**MUST.** Ping/Pong-Frames im Foreground halten Connection auf +zombie-detection-bewussten iOS-Stacks. + +*Rationale:* iOS suspended WS gerne ohne Notification. +*Dependencies:* S-02 + +#### iOS-F-02 — Wake-up-Sync +**MUST.** App-Foreground-Event triggert sofort Reconnect + Delta-Pull. +Spinner falls > 200ms. + +*Rationale:* P-3. +*Dependencies:* iOS-A-02 + +#### iOS-F-03 — Silent-Push-Trigger für Wake +**NICE.** Server kann Silent-Push schicken um App zu wecken bevor +User selbst auf das Icon tippt → Daten sind schon da. + +*Rationale:* "App fühlt sich an als wär sie nie weg gewesen." +*Dependencies:* S-07, APNs + +joe: wann sollte sie das tun? geht das überhaupt mit ios aggresivem app lifecycle management? + +claude: Geht — aber mit Caveats die das Feature ziemlich entwerten. + +**Mechanik:** APNs unterstützt "Silent Pushes" (`content-available: 1`, kein Alert). iOS weckt die App im Background für ~30s Processing-Zeit. App kann WebSocket aufmachen, Delta pullen, schlafen. + +**Caveats:** +- iOS coalesced/dropped Silent Pushes nach eigenem Gusto. Apple gibt keine Garantie der Zustellung, nur "best effort". +- Budget ist limitiert: ~2-3 erfolgreiche Background-Wakes pro Stunde, danach drosselt iOS aggressiv. +- Wenn User die App im App-Switcher force-quit-tet, kommen **gar keine** Background-Wakes mehr — auch Silent Push nicht. +- Battery / Low-Power-Mode killt das komplett. + +**Realistischer Wert für uns:** marginal. Das echte Killer-Feature ist iOS-C-02 (sichtbare Push wenn pi `awaiting-input` erreicht) — die ist nicht silent und wird zuverlässig zugestellt. + +**Vorschlag:** iOS-F-03 streichen. Stattdessen verlassen wir uns auf iOS-F-02 (Wake-up-Sync beim manuellen Öffnen), das ist mit Sequence-Cursor + permessage-deflate schnell genug. Silent-Push lohnt den Aufwand nicht. + +Einverstanden mit streichen? +joe: ja + +### Gruppe F — Security + +#### iOS-G-01 — Token im Keychain +**MUST.** Bearer-Token aus S-10 wird im iOS Keychain gespeichert, +nie im Klartext. +*Dependencies:* S-10 + +#### iOS-G-02 — Face-ID / Touch-ID Gate +**SHOULD.** Optionaler Biometrie-Lock vorm Öffnen. +*Rationale:* Pi-Sessions können sensitive Daten zeigen. +*Dependencies:* — + +joe: ja, will ich haben + +claude: 👍 — soll Face-ID dann verpflichtend (`MUST`) sein oder optional toggle? Bei optional ist's Setting. Bei MUST muss jeder App-Wake erst durch Face-ID. Mein Vorschlag: **opt-in via Setting, default off** — sonst nervt das Re-Auth alle paar Minuten. joe: opt in + +#### iOS-G-03 — Certificate-Pinning +**NICE.** TLS-Pinning gegen Sidecar-Host. +*Rationale:* MITM-Hardening. +*Dependencies:* S-11 + +joe: einfaches handling von self signed certificates. pinning via onboarding qr code oder so. +kein lust auf pki xD + +claude: Siehe S-11-Reply — gleicher Mechanismus, das ist dasselbe Feature aus Client-Sicht. In v2 fasse ich S-11 + iOS-G-03 zu einem zusammenhängenden "TLS via TOFU+Pinning" Block. + +--- + +## 6. Out of Scope (locked rejections) + +- **Rich Chat-Rendering.** Wir parsen den Stream nicht. Wenn Pi Markdown + ausgibt, sieht der Nutzer ANSI-Markdown wie im Terminal. +- **Embedded Mini-Terminals / Block-Selection.** Es gibt einen Stream. +- **Multi-User-Sharing.** Solo. +- **Org/Team-Features, Cloud-Hosted-Service.** Selbstgehostet, ein Mensch. +- **Inline-Image-Rendering** (iTerm2 image protocol etc.). Eventuell + später, aber nicht in v1. +- **Eigenes Mosh.** tmux + WebSocket reicht. +- **Cross-platform-Client (Android / Web).** Nur iOS nativ. Bestehende + HTML-UI bleibt aber bestehen (separates Artefakt). + +--- + +## 7. Offene Fragen + +- **Q-1** — Wie wird `pi-remote attach` zur Default-Methode? Shell-Alias + manuell vs. in pi-Launcher gepatcht? + joe: irrelevant. kümmer ic mich selbst drum + + claude: ✂️ Q-1 raus. +- **Q-2** — Side-Channel-State: liefert die aktuelle ExtensionAPI alle + benötigten Events (`thinking`, `awaiting-input`)? Falls nicht, was + brauchen wir upstream? + joe: was kann die extension api denn? das sollten wir vorher prüfen + + claude: 👍 — das ist der zentrale Spike vor Phase 0. Ich schlage vor wir definieren das als **Spike-0a — ExtensionAPI-Audit**: + - Was liefert sie heute an Events? (`thinking`, `awaiting-input`, `tool-start/end`, Stream-Indicators?) + - Welche Slash-Commands kann sie abfragen? (Registry exposed oder nicht?) + - Tree-State zugänglich? (Read-only? Subscribe? Mutations via Command-Injection?) + - `pi.prompt()` oder Äquivalent — gibt's einen Weg Slash-Commands programmatisch zu dispatchen? + + Output: ein Markdown-Dokument `docs/EXTENSION-API-AUDIT.md` das alle Features dieser Spec zu konkreter ExtensionAPI-Capability mapped. Das blockt nichts am Stream selbst (S-01-S-05) — das funktioniert ohne API-Erweiterung — aber alles unter S-07, S-08, Gruppe C, Gruppe T hängt daran. + + Soll ich diesen Spike als ersten Punkt vor Phase 0 anlegen? +- **Q-3** — Slash-Command-Registry: ist die Liste über ExtensionAPI + abrufbar oder brauchen wir Hack? + joe: siehe Q2 + + claude: 👍 — gleicher Spike. +- **Q-4** — Marker-Detection (S-12): rein heuristisch reicht für iOS-B-04 + und iOS-E-03? Oder Extension-Hook nötig? + joe: gar nicht rein als feature + + claude: ✂️ — bestätigt. S-12, iOS-E-03, iOS-B-04 fallen alle damit. +- **Q-5** — Apple Developer Program ($99/Jahr) vs. AltStore / Sideloadly? + Für Solo-Use beides möglich. + joe: apple developer + + claude: 👍 — $99/Jahr. Closed. +- **Q-6** — APNs: eigener Push-Server vs. simple background-fetch? + joe: bietet apple da keine lösung? + + claude: Doch, **APNs** (Apple Push Notification service) ist Apples Lösung. Aber Apple stellt nur den Push-**Endpunkt** bereit (`api.push.apple.com`), nicht den Server der die Pushes auslöst — das musst du selbst betreiben. + + In unserem Setup ist der "Push-Server" einfach der Sidecar selbst: + - Sidecar hält ein APNs-Auth-Token (JWT mit deinem Developer-Key, signed via Apple) + - Wenn Pi-State zu `awaiting-input` wechselt → Sidecar POSTet an APNs-Endpunkt mit der Device-Token (kommt vom iOS-Client beim Pairing) → Apple liefert die Notification ans iPhone aus. + - Keine zusätzliche Infrastruktur, kein Firebase / OneSignal / Push-Gateway dazwischen. Apples APNs ist HTTP/2 mit JWT, ca. 50 Zeilen Node. + + Library: `node-apn` oder `@parse/node-apn`. Setup: APNs-Auth-Key generieren im Developer-Portal (gehört zu deinem Apple-Dev-Account, $99 Q-5), Key in Sidecar-Config, fertig. + + Also: **kein eigener Push-**Server**, nur ein eigener Push-**Client** (= der Sidecar). Closed? + diff --git a/docs/SPEC-ios-app.md b/docs/SPEC-ios-app.md index f55f9cd..1794a92 100644 --- a/docs/SPEC-ios-app.md +++ b/docs/SPEC-ios-app.md @@ -1,67 +1,50 @@ -# pi-remote — iOS Native App Spec +# pi-remote — iOS Native App Spec (v2) -> **Review-Modus:** Jedes Feature unten hat eine ID und kann einzeln gestrichen -> werden. Sektionen "Vision", "Principles", "Architecture" sind das Rückgrat — -> wenn die fallen, kollabiert der Rest. Alles unter "Server Features" und "iOS -> Features" ist verhandelbar. Features tragen einen Priority-Hinweis -> (`MUST` / `SHOULD` / `NICE`) als Vorschlag, nicht als Festlegung. +> **Status:** v2 nach Review-Runde 1. +> Review-Verlauf mit allen Diskussionen: [`SPEC-ios-app-review-v1.md`](./SPEC-ios-app-review-v1.md). +> Vor Phase 0 ist ein API-Audit nötig — siehe [Spike-0a](#spike-0a--extensionapi-audit). --- ## 1. Vision -Eine iOS-App, die eine laufende `pi`-Session **byte-genau spiegelt**, wie sie -im SSH-Terminal aussieht — und durch native iOS-Mittel (Touch, Sprache, +Eine iOS-App, die laufende `pi`-Sessions **byte-genau spiegelt**, wie sie im +SSH-Terminal aussehen — und durch native iOS-Mittel (Touch, Sprache, Notifications, System-Integration) die Bedienung dieser Terminal-Umgebung unterwegs angenehm macht. **Kein Hybrid-Rendering.** Was im Terminal ANSI ist, bleibt in der App ANSI. Die App fügt eine *Augmentation-Schicht* hinzu, kein paralleles UI. -**Kernszenario:** Eine Pi-Session läuft tagelang. Der Nutzer "besucht" sie -unregelmäßig — vom Mac via SSH, vom iPhone via App. Beide sehen jederzeit -denselben State. Reattach ist instant. +**Kernszenarien:** -joe: anderer use case ist das spawnen und wechseln aktiver sessions. es muss -super easy sein, sessions zu spawnen und zu switchen. das session handling auf server seite sollte robust sein. - -claude: Großer Punkt — das verändert die Gewichtung deutlich. Konkret: -- S-09 (Multi-Session) wird von SHOULD zu **MUST**, ebenso iOS-E-01. -- Wir brauchen explizite Server-Endpoints: `POST /sessions` (spawn), `DELETE /sessions/` (kill), `PATCH /sessions/` (rename), `GET /sessions` (list inkl. Status). -- Robustheit heißt für mich konkret: jede Session überlebt Sidecar-Restart (tmux-Sessions bleiben sowieso, wir reattach beim Boot), getrennte Ringbuffer/Log-Files pro Session, Crash einer Session zieht die anderen nicht runter. -- Spawn-Flow: User tippt in der App "+" → optional Name oder leer (dann Auto-Name, siehe S-09-Kommentar) → Server macht `tmux new-session -d -s 'pi'` → Stream direkt verfügbar. -- Switching-UX siehe iOS-E-01-Reply. - -Ich trage das in v2 der Spec ein und ziehe S-09 + iOS-E-01 hoch. +1. **Long-Visit-Sync** — Eine Session läuft tagelang. Nutzer "besucht" sie + unregelmäßig vom Mac (SSH) und iPhone (App). Beide zeigen jederzeit + denselben State. Reattach ist instant. +2. **Session-Lifecycle vom Phone** — Sessions vom iPhone aus spawnen, + benennen, switchen und beenden — so easy wie native iOS-Tabs. --- -## 2. Principles (locked) +## 2. Principles -- **P-1** — Der Terminal-Stream ist die einzige Wahrheit. Strukturierte Events - dienen ausschließlich Statusanzeigen und Notifications, nie dem Rendering - des Inhalts. -- **P-2** — SSH-Erfahrung bleibt unverändert. Der Mac-Workflow ändert sich - durch dieses Projekt nicht spürbar. -- **P-3** — Reconnect ist die wichtigste Operation. Sie muss < 1s nach - App-Wake sichtbares Ergebnis liefern. joe: warum is das so wichtig? - - claude: Weil iOS deine App alle paar Minuten suspended und du sie dutzendfach - am Tag öffnest. Wenn jedes Öffnen 3-5s mit einem Spinner verbringt, fühlt - sich die App "kaputt" an, selbst wenn technisch nichts kaputt ist. Bei < 1s - ist es "die App war nie weg". Das ist das Gefühl, das du willst, wenn du - in einer langen Session immer wieder reinguckst. - - Wenn du das anders gewichtest, können wir es zu "< 2s" lockern — aber - irgendein hartes Ziel sollten wir haben, sonst optimieren wir es nicht. - Soll das raus / weicher werden? -- **P-4** — Touch-UX wird *zur Terminal-Bedienung* gebaut, nicht *anstelle* - von Terminal-Bedienung. +- **P-1** — Der Terminal-Stream ist die einzige Wahrheit für Inhalt. + Strukturierte Events dienen ausschließlich Statusanzeigen, + Notifications und Meta-State (z.B. Tree-Navigation). Niemals + Re-Rendering von Stream-Inhalt. +- **P-2** — SSH-Erfahrung bleibt unverändert. Der Mac-Workflow ändert + sich durch dieses Projekt nicht spürbar. +- **P-3** — Reconnect ist die wichtigste Operation. Sichtbares Ergebnis + < 1s nach App-Wake. +- **P-4** — Touch-UX wird *zur Terminal-Bedienung* gebaut, nicht + *anstelle* von Terminal-Bedienung. - **P-5** — Solo-Use. Multi-User, Sharing, Org-Features sind out of scope. +- **P-6** — Server-State wird in tmux gehalten, wo möglich. Kein + paralleler State-Store im Sidecar. --- -## 3. Architecture (locked) +## 3. Architecture ``` ┌──────────────────────────────────────────────────────────┐ @@ -74,10 +57,10 @@ Ich trage das in v2 der Spec ein und ziehe S-09 + iOS-E-01 hoch. │ ▼ │ │ pi-remote sidecar │ │ │ │ -│ │ WebSocket │ -│ │ ├─ raw ANSI stream │ -│ │ ├─ control (send-keys) │ -│ │ └─ side-channel (state, meta) │ +│ │ WebSocket (wss://) │ +│ │ ├─ raw ANSI stream (binary) │ +│ │ ├─ control (send-keys, JSON) │ +│ │ └─ side-channel (state, JSON) │ └─────────────────────────┼────────────────────────────────┘ │ ▼ @@ -88,9 +71,6 @@ Ich trage das in v2 der Spec ein und ziehe S-09 + iOS-E-01 hoch. │ └────────────────────┘ │ │ ┌────────────────────┐ │ │ │ Augmentation layer │ │ - │ │ (slash, snippets, │ │ - │ │ status, voice, │ │ - │ │ markers, ...) │ │ │ └────────────────────┘ │ └──────────────────────────┘ ``` @@ -98,193 +78,189 @@ Ich trage das in v2 der Spec ein und ziehe S-09 + iOS-E-01 hoch. **Komponenten:** - **tmux** — Session-Persistenz, Multi-Client-Attach, Pane-Pipe, Send-Keys. - Nicht selbst gebaut. -- **Sidecar** (`pi-remote-control` erweitert) — Node-Prozess. Spawn/Reattach - tmux, exponiert WebSocket-API, hält Ringbuffer für Replay, leitet - ExtensionAPI-Metadaten als Side-Channel weiter. + Sessions als stable IDs, Metadaten via tmux User-Options + (`@description`, `@project`, etc.). +- **Sidecar** (`pi-remote-control` erweitert) — Node-Prozess. Spawn / + Reattach tmux, exponiert WebSocket-API, hält Ringbuffer für Replay, + leitet ExtensionAPI-Metadaten als Side-Channel weiter. - **iOS-App** — SwiftUI-Shell, SwiftTerm als Renderer-View, eigener Augmentation-Layer. -**Datenfluss Stream:** `pi` → tmux pipe-pane → sidecar ringbuffer → WS → iOS -SwiftTerm. +**Datenfluss Stream:** `pi` → tmux pipe-pane → sidecar ringbuffer → WS +(binär, `permessage-deflate`) → iOS SwiftTerm. -**Datenfluss Input:** iOS → WS → sidecar → `tmux send-keys` → tmux pane → pi -stdin. Der Mac-SSH-Client sieht denselben Input. +**Datenfluss Input:** iOS → WS → sidecar → `tmux send-keys` → tmux pane → +pi stdin. Mac-SSH-Client sieht denselben Input. + +**Konventionen:** + +- Terminal-Size: tmux pane fixiert auf **120 × 40**. Client rendert mit + Pinch-Zoom auf die physische Display-Größe. +- WebSocket-Frames: binär = ANSI-Stream-Chunk; text = JSON-Control + (input, side-channel, snapshot-request). +- Alternate-Screen-Buffer wird vom Client erkannt und nicht in den + Scrollback-Cache aufgenommen. +- Terminal-Title-Sequences werden ignoriert. +- Mouse-Tracking-Sequenzen werden weitergeleitet, vom Client aber nicht + visualisiert (Touch-UX). --- ## 4. Server Features (Sidecar) ### S-01 — tmux launcher / attach -**MUST.** Sidecar startet eine tmux-Session (`pi-main` oder pro Projekt -benannt), spawned `pi` darin. Falls Session existiert → reattach. Existiert -als CLI-Befehl `pi-remote attach`. +**MUST.** CLI `pi-remote attach []`. Existiert die Session, +reattach; sonst neu spawnen (`tmux new-session -d -s 'pi'`) und +attach. Default-Session: `pi-main`. Per Projekt benennbar. -*Rationale:* P-2 — User-Workflow bleibt fast identisch. *Dependencies:* — -### S-02 — Raw ANSI WebSocket-Endpoint -**MUST.** Endpoint `/stream` liefert die Bytes von `tmux pipe-pane` als -binäre WS-Frames. Eine Verbindung = ein Pane. +### S-02 — Raw ANSI WebSocket Stream +**MUST.** Endpoint `/stream/`. +- Binäre Frames für ANSI-Bytes aus `tmux pipe-pane`. +- WebSocket-Extension `permessage-deflate` aktiv (3–5× Compression + typisch für ANSI). +- Eine Verbindung = ein Pane. +- Alternate-Screen-Sequenzen werden durchgereicht; Sidecar markiert sie + nicht separat (Client tracked). -joe: warum können wir sicher sein, dass der stream der kommt von der ios app bzw der library so angenommen werden? sind hier fallstricke, die wir noch nicht sehen? ist der binary stream das beste für den use case? bringt uns compression hier was? - -claude: Drei Teilfragen, separat beantwortet. - -**(1) Kann SwiftTerm den Stream verarbeiten?** -Ja, mit Caveat. SwiftTerm ist eine vollständige VT100/xterm-Implementierung — alles was tmux/Ink rauswirft (CSI/SGR/cursor-positioning/alternate-screen-buffer/title) wird verarbeitet. **Fallstricke** die ich sehe: -- *Alternate Screen Buffer*: pi/Ink nutzt `\e[?1049h` für Full-Screen-Modus. SwiftTerm handhabt das, aber wir müssen den Scrollback-Cache (iOS-A-04) korrekt davon trennen (alternate-screen-Inhalt nicht im scrollback persistieren). -- *Mouse-Mode*: pi nutzt vermutlich kein Mouse-Tracking, aber falls eine Extension das tut, schicken wir die ANSI-Sequenzen blind an SwiftTerm — könnte auf Touch komisch sein. Lösung: Mouse-Tracking-Sequenzen optional ignorieren. -- *Terminal-Size-Mismatch*: tmux ist auf eine feste Größe konfiguriert. Wenn iPhone-Portrait nur 40 Spalten zeigt, sieht die Ausgabe gequetscht aus. Lösung: tmux per Session auf eine sinnvolle Größe (z.B. 120×40) fixen, Client rendert mit Pinch-Zoom passend. -- *Truecolor / 24bit*: SwiftTerm + Ink unterstützen beide, sollte sauber sein. -- *Title sequences*: pi setzt evtl. den Terminal-Title — wir können das als Side-Channel-Hinweis nutzen oder ignorieren. joe: brauchen wir nicht - -**(2) Binär vs. Text-Frame?** -Binär. Begründung: ANSI ist 8-bit-clean (256-color codes, UTF-8 in vielen Bytes) — als Text-Frame müssten wir Base64'en (33% Overhead) oder JSON-escapen (variabel, oft schlimmer). Binär ist auch das, was WebSocket-Stacks für `ArrayBuffer`/`Data` direkt vertragen. Side-Channel-Events bleiben separate Text-Frames (JSON). Multiplexing per Frame-Typ. - -**(3) Compression?** -Ja, lohnt sich. `permessage-deflate` (WebSocket-Standard-Extension) ist genau dafür gemacht. ANSI-Streams sind hochgradig redundant (gleiche SGR-Codes, gleiche Sequenzen), typische Kompressionsrate 3-5×. Für Mobile-Daten und Reconnect-Snapshots (die schnell mehrere MB groß werden) macht das einen spürbaren Unterschied. Auf Server-Seite billig in `ws` (Node-Library), auf iOS-Seite muss der WS-Client `permessage-deflate` unterstützen — `Starscream` (Swift WS-Lib) tut das. - -**Empfehlung:** binär + `permessage-deflate` an, alternate-screen sauber tracken, Terminal-Size auf 120×40 fixieren als Default. Soll ich das in der Spec konkret festschreiben? - -*Rationale:* P-1 — Direkter Stream als einzige Render-Quelle. *Dependencies:* S-01 ### S-03 — Send-Keys-Endpoint -**MUST.** WS-Nachrichten vom Client mit Keystroke-Payload werden in -`tmux send-keys -l` (literal) oder Key-Namen übersetzt und injiziert. +**MUST.** WS-Text-Frames als JSON: `{type:"keys", data:"..."}` für +literal-Text, oder `{type:"key", name:"escape"|"tab"|"up"|...}` für +Spezialtasten. Sidecar mapped auf `tmux send-keys` oder +`tmux send-keys -l`. Bracketed-Paste-Frames +(`{type:"paste", data:"..."}`) wrappen automatisch mit `\e[200~ ... \e[201~`. -*Rationale:* Input-Pfad ohne den der Stream nutzlos ist. *Dependencies:* S-01 ### S-04 — Sequence-Cursor & Delta-Replay -**MUST.** Sidecar nummeriert ausgehende Bytes (oder Chunks). Client sendet -bei Reconnect `lastSeq`, Server liefert ab dort weiter. +**MUST.** Sidecar nummeriert Chunks ausgehend (monotone u64). Client +sendet bei Reconnect `{type:"resume", lastSeq:}`. Server liefert ab +`lastSeq+1` weiter. Falls Lücke → Snapshot (S-05). -*Rationale:* P-3 — Reconnect-Performance. *Dependencies:* S-02 ### S-05 — Snapshot-Endpoint -**MUST.** Wenn `lastSeq` außerhalb des Ringbuffers liegt: Server liefert +**MUST.** Wenn `lastSeq` außerhalb des Ringbuffers liegt: `tmux capture-pane -p -e -S -10000` als Snapshot + neuen Start-Seq. +Snapshot wird komprimiert über WS geliefert. -*Rationale:* Fallback wenn App lange weg war. *Dependencies:* S-01, S-04 -### S-06 — Ringbuffer auf Disk -**SHOULD.** Sidecar persistiert die letzten N MB ANSI-Bytes pro Pane auf -Disk (z.B. `/var/lib/pi-remote/buffer/.log`). Erlaubt Replay -weiter zurück als tmux-Scrollback. +### S-06 — Per-Session Disk Buffer +**SHOULD.** Sidecar persistiert ANSI-Stream pro Session in eine +einzelne Datei `/var/lib/pi-remote/buffer/.log`. -joe: fokus hier auch auf das log file management. ich würde vermeiden wollen das die platte irgendwann vollläuft. - -claude: Berechtigt. Konkretisierungsvorschlag für die Spec: -- **Pro Session**: Ringbuffer-Datei `/var/lib/pi-remote/buffer/.log`, hartes Limit (default 100MB), Rotation in 10MB-Chunks, älteste Chunks werden gelöscht sobald Limit überschritten. joe: keine rotation. eine file pro session. wenn die 100mb überlaufen, dann is das halt so. -- **Global**: Hartes Total-Cap (default 1GB) über alle Sessions; wenn überschritten, werden die ältesten Chunks der größten Session gekürzt. -- **Idle-Cleanup**: Sessions die seit > 30 Tagen keinen Output erzeugt UND keinen Client gesehen haben → Buffer wird komprimiert/archiviert oder gelöscht (konfigurierbar). -- **Disk-Watchdog**: Sidecar checkt periodisch verfügbaren Platz; wenn < 1GB frei, schaltet er Buffer-Schreiben ab und meldet Health-Status "degraded" (S-13). +- **Pro Session**: hartes Cap = **100MB** (configurable). Bei + Überlauf wird der Schreibvorgang gestoppt; existierender Inhalt + bleibt unangetastet. Neue Bytes ab dem Punkt fehlen im Buffer — + Snapshot via S-05 funktioniert weiterhin direkt aus tmux. +- **Global**: Hartes Total-Cap = **1GB** über alle Sessions; wenn + überschritten, schaltet der Sidecar Buffer-Schreiben global ab und + meldet `degraded` über S-13. +- **Idle-Cleanup**: Sessions ohne Output UND ohne Client seit > 30 + Tagen → Buffer-Datei wird gelöscht (Session selbst nur falls + ebenfalls beendet). +- **Disk-Watchdog**: Bei freiem Platz < 1GB → Buffer-Schreiben global + aus, Health-Status `degraded`. - Konfigurierbar via `~/.config/pi-remote/config.toml`. -Fertig in die Spec übernehmen? joe: ja - -*Rationale:* "Sessions die tagelang laufen" können sehr großen Output -ansammeln; tmux-history wird truncated. *Dependencies:* S-02 ### S-07 — State Side-Channel -**SHOULD.** Sidecar abonniert pi-ExtensionAPI-Events (`thinking`, -`tool-start`, `tool-end`, `idle`, `awaiting-input`) und sendet sie über -denselben WS als JSON-Control-Frames. +**SHOULD.** Sidecar abonniert pi-ExtensionAPI-Events und publiziert als +JSON-Control-Frames: +```json +{"type":"state","value":"thinking"|"tool"|"idle"|"awaiting-input", + "tool":"Edit"|"Read"|...,"ts":1234567890} +``` +Realisierbarkeit hängt vom Outcome von [Spike-0a](#spike-0a--extensionapi-audit) ab. -*Rationale:* Quelle für Status-Bar und Notifications. Nicht für Rendering. -*Dependencies:* S-01 +*Dependencies:* S-01, Spike-0a ### S-08 — Slash-Command-Registry -**SHOULD.** Endpoint `/commands` liefert JSON-Liste der verfügbaren -Slash-Commands (Name, Beschreibung, Argument-Schema) aus pi's Registry. -Dynamisch — Extensions die Commands hinzufügen erscheinen automatisch. +**SHOULD.** Endpoint `/sessions//commands` liefert JSON-Liste der +verfügbaren Slash-Commands inkl. Beschreibung und Argument-Schema, aus +pi's Registry abgefragt. Dynamisch — Extensions die Commands +hinzufügen erscheinen automatisch. Realisierbarkeit hängt von Spike-0a +ab. -*Rationale:* Speist die iOS-Command-Palette (iOS-D-01). -*Dependencies:* S-01 +*Dependencies:* S-01, Spike-0a ### S-09 — Multi-Session-Management -**SHOULD.** Sidecar kann mehrere tmux-Sessions parallel verwalten -(z.B. eine pro Projekt). Endpoint `/sessions` listet sie, Client wählt -beim Connect. +**MUST.** Sidecar verwaltet mehrere tmux-Sessions parallel, alle robust +gegen Sidecar-Restart (tmux überlebt, Sidecar reattached beim Boot). -joe: client (also ios ux) sollte am besten alle sessions vom looka nd feel so anzeigen als wären sie direkt connected/zugreifbar. können wir mit tmux da auch irgendwie session namen oder descriptions vergeben? optional dynamisch von pi benannt (mini prompt, wenig tokens)? +**Endpoints:** +- `GET /sessions` — Liste mit Metadaten (Name, Description, Created, + letzter Output, Connected-Clients, Pi-State). +- `POST /sessions` — neue Session spawnen, Body optional `{name, project}`. +- `PATCH /sessions/` — umbenennen / Description ändern. +- `DELETE /sessions/` — Session beenden (tmux kill-session + Buffer + optional löschen). -claude: Drei separate Themen, alle ja: +**State:** Ausschließlich in tmux gehalten. Sessions haben Namen +(stable ID), Metadaten via tmux User-Options: +- `@description` — Auto- oder manuell vergeben (siehe S-09a) +- `@project` — optional, vom User gesetzt +- `@created` — Timestamp -**(a) Sessions "als wären sie connected":** -Die App connectet im Hintergrund parallel zu allen bekannten Sessions, hält für jede einen rolling Mini-Buffer (z.B. letzte 100 Zeilen) im RAM, plus die letzte gerenderte Frame-Snapshot. Switch = Render-View wechselt instant, im Hintergrund läuft Sync für volle Historie wenn nötig. → siehe auch iOS-E-01-Reply. +Kein eigener JSON-State-Store im Sidecar. -**(b) Session-Namen & Descriptions in tmux:** -- Name: tmux-Sessions HABEN bereits Namen (`tmux new -s `). Wir nutzen den als stable ID. -- Description: tmux unterstützt User-Options, `tmux set-option -t @description "…"`. Lesbar via `tmux show-options -t -v @description`. Beliebige Metadaten so anhängbar (z.B. `@project`, `@created`). -- Wir können das mit unserem eigenen JSON-State-File im Sidecar kombinieren wenn wir mehr brauchen. joe: keinen weiteren state selbst handlen. tmux oder gar nicht - -**(c) Auto-Naming via Pi (mini-prompt):** -Gute Idee. Konkret: nach ~3 User-Messages in einer namenlosen Session feuert der Sidecar einen Cheap-LLM-Call (haiku-tier, ~50 Tokens Output) gegen den bisherigen Verlauf: "Gib einen 2-4-Wort-Titel für diese Konversation." Ergebnis landet als `@description`. Manuelles Umbenennen aus der App jederzeit möglich und überschreibt das. -Open Question: Welcher Mini-LLM, welche Credentials? Wahrscheinlich derselbe Provider wie pi selbst — wir borgen pi's Konfiguration aus der Extension. Soll ich das als sub-feature S-09a notieren? joe: wir nutzen pi direkt. die cli bietet one shot an. als model nutzen wir haiku. anthropic auth ist in pi schon configuriert. - -*Rationale:* Speist Session-Switcher (iOS-E-01). *Dependencies:* S-01 -### S-10 — Bearer-Token-Auth -**MUST.** WS-Verbindungen erfordern Bearer-Token aus Sidecar-Config. -Per-Device-Tokens möglich. +### S-09a — Auto-Naming via pi +**NICE.** Nach ~3 User-Messages in einer namenlosen Session triggert +der Sidecar einen One-Shot-Call via pi-CLI: -joe: auth mit mbile sollte einfach gehen, qr code im terminal oder so. +```bash +pi --one-shot --model claude-haiku-4-5 \ + --prompt "Title for this conversation in 2-4 words: " +``` -claude: Ja, sauberer Pfad. Konkret: -- Sidecar hat einen CLI-Befehl `pi-remote pair`. Generiert ein Pairing-Token (kurzlebig, z.B. 5min gültig). -- Druckt einen QR-Code im Terminal (Unicode block chars, funktioniert per SSH). -- QR enthält: WS-URL + Pairing-Token + (siehe S-11-Reply) TLS-Fingerprint. -- iOS-App scannt → tauscht Pairing-Token gegen permanenten Bearer-Token, speichert im Keychain (iOS-G-01). -- Token kann per Device benannt sein ("jay's iPhone"), revocable via CLI (`pi-remote auth list/revoke`). +(genaue CLI-Flags TBD; pi's eigene Anthropic-Auth wird verwendet, keine +separaten Credentials nötig) -QR-Library Server-seitig: `qrcode-terminal` (Node) — winzig, kein Aufwand. +Ergebnis landet als `@description` in tmux. Manuelles Umbenennen aus +der App jederzeit möglich und überschreibt. + +*Dependencies:* S-09, pi-CLI one-shot mode + +### S-10 — Pairing & Bearer-Token-Auth +**MUST.** CLI `pi-remote pair` generiert ein kurzlebiges (5min) +Pairing-Token und druckt einen QR-Code im Terminal (Unicode block +chars, via `qrcode-terminal`). + +QR-Inhalt: `pi-remote://:?pair=&fp=` + +iOS-App scannt → tauscht Pairing-Token gegen permanenten Bearer-Token, +pinnt den TLS-Fingerprint. Token im Keychain (iOS-G-01). + +CLI: +- `pi-remote auth list` — alle Tokens +- `pi-remote auth revoke ` — Token widerrufen +- `pi-remote auth name ` — Token umbenennen (z.B. "jay's iPhone") + +*Dependencies:* S-11 + +### S-11 — TLS via Self-Signed + Trust-on-First-Use +**MUST.** Sidecar generiert beim ersten Start ein selbstsigniertes +Cert (ED25519 oder RSA-2048), 10 Jahre gültig, persistent auf Disk. +SHA-256-Fingerprint wird in den QR aus S-10 mit aufgenommen. Client +pinnt den Fingerprint beim Pairing. Bei Cert-Rotation (z.B. neuer +Host) muss re-paired werden. + +Kein Let's Encrypt, keine CA, kein Reverse-Proxy nötig. -*Rationale:* Sidecar ist ggf. öffentlich erreichbar. *Dependencies:* — -### S-11 — TLS-Termination (Hinweis) -**MUST in Production.** WS via `wss://` hinter Reverse-Proxy oder -nativ. Kein Plaintext über öffentliche Netze. +### S-12 — Health & Metrics +**NICE.** `/health` Endpoint mit Session-Count, Buffer-Size, +Connected-Clients, Disk-Watchdog-Status. Für Monitoring und +Selbst-Debugging. -joe: einfaches handling von self signed certificates. pinning via onboarding qr code oder so. -kein lust auf pki xD - -claude: Genau der Plan. Trust-on-First-Use via QR: -- Sidecar generiert beim ersten Start ein selbstsigniertes Cert (ED25519 oder RSA-2048), persistent auf Disk. -- `pi-remote pair` QR enthält neben Token auch den SHA-256-Fingerprint des Server-Certs. -- iOS-App pinnt den Fingerprint beim Pairing fest. Jede künftige Connection verifiziert dagegen. -- Cert läuft nie ab (oder 10 Jahre — egal, wir validieren ja nur den Fingerprint, nicht die CA-Chain). -- Kein Let's Encrypt, kein DNS, kein Reverse-Proxy nötig — solange du eine IP/Hostname hast die das iPhone erreicht. -- Falls Cert rotiert wird (z.B. neuer Sidecar-Host): re-pairing via neuem QR. - -Kein PKI, kein CA, kein Browser-Warning-Hell. Klar? - -### S-12 — Marker-Detection / Prompt-Grenzen -**NICE.** Sidecar versucht heuristisch oder via Extension-Hook, -Prompt-Grenzen im Stream zu erkennen und als Marker im Side-Channel zu -publizieren (`{type: "marker", seq: 12345, label: "user-prompt-start"}`). - -joe: wie robust ist das. gibt pi hier consistente marker? - -claude: Ehrlich: ich weiß es nicht sicher. Vermutung: pi/Ink gibt **keine** expliziten ANSI-Marker-Sequenzen aus, die wir parsen könnten — Ink redrawt komplette Frames, das ist alles ein einziger Stream-of-Updates. Heuristisch ginge nur "Zeile beginnt mit `> ` oder `❯`" o.ä., das ist brüchig. - -DU hast in Q-4 schon gesagt "gar nicht rein als feature". Damit fällt S-12 + iOS-E-03 + iOS-B-04 zusammen. Cleaner so. → Ich streiche S-12 in v2. - -*Rationale:* Speist Jump-to-Marker (iOS-E-03). -*Dependencies:* S-07 - -### S-13 — Health & Metrics -**NICE.** `/health` Endpoint mit Session-Count, Buffer-Size, Connected -Clients. Für Monitoring. - -*Rationale:* Selbst-Debugging im Long-Running-Setup. *Dependencies:* — --- @@ -294,456 +270,365 @@ Clients. Für Monitoring. ### Gruppe A — Rendering & Stream #### iOS-A-01 — SwiftTerm-Renderer -**MUST.** Vollwertiger ANSI-Terminal-View, der den raw Stream rendert. -Konfigurierbar: Font (monospace), Größe, Farbschema. +**MUST.** Vollwertiger ANSI-Terminal-View. Renderer-Setup mit +Truecolor, 120×40 fixed grid, configurable Font + Theme. -*Rationale:* P-1 — der Stream wird gerendert, nicht interpretiert. *Dependencies:* S-02 #### iOS-A-02 — Sequence-Cursor & Reconnect -**MUST.** Client speichert lokal die letzte `seq` pro Session. -Bei WS-Reconnect: schickt `lastSeq`, verarbeitet Delta oder Snapshot. +**MUST.** Client speichert lokal pro Session den letzten `seq`. Bei +WS-Reconnect: schickt `{type:"resume", lastSeq}`, verarbeitet Delta +oder Snapshot. -*Rationale:* P-3. *Dependencies:* S-04, S-05 #### iOS-A-03 — Stale-Frame-Display -**SHOULD.** Während Sync nach App-Wake: letzten gerenderten Frame -einfrieren, "syncing…" als subtile Overlay-Pill anzeigen. Kein leerer -Screen. +**SHOULD.** Während Sync nach App-Wake: letzten Frame einfrieren, +"syncing…" als subtile Overlay-Pill anzeigen. Kein leerer Screen. -*Rationale:* Wahrnehmung: App fühlt sich immer "live" an. *Dependencies:* iOS-A-02 #### iOS-A-04 — Local Scrollback-Cache -**SHOULD.** App puffert empfangene Bytes lokal (rolling, z.B. 5MB) für -Offline-Scrolling und Suche, auch wenn WS offline. +**SHOULD.** App puffert empfangene Bytes lokal pro Session (rolling, +default 5MB) für Offline-Scrolling und Suche. Alternate-Screen-Inhalt +wird ausgenommen. -*Rationale:* Speist iOS-E-02 (Scrollback-Search). Reduziert Server-Hits. *Dependencies:* iOS-A-01 -#### iOS-A-05 — Theme-Editor -**NICE.** Color-Schemes (Solarized, Dracula, custom), Font-Size, -Line-Height. iCloud-Sync der Preferences. +#### iOS-A-05 — Themes & Fonts +**SHOULD.** Eingebaute Themes: +- Default-Dark, Default-Light +- Solarized Light, Solarized Dark +- Monokai, Dracula +- Nord, Gruvbox Dark, Gruvbox Light +- Tomorrow Night +- GitHub Light, GitHub Dark +- System (folgt iOS Light/Dark Mode) -joe: gerne eine klassische liste bekannter defaults mitliefern (inkl. monokai o.vgl.) +Eingebaute Fonts (gebundlet): +- JetBrains Mono (Default) +- Hack +- SF Mono +- Menlo +- Fira Code +- Cascadia Code +- IBM Plex Mono +- Monaspace Neon -claude: 👍 — ich schreibe in v2 eine konkrete Liste ein: **Solarized Light/Dark, Monokai, Dracula, Nord, Gruvbox Dark/Light, Tomorrow Night, GitHub Light/Dark, System (folgt iOS Dark Mode), Default**. Plus "Custom" für eigene Farbtabellen. iCloud-Sync nur für Custom-Themes. -joe: top. ich will auch unbedingt mind. "Hack" und Jetbrains Mono als font. +Custom Themes editierbar, iCloud-Sync nur für Custom. -*Rationale:* Lange Lesephasen → Augenkomfort. *Dependencies:* iOS-A-01 ### Gruppe B — Input & Modifier -#### iOS-B-01 — Software-Keyboard + Text-Input -**MUST.** Standard-iOS-Keyboard. Text wird beim Tippen oder beim -Enter-Tap an `send-keys` geschickt. +#### iOS-B-01 — Software-Keyboard, Direct-Passthrough +**MUST.** Standard-iOS-Keyboard. **Mode (b) direct-passthrough**: +jeder Tastendruck geht sofort als `send-keys`. Enter sendet `\r` an +pi → pi behandelt selbst. Kein App-eigenes Compose-Feld. -joe: leeres enter sendet new line im editor +Shift+Enter (newline) via dediziertem `⇧↵`-Button in der Modifier-Bar +(siehe iOS-B-02). -claude: Hmm, ich muss das nochmal verstehen — meinst du: - -**(a)** Eingabefeld der App ist ein **mehrzeiliges Compose-Feld** (du tippst kompletten Prompt mit Newlines, drückst dann einen separaten "Send"-Button). Enter im Feld = Newline einfügen. -Vorteil: bequem für längere Prompts, kein Risiko versehentlich abzuschicken. -Nachteil: nicht 1:1 wie im Terminal — pi sieht den Prompt erst beim Send als ein Block. - -**(b)** Eingabe ist **direkt-passthrough** (jeder Tastendruck geht sofort als `send-keys`). Enter sendet `\r` an pi → pi terminiert Eingabe und schickt sie an den Agent. Wenn du in pi's eigenem multi-line-Modus bist (Shift-Enter o.ä.), entsprechend. - -Ich hatte (b) im Kopf, weil P-1/P-4 (Terminal-Bedienung). Aber dein Kommentar klingt nach (a) mit einer Hybrid-Regel: "leeres Enter = newline einfügen" (also Modus a, nur smart). - -Welche Variante meinst du? Mein Vorschlag: **(b) als Default, (a) optional togglebar** — weil wir sonst pi's eigenes Input-Handling (z.B. multi-line-Mode in pi) duplizieren und Bugs kriegen. - -joe: du hast recht. es ist b. aber ich will den shift+enter easily available haben. termius hat shortcuts die man auf volume down und up legen kann. oder doch besser in der status leiste? - -*Rationale:* Die Basis. *Dependencies:* S-03 #### iOS-B-02 — Modifier-Bar -**MUST.** Akzessoriumsleiste über der Tastatur mit: `ESC`, `Tab`, -`Ctrl`, `Alt`, `↑↓←→`, `Enter`. Sticky-Toggle für Modifier (ein Tap = -hält bis zur nächsten Eingabe). +**MUST.** Accessory-Bar über der Tastatur: -joe: welche modifier machen hier sinn? wir sind ja pi agent centric. gibts noch alternativen. der platz ist ja begrenzt. - -claude: Guter Punkt — generischer Terminal-Approach passt nicht zu pi-centric. Konkret was pi tatsächlich braucht: - -**Aus pi-Usage tatsächlich essenziell:** -- `Esc` — pi abbrechen / aus Modi raus -- `↑/↓` — History scrollen, in Auswahllisten navigieren -- `Tab` — Autocomplete in pi-Eingabe (Dateien, Commands) -- `Enter` — senden -- `Ctrl-C` — laufendes Tool abbrechen / pi-Action stoppen joe: macht das nicht Esc eh schon? - -**Nice-to-have:** -- `Ctrl-D` — pi beenden (vielleicht versteckt, zu gefährlich für leichte Touch) -- `Ctrl-L` — clear - -**Vorschlag finale Modifier-Bar (pi-tuned):** ``` -[Esc] [Tab] [↑] [↓] [Ctrl-C] [/] [🎙] joe: lieber ctrl als ctrl+c. und / kann raus. +[Ctrl] [Esc] [Tab] [←] [↑] [↓] [→] [⇧↵] [🎙] [📋] ``` -- `/` ist Shortcut: öffnet Slash-Command-Palette (iOS-C-04) ODER tippt einfach `/` ein -- `🎙` ist Voice-Input (iOS-C-06) -- Pfeile nur ↑/↓ weil ←/→ via Touch-Cursor selten (und Touch-Cursor flog ja raus) joe: link rechts pfeile brauche ich eben weil touch cursor rausflog) -- Ctrl-C als eigener Button weil häufigste Aktion -- Kein Alt/Meta, kein Ctrl-als-Modifier-Toggle — wir mappen 2-3 wichtige Ctrl-Combos auf eigene Buttons, fertig joe: s.o. -Wenn man im pi-internen Editor (für längere Prompts) ist, könnten ←/→ einblendbar sein context-aware. Aber MVP ohne. +- **Ctrl** ganz links — Sticky-Toggle: ein Tap → leuchtet → nächste + Taste wird als Ctrl+X gesendet. Beliebige Ctrl-Combos möglich + (Ctrl-C, Ctrl-D, Ctrl-L, Ctrl-U, ...). +- **Esc** — eigener Button, dauerhaft sichtbar. +- **Tab** — Autocomplete in pi. +- **Pfeiltasten** (Mitte) — History scrollen, in Menüs navigieren, + Input-Cursor bewegen. +- **`⇧↵`** — Shift+Enter, ein Tap = `\n` (newline in pi multi-line). +- **`🎙`** — Voice-Input (iOS-C-06). +- **`📋`** — Paste-Button ganz rechts (öffnet Paste-Sheet, iOS-B-08). -Passt das? Was fehlt dir noch in deinem täglichen pi-Flow? +Bei knappem Platz (iPhone-Portrait) ist die Bar horizontal scrollbar. -*Rationale:* Ohne ESC und Pfeile ist iOS-Terminal-Nutzung untauglich. *Dependencies:* iOS-B-01 #### iOS-B-03 — Long-Press-Repeat -**SHOULD.** Long-Press auf Pfeiltasten / Backspace → repeat mit +**SHOULD.** Long-Press auf Pfeiltasten oder Backspace → repeat mit Beschleunigung. -*Rationale:* Lange Navigation in Prompts. *Dependencies:* iOS-B-02 -#### iOS-B-04 — Tap-to-Cursor -**SHOULD.** Tippen auf eine Position in der aktuellen pi-Eingabezeile → -App schickt entsprechend viele `←`/`→` um den Cursor dahin zu bewegen. - -*Rationale:* Cursorbewegung per Touch — einer der "killer" Hebel den ein -dummes Terminal nicht hat. -*Dependencies:* iOS-A-01, iOS-B-02. Heuristik: aktuelle Zeile = letzte -Zeile vor Prompt-Marker; ohne S-12 nur best-effort. - -joe: brauchen wir nicht fliegt raus. - -claude: ✂️ raus. Konsistent mit dem Marker-Drop. - -#### iOS-B-05 — Selection & Copy -**MUST.** Doppel-Tap → Wort. Tripel-Tap → Zeile. Lange-Press + Drag → +#### iOS-B-04 — Selection & Copy +**MUST.** Doppel-Tap → Wort. Tripel-Tap → Zeile. Long-Press + Drag → Range. Native iOS-Copy-Menu. -*Rationale:* Output ist oft das Wertvolle (Code, IDs, Pfade). *Dependencies:* iOS-A-01 -#### iOS-B-06 — Pinch-Zoom Font +#### iOS-B-05 — Pinch-Zoom Font **SHOULD.** Pinch in der Terminal-View → Font-Size live. -*Rationale:* Lesbarkeit. + *Dependencies:* iOS-A-01 -joe: love it +#### iOS-B-06 — Hardware-Keyboard-Support +**SHOULD.** Externe iPad-Keyboards: +- Caps→Esc-Remap (optional) +- Modifier-Pass-Through (Cmd, Option, Ctrl) +- App-Shortcuts: + - Cmd-K — Clear + - Cmd-T — New session + - Cmd-1..9 — Session switch + - Cmd-F — Scrollback-Search + - Cmd-, — Settings + - Cmd-Shift-P — Slash-Command-Palette -claude: 🤝 - -#### iOS-B-07 — Hardware-Keyboard-Support -**SHOULD.** Externe iPad-Keyboards: Caps→Esc-Remap (optional), -Modifier-Pass-Through (Cmd, Option, Ctrl), Shortcuts wie Cmd-K → klar -definierte App-Aktionen. - -*Rationale:* iPad mit Magic Keyboard = ernsthafte Pi-Workstation. *Dependencies:* iOS-B-01 -joe: love it +#### iOS-B-07 — Reachability / One-Hand-Mode +**NICE.** iPhone-Querformat: Modifier-Bar gespiegelt für einhändige +Bedienung. -claude: 🤝 — wird in v2 noch konkretisiert welche Cmd-K Shortcuts sinnvoll sind (Cmd-K Clear, Cmd-T new session, Cmd-1..9 session switch, Cmd-F search). - -#### iOS-B-08 — Reachability / One-Hand-Mode -**NICE.** iPhone-Querformat: Modifier-Bar + Action-Buttons auf einer -Seite gespiegelt, daumengerecht. - -*Rationale:* Einhandbedienung. *Dependencies:* iOS-B-02 -#### iOS-B-09 — Smart Paste -**SHOULD.** Clipboard-Indicator in der Modifier-Bar. Tap → Preview -(insb. multi-line), Confirm → paste. Bei Pi-detection (z.B. bracketed -paste mode an) korrekt einrahmen. +#### iOS-B-08 — Smart Paste (Confirm) +**SHOULD.** `📋`-Button in der Modifier-Bar zeigt Clipboard-Vorschau- +Chip ("📋 12 lines, 847 chars"). Tap → Sheet mit vollem Preview, +Insert / Cancel. Verhindert versehentliches Paste großer Blobs. -joe: check ich nicht. erkläre nochmal - -claude: Sorry, zu kryptisch. Zwei Sachen die zusammenhängen: - -**"Smart Paste" = Confirm-vor-Paste** -Problem: du kopierst auf dem iPhone aus Safari einen Stacktrace, wechselst in die pi-App, tappst aus Versehen auf "Paste" → riesiger Blob wird an pi geschickt, möglicherweise mit ANSI-Escape-Codes oder Geheimnissen drin. Schwer zu undo. -Lösung: Clipboard-Indicator in der Modifier-Bar zeigt einen kleinen Vorschau-Chip ("📋 12 lines, 847 chars"). Tap → Sheet mit vollem Preview + "Insert" / "Cancel". Erst dann geht's an pi. - -**"Bracketed Paste Mode" = pi unterscheidet getippte vs. gepastete Eingabe** -Technischer Hintergrund: moderne Terminals wrappen gepastete Eingabe in spezielle ESC-Sequenzen (`\e[200~` vor Paste, `\e[201~` danach). pi/Ink kann dann erkennen "das war Paste, nicht getippt" und z.B. den ganzen Block als einen Prompt nehmen statt nach jedem Newline zu submitten. Wenn pi diesen Modus aktiviert (`\e[?2004h` im Stream), muss unsere App das mitkriegen und beim Paste die richtigen Sequenzen drumherum schicken. - -Die beiden Sachen sind orthogonal — Confirm-vor-Paste ist UX, Bracketed-Paste ist Protocol-Correctness. Ich hatte sie in einem Bullet gemischt, das war unklar. In v2 trenne ich sie in **iOS-B-09a (Paste-Confirm)** und **iOS-B-09b (Bracketed-Paste-Compliance)**. - -Macht das mehr Sinn? Beide drin lassen, oder nur eines? -joe: passt. - -*Rationale:* Sicherer als blindes Paste. *Dependencies:* iOS-B-01 +#### iOS-B-09 — Bracketed-Paste-Compliance +**SHOULD.** Client trackt im Stream `\e[?2004h` / `\e[?2004l`. Wenn +aktiv, wird bei Paste die `{type:"paste"}` Variante an S-03 geschickt, +sodass pi den ganzen Block als Paste erkennt. + +*Dependencies:* S-03, iOS-B-08 + ### Gruppe C — Pi-aware Augmentation #### iOS-C-01 — Status-Bar -**MUST.** Top-Bar zeigt Connection-Status, Session-Name und -abgeleiteten Pi-State (`● thinking` / `⏵ tool: Edit` / `▶ waiting` / -`⏸ idle`) basierend auf Side-Channel-Events. +**MUST.** Top-Bar zeigt: Connection-Status, Session-Name, Pi-State +(`● thinking` / `⏵ tool: Edit` / `▶ awaiting` / `⏸ idle`) basierend auf +S-07. -*Rationale:* Schneller Blick: was tut Pi gerade? *Dependencies:* S-07 -#### iOS-C-02 — Push-Notification bei State-Change -**MUST.** Wenn App im Background und Pi wechselt zu -`awaiting-input` → Push-Notification "Pi ist fertig". Tap → -App in der richtigen Session. +#### iOS-C-02 — Push-Notification bei Awaiting-Input +**MUST.** Wenn App im Background und Pi wechselt zu `awaiting-input`: +Push-Notification "Pi ist fertig". Tap → App öffnet in der richtigen +Session. -*Rationale:* Killer-Feature für "Session läuft lange, kommt wieder -wenn fertig". -*Dependencies:* S-07, APNs-Setup +*Dependencies:* S-07, APNs #### iOS-C-03 — Haptic Feedback bei State-Wechseln -**NICE.** Subtile Vibration bei `thinking → idle` Übergang. -*Rationale:* Fühlt sich "lebendig" an. +**NICE.** Subtile Vibration bei `thinking → idle` oder +`thinking → awaiting-input`. + *Dependencies:* S-07 #### iOS-C-04 — Slash-Command-Palette -**SHOULD.** Swipe-up-Sheet mit nativer Liste aller Slash-Commands -(aus S-08), fuzzy-search, Tap → injiziert via `send-keys`. Bei -Commands mit Argumenten → Formular-View für die Eingabe. +**SHOULD.** **Long-Press auf die Modifier-Bar** → öffnet Sheet mit +nativer Liste aller Slash-Commands (aus S-08), Fuzzy-Search. Tap → +injiziert via `send-keys`. Commands mit Argumenten → Formular-View. -*Rationale:* Slash-Commands sind im Terminal mühsam zu tippen. *Dependencies:* S-03, S-08 -#### iOS-C-05 — Snippet-Library -**SHOULD.** Vorgefertigte Prompts mit Variablen ("review {{branch}} -for security"). iCloud-Sync. Native Edit-UI. - -*Rationale:* Wiederkehrende Prompts. -*Dependencies:* iOS-B-01 - -joe: will ich mit pi internen tools lösen, kann raus - -claude: ✂️ raus. Macht Sinn — Snippets gehören thematisch zu pi, nicht zum Remote-Client. - -#### iOS-C-06 — Voice-to-Prompt -**NICE.** Mikro-Button → Speech-Recognition (lokal, iOS native) → +#### iOS-C-05 — Voice-to-Prompt +**NICE.** `🎙`-Button → Speech-Recognition (lokal, iOS native) → editable Preview → Send. -*Rationale:* Unterwegs schneller als tippen. *Dependencies:* iOS-B-01, Mic-Permission ### Gruppe D — Session & Navigation -#### iOS-E-01 — Session-Switcher -**SHOULD.** Native iOS-Liste der verfügbaren tmux-Sessions (aus S-09). -Wechsel = neuer WS-Stream, vorhergehender wird im Hintergrund weiter -gepuffert. +#### iOS-D-01 — Session-Switcher (MUST) +**MUST.** Native iOS-Liste der verfügbaren Sessions (via S-09). Spawn / +Rename / Kill direkt aus dem Switcher. -joe: kann man hier schon auf basis einer bestimmten aktion des users preloaden? wäre nice, wenn es sich so anfühlt als wäre die session "schon immer da gewesen" :D - -claude: Yes! Konkret zwei Strategien kombinierbar: - -**(1) Background-Pre-Connect aller Sessions** -App connectet im Hintergrund zu allen bekannten Sessions parallel, hält für jede einen kleinen In-Memory-Stream-Buffer. Switch = Renderer wechselt instant, ist schon synchronisiert. Kosten: paar Sockets + paar MB RAM für Buffer pro Session. Akzeptabel solange du < ~10 aktive Sessions hast. - -**(2) Optimistic-Switch mit Stale-Frame** -Swipe zu Session B → sofort den letzten gerenderten Frame von B zeigen (lokal gecached), gleichzeitig live-Stream nachholen. Du siehst niemals einen leeren Screen, nur eine kurze "sync"-Pille. - -**(3) Bonus: Predictive Pre-Render bei Switcher-Open** -Wenn User die Switcher-Liste öffnet → App pingt alle Sessions, holt Frame-Snapshots, zeigt Live-Thumbnails (kleine Mini-Terminal-Vorschauen in der Liste). Switch ist dann visuell "ich sehe schon was drin ist". - -Kosten: trivial server-seitig (capture-pane), client-seitig moderate Memory. - -Ich trage (1) + (2) + (3) in v2 als iOS-E-01a/b/c ein. Soll (3) rein? Wäre wirklich "feels-magic"-Material. - -*Rationale:* Pro Projekt eine Pi-Session. *Dependencies:* S-09 -#### iOS-E-02 — Scrollback-Search -**SHOULD.** Such-Sheet sucht im lokalen Scrollback-Cache (iOS-A-04) -mit Highlight + Jump-to-Match. +#### iOS-D-01a — Background Pre-Connect +**SHOULD.** App connectet im Hintergrund zu allen bekannten Sessions +parallel, hält pro Session einen kleinen In-Memory-Stream-Buffer + +letzten Frame. Switch = Renderer wechselt instant. + +Kosten: ein Socket pro Session, ein paar MB RAM. Akzeptabel solange +< ~10 aktive Sessions. + +*Dependencies:* iOS-D-01 + +#### iOS-D-01b — Optimistic-Switch mit Stale-Frame +**SHOULD.** Swipe zu Session B → sofort gecachten Frame zeigen, +parallel live-Sync. Niemals leerer Screen, nur kurze "sync"-Pille. + +*Dependencies:* iOS-D-01, iOS-D-01a + +#### iOS-D-01c — Predictive Thumbnails +**SHOULD.** Im Session-Switcher zeigt jede Session eine kleine Live- +Mini-Vorschau (z.B. 40×12 Zeichen Snapshot via `tmux capture-pane`). +Beim Öffnen des Switchers werden die Thumbnails aktualisiert. + +*Dependencies:* iOS-D-01 + +#### iOS-D-02 — Scrollback-Search +**SHOULD.** Such-Sheet sucht im lokalen Scrollback-Cache (iOS-A-04) +mit Highlight + Jump-to-Match. Auch offline nutzbar. -*Rationale:* "Wo war das eine Snippet?" — Standard-Bedarf. *Dependencies:* iOS-A-04 -#### iOS-E-03 — Jump-to-Marker -**NICE.** Vertikales Swipe-Gesture springt zwischen Prompt-Grenzen. -Visualisiert als kleine Edge-Indicator-Linien. +### Gruppe E — Background & Lifecycle -*Rationale:* "Vor 3 Antworten gab's diese Idee" — Standard-Bedarf. -*Dependencies:* S-12, iOS-A-01 +#### iOS-E-01 — WS-Keepalive +**MUST.** Ping/Pong-Frames im Foreground halten Connection. iOS +suspended WS gerne ohne Notification. -joe: lassen wir raus. dazu aber ein anderer gedanke. wie viel info können wir reliable von pi über die session und die tree view bekommen? das feature vom pi ist so zentral, dass es nativen ios support verdient +*Dependencies:* S-02 -claude: ✂️ iOS-E-03 raus. Aber der **andere Gedanke** ist groß — wert eines eigenen Threads. +#### iOS-E-02 — Wake-up-Sync +**MUST.** App-Foreground-Event triggert sofort Reconnect + Delta-Pull. +Spinner falls > 200ms. Ziel < 1s sichtbarer Sync (P-3). -**Zur Frage: Tree-View nativ in iOS rendern.** +*Dependencies:* iOS-A-02 -Das wäre die einzige Stelle, an der wir P-1 ("Stream ist die einzige Wahrheit") aufweichen. Argument dafür: Tree-State ist **Meta-State der Session**, nicht **Inhalt der Session**. Das ist eine andere Kategorie als "Markdown rendern statt ANSI". Konsistent argumentierbar. +### Gruppe F — Security + +#### iOS-F-01 — Token im Keychain +**MUST.** Bearer-Token aus S-10 wird im iOS Keychain gespeichert. + +*Dependencies:* S-10 + +#### iOS-F-02 — Face-ID / Touch-ID Gate +**SHOULD.** Biometrie-Lock vorm Öffnen der App. **Opt-in via Setting, +default off.** + +*Dependencies:* — + +#### iOS-F-03 — TLS-Pinning via QR-Pairing +**MUST.** Beim Pairing (S-10) wird der Cert-Fingerprint gepinnt. Jede +künftige Connection verifiziert dagegen. Re-Pairing bei Cert-Rotation. + +*Dependencies:* S-10, S-11 + +### Gruppe T — Tree Navigation (PENDING) + +> **Status:** Pending — abhängig vom Outcome von +> [Spike-0a](#spike-0a--extensionapi-audit). Wenn pi's Tree über die +> ExtensionAPI nicht zugänglich ist, fällt diese Gruppe weg oder wird auf +> reine Slash-Command-Injection reduziert (`/fork`, `/new`, `/compact`). + +#### S-13 (NEU) — Tree-State Side-Channel +**TBD.** Sidecar liefert pi's Conversation-Tree als JSON über +Side-Channel + Live-Updates bei Branch/Fork/Compact/Checkout: -**Was wir bräuchten (Server-seitig):** -Ein neuer Side-Channel-Endpoint, der pi's Tree als strukturiertes JSON liefert + Live-Updates bei Branch/Fork/Compact. Format ungefähr: ```json { + "type": "tree", "nodes": [ - {"id": "abc", "parent": null, "summary": "main", "messageCount": 42, "createdAt": "..."}, - {"id": "def", "parent": "abc", "summary": "explored iOS", "messageCount": 18, ...} + {"id":"abc","parent":null,"summary":"main","msgCount":42,"createdAt":"..."}, + {"id":"def","parent":"abc","summary":"explored iOS","msgCount":18} ], "current": "def" } ``` -**Was wir brauchen (Client-seitig):** -Ein natives SwiftUI-Tree-View-Sheet. Tap auf Knoten → entweder direkter Switch via `/checkout ` an pi gesendet, oder Preview-Modus mit Stream-Snapshot des Knotens. +*Dependencies:* Spike-0a -**Großes Aber:** -Das hängt komplett davon ab, was die ExtensionAPI hergibt. Das ist genau das Thema von Q-2/Q-3 (was kann die ExtensionAPI?). Wir hatten in der vorherigen Session festgestellt: `pi.sendUserMessage()` umgeht Slash-Command-Processing, `session.prompt()` ist nicht exposed, der Tree wird intern in der Runtime gehalten — Zugriff auf Tree-State ist nicht öffentlich. +#### iOS-T-01 — Native Tree-View +**TBD.** SwiftUI-Tree-Sheet mit allen Knoten. Tap auf Knoten → +Checkout via Slash-Command-Injection. Aktueller Knoten visuell +hervorgehoben. -**Vorschlag:** Ich trage in v2 einen neuen Feature-Block **Gruppe T — Tree Navigation** ein, mit dem expliziten Hinweis "depends on ExtensionAPI investigation". Konkret: -- **S-14 (NEU)** — Tree-State-Endpoint + Live-Updates (server-seitig, bedingt machbar) -- **iOS-T-01 (NEU)** — Native Tree-View mit Checkout-Action -- **iOS-T-02 (NEU)** — Branch/Fork-Action aus der App heraus (`/fork`, `/new`, `/compact` via slash-command-injection) +*Dependencies:* S-13 -Ohne ExtensionAPI-Verbesserung sind iOS-T-01/T-02 nur via Slash-Command-Injection halbgar machbar (wir sehen den Tree nicht, können aber Commands schicken). Mit ExtensionAPI-Erweiterung wird's mächtig. +#### iOS-T-02 — Branch / Fork Actions +**TBD.** Aus dem Tree-Sheet heraus `/fork`, `/new`, `/compact` als +native Buttons. Werden via Slash-Command-Injection an pi gesendet. -Soll ich diese Gruppe T in v2 mit aufnehmen + als ersten Spike-Punkt "ExtensionAPI-Audit" definieren? - -#### iOS-E-04 — Bookmarks -**NICE.** Long-Press auf Stelle im Scrollback → benannte Marke. -Bookmarks-Sheet listet sie, Tap → Jump. - -*Rationale:* Manuelle Marker für wichtige Stellen. -*Dependencies:* iOS-A-04 - -joe: brauchen wir nicht. raus - -claude: ✂️ raus. - -### Gruppe E — Background & Lifecycle - -#### iOS-F-01 — WS-Keepalive -**MUST.** Ping/Pong-Frames im Foreground halten Connection auf -zombie-detection-bewussten iOS-Stacks. - -*Rationale:* iOS suspended WS gerne ohne Notification. -*Dependencies:* S-02 - -#### iOS-F-02 — Wake-up-Sync -**MUST.** App-Foreground-Event triggert sofort Reconnect + Delta-Pull. -Spinner falls > 200ms. - -*Rationale:* P-3. -*Dependencies:* iOS-A-02 - -#### iOS-F-03 — Silent-Push-Trigger für Wake -**NICE.** Server kann Silent-Push schicken um App zu wecken bevor -User selbst auf das Icon tippt → Daten sind schon da. - -*Rationale:* "App fühlt sich an als wär sie nie weg gewesen." -*Dependencies:* S-07, APNs - -joe: wann sollte sie das tun? geht das überhaupt mit ios aggresivem app lifecycle management? - -claude: Geht — aber mit Caveats die das Feature ziemlich entwerten. - -**Mechanik:** APNs unterstützt "Silent Pushes" (`content-available: 1`, kein Alert). iOS weckt die App im Background für ~30s Processing-Zeit. App kann WebSocket aufmachen, Delta pullen, schlafen. - -**Caveats:** -- iOS coalesced/dropped Silent Pushes nach eigenem Gusto. Apple gibt keine Garantie der Zustellung, nur "best effort". -- Budget ist limitiert: ~2-3 erfolgreiche Background-Wakes pro Stunde, danach drosselt iOS aggressiv. -- Wenn User die App im App-Switcher force-quit-tet, kommen **gar keine** Background-Wakes mehr — auch Silent Push nicht. -- Battery / Low-Power-Mode killt das komplett. - -**Realistischer Wert für uns:** marginal. Das echte Killer-Feature ist iOS-C-02 (sichtbare Push wenn pi `awaiting-input` erreicht) — die ist nicht silent und wird zuverlässig zugestellt. - -**Vorschlag:** iOS-F-03 streichen. Stattdessen verlassen wir uns auf iOS-F-02 (Wake-up-Sync beim manuellen Öffnen), das ist mit Sequence-Cursor + permessage-deflate schnell genug. Silent-Push lohnt den Aufwand nicht. - -Einverstanden mit streichen? -joe: ja - -### Gruppe F — Security - -#### iOS-G-01 — Token im Keychain -**MUST.** Bearer-Token aus S-10 wird im iOS Keychain gespeichert, -nie im Klartext. -*Dependencies:* S-10 - -#### iOS-G-02 — Face-ID / Touch-ID Gate -**SHOULD.** Optionaler Biometrie-Lock vorm Öffnen. -*Rationale:* Pi-Sessions können sensitive Daten zeigen. -*Dependencies:* — - -joe: ja, will ich haben - -claude: 👍 — soll Face-ID dann verpflichtend (`MUST`) sein oder optional toggle? Bei optional ist's Setting. Bei MUST muss jeder App-Wake erst durch Face-ID. Mein Vorschlag: **opt-in via Setting, default off** — sonst nervt das Re-Auth alle paar Minuten. joe: opt in - -#### iOS-G-03 — Certificate-Pinning -**NICE.** TLS-Pinning gegen Sidecar-Host. -*Rationale:* MITM-Hardening. -*Dependencies:* S-11 - -joe: einfaches handling von self signed certificates. pinning via onboarding qr code oder so. -kein lust auf pki xD - -claude: Siehe S-11-Reply — gleicher Mechanismus, das ist dasselbe Feature aus Client-Sicht. In v2 fasse ich S-11 + iOS-G-03 zu einem zusammenhängenden "TLS via TOFU+Pinning" Block. +*Dependencies:* S-13 --- -## 6. Out of Scope (locked rejections) +## 6. Out of Scope (locked) -- **Rich Chat-Rendering.** Wir parsen den Stream nicht. Wenn Pi Markdown - ausgibt, sieht der Nutzer ANSI-Markdown wie im Terminal. +- **Rich Chat-Rendering.** Wir parsen den Stream nicht. ANSI bleibt ANSI. - **Embedded Mini-Terminals / Block-Selection.** Es gibt einen Stream. - **Multi-User-Sharing.** Solo. -- **Org/Team-Features, Cloud-Hosted-Service.** Selbstgehostet, ein Mensch. -- **Inline-Image-Rendering** (iTerm2 image protocol etc.). Eventuell - später, aber nicht in v1. +- **Org/Team-Features, Cloud-Hosted-Service.** Selbstgehostet. +- **Inline-Image-Rendering** (iTerm2 protocol etc.). Eventuell später. - **Eigenes Mosh.** tmux + WebSocket reicht. -- **Cross-platform-Client (Android / Web).** Nur iOS nativ. Bestehende - HTML-UI bleibt aber bestehen (separates Artefakt). +- **Cross-platform-Clients (Android / Web).** Nur iOS nativ. Die + bestehende HTML-UI bleibt als separates Artefakt. +- **Silent-Push-Background-Wake.** iOS coalesced unzuverlässig; manueller + Wake-up-Sync (iOS-E-02) reicht. +- **Marker-Detection / Jump-to-Marker.** Pi liefert keine zuverlässigen + Marker. +- **Tap-to-Cursor.** Ohne Marker-Detection nicht robust machbar. +- **Eigener Sidecar-State-Store.** State lebt in tmux (P-6). +- **Disk-Buffer-Rotation.** Ein Cap, drüber hinaus = Daten verloren. + Snapshot funktioniert weiter. +- **Bookmarks / manuelle Marker.** Pi-interne Tools sollen das übernehmen. +- **Snippet-Library** (Prompts mit Variablen). Pi-interne Tools. --- -## 7. Offene Fragen +## 7. Spike-0a — ExtensionAPI-Audit -- **Q-1** — Wie wird `pi-remote attach` zur Default-Methode? Shell-Alias - manuell vs. in pi-Launcher gepatcht? - joe: irrelevant. kümmer ic mich selbst drum +**Status:** Muss vor Phase 0 abgeschlossen sein. - claude: ✂️ Q-1 raus. -- **Q-2** — Side-Channel-State: liefert die aktuelle ExtensionAPI alle - benötigten Events (`thinking`, `awaiting-input`)? Falls nicht, was - brauchen wir upstream? - joe: was kann die extension api denn? das sollten wir vorher prüfen +**Ziel:** Klären, welche der Features S-07, S-08, S-13 sowie Gruppe T +mit der heutigen pi-ExtensionAPI realisierbar sind und welche Upstream- +Erweiterungen wir brauchen. - claude: 👍 — das ist der zentrale Spike vor Phase 0. Ich schlage vor wir definieren das als **Spike-0a — ExtensionAPI-Audit**: - - Was liefert sie heute an Events? (`thinking`, `awaiting-input`, `tool-start/end`, Stream-Indicators?) - - Welche Slash-Commands kann sie abfragen? (Registry exposed oder nicht?) - - Tree-State zugänglich? (Read-only? Subscribe? Mutations via Command-Injection?) - - `pi.prompt()` oder Äquivalent — gibt's einen Weg Slash-Commands programmatisch zu dispatchen? - - Output: ein Markdown-Dokument `docs/EXTENSION-API-AUDIT.md` das alle Features dieser Spec zu konkreter ExtensionAPI-Capability mapped. Das blockt nichts am Stream selbst (S-01-S-05) — das funktioniert ohne API-Erweiterung — aber alles unter S-07, S-08, Gruppe C, Gruppe T hängt daran. - - Soll ich diesen Spike als ersten Punkt vor Phase 0 anlegen? -- **Q-3** — Slash-Command-Registry: ist die Liste über ExtensionAPI - abrufbar oder brauchen wir Hack? - joe: siehe Q2 +**Output:** Markdown-Dokument `docs/EXTENSION-API-AUDIT.md`, das für +jede ExtensionAPI-Capability dokumentiert: +- API-Signatur +- Welche Events / Daten exposed sind +- Welches Spec-Feature darauf basiert +- Workaround falls API fehlt (`(pi as any)`, Slash-Injection, etc.) +- Falls Upstream-Change nötig: konkreter Vorschlag - claude: 👍 — gleicher Spike. -- **Q-4** — Marker-Detection (S-12): rein heuristisch reicht für iOS-B-04 - und iOS-E-03? Oder Extension-Hook nötig? - joe: gar nicht rein als feature +**Konkrete Fragen:** +- Welche Lifecycle-Events liefert die ExtensionAPI? + (`thinking-start/end`, `tool-start/end`, `awaiting-input`, ...) +- Ist die Slash-Command-Registry abrufbar? In welcher Form? +- Wie ist der Conversation-Tree intern repräsentiert? Read-Access? + Subscribe? Mutations? +- Gibt es einen `pi.prompt(text)` oder Äquivalent, das Slash-Commands + korrekt dispatched? +- Welche Tool-Call-Daten sind sichtbar (Tool-Name, Argumente, + Ergebnis)? - claude: ✂️ — bestätigt. S-12, iOS-E-03, iOS-B-04 fallen alle damit. -- **Q-5** — Apple Developer Program ($99/Jahr) vs. AltStore / Sideloadly? - Für Solo-Use beides möglich. - joe: apple developer +**Nicht-Blockend:** Stream-Path (S-01–S-06) und Input (S-03) sind vom +Audit unabhängig — die können parallel oder vorab beginnen. - claude: 👍 — $99/Jahr. Closed. -- **Q-6** — APNs: eigener Push-Server vs. simple background-fetch? - joe: bietet apple da keine lösung? +--- - claude: Doch, **APNs** (Apple Push Notification service) ist Apples Lösung. Aber Apple stellt nur den Push-**Endpunkt** bereit (`api.push.apple.com`), nicht den Server der die Pushes auslöst — das musst du selbst betreiben. +## 8. Phasen - In unserem Setup ist der "Push-Server" einfach der Sidecar selbst: - - Sidecar hält ein APNs-Auth-Token (JWT mit deinem Developer-Key, signed via Apple) - - Wenn Pi-State zu `awaiting-input` wechselt → Sidecar POSTet an APNs-Endpunkt mit der Device-Token (kommt vom iOS-Client beim Pairing) → Apple liefert die Notification ans iPhone aus. - - Keine zusätzliche Infrastruktur, kein Firebase / OneSignal / Push-Gateway dazwischen. Apples APNs ist HTTP/2 mit JWT, ca. 50 Zeilen Node. +> Phase-Aufwände bewusst weggelassen (sind in v1 gestrichen worden). - Library: `node-apn` oder `@parse/node-apn`. Setup: APNs-Auth-Key generieren im Developer-Portal (gehört zu deinem Apple-Dev-Account, $99 Q-5), Key in Sidecar-Config, fertig. +- **Spike-0 — Stream-PoC** — `pi-remote-control` um tmux pipe-pane + + WS-Stream erweitern. Verifizieren dass pi sauber in tmux läuft (kein + Crash, Alternate-Screen sauber, kein Latency-Problem). +- **Spike-0a — ExtensionAPI-Audit** — siehe oben. +- **Phase 1 — Sidecar production-ready** — S-01 bis S-06, S-09, S-10, + S-11. Optional je nach Audit: S-07, S-08, S-13. +- **Phase 2 — iOS-App MVP** — Renderer (Gruppe A), Input (Gruppe B + ohne Hardware-KB), Status-Bar (iOS-C-01), Session-Switcher + (iOS-D-01 + a/b), Reconnect-Lifecycle (Gruppe E), Auth (Gruppe F). +- **Phase 3 — iOS-App Augmentation** — Slash-Palette (iOS-C-04), + Voice (iOS-C-05), Thumbnails (iOS-D-01c), Scrollback-Search + (iOS-D-02), Hardware-KB (iOS-B-06). +- **Phase 4 — Tree (conditional)** — Gruppe T, sofern Spike-0a + positiv. - Also: **kein eigener Push-**Server**, nur ein eigener Push-**Client** (= der Sidecar). Closed? +--- + +## 9. Offene Punkte für v3 + +- **Q-A** — pi-CLI one-shot mode für S-09a: CLI-Flags müssen verifiziert + werden. Existiert das in der heutigen pi-CLI? +- **Q-B** — Wenn Spike-0a zeigt dass S-07/S-08 nicht ohne Hack möglich: + Hack akzeptieren (`(pi as any)`) oder Upstream-Change pushen? +- **Q-C** — APNs-Setup Details (Auth-Key-Provisioning, Token-Lifecycle).