Repository navigation
Cut the desktop app's render load with many hosted agents - #1293
alexeyzimarev merged 6 commits into
Conversation
OpenGL on macOS is Apple's emulation over Metal and costs CPU in the driver for every frame; the alternate-frame colour flicker that kept Metal out no longer reproduces. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude Code prints a thinking block as prose in its own terminal, so a chat that dropped it read as a transcript running behind. Like any prose, a thinking row closes the open tool group, which is also how the terminal groups the calls around it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A prompt sent mid-turn is echoed to the transcript only when the runtime picks it up at its next step boundary, so the row can outlive a long tool run by minutes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A keyframe opacity animation in a style runs per marker at the display's refresh rate, so one working session kept the compositor drawing the whole window 60 times a second; the shared clock ticks ten times a second and only for markers whose state is in flight. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The terminal control stays laid out at opacity 0 behind the chat tab, because the attach takes its PTY size from the measured surface, and it re-lays out every row it draws on each feed. Holding chunks while the tab is hidden keeps that cost off the chat the user is reading; the backlog is fed first the moment the tab is shown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
PR Summary by QodoReduce desktop render load and clarify hosted-agent chat
AI Description
Diagram
High-Level Assessment
Files changed (18)
|
Code Review by Qodo
1.
|
| _heldTimer ??= _time.CreateTimer(_ => RxSchedulers.MainThreadScheduler.Schedule(FeedHeld), null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); | ||
| _heldTimer.Change(HiddenFeedInterval, Timeout.InfiniteTimeSpan); |
There was a problem hiding this comment.
2. Closing a workspace can fault an output feed 🐞 Bug ☼ Reliability
Deliver arms _heldTimer without synchronizing with TeardownAsync, which disposes that timer before retiring the output generation. If teardown runs concurrently after an output callback passes its Retired check, the callback can call Change on the disposed timer and throw ObjectDisposedException.
Agent Prompt
## Issue description
A hidden terminal feed can arm its timer while concurrent teardown disposes it.
## Fix Focus Areas
- src/Capacitor.App/ViewModels/TerminalTabViewModel.cs[302-328]
- src/Capacitor.App/ViewModels/TerminalTabViewModel.cs[608-612]
- src/Capacitor.App/ViewModels/TerminalTabViewModel.cs[683-698]
## Recommended Fix
Coordinate delivery and timer disposal on the same serialized execution path, and retire the attempt before a queued output callback can arm the timer.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41da81b984
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .With(new AvaloniaNativePlatformOptions { | ||
| RenderingMode = [AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software], | ||
| RenderingMode = [ | ||
| AvaloniaNativeRenderingMode.Metal, AvaloniaNativeRenderingMode.OpenGl, AvaloniaNativeRenderingMode.Software, |
There was a problem hiding this comment.
Keep Metal disabled on affected macOS 26 systems
On macOS 26 configurations affected by the renderer issue documented immediately before this change, putting Metal first makes Avalonia select the backend known to alternate color matching between frames, so focused windows flicker whenever the caret triggers a redraw. A successful run on one machine does not make that backend safe across affected displays; preserve OpenGL-first rendering or gate Metal behind a configuration known not to exhibit the issue.
Useful? React with 👍 / 👎.
…listed A collapsed section keeps its rows attached, so an active marker there would cost a render pass per tick for nothing; and a dispatch the server stopped listing was delivered or withdrawn, which only the transcript echo can tell apart. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
AI-3451 — no GitHub issue: the report was filed in Linear only.
What & why
With nine hosted agents the app burned half a core and typing in the terminal lagged, while the chat looked behind the terminal. The chat was not behind: the terminal prints thinking blocks that the chat dropped. The CPU went on the compositor drawing the whole window at 60 fps, because every in-flight marker ran its own keyframe opacity animation, through OpenGL on Metal, while the hidden terminal re-laid out every row per PTY chunk behind the chat. The app now renders through Metal, fades markers on one shared 10 Hz clock bound to their state, holds a hidden terminal's output for one second at a time, shows thinking as a muted row, and says why a queued message is still queued.
Where to look
PulseClock replaces the
.pulsingandBorder.toolRunningkeyframes; the classes stay as markers for tests. A thinking row closes an open tool group the way prose does, so groups split where the terminal splits them.Verification
Live app, nine agents, 60 Hz display, dotnet-trace over 45 s and then 6 s:
The Metal flicker the removed comment described did not reproduce on macOS 26 in a side-by-side run.
Capacitor.App.Tests.Unit: 2843 of 2844 pass;Size_label_and_image_flagfails only under a comma-decimal culture ("2,3 MB") and is untouched here.🤖 Generated with Claude Code