feat(app): native diff renderer, replacing the Pierre-based one - #1728
Open
Scoteezy wants to merge 8 commits into
Open
feat(app): native diff renderer, replacing the Pierre-based one#1728Scoteezy wants to merge 8 commits into
Scoteezy wants to merge 8 commits into
Conversation
Diffs were rendered two different ways: a React Native renderer for most surfaces and @pierre/diffs for the web split view, with a second, older line-diff module feeding the +N/-M counts. Stats and rendering could therefore disagree, and the Codex patch detail screen had no renderer at all, so it fell back to dumping the tool payload as raw JSON. Everything now goes through one engine that parses a patch, tokenizes and word-diffs it once, and hands the render layer immutable rows. - Add split (side-by-side) layout so the Pierre dependency can go. Both cells share one flex row, so a wrapped line grows the row and the two sides cannot drift apart. - Register full-screen views for CodexPatch/CodexDiff and drop the collapse toggle that hid every Codex diff behind an extra tap. - Read added and deleted files correctly: Codex reuses the `diff` field for the whole file body on add/delete, which parsed as zero hunks and rendered as "no changes". - Stop a trailing newline from becoming a blank row at the end of a diff. - Mount long files over a few frames instead of all at once. Measured on an iPhone 17 simulator: 2017 rows go from 414ms to 179ms to first paint, and the UI thread holds 60fps while the rest arrives. Chunks are scheduled with setTimeout, not rAF, because a backgrounded tab stops firing frames and leaves the file silently truncated. - Never cut a collapse boundary between removals and the additions that replaced them, which side by side reads as deleted code. - Add a session Changes screen and make chat diff cards open the full-screen diff for that file; both were previously reachable only from the wide-screen sidebar. Covered by pure tests for patch parsing, split pairing, collapse and gutter geometry, plus render tests over a mocked React Native.
A patch tool card carried a header naming the file, and then each diff inside repeated the same name in its own header — a box inside a box saying the same thing twice. The web build already hid the outer header; do it everywhere, for Gemini patches too.
`git status` carries no line counts for untracked files, so every new file claimed zero changed lines: no +N in its header, and a collapsed section offering to expand "0 changed lines". Recount from the content once it arrives — from the patch for tracked files, from the blob for untracked ones — so the header always matches the diff under it.
Three things the removed Pierre renderer or GitHub could do and this one could not. All three come down to asking git a different question, so the command now has one builder instead of being inlined at each call site. - Tapping an "N unchanged lines" separator re-fetches the file with full context. A patch does not carry the lines it skipped, so the separator is only tappable where the caller can ask for a wider diff — in chat, where the file has already moved on, it stays inert rather than lying. - A header toggle folds whitespace-only changes away via `git diff -w`, which turns a reformatted file from a wall of red and green into the handful of lines that actually changed. - Images render as before/after pictures instead of "binary file", read from HEAD and from disk. SVG is deliberately left to the text renderer, where it diffs properly. Paths are now escaped for the shell as well; a filename containing a quote or a backtick previously broke the command apart.
A changeset of thirty files opened as thirty diffs stacked end to end, which is a lot of scrolling before you know what is even in it. It now opens as a list of what changed — path, kind and counts — and a file opens when you ask for it. The "N changed lines — tap to expand" row is kept only where the size forced the collapse; when everything starts closed it explains nothing and doubles the height of the list, so the header's own chevron carries it instead.
Scoteezy
force-pushed
the
new-file-diff-mobile
branch
from
August 26, 2026 12:50
85696a6 to
10938dc
Compare
TaskCreate, create_agent, web_fetch and their siblings had no entry in knownTools and no view of their own, so each one fell back to a half-screen card of raw input JSON. Describe the SDK tools in knownTools so they render as one-line activity rows, and let getToolSummaryDetail read the subject-like fields they use (subject, title, query, name) instead of only paths and commands. Unknown tools now take the same compact row on every flavor rather than only on Gemini: providers keep adding tools faster than we describe them. Tools that failed still expand, since the payload is the only thing explaining the error.
The badges read unstagedLinesAdded/Removed, which come from a bare 'git diff --numstat'. That misses two things: staged hunks, so the count collapsed the moment an agent ran 'git add', and untracked files, which git diff never reports at all, so every file the agent created counted as zero lines. Count untracked lines with 'git ls-files --others --exclude-standard -z | xargs -0 wc -l' and fold them into the unstaged additions, skipping wc's per-batch total line and binary-looking paths whose newline counts mean nothing. The pipeline needs a POSIX shell, so on Windows it fails and untracked lines stay uncounted, as they are everywhere today. Show staged + unstaged everywhere through one helper, so the session status bar, the badges and the project rows agree on a single number.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Diffs were rendered two different ways: a React Native renderer for most surfaces and
@pierre/diffsfor the web split view, with a second, older line-diff module feeding the+N/−Mcounts. Stats and rendering could therefore disagree, and the Codex patch detail screen had no renderer registered at all, so it fell back to dumping the tool payload as raw JSON.Everything now goes through one engine that parses a patch, tokenizes and word-diffs it once, and hands the render layer immutable rows.
@pierre/diffsis dropped.What changed
CodexPatch/CodexDiff, and the collapse toggle that hid every Codex diff behind an extra tap is gone.difffield for the whole file body on add/delete, which parsed as zero hunks and showed as "no changes".git statusreports none for untracked files, so every new file claimed zero.setTimeout, notrequestAnimationFrame, because a backgrounded tab stops firing frames and leaves the file silently truncated.git diff -w; images render as before/after pictures instead of "binary file". SVG is deliberately left to the text renderer.+N/−Mbadges count staged and untracked lines too. They read a baregit diff --numstat, so the count collapsed the moment an agent rangit add, and files the agent created counted as zero — the same gap already fixed inside the changes view. Untracked lines now come fromwc -lovergit ls-files --others, and every surface reads one shared helper.TaskCreate,create_agent,web_fetchand their siblings had no entry inknownTools, so each fell back to a half-screen card of raw input JSON. Unknown tools now take that compact row on every flavor, not just Gemini; failed ones still expand.Shell paths are now escaped as well — a filename containing a quote or a backtick previously broke the command apart.
Testing
pnpm typecheckis clean andvitestpasses (115 files, 1216 tests). Coverage includes patch parsing, split pairing, collapse and gutter geometry, the Codex payload shapes, untracked line counting, tool activity labels, and render tests over a mocked React Native.Verified by hand on an iPhone 17 simulator against a live Codex session — the patch detail screen, inline chat diffs, the changes screen, context expansion, the whitespace toggle and image diffs were each exercised on real data.