fix(threads): close the backend thread timeline when its last view closes (#308) - #328
Merged
Merged
Conversation
…oses (#308) Opening a thread created a focused Timeline, a subscriber task and channels in `JoinedRoomDetails::thread_timelines`, but closing the tab (desktop) or popping the view (mobile) never removed them, so a long session retained one timeline + task per distinct thread ever opened. - sliding_sync: `ThreadTimelineTable<T>` replaces the `thread_timelines` + `pending_thread_timelines` pair and keeps `live ∩ pending = ∅`; `close()` forgets a pending build so a later `finish_create()` is rejected and the built timeline is dropped instead of leaking (Rule th-5). New `MatrixRequest::CloseThreadTimeline` drops the entry, which aborts its subscriber task via `PerTimelineDetails::drop`. `thread_timeline_counts()` and live/pending counts in the create/close logs for observability. - room_screen/state: `TIMELINE_STATES` becomes `TimelineStateCache<S>` with per-kind `invalidate` (drop the cached state now, or mark it so a shown RoomScreen's late save is dropped) and `take` (clears the mark), plus `close_thread_timeline(cx, kind)`. - room_screen/thread_lifecycle: pure `close_if_unreferenced(closed, remaining)` decides "last view gone?" so mobile deep navigation (room → thread → room → thread) never closes a thread still on the stack (Rule th-3). - main_desktop_ui `close_tab` / `close_all_tabs` and app `StackNavigation Pop` call it; desktop ignores the undrawn mobile stack. Spec: specs/task-thread-timeline-lifecycle.spec.md — 9 automated scenarios (3 property tests) pass; 6 manual scenarios pending user verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…race Review finding on #328: `pending` was keyed by event id only, so begin(A) → close(A) → begin(A) let the *old* build's finish_create consume the *new* attempt's pending slot (registering a timeline that was closed semantically) and its fail_create cancel the new attempt. `begin_create` now returns a `CreateToken { id, generation }` from a monotonic counter; `finish_create(&token, ..)` / `fail_create(&token)` act only if the token is the current pending generation for that id; `close` still forgets any generation. The create task carries its token. Tests: deterministic `thread_table_stale_generation_cannot_hijack_reopened_thread` plus the property model now tracks generations and lets ops replay stale tokens; both catch the id-only mutant. Spec th-5 updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #308. Draft — manual scenarios not yet verified.
Problem
Every opened thread creates a focused
Timeline+timeline_subscriber_handlertask + channels inJoinedRoomDetails::thread_timelines; closing the tab / popping the view never removed them. Long desktop sessions retained one timeline + task per distinct thread ever opened.Invariants (spec
task-thread-timeline-lifecycle)Changes
sliding_sync: genericThreadTimelineTable<T>(begin_create / finish_create / fail_create / close) replaces the two fields;MatrixRequest::CloseThreadTimeline;finish_createrejection path logs "closed while building";thread_timeline_counts(); live/pending counts in logs.room_screen/state:TimelineStateCache<S>with per-threadinvalidate/take/store;close_thread_timeline(cx, kind).room_screen/thread_lifecycle(new): pureclose_if_unreferenced.main_desktop_uiclose_tab/close_all_tabs,appmobilePopwired.Verification
prop_thread_table_invariantsvs. reference modelprop_close_fires_iff_last_reference_removedinvalidated_timeline_state_is_dropped_and_fresh_state_is_storedcargo test --lib: 690 green.agent-spec lifecycle: 10 passed / 0 failed / 6 manual.🤖 Generated with Claude Code