feat(mobile): arrange active threads from both thread lists#9730

merged3
sidebar/active-order
sidebar/mobile-active-order
openedSep 4, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Active threads could not be arranged from mobile. Home and the navigation sidebar now offer Move up and Move down when the server supports saved Active order. Moves use the complete section even when the visible list is filtered, and hold the chosen order until command receipts and canonical updates arrive so partially saved keys do not make rows jump.

Menu availability and execution share the same planner. A move stays available around unsupported servers' existing keys, but is disabled when it would require writing keys to an unsupported server.

The planner reserves snoozed and filtered rows' saved keys without changing those rows.

Verified with 62 focused list and pending-order tests, mobile typecheck, and targeted lint. An Android emulator pass covered Home and the wide navigation sidebar, saved order after relaunch, pinned moves in both directions, first-row disabled controls, and a filtered middle row retaining both directions.

Home: move an Active thread up, then verify its first-position control is disabled.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a61e68e9ccbf1707/mobile-home-move.mp4

Navigation sidebar: move the same thread back down.

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fa7ae5b4e9202b12/mobile-sidebar-move.mp4

Navigation sidebar with Move up disabled at the first Active position

Depends on #9729.

Prepared with Codex.


Note

Medium Risk Changes thread list ordering and multi-step server reorder mutations with optimistic UI holds; incorrect reconciliation could show wrong order until sync, but scope is mobile list UX rather than auth or data integrity.

Overview Mobile thread list v2 now supports Move up / Move down for active threads (not only pinned), gated on the server threadActiveReorder capability. The action is renamed from movePinnedThread to moveThread and routes pinned moves through reorderPin and active moves through reorderActive.

Ordering aligns with web: active rows sort via activeOrderKey (sortActiveThreadsByOrderKey), with new/unsettled threads still above saved arrangement. getThreadListV2OrderedSection defines the full pinned/active section for planning so search and project filters do not change valid moves.

A shared pending-order layer (threadOrder.ts, state/thread-order.ts) holds the target order until reorder commands finish and canonical shells catch up, blocks overlapping moves, and keeps filtered lists in the pending section order. createThreadMovePlanner drives both menu enablement and execution, including mixed-environment lists where only reorder-capable servers get writes.

Home, navigation sidebar, and ThreadListV2Row expose generic reorder props (reorderSupported, canMoveUp/canMoveDown, onMoveThread) on card rows. Tests cover sorting, pending reconciliation, and move availability.

Reviewed by Cursor Bugbot for commit eeb5a1e3d61b87778fdee355bcfaaae0a9035320. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add active thread reordering to mobile home and sidebar lists

  • Adds "Move up" and "Move down" menu actions for active threads to HomeScreen and ThreadNavigationSidebar when the environment supports reordering
  • Replaces the pinned-only move action with a generalized moveThread in useThreadListActions that builds and executes plans for either active or pinned sections
  • Introduces a shared pending order coordinator in thread-order.ts that maintains the requested UI order across views while server assignments are pending
  • Risk: The beginPendingThreadOrder coordinator blocks overlapping moves until all assignments are confirmed or the move is cancelled, and releases the hold if the section membership or foreign keys change unexpectedly

Macroscope summarized a41887d.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire—13.6 KiB—15.1 KiB✅
CodexThread snapshot wire—7.0 KiB—7.3 KiB✅
CodexLive turn WebSocket wire—6.5 KiB—7.8 KiB✅
CodexLive turn WebSocket decoded—57.0 KiB—66.4 KiB✅
CodexLive turn messages—8—21✅
ClaudeTotal thread wire—13.5 KiB—15.1 KiB✅
ClaudeThread snapshot wire—7.0 KiB—7.3 KiB✅
ClaudeLive turn WebSocket wire—6.5 KiB—7.8 KiB✅
ClaudeLive turn WebSocket decoded—57.8 KiB—66.4 KiB✅
ClaudeLive turn messages—8—21✅

Baseline: unavailable · PR result: a41887d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.9 KiB
  • Claude decoded thread snapshot: 114.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

CcursorBotreviewed · 1 inline threadSep 4, 2026
Synced from GitHub

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit d7f93bfe63939919365f0f24dd6042ea910e95b8. Configure here.

apps/mobile/src/features/threads/threadListV2.tsline 206Outdated · d7f93bfOpen thread
……@@ -150,28 +156,54 @@ function parseTimestampMs(isoDate: string): number {
203+ input.pendingOrder?.section === input.section
204+ ? reconcilePendingThreadOrder(input.pendingOrder, ordered)
205+ : null;
206+ return applyPendingThreadOrder(ordered, input.section, pending);
CcursorBoton line 206Sep 4, 2026

Move controls ignore reorder capability

Medium Severity

getThreadListV2OrderedSection builds the move section from every live pinned or active row and never drops environments that lack threadPinReorder or threadActiveReorder. canMoveUp and canMoveDown then follow that list, so a row beside a keyless thread on an older server looks movable. The write path rejects that plan and surfaces an error. Pinned moves previously considered only reorder-capable threads, so this failure path did not exist.

Additional Locations (2)

Reviewed by Cursor Bugbot for commit d7f93bfe63939919365f0f24dd6042ea910e95b8. Configure here.

Fixed in db60895f3f97351263573cdf479cdf58ad6ebeef. The shared planner now checks every required key assignment against environment capabilities. Home, the navigation sidebar, and execution all use that planner, so impossible directions are disabled before a tap.

The full visible section remains intact: unsupported keyed neighbors can still anchor a supported move, while plans that must materialize an unsupported row's key are rejected. Focused coverage exercises both Active and pinned sections, unsupported keyless rows, and independent keyed moves around them. All 60 focused mobile tests and the mobile typecheck pass.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds active-thread arrangement as a new mobile capability and changes the default active-list ordering to use persisted manual order. It also introduces cross-screen asynchronous order reconciliation and server-side order writes, creating a broad runtime surface that merits human review.

No code changes detected at a41887d. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Synced from GitHub

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Added 3 commits

  • fix(mobile): disable thread moves requiring unsupported writes2fa489a
  • feat(mobile): arrange active threads from both thread lists5821a56
  • fix(mobile): preserve hidden slots when arranging threadsa41887d
Synced from GitHub

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.