Command output, documents, and previews
How command output is captured and read in ranges, how HTML documents run in a sandboxed origin on three platforms, how web pages become Markdown, and how files travel in chunks.
Checked against the source on 17 September 2026
On this page
Command output as an artifact
- Live rowcommandOutput.artifact
The desktop appends each output delta to its artifact before the row changes, then sends runId, byteLength, generation, complete, and sourceIncomplete at most every 100 ms per command. No output byte enters the timeline.
- Range readrun.codexChatCommandOutputRead
Only an open viewer reads, 1 byte to 256 KiB per request, and appSessionId binds the read to the owning base session. A generation the artifact no longer holds answers with a conflict, and the viewer refreshes the row before it reads again. Phones call it through the relay, and the desktop viewer calls the same read directly.
- Completionitem/completed
The desktop compares its bytes with Codex’s aggregatedOutput, which Codex caps at 1 MiB. Identical bytes only mark the artifact complete. Different bytes replace it as the next generation, and a known truncation marker in that text sets sourceIncomplete.
- Settled rowprotocolOutput
Once thread/items/list returns the command, the row points at the capture file Codex wrote while it ran, and an open desktop viewer follows it by the unchanged block id. The desktop forwards reads as thread/commandOutput/read and checks the echoed identity. capturedComplete is false when Codex kept no full capture.
HTML documents in a sandbox
- Resource rootopen_html_preview_command
The project folder when it contains the document, otherwise the document’s own folder. The caller generates the preview UUID, and the desktop refuses to bind that ID to a second document.
- Path checkptc-document://<preview-id>.localhost/<path>
The handler refuses a decoded path with .., a backslash, a colon, or NUL, serves index.html for a folder, and answers 404 when the canonical path is missing or leaves the root.
- Sandboxsandbox allow-scripts
Scripts, modules, and fetch run in an opaque origin with no access to the app’s page or storage. A plantocode.html.open or plantocode.html.ready message from that frame is the one thing the app acts on, and only for a file under the root or an http(s) page.
- Phonesfiles.readHtmlPreviewResource
The iPhone app at ptc-document://<preview-id>.localhost and the Android app at https://ptc-<preview-id>.invalid resolve the same paths through the desktop, in verified chunks of up to 4 MiB, with no script bridge into the app.
The protocol answers OPTIONS with 204, serves GET and HEAD only, sets Cache-Control no-store and X-Content-Type-Options nosniff, and appends navigation.js after the document end without touching the file; that script posts plantocode.html.open to the parent and blocks every scheme other than ptc-document and http(s). Local links inside a document therefore enter the preview history, web links open in the web preview, and Reload or a file change refreshes the rendering.
Web pages as Markdown
A web link opens as a Markdown rendering instead of a live page. The WebView extracts the article content and converts it with a GFM-aware Turndown pipeline, the Rust side converts with quick_html2md for content that never reaches the WebView, and the Browser Bridge has a third, hand-written DOM-to-Markdown renderer whose output the desktop stores as capturedBrowserMarkdown and wraps with the same title header. Three converters, one target format.
File reads and transfers
files.readContent returns a text document of at most 2 MiB; a larger file fails with “Text preview is too large”. Its request carries projectDirectory, path, and optional encoding and allowExternalFile, and the encoding is accepted but ignored; it has no chunk offset or media fields, because text and binary requests stay separate. files.readBinary and files.readMediaRendition read sequential chunks from offset zero. Each response carries offset, nextOffset (null at EOF), dataBase64, byteCount, contentVersion, modifiedAt, and mimeType, and renditions add originalByteCount. Neighbouring limits: media rendition sources up to 1 GiB, stream transcodes up to 256 MiB with 40-second timeouts, media range reads of 1 MiB, and chat-attachment uploads in 1 MiB chunks, at most 2,048 of them.
- Chunk readfiles.readBinary
Each request names the path and an offset. The desktop opens the file, reads up to 4 MiB there, and answers with the next offset, the total byteCount, and a contentVersion. HTML preview resources and media renditions use the same reader.
- Version pincontentVersion
For files.readBinary it hashes the path, size, and modification time each time a read opens the file. Both phones keep the first answer’s version and size and discard the whole read when a later answer differs. A file that shrank below the offset gets the same message from the desktop.
- Relay frameDEVICE_LINK_MAX_FRAME_BYTES
A 4 MiB chunk is about 5.3 MiB of base64, so every answer stays well inside the relay’s 32 MiB frame limit, and the file itself has no size cap.
Syntax highlighting
Source previews on the desktop use Shiki grammars, and each phone app highlights with its own engine.