Skip to content

fix(timeline): diff-based UI updates to address Android freeze#131

Draft
TigerInYourDream wants to merge 3 commits into
mainfrom
fix/ime-bug
Draft

fix(timeline): diff-based UI updates to address Android freeze#131
TigerInYourDream wants to merge 3 commits into
mainfrom
fix/ime-bug

Conversation

@TigerInYourDream
Copy link
Copy Markdown

Summary

  • Replace full timeline snapshot clones with diff batches. The background subscriber now sends TimelineUpdate::ItemsChanged { item_diffs, .. } instead of NewItems { new_items: timeline_items.clone() }.
  • Apply diffs to TimelineUiState.items in place on the UI thread via a new TimelineDiff<T> covering all 11 SDK VectorDiff variants. TimelineUpdate::FirstUpdate remains the only full-snapshot path.
  • Cap timeline updates per Event::Signal at MAX_TIMELINE_UPDATES_PER_SIGNAL = 8 and re-trigger SignalToUI for the remainder, so the main thread does not infinitely drain the receiver.
  • Narrow bot discovery to changed_indices via the new timeline_update_scan_range helper instead of scanning the full timeline.
  • Bonus: clean up two pre-existing clippy::field_reassign_with_default warnings in test fixtures so cargo clippy --lib --tests is now warning-free.

Why

On Android the timeline page can stall or hit ANR after the app has been running for a while. Root cause is Robrix UI-thread amplification, not Makepad: every incremental SDK timeline diff was being cloned into a full timeline snapshot and RoomScreen::process_timeline_updates was draining the channel without any per-signal upper bound. Both pressures land on the main thread.

Test plan

Automated (all green):

  • cargo check
  • cargo clippy --lib --tests — 0 warnings
  • cargo test timeline_update_batch_yields_after_budget
  • cargo test timeline_update_scan_range_limits_incremental_work
  • cargo test timeline_items_apply_append_and_set_diffs
  • cargo test timeline_items_apply_remove_truncate_clear_reset_diffs

Manual (pending — this is why the PR is draft):

  • Android device, large active room with bursty updates: no ANR, main thread is not stuck inside process_timeline_updates for multiple seconds, touch input still responsive during the burst
  • Scroll position preserved when older events insert above the visible range
  • Tail mode preserved when sitting at the bottom and a new message arrives

Spec

specs/task-android-timeline-freeze.spec.md — full task contract with BDD acceptance scenarios.

Out of scope

iOS IME, PortalList internals, room-list startup pagination, media/avatar cache redesign. See spec.

…work

Switch TimelineUpdate from NewItems(full snapshot) to ItemsChanged(diff
batch) so the background subscriber stops cloning the entire timeline
on every incremental SDK diff. Introduce TimelineDiff<T> covering all
11 SDK VectorDiff variants and apply diffs to TimelineUiState.items in
place on the UI thread.

Cap timeline updates per Event::Signal at MAX_TIMELINE_UPDATES_PER_SIGNAL
and re-trigger SignalToUI when the receiver still has pending work, so
the Makepad event loop is not blocked by an unbounded try_recv drain.

Narrow bot discovery to changed_indices via a timeline_update_scan_range
helper instead of scanning the full timeline. TimelineUpdate::FirstUpdate
remains the only full-snapshot path.

Addresses Android timeline freeze caused by UI-thread amplification of
small SDK diffs into full-snapshot work. See
specs/task-android-timeline-freeze.spec.md for the task contract.
clippy::field_reassign_with_default suggested initializing AppState in a
single struct literal with ..Default::default() instead of mutating the
default value field-by-field. Apply this in the two test sites that
hit the lint; the result is functionally identical and removes the
remaining warnings reported by `cargo clippy --lib --tests`.
Capture the root cause as Robrix UI-thread amplification of small SDK
diffs (RoomScreen::process_timeline_updates draining the receiver in an
unbounded try_recv loop, sliding_sync::timeline_subscriber_handler
cloning the full timeline on every incremental update) and record the
decisions implemented in this branch:

- bound timeline updates per Event::Signal via
  MAX_TIMELINE_UPDATES_PER_SIGNAL and resume via SignalToUI
- replace TimelineUpdate::NewItems with ItemsChanged carrying a
  Robrix-owned TimelineDiff covering the SDK VectorDiff operations
- limit bot discovery to changed_indices via timeline_update_scan_range
- keep TimelineUpdate::FirstUpdate as the only full-snapshot path

Includes BDD acceptance scenarios for the four focused unit tests and
the manual Android verification path. Inherits specs/project.spec.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant