todo-app/docs/desktop-performance-findings.md
Krzysztof kuhy Rudnicki 25e2c9bb20 Split CLAUDE.md and the performance doc; the whole repo is under the cap
CLAUDE.md 925 -> 165. 789 of those lines were a verbatim paste of upstream's
flutter/flutter rules.md -- 85% of an always-loaded file given over to a
document nobody edits. It moves to five topic files under docs/, linked
rather than @-imported: inlining it back would pass the cap while keeping it
in context on every turn, which is the cost the cap exists to remove.

Every `###`/`####` heading in the upstream block survives the split; the two
that do not are its own wrapper headings, replaced by the per-file titles
(verified by diffing the sorted heading lists).

desktop-performance-findings.md 278 -> 73 + 216. It was already narratively
two-part; the author signposted "Part 2" explicitly at the seam.

Also updates two things that had drifted: the suite is 313 tests at 100%
coverage (not 238), and Conventions now documents the cap, where the gate
lives, and the two patterns used to split without breaking callers
(re-export barrels, and `part` files where a private State must stay in one
library).

`bash ~/utils/scripts/check_file_length.sh --all` now exits 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 16:46:54 +02:00

3.7 KiB

Linux desktop performance: measured findings

Investigation date: 2026-07-20. Machine: Arch, X11 + i3, NVIDIA proprietary 610.43.03, RTX 3090. DP-0 = 3840x2160 @144 Hz (primary), HDMI-0 = 2560x1440 @144 Hz. Flutter 3.44.6 stable.

Conclusion

The previous session was right: Flutter's Linux desktop embedder cannot drive this machine's 4K display, and switching technology for the desktop app is justified.

Two independent defects, neither fixable in application code:

  1. Hard 60 FPS cap. Flutter's Linux embedder schedules frames at a fixed 60 FPS regardless of the monitor (upstream #183703, P2, no PR).
  2. ~20 FPS at 4K. Far worse, and the actual cause of the reported lag. At 3840x2160 the app achieves 18.6 fps while each frame costs only ~3 ms of a 16.7 ms budget. The stock flutter create counter app behaves identically, so this is the toolkit, not todo.

Application code is not implicated anywhere: build (Dart) time is 0.1-0.4 ms in every configuration and at every window size measured.

An earlier revision of this document concluded that a rewrite was not warranted. That conclusion was based only on defect 1 and is superseded by the 4K measurements below.

Evidence

Measured with a temporary in-app instrument (lib/frame_stats.dart, armed by TODO_FRAME_STATS=1) reporting frame-to-frame cadence alongside build/raster times, driven by a perpetual animation so frames are produced continuously.

Configuration Cadence p50 Achieved build p50 raster p50
Release, Skia GL (default) 16.67 ms 60.0 fps 0.30 ms 1.50 ms
Release, Impeller 16.67 ms 60.0 fps 0.19 ms 1.33 ms
Release, Impeller + Vulkan 16.67 ms 60.0 fps 0.26 ms 1.33 ms
Driver vsync off (__GL_SYNC_TO_VBLANK=0) 16.67 ms 60.0 fps 0.28 ms 1.40 ms
picom compositor + Skia GL 16.67 ms 60.0 fps 0.27 ms 1.62 ms
picom compositor + Impeller 16.67 ms 60.0 fps 0.30 ms 1.44 ms
Stock flutter create app (no todo code) 16.67 ms 60.0 fps 0.12 ms 1.35 ms
Same Flutter code as a web build, in Chrome 7.00 ms 142.9 fps — —

Retracted: an earlier revision of this table had a "real typing + scrolling" row. It was invalid. It used xdotool type --window, which sends synthetic XSendEvent keystrokes that GTK/Flutter ignore; the keystrokes actually landed in a different application. That run measured an idle app. Interaction is measured properly in the next section instead.

Confounds on the web row, stated for honesty: it ran on HDMI-0 (1440p@144) with a game occupying the 4K GPU, which is why its p95 was 13.90 ms. The desktop 60.0 fps lock, by contrast, is clean, jitter-free and monitor-independent, so the comparison's conclusion holds — but the web number should be re-confirmed on the 4K monitor with the GPU idle.

Key readings:

  • Cadence p50 equals p95 at exactly 16.67 ms in every desktop run — a hard lock with zero jitter, not contention.
  • Total per-frame work is ~1.8 ms against a 6.94 ms 144 Hz budget. The renderer has 74% headroom at 144 Hz and 89% at 60 Hz.
  • Under real typing and scrolling, one frame exceeded the 144 Hz budget in 14 seconds and zero exceeded 60 Hz. There is no dropped-frame jank.
  • The stock counter app behaves identically, so this is not todo's code.
  • Neither renderer choice, nor driver vsync, nor a compositor moves the number.

Part 2 — the interaction-lag half of this investigation, and the decision that came out of it — is in desktop-performance-interaction-lag.md.