pi-remote-control/docs/reference/SPEC-ios-app-review-v1.md

750 lines
38 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/<id>` (kill), `PATCH /sessions/<id>` (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 <id> '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/<session>.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/<session>.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 <name>`). Wir nutzen den als stable ID.
- Description: tmux unterstützt User-Options, `tmux set-option -t <session> @description "…"`. Lesbar via `tmux show-options -t <session> -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 <id>` 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?