perf(desktop): low-risk fixes from the canary performance review

HA
13h ago
+158-20

Links

Summary

Six small, independent fixes from the canary performance review, one commit each:

  1. React Query persister: throttleTime 20 s. It saved the whole persisted cache about once a second while idle.
  2. Login-shell env (host-service): one shared in-flight spawn, a 15 min TTL, and rc-file stderr logged once per process. Before, concurrent callers each spawned their own zsh -i -l -c, and a real log had 7,462 spawns.
  3. xterm image addon: storageLimit: 16 (MB) and pixelLimit: 2048². The defaults were 128 MB and 4096² per terminal, and up to 12 parked terminals stay alive. See Known Limitations: open question.
  4. SQLite: synchronous = NORMAL after WAL in the chat-runtime, host-service and desktop local DBs. It was FULL, so every commit did an fsync.
  5. app.asar: files excludes *.map, *.d.ts, *.d.mts and *.d.cts. 418 MB of the 1.13 GB asar is maps.
  6. Auto-updater: download progress is logged at 25/50/75/100% only. It was 66% of a real main.log.

How It Works

  • Shell env: the cache is now createShellEnvCache(resolveEnv, now, ttlMs), so tests can inject the spawn function and the clock. A result that arrives after clear() is not cached, and a failed spawn clears the in-flight promise. Callers still get a copy.
  • Updater: a small createDownloadProgressMilestones() helper. It resets on update-available, which electron-updater emits before each download (autoDownload is the only download path).
  • Merge with main: #9149 had already set throttleTime: 10_000. I kept its comment and used the 20 s from the review. Reviewers can choose either value.

Manual QA (before/after on a cloud dev box, same route and signed-in org)

  • Persister, running app, 2 min idle: 74 → 6 IndexedDB cache writes, 10.35 → 0.84 MB written. Renderer CPU did not change (6.5/18.2% → 6.6/19.3% script/task). This account's persisted cache is small (137 KB, about 4 ms per save), so the CPU gain for large PR/issue caches is not measured.
  • Shell env, real zsh and real module: 8 concurrent calls went from 8 spawns and 8 stderr logs to 1 and 1. With a failing shell, the next call spawns again, so the failure is not cached.
  • Image addon, the app's loadAddons() on a real xterm with scrollback: 5000, 100 × 1024² inline images: 126.5 → 14.6 MB held. A 3000² image is now dropped. This was a synthetic check on a test terminal, not a terminal opened through the UI.
  • SQLite: both createChatDb and the host-service createDb report synchronous = 1 (NORMAL). 2,000 single-row commits went from 122 to 20 µs each on xfs.
  • Updater: replaying 4,367 progress events gives 4 log lines. It logs again after a reset.
  • app.asar size: not tested. It needs a packaged build.

Source maps (fix 5)

  • Sentry still gets the maps. In build-desktop.yml, compile:app runs the vite build with SENTRY_AUTH_TOKEN, and the Sentry vite plugin uploads during that step, before the separate bun run package step.
  • No runtime reader. Nothing loads .map or .d.ts files at runtime: no source-map-support, no --enable-source-maps, and worker execArgv is unset in production.
  • Partial coverage. Top-level ! patterns apply to dist/ and to the node_modules that electron-builder collects. They do not apply to the object-form copies in runtime-dependencies.ts (native modules), so those still ship their maps.

Testing

  • bun run typecheck in apps/desktop, packages/host-service and packages/chat-runtime: pass
  • bun run lint (root): pass
  • bun test:
    • apps/desktop: 8,981 pass, 0 fail
    • packages/chat-runtime: 430 pass, 0 fail
    • packages/host-service: 2,709 pass, 1 fail. The failure is health.test.ts, which reads the cloud box's real /opt/superset/bundle/current.bundle-hash. This PR does not touch that code.
  • New tests: clean-shell-env.test.ts (dedupe, failure not cached, TTL) and download-progress-milestones.test.ts.

Known Limitations

  • Image limits may be too tight (open).
    • Images larger than 2048² (for example a 2880×1800 Retina screenshot shown with imgcat or kitty) are now dropped without a placeholder.
    • storageLimit: 16 is 4,000,000 pixels, which is smaller than one image at the new pixelLimit. So one large image evicts all older ones.
    • A safer pair is roughly pixelLimit 3840×2400 and storageLimit 64.
  • Slower rc updates for git/gh. With the 15 min TTL, git/gh calls can use the old login-shell env for up to 15 min after a user edits their rc files. Terminals take their env once at startup and are not affected.
  • Persister CPU unproven. The persister still builds a cache snapshot on every cache event before the throttle. If idle CPU stays high, profile that next.

Summary by cubic

Picks six low-risk performance fixes from the canary review, targeting desktop idle CPU, shell spawn volume, SQLite write latency, packaged app size, and log volume.

  • The React Query IndexedDB persister now throttles to 20 s (was 10 s after a merge), cutting idle cache writes from about once a second to once per 20 s.
  • getStrictShellEnvironment now shares a single in-flight zsh -i -l -c spawn per process, keeps results for 15 minutes instead of 60 s, and logs rc-file stderr once per process instead of per spawn.
  • The xterm image addon now caps storage at 16 MB and pixels at 2048² per terminal; a real log previously had 7,462 concurrent shell spawns.
  • The chat-runtime, host-service, and desktop-local SQLite DBs now use synchronous = NORMAL with WAL, avoiding an fsync per commit.
  • The packaged app.asar excludes *.map, *.d.ts, *.d.mts, and *.d.cts, removing about 445 MB; Sentry still receives source maps because they upload during the build before packaging.
  • Auto-updater download progress logs only at 25/50/75/100% instead of on every chunk event.

Known limitations

  • Images larger than 2048² (for example a Retina screenshot) are dropped without a placeholder, and one max-size image exceeds the 16 MB storage limit so it evicts all older images.
  • git and gh calls can use a login-shell env up to 15 minutes stale after rc-file edits; terminals take their env once at startup and are unaffected.

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal reliability by reusing shell environment results and refreshing them periodically; failed lookups can be retried.
    • Limited terminal image rendering to help prevent excessive resource use.
  • Performance
    • Updated local data storage settings and reduced the frequency of cache saves.
  • Other Improvements
    • Update download progress is logged at key milestones, while in-app progress updates keep their existing cadence.