Summary

Description

There have been a few questions and comments about performance in recent weeks and so this release is almost exclusively about performance. The changes target what users actually feel: faster first load, a snappier chat list and message view on long-lived sessions, smoother scrolling and gestures on mobile, and less memory held over time. We also identified a number scenarios where the system degrades over time which have now been addressed. There are a lot of changes in here so please let us know if there are any regression.

Highlights:

  • Faster first load. The app now downloads only the layout (desktop or mobile) it is going to show, rarely used dependencies (video calling, QR scanning, syntax highlighting, terms text, and ~30 mobile screens) load on demand, and the main/vendor chunks are preloaded instead of discovered late. JS transferred on initial load drops from 1,981 KB to 1,643 KB gzipped (−17%) and JS heap after load from 80 MB to 68 MB.
  • Much faster message loading. Response validation in the worker was the single biggest CPU cost when loading messages: ~176 ms per 200-message page, now ~3.7 ms (~48× faster). Scrolling back through history should now be much less prone to stuttur and/or degrade.
  • A snappier app when messages arrive. Every reaction, tip, edit or incoming message used to deep-clone every chat and recompute all unread counts from scratch. Unread recomputation drops from ~55,000 chat evaluations to ~50 (~50 ms → ~16 ms for a 300-chat user), the core chat store recompute is 80–87% cheaper, and no-op store publishes are skipped entirely — so the UI does dramatically less work per event, continuously.
  • Smoother mobile. Message-bubble pan gestures no longer hold a compositor layer per row, covered modal pages stop being painted, listeners are passive, and a series of teardown leaks (retained images, timers, pollers) are fixed — so long sessions stay smooth instead of degrading.
  • Lower bandwidth and battery cost in the background. The service worker shrank 34% (124 KB → 82.5 KB gzipped) and starts faster on cold launches (the iOS white-screen area); its media cache is now bounded. Emoji data is fetched once, lazily, instead of twice at every boot.

Two small user-visible behaviour changes: composer syntax colouring while typing now covers the same four languages as rendered messages (others were never highlighted once sent), and the 'hide token balances' / 'accounts section' settings reset to defaults once, because they were previously stored under the wrong keys.

Changed

Load time and bundle size

  • Load only the selected App tree (desktop or mobile) at startup: initial JS −17% gz, heap 80 → 68 MB (#9209)
  • Lazy-load rarely used dependencies (highlight.js grammars, snow, terms, QR, peerjs, tracking): vendor −671 KB, main −367 KB raw (#9222)
  • Load daily-js only when a video call starts: vendor −222 KB raw (#9228)
  • Preload main/vendor chunks (saves a 240–390 ms round trip on cold load); replace a ~250 KB locale polyfill with native Intl.getCanonicalLocales (−60 KB gz) (#9226)
  • Service worker: notification-only imports (−34% gz, faster cold start) and a capped, quota-safe media cache (#9235)
  • Construct one emoji database lazily instead of two at boot (was two CDN requests every launch, ~1.4 MB on first run) (#9221)

Message loading and per-event work

  • Worker: 48× faster response validation (176 ms → 3.7 ms per 200-message page); no reply-buffer copies (#9237)
  • Incremental unread counts (55,000 → 50 chat evaluations per mark-read); batch observer-driven mark-read calls into one publish (#9238)
  • Stop deep-cloning every chat on each chat-store recompute: −80–87% time and ~200 KB less garbage per local-update event (#9227)
  • Skip store publishes that change nothing (the 60-second user poll no longer re-derives chat summaries when nothing changed) (#9212)
  • Stop JSON-serialising map keys on every access: chat-map iteration ~15× faster (#9223)
  • Trim the events merge pipeline: per-message fast path, single index range, no reverse copy (~23% faster merge) (#9225)
  • Batch IndexedDB reads into one transaction per call (user poll, event and message loads) (#9218)
  • Cap and dedupe cached mentions at the 50 newest; tidy summary-merge hot paths (#9216)
  • Memoise identicon generation in the worker (was regenerated for every avatarless user on every 60-second poll) (#9215)
  • Memoise Intl date/number formatters (previously constructed per message row) (#9214)

Rendering and interaction

  • Markdown: re-render a message only when its text or a mentioned name actually changes, instead of on every user-store publish; stricter pure HTML escaper (independently security-reviewed) (#9234)
  • Composer: one document serialisation per keystroke instead of two; far fewer DOM nodes recreated after each send on mobile (#9232)
  • Virtual list: shared resize/intersection observers (3 → 0 observer create/teardowns per recycled row), incremental prefix sums, no debug payloads on the hot path (#9231)
  • Mobile gestures and scrolling: release compositor layers when pans are idle (N per chat → 0), passive listeners, lazy style reads (#9229)
  • Mobile: stop painting covered modal pages; lazy-load ~30 rarely used screens (#9230)
  • Stop chat-list rows rebuilding on the unused 5-second clock (#9210)
  • Share one derived store across all ~2,000 translatable nodes (#9213)
  • Small perf fixes: panel-divider drag no longer writes localStorage per mousemove; SNS function labels hydrated once at startup (#9224)

Fixed

  • Two settings stores wrote to another setting's localStorage key (toggling hide-token-balances also hid the CHIT icon); both reset to defaults once (#9220)
  • Mobile component teardown leaks: retained zoomed-image bitmaps, unrevoked object URLs, stray timers and intervals (#9219)
  • Leaked pollers and unbounded client caches (BTC balance poller stacked per emission; link-preview and video-call caches now bounded) (#9211)
  • Replaced worker agents are now disposed (cache primer stopped, IndexedDB connection released); requests arriving with no agent reject instead of hanging forever (#9217)
Payload

Proposal to execute nervous system function:

Nervous system function:

NervousSystemFunction { id: 10000, name: "Update OpenChat frontend assets", description: Some( "This will commit a newly prepared batch of assets which have been uploaded to the asset canister by a principal which has been granted the "Prepare" permission.", ), function_type: Some( GenericNervousSystemFunction( GenericNervousSystemFunction { target_canister_id: Some( 6hsbt-vqaaa-aaaaf-aaafq-cai, ), target_method_name: Some( "commit_proposed_batch", ), validator_canister_id: Some( 6hsbt-vqaaa-aaaaf-aaafq-cai, ), validator_method_name: Some( "validate_commit_proposed_batch", ), topic: Some( DappCanisterManagement, ), }, ), ), }

Payload sha256:

02bbd5db625191cab7cd7f69d7bdfb6ee8e5c46a7df933df61970c0356729cdf

Payload:

commit proposed batch 255 with evidence a78050bc0e3133ad88097b00e40f2dff35dc32ae7f474b2169ba7a9cbc0bec74

Overview