Zum Artikel springen
PlanToCodeDocsApp herunterladen

HandbuchiOS-App

iOS: Betrachter, Review Mode, Diktat und Käufe

Die Code- und Dokumentbetrachter der iPhone-App, wie der Review Mode Erzählung und Screenshots in ein Video verwandelt, wie das Diktat hochlädt, und wie StoreKit, die verbundene Vorschau und Push funktionieren.

Gegen den Quellcode geprüft am 17. September 2026

Auf dieser Seite

Code mit Runestone und Tree-sitter

Dateivorschauen und Codeblöcke verwenden Runestone mit 37 Tree-sitter-Grammatiken aus dem fest gepinnten Paket plantocode-tree-sitter-languages, von Swift, Kotlin, TypeScript und Rust bis zu SQL, LaTeX und Diff. Eine Tree-sitter-Abfrage zu kompilieren ist teuer, deshalb hält PlanRunestoneLanguageProvider vorbereitete Sprachen am Leben und teilt sie zwischen Vorschauen und Inline-Codeblöcken, und Runestones TextViewState wird abseits des Main Threads vorbereitet. Swift wird mit SwiftSyntax hervorgehoben, und jq, AWK und in Shell-Befehle eingebetteter Code haben eigene Highlighter. Hervorhebungsergebnisse werden zwischengespeichert, 48 davon innerhalb von 16 MiB, und eine bereits laufende Hervorhebung wird mit jeder Ansicht geteilt, die danach fragt.

Befehlsausgabe wählt ihren Darstellungsmodus nach Größe. Bis 8 KiB wird sie als ein Text hervorgehoben, bis 512 KiB in virtualisierten Zeilen, und alles Größere erscheint als schlichte virtualisierte Zeilen, sodass CoreText nie ein ganzes großes Dokument auf dem Main Thread setzt. Die Markdown-Darstellung von Ausgabe endet bei 2 MiB.

Markdown, Mathematik und Mermaid

Chat-Markdown wird mit MarkdownUI auf swift-cmark dargestellt, Mathematik mit SwiftMath. Aufbereitetes Markdown wird pro Timeline-Block zwischengespeichert, 256 Einträge innerhalb von 16 MiB, und eine lange Nachricht wird ab 3.200 Zeichen oder 48 Zeilen eingeklappt.

Mermaid-Diagramme werden in einer WKWebView mit nicht persistentem Datenspeicher dargestellt, mit einer gebündelten mermaid.min.js 11.16.0, deren SHA-256 in einem Manifest festgehalten ist. Die Seite läuft mit securityLevel strict und htmlLabels false und meldet die Größe des Diagramms an die App zurück. Ein LRU-Cache mit 48 Einträgen und 4 MiB behält das gerenderte SVG, sodass das Zurückscrollen zu einem Diagramm die Laufzeit nicht erneut startet. Eine Diagrammquelle ist auf 50.000 Zeichen begrenzt.

HTML-Dokumente über ein eigenes URL-Schema

Jeder Pfad unter dem Vorschau-Host wird aus einem Ordner auf dem Desktop gelesen
Jeder Pfad unter dem Vorschau-Host wird aus einem Ordner auf dem Desktop gelesenWebKit zeigt docs/report.html von ptc-document://<preview id>.localhost. Die Seite fragt ../app.js, img/chart.png und shared/theme.css an, und jede Angabe wird zu einem Pfad unter diesem Host. Über das Relay liest der Desktop die Seite, app.js und docs/img/chart.png aus dem Stammordner der Freigabe, ~/project. docs/shared ist ein Symlink auf ~/Downloads, deshalb verlässt der echte Pfad des Stylesheets den Stammordner, und die Anfrage wird abgewiesen. Ein angetippter Weblink öffnet sich im Systembrowser.
iPhone · WKWebView
Desktop · Freigabe für diese Vorschau
ptc-document://<preview id>.localhost/
~/project/
Stammordner der Freigabe
/docs/report.htmldie Seite
docs/report.html
/app.jssrc="../app.js"
app.js
/docs/img/chart.pngsrc="img/chart.png"
docs/img/chart.png
/docs/shared/theme.csshref="shared/theme.css"
docs/shared → ~/Downloads
der echte Pfad verlässt den Stammordner, deshalb wird theme.css abgewiesen
https://example.com
Tippen
Systembrowser
Relay · files.readHtmlPreviewResource · 4 MiB
Freigabe bis files.closeHtmlPreview gespeichert
  • Vorschau-Hostptc-document://<preview id>.localhost

    Das Telefon erzeugt für jedes geöffnete Dokument eine zufällige Vorschau-ID, und WebKit übergibt jede ptc-document-Anfrage dem Schema-Handler. Er akzeptiert nur GET für den Host dieser Vorschau und weist einen Pfad mit .., einem Backslash, einem Doppelpunkt oder NUL ab.

  • Freigabefiles.openHtmlPreview

    Der Desktop bindet die Vorschau-ID an ein Dokument und einen Stammordner: den Projektordner oder den eigenen Ordner des Dokuments, wenn es außerhalb des Projekts liegt. Die Freigabe bleibt im Speicher des Desktops, bis files.closeHtmlPreview kommt oder der Desktop neu startet.

  • Ressource lesenfiles.readHtmlPreviewResource

    Der Desktop hängt den Pfad an den Stammordner an, liefert für einen Ordner index.html und weist alles ab, dessen echter Pfad den Stammordner verlässt. Blöcke von 4 MiB müssen alle aus einer Dateiversion stammen, und schon die erste Antwort trägt die ganze Content-Length.

  • Die Seite verlassenUIApplication.open

    Ein angetippter http- oder https-Link öffnet sich im Systembrowser. Antworten tragen no-store und nosniff, und der Datenspeicher der Web-View ist nicht persistent, deshalb behält die Seite nach dem Schließen nichts.

Eine .html- oder .htm-Datei öffnet sich als lebendiges Dokument. Jeder Pfad unter ihrem Host ptc-document://<preview ID>.localhost wird aus einem Ordner auf dem Desktop gelesen: dem Projektordner, wenn das Dokument im Projekt liegt, sonst dem eigenen Ordner des Dokuments. Relative Links lösen sich innerhalb dieses Ordners auf, der Desktop weist jeden Pfad ab, dessen echter Ort ihn verlässt, und jede Antwort trägt den MIME-Typ, den der Desktop aus dem Dateinamen ableitet.

Alle binären Lesevorgänge teilen sich einen Blockleser. WorkspaceFileChunks holt jeweils einen Block und prüft, dass der Offset der erwartete ist, dass sich Inhaltsversion und Größe nicht geändert haben, dass der nächste Offset ohne Überlauf folgt und dass der Block innerhalb der Datei bleibt. Die Base64-Dekodierung läuft abseits des Main Threads, und ein gescheiterter Lesevorgang wird nicht fortgesetzt.

Medien, PDFs, Bilder und der Vorschau-Cache

Audio und Video spielen über einen AVAssetResourceLoader-Delegate, der files.readMediaRange in Blöcken von 512 KiB liest, gestützt auf einen Bereichs-Cache von 128 MiB. Desktopgestützte Wiedergabe hält das Relay über den Audio-Hintergrundmodus auch im Hintergrund offen.

Vorschauen werden von einem Actor mit 512 MiB Budget und 3 Tagen Aufbewahrung zwischengespeichert, und zwischengespeicherte Einträge sind an den SHA-256-Digest aus den Vorschau-Metadaten des Desktops gebunden. PDFs werden mit PDFKit dargestellt. Bilder werden mit höchstens 2.560 Pixeln an der langen Seite für volle Ansichten und 480 für Karten dekodiert, mit höchstens zwei Dekodierungen gleichzeitig.

Der Review Mode baut ein Video aus Erzählung und Screenshots

Eine Uhr verbindet jeden Screenshot mit den Worten, die um ihn herum fallen
Eine Uhr verbindet jeden Screenshot mit den Worten, die um ihn herum fallenEin Beispiel mit 18 Sekunden Erzählung und Screenshots bei 2, 11 und 13 Sekunden. Die Erzählung wird an den Mittelpunkten zwischen den Screenshots, bei 6,5 und 12 Sekunden, in drei Stücke geschnitten, die der Server transkribiert. Das Video zeigt jeden Screenshot ab seinem Aufnahmezeitpunkt bis zu 3,5 Sekunden lang oder bis zum nächsten, mit schwarzen Bildern dazwischen. Der Prompt neben dem Video ordnet Screenshot 2 seine Aufnahmezeit und sein Sprechfenster zu. Der Server schickt Video und Prompt an Gemini, und die Analyse landet im Chat-Entwurf.
0 s
5 s
10 s
15 s
Erzählungbis Stop
transkribiertauf dem Server
ScreenshotsdrawHierarchy
VideoAVAssetWriter
Mittelpunkt
Mittelpunkt
1
2
3
1
2
3
1
schwarz
2
3
3,5 s gehalten
Prompt neben dem VideoScreen 2- capturedAt: 00:11.000- spokenWindow: 00:06.500-00:12.000
Server
Gemini
Analyse → Chat-Entwurf
  • Transkriptfenster/api/audio/transcriptions

    Das Telefon schneidet die Erzählung auf halbem Weg zwischen benachbarten Screenshots und lädt jedes Stück einzeln für openai/gpt-4o-transcribe hoch. Ein Stück unter einer halben Sekunde wird nicht gesendet, und ein gescheitertes Stück wird als nicht verfügbar markiert, während der Review weiterläuft.

  • VideobildAVAssetWriter · H.264

    Ein Screenshot erscheint zu seinem Aufnahmezeitpunkt und bleibt bis zu 3,5 s oder bis zum nächsten stehen, dazwischen schwarze Bilder, unter der ganzen Erzählung. Bilder haben höchstens 2.560 px an der langen Seite, bei 4 bis 12 Mbit/s.

  • Prompt-ZeitleisteScreen · capturedAt · spokenWindow · spokenText

    Geht neben dem Video mit. Sie nennt für jeden Screenshot Aufnahmezeit, Sprechfenster und Worte und kürzt die Worte nach 1.200 Zeichen.

  • Analyse/api/llm/video/analyze

    Der Server schickt Video und Prompt an Gemini, standardmäßig google/gemini-3.1-pro-preview, mit einer Bildrate von 1, und das Telefon wartet bis zu 600 s. Die Antwort wird dem Chat-Entwurf hinzugefügt, wo Sie sie bearbeiten, bevor irgendetwas den Agenten erreicht.

