cmux-next iOS: every vertical list scrolls at full frame rate

Closed
AZ
5d ago
+12487-2071

Summary

Every vertical list in the cmux-next iOS app now scrolls at full frame rate, idle and while live data changes (agents streaming, a busy Mac publishing workspace updates). Do not merge until it is dogfooded: the owner tries the tagged build first.

What changed, by list:

  • Agent chat transcript (CmuxMobileAgentChat): one UIHostingController holding every turn becomes a UICollectionView of reused rows with a diffable data source of stable row ids.
    • Common rows (markdown blocks, prompt bubble, finished tools, snippets, edited files, action row) are native UIKit, laid out off the main thread once per revision and width; rare cards stay SwiftUI in their own reuse pools.
    • Streamed chunks apply once per display frame; only the unfinished tail block re-parses and re-measures per token.
    • Each edit's diff is computed once, off main (this was the 45-50 s stall when opening a chat with a large file edit).
    • Keyboard docking, follow-bottom, the jump button, links, menus and VoiceOver access to every action are kept; accessibility text sizes fall back to the SwiftUI rows.
  • Workspaces list (CmuxMobileWorkspaces): SwiftUI List becomes a UICollectionView with stable section ids; a live update applies only moved or changed rows. Row menus are built when opened, the shimmer runs on the render server, the inbox is memoized per store revision, and an equal workspaces write no longer notifies.
  • Tab grid (CmuxTabsKit + host): only cards near the screen are built, the hidden grid does no work, agent cards draw as pictures refreshed while the grid rests, and browser models are made on first view. The kit-only commits are proposed to feat-cmux-next-tabs-kit in #26818.
  • Diff file screen (CmuxMobileDiff): a UICollectionView with rows, highlighting and heights prepared off main per file and options; horizontal panning moves one shared offset with no SwiftUI body work. hunks(context:) is one forward pass; viewed keys are unchanged.
  • Files and artifacts (CmuxMobileArtifacts): thumbnails decode off main, one shared make per artifact, no web snapshot under a moving grid, linear syntax highlighter off main.
  • Home (CmuxMobileChief): Equatable rows, memoized list, menus on demand, typing dots on the render server.
  • Feed store (CmuxMobileFeed, CmuxMobileLive): items indexed by id, read marks written off main, the Mac's reads in one write, linear echo check.

A DEBUG-only rig (ListStressRig, verify/listperf/) seeds any of these lists, scrolls it and records frame intervals, main-thread CPU per frame and memory.

Before and after

