{"slug": "rendering-diffs-by-pierre-computer-company", "title": "Rendering Diffs, by Pierre Computer Company", "summary": "Pierre Computer Company released a new CodeView component for its Diffs product, designed to render code diffs of any size nearly instantly in a browser. The company built the virtualization-first component to address performance issues that arise when reviewing large diffs, such as those generated by AI agents or branches with many files. CodeView is available in the latest version of the @pierre/diffs npm package and can be tested via the DiffsHub playground, which allows users to view any public GitHub diff by replacing \"github\" with \"diffshub\" in the URL.", "body_md": "# PIERRE COMPUTER COMPANY █\n\n## ON RENDERING DIFFS\n\nPosted on May 29, 2026 by [@amadeus](https://x.com/amadeus)\n\n```\n             ██████╗ ██╗███████╗███████╗███████╗\n             ██╔══██╗██║██╔════╝██╔════╝██╔════╝\n██████████╗  ██║  ██║██║█████╗  █████╗  ███████╗\n╚═════════╝  ██║  ██║██║██╔══╝  ██╔══╝  ╚════██║\n             ██████╔╝██║██║     ██║     ███████║\n             ╚═════╝ ╚═╝╚═╝     ╚═╝     ╚══════╝\n██╗      ██████╗ ██╗███████╗███████╗███████╗\n    ██║      ██╔══██╗██║██╔════╝██╔════╝██╔════╝\n██████████╗  ██║  ██║██║█████╗  █████╗  ███████╗\n╚═══██╔═══╝  ██║  ██║██║██╔══╝  ██╔══╝  ╚════██║\n    ██║      ██████╔╝██║██║     ██║     ███████║\n    ╚═╝      ╚═════╝ ╚═╝╚═╝     ╚═╝     ╚══════╝\n```\n\nYou open a pull request expecting to understand what changed.\n\nFor small and medium changes, everything works. The code is readable, the files are there, you scroll around, add comments, and it’s all pretty seamless.\n\nThen you open something larger. Maybe an agent generated the implementation, tests, fixtures, and snapshots. Maybe the branch just touched more files than expected. Either way, the review surface starts to degrade. It might only show you one file at a time, or require each file to be loaded separately before you can read it, or even make basic navigation feel sluggish.\n\nSome of these are reasonable trade-offs for genuinely hard problems. But they still have a cost: reviewers feel the limits of the tool, and product teams have to build workarounds for these limits.\n\nDiff rendering matters, but for most tools it is not the product. The product is what happens around the code: review workflows, automation, agent output, CI results, and collaboration. Code review should support that work, not become something every team has to build from scratch.\n\nThat is why, about 6 months ago, we released\n[Diffs](https://diffs.com/). Our goal was to make\nthe code and diff rendering part just work, so teams could spend their time on the product\naround it.\n\nOriginally we launched with just the basic pieces: `File`\n\nand\n`FileDiff`\n\ncomponents. We quickly got feedback about performance issues, so we\nfollowed up with a simple virtualizer that avoided rendering code when it was out of view\nand an API to move syntax highlighting into worker threads. The simple virtualizer helped,\nbut it was a stopgap. There was still a lot of O(n×m) complexity, high memory usage, and virtualization blanking. What was missing was a\nhigher-level component that could manage an entire review surface and handle the hard\nproblems related to scale.\n\nThat missing layer became `CodeView`\n\n: a virtualization-first component for\nreviewing code and diffs. And we built it around a deliberately impossible goal:\n\nYou should be able to just render any diff.\n\nNot literally, of course. There are physical limits to browsers, compute, and memory. But practically speaking, I think we’ve come pretty close, and I’d like to share a bit about how we got there.\n\nIf you find long-form blog posts boring, go check out the `CodeView`\n\nplayground\nat [DiffsHub.com](https://diffshub.com/) where you can pretty\nmuch view any PR or diff that GitHub will send our way. Nearly any diff, at any scale,\nnearly instantly.\n\ndiffshub[dot]com\n\n— Pierre (@pierrecomputer)\n\nTake any public diff from GitHub and virtualize it nearly instantly, no matter how large, with DiffsHub. Built to show off our brand new CodeView component.\n\nTo try it out, replace `github` with `diffshub` in your address bar.[pic.twitter.com/5X30YwbpHn][May 20, 2026]\n\nYou can check out the CodeView component and more in the latest version of the diffs package\non npm:\n[@pierre/diffs](https://www.npmjs.com/package/@pierre/diffs), or\n[read the docs](https://diffs.com/docs#codeview).\n\n## DIFFS LOOK SIMPLE UNTIL THEY ARE NOT\n\nOn the surface, rendering diffs in a browser may not seem very hard. It’s just text, right? Browsers are purpose-built to take raw HTML and turn that into something you can look at and interact with. Code is just text, after all.\n\nBut a good review surface needs more than text. It needs syntax highlighting, line numbers, annotations, comments, theming, split and unified layouts, wrapping modes, and enough customization to fit into someone else’s product. Each of those features adds cost and complexity. Syntax highlighting adds processing time and inflates DOM count. Comments involve additional layout complexity that we can’t fully control, and they still have to work seamlessly with your existing design system.\n\nWith `CodeView`\n\n, we take that per-file complexity and scale it up; work that was\ncheap for a single diff now has meaningful cost across a large review. We can roughly break\ndown the problems into three categories:\n\n-\n**Rendering**— DOM complexity grows quickly, and the browser can become overloaded while scrolling or interacting with the page. -\n**Processing**— Every file or diff operation gets multiplied, so work that was fast in isolation can become expensive when repeated thousands of times. -\n**Memory**— Large files and diffs get transformed into rendering data structures, which can push against browser memory limits and make garbage collection more frequent.\n\nOur simple virtualizer helped with some rendering problems, and moving highlighting off the\nmain thread helped with parts of the processing problem. But `CodeView`\n\nneeded to\ntreat rendering, memory, and processing as connected parts of the same problem.\n\n## VIRTUALIZATION\n\nVirtualization, or windowing, is a way of tackling the rendering problem. In its simplest form, the idea is to only render the part of the content near the viewport. As you scroll, the virtualizer renders the new content coming into view and removes content that has moved off screen.\n\nKeeping the DOM small has a lot of benefits: lower memory usage, less layout work, less paint work, and fewer elements for the browser to manage. The trade-off is that the virtualizer has to estimate or measure how tall everything is, and it must coordinate those changes dynamically.\n\nOne thing that adds to this complexity is that browsers generally manage scroll compositing separately from JavaScript execution. This can help scrolling feel more responsive to user interactions, but it also means that JavaScript can easily lag behind scroll updates. This is often most noticeable when using the scrollbar to make large jumps or scrolling extremely quickly — the virtualizer can’t keep up and you’ll scroll into blank regions before the JavaScript has time to render the updated content.\n\n### Common Virtualization Techniques\n\nThere are a few common ways to virtualize content in a browser, and each comes with its own set of trade-offs.\n\nThe most common approach is to create a real scrollable region with the full estimated height of the content, then position the visible items where they belong. This keeps scrolling native: the scrollbar, momentum, input handling, and accessibility all stay with the browser. The trade-off is that the rendered window can fall behind the visual scroll position. Fast scrolls and large scrollbar jumps can expose blank space before JavaScript has a chance to render the next range. You can reduce that by rendering a larger buffer outside the viewport, but that gives back some of the DOM, layout, and memory savings that virtualization was supposed to buy you.\n\nAnother approach is to keep the visible content in a sticky or fixed container and update\nwhat it shows with `requestAnimationFrame`\n\n. In this model, blanking is\nimpossible: the content container cannot scroll out of view because it’s not moving with the\nscroll position; it just looks like it is. However, if JavaScript cannot keep up, then\nscrolling can hitch or stutter because JavaScript is now part of the render update path.\nBrowser behavior matters here too. Safari, for example, currently caps\n`requestAnimationFrame`\n\nat 60Hz even on higher refresh-rate displays, which makes\nthis approach feel worse than native scrolling on those devices.\n\nA more extreme version is to emulate scrolling entirely: no native scrollable region, just a\ncustom viewport, a fake scrollbar, and content updated via\n`requestAnimationFrame`\n\nas the user *moves* through the document. This can\navoid browser scroll-size limits because the scroll position is now your own state, not the\nbrowser’s. But the cost is larger: you now own the details of making scrolling feel native,\naccessible, and correct across different operating systems and browsers.\n\n### The Inverse Sticky Technique\n\nFor `CodeView`\n\n, many of those virtualization trade-offs were not acceptable.\nNative browser scrolling mattered. WebKit-based environments needed to feel good because\nTauri is a common target for developer tools. And blanking was not an option.\n\nThis left us stuck between different approaches that weren’t quite right. After some\nexperimentation and frustration, we figured out a hybrid approach that could keep scrolling\nnative, mostly decouple positioning from `requestAnimationFrame`\n\nupdates, and\nmake blanking effectively impossible.\n\nWe’ve called our new technique the `Inverse Sticky Technique`\n\n, but before we talk\nabout how it works, first a quick primer on how `sticky`\n\npositioning works. The\ntypical use case for sticky positioning is ensuring that section headers in a scrollable\nlist stay in view as you scroll through it. You set `position: sticky; top: 0`\n\non\nyour section headers and then when they should normally be scrolled out of view, they stay\nfixed to the top of the scroll view as the content below scrolls underneath.\n\nFor `CodeView`\n\n, we invert the usual sticky behavior. Instead of pinning the top\nof the rendered content to the top of the viewport as you scroll down, the bottom edge of\nthe rendered region sticks to the bottom of the viewport when you scroll past it. When you\nscroll back up, the top edge sticks to the top of the viewport.\n\nThis gives us native scrolling while the viewport is inside the rendered range. If\nJavaScript falls behind, the rendered region sticks to one edge instead of scrolling away\nand exposing blank space. We can get that behavior with negative `top`\n\nand\n`bottom`\n\nsticky offsets, both calculated with the same formula:\n`(contentHeight - viewportHeight) * -1`\n\n.\n\nHere’s a quick demo showing the `Inverse Sticky Technique`\n\n. We are\ncurrently halfway scrolled down a larger scroll region.\n\nTry scrolling up or down. This content scrolls with you until you hit the sticky bounds, at which point this content will get stuck to the top or bottom.\n\nSo to circle back to the goals we set for ourselves: we preserve native scrolling, render updates do not need to be frame-perfect to keep scrolling feeling smooth, and even large jumps cannot scroll past the rendered content into blank space.\n\n```\n  ┌────────────────────────────────────────────────────┐\n  │ ┌────────────────────────────────────────────────┐ │\n  │ │                                                │ │\n  │ │           Full-height content element          │ │\n  │ │                                                │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓                                    ▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓            Buffer element          ▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓     before virtualized content     ▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓                                    ▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ │\n  │ │                                                │ │\n  │ │ ┌────────────────────────────────────────────┐ │ │\n  │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │\n┌────────────────────────────────────────────────────────╖\n│ ▀ ▀ ▀               ▓▓▓ Browser ▓▓▓                    ║\n├────────────────────────────────────────────────────────╢\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░                    ░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░  Rendered content  ░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░                    ░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n│ │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ │ ║\n╘════════════════════════════════════════════════════════╝\n  │ │ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │ |\n  │ │ └────────────────────────────────────────────┘ │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ │                                                │ |\n  │ └────────────────────────────────────────────────┘ |\n  └────────────────────────────────────────────────────┘\nWhile we were shooting for\n```` impossible to blank`\n\n, Safari still found a way to\nbreak our hearts. Under sufficiently aggressive scrolling, it can get backed up at the\ncompositing layer and expose blank space. It usually takes some work to pull off, but it is\nstill technically possible.\n\n## SCALABLE LAYOUTS\n\nWith virtualization in place, the next problem was calculating the layout and size of the scrollable region. A virtualizer works best when its estimates are close to reality. Bad estimates mean more corrective work after render: measuring DOM, updating item positions, adjusting scroll height, and sometimes fixing the scroll offset to keep the current content in place. The more often that happens, the more likely the page is to stutter or make the scrollbar jump around.\n\nFortunately, the first pass is pretty cheap. Files are basically\n`lineHeight * totalLines`\n\n. Diffs are only a little more complex because we\nalready have the parsed line counts and hunk metadata. From there, we just add the hunk\nseparators into the estimate. Simplified, it looks like this:\n`(lineHeight * diff.splitLineCount) + (diff.hunks.length * hunkSeparatorHeight)`\n\n.\n\n### Rendering Line Ranges\n\nWith our rough estimates in place, `CodeView`\n\ncan determine which files should be\nrendered. From there, each rendered file or diff gets the viewport size and position, and\nuses that to decide which lines should be rendered internally.\n\nThis architecture came from the previous `Virtualizer`\n\n, but\n`CodeView`\n\npushed us to optimize some of the expensive paths. The old\nimplementation could end up iterating through a file or diff from the beginning to find\nwhere the rendered range should start and end. For most files and diffs, that cost was\neffectively invisible. But once we started testing much larger change sets, it became a\nproblem. A hunk with hundreds of thousands of lines could become pathologically expensive\nbecause the lookup still had to start from zero.\n\nTo work around this, we added a cached `position to line`\n\ncheckpoint system. That\nlets us use binary search to find a closer starting point before doing the remaining range\nsearch.\n\nOnce a line range is rendered, each file can verify its internal estimate against the actual DOM and store any deltas. That lets the first-pass layout stay cheap while still correcting the cases where the estimate was wrong.\n\n### Scroll Anchoring\n\nScroll anchoring is less about raw performance and more about keeping the view stable while layout changes. If content above your scroll position changes height, the browser normally tries to preserve what you were looking at instead of letting it jump around.\n\nBrowsers have built-in scroll anchoring for this, but virtualized views make that mostly\nimpossible. The mounted DOM is constantly changing, and the browser cannot make a safe\ndecision about which element to anchor to. For `CodeView`\n\n, we disable the\nbrowser’s built-in anchoring with `overflow-anchor: none`\n\nand handle it\nourselves.\n\nThe core idea is that `CodeView`\n\ncan choose an anchor from its own layout model\nbefore committing DOM changes. It does not need to ask the DOM what the user is looking at;\nit already knows which file or line should be visible at each scroll position.\n\nA typical render update looks roughly like this:\n\n- Find the first fully visible line or file.\n- Store that line or file, along with its viewport offset, as the anchor.\n- Commit DOM changes for the new rendered range.\n- Reconcile any measured height changes.\n- Check whether the anchor is still at the same viewport offset.\n- If it moved, adjust the scroll position to put it back.\n\nTaken together, rough estimates, line-range rendering optimizations, incrementally measured\ndeltas, and scroll anchoring let `CodeView`\n\nstay fast even with very large diffs,\nwithout requiring perfect layout information up front.\n\n## DON’T FORGET ABOUT MEMORY\n\nAt this point `CodeView`\n\nwas in a pretty good place. It could render diffs as\nlarge as Bun’s\n[Zig to Rust rewrite](https://diffshub.com/oven-sh/bun/pull/30412)\nor an even larger Node.js\n[V8 update](https://diffshub.com/nodejs/node/pull/59805) without\nfalling over.\n\nSo, in typical Pierre fashion, we found a larger diff and kept going (we’re probably\n[hiring](https://pierre.computer/) btw). The next set of work\ncame from trying to render the diff between Linux\n[v6 and v7](https://diffshub.com/torvalds/linux/compare/v6.0...v7.0)\nmore efficiently.\n\n### Detaching Parsed Strings\n\nPathological cases like the Linux diff above can mean more than 700 MB of patch content to parse and render. One of the first things our diff renderers need is a data structure built from that patch file: line content and hunk metadata needed to render them efficiently and correctly.\n\nThe subtle problem is that parsed strings can keep more memory alive than you expect. Depending on how the JavaScript engine represents substrings, a small string can still reference the much larger string it came from. That means you can parse a huge patch, keep only the lines you need, and still accidentally retain the original giant input string.\n\nIn that case, copying strings can actually save memory. By forcing the parsed line content to detach from the original patch input, Diffs can keep the data they need without keeping the entire source string alive.\n\nThis was a good fit for an agent loop because the problem was narrow and easy to test. We had a clear hypothesis, a parser function with well-tested inputs and outputs, and an easy way to check whether each change improved memory usage and parse time.\n\n```\n┌──────────────────────────────────────────────────────╖\n│                                                      ║\n│  Memory usage compared (Linux v6...v7 diff)          ║\n│                                                      ║\n├──────────────────────────────────────────────────────╢\n│                                                      ║\n│  ████████████████████████████████████████████████░░  ║\n│  ████████████████████████████████████████████████░░  ║\n│                                                      ║\n│  Original (2.4 GB)                                   ║\n│                                                      ║\n│  ──────────────────────────────────────────────────  ║\n│                                                      ║\n│  ████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░  ║\n│  ████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░  ║\n│                                                      ║\n│  Optimized (1.15 GB)                                 ║\n│                                                      ║\n╘══════════════════════════════════════════════════════╝\n```\n\nAfter about an hour of iteration, we had some clear wins. Memory usage on the Linux diff dropped from 2.4 GB to around 1.15 GB, and parse time dropped by about 80%.\n\n### Pooling DOM Elements\n\nVirtualization keeps the mounted DOM small, but it can also create a lot of DOM element\nchurn. During aggressive scrolling, `CodeView`\n\nmay remove one set of file or diff\nelements and mount another. Those allocations do not disappear for free: JavaScript objects,\nderived data, event handlers, and DOM nodes all eventually have to be cleaned up, and enough\ngarbage collection can show up as main-thread pauses.\n\nThere was also repeated setup work every time a new file or diff was rendered. Each one lives inside a Shadow DOM wrapper that includes things like stylesheets, theme styles, and an SVG icon atlas. Recreating all of that every time an item scrolled in or out of view was unnecessary work.\n\nSo we added a pool for those containers. Instead of throwing the whole wrapper away,\n`CodeView`\n\ncan clean out the item-specific DOM and reuse the shell for the next\nfile or diff. That reduces allocation churn, avoids rebuilding the same wrapper structure\nover and over, and keeps garbage collection further away from the scrolling path.\n\nA nice side effect of building the pool was that it forced us to be more deliberate about cleanup. Reusing containers safely meant being explicit about clearing references and removing item-specific state, which helped patch a few leaks along the way.\n\n### Sharing `options`\n\nState\n\nWhile we were testing against the\n[Linux diff](https://diffshub.com/torvalds/linux/compare/v6.0...v7.0), one thing we noticed was that configuration changes were extremely expensive.\n\n`File`\n\nand `FileDiff`\n\nwere originally designed to each have their own\n`options`\n\nobject. That worked well for rendering a single file or diff, but it\nscaled poorly once `CodeView`\n\nwas managing tens of thousands of them. Options\ninclude things like split or unified layout, line numbers, line wrapping, and other display\nsettings. When one of those values changed, we would end up walking every file or diff\ninstance to give it a newly spread options object.\n\nWith enough instances, that became expensive quickly.\n\nThe fix was to keep the `options`\n\nshape, but change where the state actually\nlived. Instead of giving every file or diff its own fresh config object,\n`CodeView`\n\nowns the current options as the source of truth. Each rendered item\ngets a stable options object with specialized getters that read from that shared state.\n\nFrom the item’s perspective, it still reads `options`\n\nlike normal. Underneath,\nthose values are always coming from the latest `CodeView`\n\nconfiguration.\n\nThat means cosmetic changes no longer require rewriting configuration across every item in\nthe review. `CodeView`\n\ncan update the shared state, re-render the mounted range,\nand let the visible files and diffs read the latest values through the same options object.\nLayout-affecting changes may still need to invalidate estimates, but we already spent time\nmaking those much more efficient earlier in the post. Anecdotally, we also noticed a 20-30\nMB memory reduction on the Linux diff after implementing this.\n\n### Deferred Syntax Highlighting\n\nThis is a feature we’ve had in Diffs for a while, but it is still an important piece of\nmaking large reviews feel smooth. Syntax highlighting is one of the most expensive\nprocessing tasks we do. We use\n[Shiki](https://shiki.style/) because it is fast enough for most\ncases, has a ton of language support, and can run in a variety of contexts: the browser, web\nworkers, and server-side rendering.\n\nBut “fast enough” changes when you multiply it across a large review. A 2,000-line TypeScript file might only take a few milliseconds to highlight, but that is still expensive inside a strict frame budget, especially when many files are being rendered or updated at once and the main thread is already busy with rendering and virtualization work.\n\nWhat’s important is that highlighting is deferred. Files and diffs can render first as plain text, then request highlighted output from the worker pool. Each worker owns its own Shiki highlighter, keeping the expensive work off the main thread while still allowing multiple highlight jobs to make progress.\n\nAdditionally, we keep an LRU cache of highlighted results and provide APIs to\n`prime`\n\nthe highlight cache if we know a file will be rendered soon. That helps\navoid repeating work when code comes back into view, while still putting a hard limit on how\nmuch highlighted output can be retained.\n\nThe goal is for highlighting to improve the review surface, not block it. Code is readable immediately, and highlighting can progressively enhance the experience.\n\n## WRAP THIS UP ALREADY\n\nIf you’ve made it this far: damn, thank you. ♥\n\nThis has been a large project, with a lot of complicated work behind it. I’m proud of what we’ve managed to pull off inside the confines of a browser. Should all of this be happening in a browser? Probably not. But, you know, challenge accepted.\n\nSo far, we’ve mostly talked about the wins: the virtualization techniques, layout estimation, memory improvements, DOM pooling, shared options, and deferred highlighting. Those are real improvements, but there are still plenty of rough edges.\n\nOne of the bigger ones is CSS. Some of the most expensive parts of the virtualization system\nnow come from layout and paint. In normal use, that’s usually fine. During aggressive\nscrolling, though, those costs can become the majority of the work. We’ve thrown some\n[agentic research loops](https://github.com/davebcn87/pi-autoresearch)\nat this, but so far we haven’t made much progress.\n\nAnother unresolved issue is serialization in the highlighting pipeline. If you syntax-highlight a file with tens of thousands of lines, sending that data back and forth through the worker pool gets noticeably expensive. At times, it’s enough to dominate the main thread. This is probably an area where a more server-based streaming approach would make sense.\n\nFinally, while we do line-based virtualization, we don’t virtualize horizontal scrolling or extremely long lines, like you might see in minified JS or CSS. That means mounting one of those lines can still create a sizable DOM hit. We do have safeguards to stop the highlighter from crashing on very long lines, but that’s a separate problem.\n\nFuture plans for Diffs include things like lightweight editing, semantic diffs, and maybe even moving some of this work onto the server where it makes sense. In the meantime:\n\nYou should be able to just render any diff.\n\nSo\n[hit play on Sandstorm](https://www.youtube.com/watch?v=y6120QOlsfU)\nand scroll some [diffs](https://diffshub.com/).\n\n### P.S. Apple, Safari, Please\n\nBefore I wrap this up, I wanted to end on a quick note about Safari. A lot of\n`CodeView`\n\nis built on browser primitives that worked consistently across Chrome\nand Firefox. In WebKit, that was not always the experience. Between poor performance and\nlimited observability, many wins felt like one step forward and then a half step back.\n\nThis is not meant as a dunk on Safari. WebKit is an important target for us, especially because of macOS and the popularity of Tauri. I want nothing more than to build first-class experiences on WebKit.\n\nHere’s a non-exhaustive list of issues we’ve run into while developing\n`CodeView`\n\nand Diffs:\n\n-\nSticky compositing performance that appears significantly worse than Chrome or Firefox\n(cases where the\n`Inverse Sticky Technique`\n\ncan still blank under aggressive scrolling). - Developer tools that make it difficult to trace performance issues across JavaScript, layout, paint, and compositing.\n-\nWhat does the gray\n`other`\n\nrepresent in frame timelines, and why is it often blowing up our frame buffers? - Off-screen compositing rules that were difficult to predict\n-\nDeep scrolling/layout bugs when injecting slot containers. See\n[https://bugs.webkit.org/show_bug.cgi?id=308027](https://bugs.webkit.org/show_bug.cgi?id=308027). -\n`requestAnimationFrame`\n\nstill being capped at 60Hz, even on higher refresh-rate displays.\n\nWork on Safari or on WebKit? Would love to talk — email amadeuspierre.co.", "url": "https://wpnews.pro/news/rendering-diffs-by-pierre-computer-company", "canonical_source": "https://pierre.computer/writing/on-rendering-diffs", "published_at": "2026-05-29 19:04:54+00:00", "updated_at": "2026-05-29 19:17:13.046576+00:00", "lang": "en", "topics": ["ai-tools", "ai-products", "ai-agents"], "entities": ["Pierre Computer Company", "amadeus"], "alternates": {"html": "https://wpnews.pro/news/rendering-diffs-by-pierre-computer-company", "markdown": "https://wpnews.pro/news/rendering-diffs-by-pierre-computer-company.md", "text": "https://wpnews.pro/news/rendering-diffs-by-pierre-computer-company.txt", "jsonld": "https://wpnews.pro/news/rendering-diffs-by-pierre-computer-company.jsonld"}}