Der Review Mode auf dem iPhone nimmt Ihre Stimme auf und macht bei jedem Tippen auf Capture einen Standbild-Screenshot. Der erste Start fragt nach Ihrer Zustimmung, und der Hinweis nennt Gemini als den Dienst, der die Aufnahme analysiert. Audio wird mit AVAudioRecorder als AAC mit 44,1 kHz, mono, 128 kbit/s aufgenommen, in einer Audiositzung für gesprochene Sprache, die Bluetooth-Headsets erlaubt, andere Audiowiedergabe absenkt und den Lautsprecher nutzt. Die Review-Steuerung liegt in einem eigenen durchlässigen Fenster über der App.

Eine Aufnahme blendet die Steuerung aus, wartet 120 Millisekunden, zeichnet das Hauptfenster mit drawHierarchy und speichert ein JPEG mit Qualität 0,82 zusammen mit seinem Zeitversatz. Aufnahmen und Bilder werden mit Dateischutz geschrieben und beim Abmelden gelöscht.

Stop führt drei Schritte nacheinander aus. Das Telefon schneidet das Audio an den Mittelpunkten zwischen benachbarten Aufnahmen und lädt jedes Segment nach /api/audio/transcriptions hoch, das es mit openai/gpt-4o-transcribe transkribiert. Dann setzt es das MP4 mit AVAssetWriter zusammen und fügt das Audio über eine Komposition hinzu, die als Passthrough exportiert wird, wenn das Format es erlaubt. Zuletzt lädt es das Video mit seinem Prompt nach /api/llm/video/analyze hoch, und die Analyse kommt als Text zurück, den Sie im Chat-Entwurf prüfen, bevor irgendetwas den Agenten erreicht.

Diktat

Das Diktat nimmt eine m4a-Datei auf. Während der Aufnahme hält die App eine Hintergrundaufgabe und schaltet die Näherungserkennung ein. Der Upload geht an /api/audio/transcriptions auf dem regionalen Server, mit openai/gpt-4o-transcribe und bis zu 3 Versuchen im Abstand von 0,8 und dann 1,6 Sekunden. Der Prompt mit Ihrem Vokabular kommt über system.voiceTranscriptionContext vom Desktop, und auf dem Telefon ist kein Anbieterschlüssel gespeichert.

StoreKit, die verbundene Vorschau und Push

PlanToCode Pro auf dem iPhone ist ein StoreKit-2-Abonnement, das auf dem Gerät geprüft wird. Die App lädt die Produkte, kauft über StoreKit, akzeptiert nur verifizierte Transaktionen, hört auf Transaction.updates, liest die aktuellen Berechtigungen und ruft zum Wiederherstellen AppStore.sync auf. Kein App-Store-Beleg geht an den PlanToCode-Server.

Die 10-tägige verbundene Vorschau gehört dem Server. GET api/billing/workspace-preview liest sie, PUT startet sie, und die App sendet das PUT nach der ersten erfolgreichen RPC-Antwort eines Desktops, sofern Region, Konto und Server-URL dieses Anspruchs noch aktuell sind. Eine Antwort mit unerwarteten Schlüsseln wird abgewiesen, und der Ablauf wird auf einer monotonen Uhr verfolgt.

Das APNs-Token wird mit PUT api/devices/push-token registriert, in bis zu 4 Versuchen, die nach einer Ratenbegrenzung 15 bis 60 Sekunden und nach einem vorübergehenden Fehler 1 bis 8 Sekunden warten. Im Vordergrund erscheint ein Banner nur für Benachrichtigungen, die zum angemeldeten Konto gehören. Ein stiller desktop_online-Push startet die Relay-Wiederherstellung, und eine Agent-Benachrichtigung wechselt die geöffnete Session nur, wenn Sie darauf tippen.