60 Hz iPhone 17 Pro simulator, iOS 26.5, aws-m4pro-6, one simulator, base (feat-cmux-next at 4e28d9cf44d9, with the same rig files) and head alternating, 2 runs each. Hitch time is time late per second of scrolling (Apple's hitch ratio; under 5 ms/s is the target).

ListHitch ms/sWorst frameMain CPU ms/sOpen, worst frameMemory
Workspaces, 200 rows, 4 updates/s795–810 → 0–1.1661–815 → 17–33 ms942–992 → 178–202–76–84 → 56 MB
Workspaces, 500 rows, 8 updates/s990 → 0.4–6.14,608 → 33–39 ms1,179–1,232 → 231–251–92–170 → 59–60 MB
Agent chat, 40 turns0 → 017 → 17 ms86–92 → 59–7445,323–45,843 → 103–199 ms349 → 59 MB
Agent chat, 200 turns0 → 017 → 17 ms89–94 → 56–70453–44,313 → 470 ms1,344 → 68 MB
Agent chat, answer streaming647–706 → 0215–238 → 17 ms1,000 → 128–154480–40,171 → 56–120 ms405–475 → 60 MB
Tab grid, 30 tabs, chat streaming173–396 → 0–1.755–68 → 17–27 ms930–1,014 → 158–163509–546 → 84 ms217–222 → 61 MB
Tab grid, 80 tabs18–75 → 0–5.237–54 → 17–35 ms469–709 → 204–227679–779 → 79–92 ms395 → 66 MB
Diff, 2,400-line file3.5–60 → 037 → 17 ms749–831 → 118–17540,047–50,366 → 140–231 ms816 → 76 MB
Diff, horizontal pan612–639 → 056–74 → 17 ms803–886 → 56–109–819 → 66 MB
Files and artifacts grid2.1–2.3 → 033–46 → 17 ms143–160 → 90–9139,976–53,465 → 207–213 ms411 → 78 MB
Home, 300 conversations1.6 → 1.0–1.285 → 60–66 ms248–272 → 202–216–57 → 53–56 MB
Feed (reference, unchanged)0.3 → 0.2–0.434–35 → 35–36 ms58–61 → 57–58–64 MB

Chat-40 and artifacts head rows come from the post-fix reruns (5 and 3 runs); see the report.

The full report with before/after recordings is local to the author's Mac for now (artifact publishing needs a claude.ai login in that session); the review findings and their resolution are in the first PR comment.

Testing

  • Full CmuxMobileKit suite on aws-m4pro-5 at e7e3c9c53b54: 1,287 tests in 314 suites passed.
  • New suites include FeedStoreScaleTests, SmoothChatTests, EditPreviewTests, ChatNativeEligibilityTests, DiffPrepareTests, WorkspaceInboxIdentityTests, WorkspacesListPlanTests, WorkspacePublishTests, HomeListMemoTests, TitleActivityTests, CardResidencyPlanTests.
  • UITests/WorkspacesListUITests (new) on a simulator: long-press menu, trailing swipe Rename/Close, offline Mac fold, pull-down search, opening a workspace: passed twice.
  • CmuxTabsKit swift test 62 passed; WorkspaceTabsSourceListTests / WorkspaceTabsSourceUpdateTests on a simulator 16 passed.
  • An independent correctness review found 2 high, 6 medium and 5 low issues; all are fixed in this branch (frozen tab-card picture, VoiceOver in native chat rows, accessibility text sizes, shimmer restart, snapshot from layoutSubviews, stale heights, thumbnail cache key, read-mark loads waiting on disk, Reduce Motion for typing dots, HTML snapshot scale) or checked (trailing swipe works with the new cells).
  • prepush-gate.py: no new violations.

Observed / Not run

  • Observed: rig frame logs, Instruments-free sample profiles, simulator recordings and stills before and after, on aws-m4pro-6 and the lane minis.
  • Not run: the real iPhone (simulator only, per the owner's current rule); VoiceOver itself (accessibility trees were logged and checked); pull to refresh in Workspaces (mock mode has no refresh action); TabReorderUITests fails at its tab-grid drag check the same way on base and head.
  • Known remaining costs: opening a workspace from the list has a 120-210 ms frame, mostly off the main thread (suspected simulator render-server commit, not verified); main-thread CPU p99 per frame is 12-18 ms for Workspaces under live updates while hitches stay in budget; chat at accessibility text sizes is SwiftUI (13.5 ms p99 per frame).

No capture: iOS app (no Mac native UI changes); simulator recordings and stills are in the linked report.

Changelog

Changed: Lists in the cmux iPhone app (workspaces, agent chat, tab grid, diffs, files, Home) scroll smoothly while agents work, open large chats and diffs at once, and use far less memory.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Every vertical list in the cmux-next iOS app now scrolls at full frame rate, idle and while live data changes (agents streaming, a busy Mac publishing workspace updates). Workspaces, agent chat, tab grid, diff screen, files/artifacts, and Home lists are UICollectionViews of reused rows with stable IDs and diffable data sources; text, heights, diffs, thumbnails, and syntax highlighting are prepared off the main thread once per change.

  • Streaming chunks coalesce to one update per display frame; opening a 200-turn chat or 2,400-line diff now takes tens of milliseconds instead of tens of seconds.
  • The tab grid builds only cards near the screen and agent cards draw as pictures while the grid rests; the kit-only changes are also proposed in #26818.
  • A store change now reaches the stored tab list in the same turn, so code that changed a tab and read the list in the same turn no longer sees the old list (four WorkspaceTabsSourceTests caught by CI).
  • The branch is merged up with feat-cmux-next: Find in Chat, Edit/Resend, failure retry and Retry with another model, diff Search and hunk jumps, workspaces group menus, the Reduce Motion jump, and the disclosure folds (a tapped tool row, group, or thought keeps its line in place while its height springs — or lands at once with Reduce Motion) are ported into the new rows.
  • Adds a DEBUG-only list stress rig (verify/listperf/) that seeds and scrolls each list and logs frame intervals, main-thread CPU, and memory.

Note: don't merge until the owner dogfoods the tagged build.