# Legend concepts on mobile

Source reviewed: [Legend apps at 4744fb8b](https://github.com/LegendApp/legend-apps/tree/4744fb8b57026a645379e826fe8ab34861f43017). This experiment uses T3 mobile source from the previously measured web stack, now on main, plus the candidate changes. The sidebar CSS layer has no effect on React Native.

## What transfers

| Legend concept | T3 already has | Decision |
| --- | --- | --- |
| Virtualized, typed rows with predictable sizes | LegendList, fixed work-row heights, row types, bounded draw distance | Keep. Replacing the list is not justified. |
| Stable document/block identity through appends | Thread identities are stable, but Markdown chunk keys included the changing end offset | Adopt stable start-based block keys so appending text does not remount the block. |
| Reuse unchanged work and incrementally process edits | Completed full-code results are cached; every new streamed code string is highlighted from scratch | Adopt per-mounted-block incremental highlighting, completed-line grammar state, stable token rows and memoized line components. |
| Native Markdown parsing | Nitro Markdown parser and selectable native iOS text | Already native. A small normal-message preparation probe measured around 0.3 ms per document, so a broad extra JavaScript document cache was not worth keeping. |
| Native-owned transcript/document handles | T3 sends normal thread data through its shared client runtime | Interesting for very large histories, but this changes ownership, incremental invalidation, and server/client integration. The current evidence does not justify a second native transcript store. |
| Lazy tool previews and image size metadata | Work summaries are folded/cached; media behavior differs | Keep these as targeted follow-up candidates. No measured gain claimed in this experiment. |
| Native macOS windows, split views and menus | iOS/Android navigation and native controls | AppKit components do not port directly. Preserve native selection, navigation and layout behavior rather than importing macOS UI machinery. |

Relevant Legend implementations: [transcript data source](https://github.com/LegendApp/legend-apps/blob/4744fb8b57026a645379e826fe8ab34861f43017/apps/chat-history/src/TranscriptDataSource.ts), [transcript rows](https://github.com/LegendApp/legend-apps/blob/4744fb8b57026a645379e826fe8ab34861f43017/apps/chat-history/src/TranscriptRow.tsx), and [native Markdown document](https://github.com/LegendApp/legend-apps/blob/4744fb8b57026a645379e826fe8ab34861f43017/packages/markdown-parser/cpp/HybridMarkdownDocument.cpp).

## Iterations and rejected results

1. A benchmark configuration accidentally combined Metro production mode with lazy chunks. Dynamic import of the native highlighter failed and selected the JavaScript fallback. Roughly 600 ms/update was a harness defect, not evidence of shipping native performance. Discarded. Disabling lazy bundling restored the native engine, which was explicitly checked through a native scanner match.
2. Incremental tokenization alone dramatically reduced highlighting work, but preserving colors while recreating thousands of React text elements made a 30-update rendered stream take roughly 12 seconds. Rejected.
3. Reusing normalized completed-line tokens, memoizing line components and stabilizing inherited text styles reduced that to roughly 4.9 seconds. Still too much repeated render work.
4. A bounded synchronous read for already-warm small appends avoids an intermediate render. Cold inputs, language/theme changes, long lines, large changes and unsupported text retain the asynchronous fallback. This version is the retained candidate, subject to the final comparison below.

The implementation keeps one weakly-owned session per mounted code block, caps retained source at 100,000 characters, limits synchronous appends to 2,000 characters and 200 lines, and preserves the existing long-line fallback. It does not change server contracts, providers, connection modes, or review-file highlighting. iOS and Android use the same callback contract; runtime validation here is iOS only.

## Measurement limits

Measurements use Hermes and the native Shiki regex engine on a fresh iPhone 17 Pro simulator running iOS 27 on the Mac mini. Metro serves minified JavaScript with development mode and lazy bundling disabled. The native shell is a compatible development client, not a release binary or physical phone. Absolute numbers are not production-device promises.

The isolated native fixture mounts the actual selectable Markdown renderer with 200 TypeScript lines, then appends 30 lines at a requested 100 ms cadence. Recordings inspect color stability and layout. JavaScript requestAnimationFrame gaps measure scheduling responsiveness, not native FPS. End-to-end readiness requires the final code line to have colored tokens and two subsequent animation-frame callbacks. Timing runs are separate from recordings. Tests compare token content, color and font style to full Shiki output, including multiline syntax, edits, truncation, chunk boundaries, overlapping requests and fallback cases.

No native binary changes are needed. Native document ownership or TextKit layout caching should be separate experiments with native traces and physical-device validation; they are not claimed as wins here.

## Retained results

Final source: [8e103351e3](https://github.com/pingdotgg/t3code/commit/8e103351e3), proposed in [#11211](https://github.com/pingdotgg/t3code/pull/11211).

Six counterbalanced microbenchmark rounds, 40 appended-line updates per variant per round, with the native engine confirmed. Median of round medians: **99.11 ms full highlighting versus 1.012 ms incremental**, a **98.98% reduction**. All six pairs improve. This is highlighting work, not overall app speed.

Three counterbalanced native-renderer rounds, fresh runtime per variant:

| Measurement | Before | After |
| --- | ---: | ---: |
| All 30 updates colored, median | 3595.6 ms | 3495.4 ms |
| Median JavaScript frame gap | 105.2 ms | 24.4 ms |
| Worst sampled JavaScript frame gap | 330.5 ms | 110.3 ms |

All three paired rounds finish fully colored sooner, though the completion-time difference is modest and should not be generalized beyond this fixture. Text-update scheduling alone takes about 3.22 seconds before versus 3.46 seconds after: keeping every update colored does more work than allowing the baseline to remain plain temporarily. Final colored readiness and frame stalls are the retained experience measures; this tradeoff is disclosed rather than hidden behind the microbenchmark.

The previous fixed 300 ms post-stream wait is preserved in the raw exploratory file but excluded from the final readiness comparison. It could stop before baseline highlighting finished. Final readiness waits for colored final-line tokens and two following animation-frame callbacks. Screenshots and recordings are separate verification runs, not the timed samples.

Focused tests: 45 passing across four files. Mobile TypeScript check passes. Targeted lint has the existing synchronous setState-in-effect warning in the highlighter hook; no new lint warnings.
