Skip to content

feat(swift-ios): arrange threads with drag handles - #11379

Merged
t3dotgg merged 10 commits into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:pr/swiftui-pinned-reorder-20260912
Sep 14, 2026
Merged

t3dotgg merged 10 commits into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:pr/swiftui-pinned-reorder-20260912

Conversation

@saphid

@saphid saphid commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

SwiftUI could not arrange threads across Pinned, Active, Snoozed, and Settled like React Native.

Open Arrange threads from a thread menu. Drag a handle to reorder Pinned or Active, move between them, settle a thread, or bring a snoozed or settled thread back. VoiceOver actions provide the same supported moves. Normal thread holds still open menus. The Home swipe and scrolling implementation is unchanged.

This updates the existing order-key implementation against the latest SwiftUI branch. Order stays shared across environments and clients. Each write goes to the thread's owning server. The planner reserves hidden keys and rejects unsupported, disabled, or disconnected writes before starting a move. Failed writes retain confirmed keys and refresh every touched environment.

Verification:

  • Swift syntax parsing and git diff --check pass.
  • Focused coverage includes command payloads, capability decoding, key generation, multi-environment ordering, cross-section lifecycle changes, stale targets, and unavailable servers.
  • Native CI builds the app and passes 708 Swift Testing tests plus 266 XCTest cases, with one skip and no failures, at 3b96928191.
  • Macroscope correctness and the remaining required checks pass on that commit.
  • Current UI interaction checks and evidence remain with the main integration session. No current visual result is claimed here.

Original order-key work is retained from this PR. Updated in Codex.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Sep 12, 2026
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift Outdated
@macroscopeapp

macroscopeapp Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a substantial iOS thread-arrangement workflow with new UI, drag/drop state management, cross-environment reorder commands, lifecycle mutations, and changes to existing sorting behavior. Unresolved findings also report a possible compile issue and stale sheet presentation state, so the scope and runtime impact require human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

saphid pushed a commit to saphid/t3code that referenced this pull request Sep 12, 2026
The move planner treated every enabled environment as writable, so a
reorder-capable server that dropped its connection kept stale rows in
the canonical section and could receive writes aimed at a dead client.
Rows outside the connected set now remain anchors but are never
assigned keys, matching the review finding on pingdotgg#11379.

Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift Outdated
saphid pushed a commit to saphid/t3code that referenced this pull request Sep 12, 2026
Two review findings on pingdotgg#11379: moveThread now captures the section
before awaiting so a mid-flight pin/unpin cannot misapply confirmed
keys, and a spread rewrite that fails partway surfaces the confirmed
assignments via FeatureThreadMovePartialError so the sidebar reflects
the writes that actually landed.

Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@saphid

saphid commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the Macroscope findings on the latest push (bc2af43d42):

  • Disconnected environments in planning — fixed: movePlanner/moveOptions now take the connected environment set, so rows from a dropped server stay as ordering anchors but are never assigned keys, and a disconnected row offers no Move actions.
  • Section read after await — fixed: moveThread captures the pinned/active section before the call, so a mid-flight pin/unpin cannot misapply confirmed keys.
  • Partial reorder dropped confirmed writes — fixed: a half-landed spread rewrite now throws FeatureThreadMovePartialError carrying the confirmed assignments; the caller applies them before surfacing the underlying error.
  • orderKeyBetween(nil, "ma") returning nil on pin — intentionally unchanged: this matches the React Native reference (pinOrderKeyBetween(null, firstKey) ?? undefined). Order-key generators never emit a trailing minimum digit, so this path only triggers on already-corrupt server data, in which case both clients fall back to a keyless pin sorted by creation order.

@saphid saphid changed the title feat(swift-ios): add Move up/down context-menu thread reordering feat(swift-ios): hold-and-drag thread reordering Sep 12, 2026
@saphid

saphid commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Note for reviewers on the Macroscope findings re-flagged against the drag-and-drop head (2d2c40b9):

  • FeatureRootModel.swift moveThread section-after-await — Macroscope itself marks this "No longer relevant as of bc2af43"; that code path was removed entirely in the drag rework (reorderThread takes the section as a parameter).
  • NativeFeatureClient.swift orderKeyBetween(nil, firstKey) returning nil on a corrupt earliest key — intentionally unchanged, same rationale as before: it matches the React Native reference (pinOrderKeyBetween(null, firstKey) ?? undefined), and order-key generators never emit a trailing minimum digit, so the path only triggers on already-corrupt server data. Both clients then fall back to a keyless pin sorted by creation order.

github-actions Bot and others added 7 commits September 13, 2026 21:15
The native client had no way to rearrange the thread list even though
servers already support the fractional order-key protocol used by web
drag and React Native mobile's Move up / Move down. Add both menu
actions on pinned and active rows, planned against the canonical
section across environments and written through thread.pin.reorder /
thread.active.reorder, capability-gated and disabled at section edges.
Pinning now also takes the top of the arranged run when the server
supports it.

Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The move planner treated every enabled environment as writable, so a
reorder-capable server that dropped its connection kept stale rows in
the canonical section and could receive writes aimed at a dead client.
Rows outside the connected set now remain anchors but are never
assigned keys, matching the review finding on pingdotgg#11379.

Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two review findings on pingdotgg#11379: moveThread now captures the section
before awaiting so a mid-flight pin/unpin cannot misapply confirmed
keys, and a spread rewrite that fails partway surfaces the confirmed
assignments via FeatureThreadMovePartialError so the sidebar reflects
the writes that actually landed.

Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with Devin (swe-2-high, T3 Code/Cursor harness)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Replace the context-menu Move up/down actions with direct hold-and-drag
reordering on the Home thread list, per review feedback. A long-press
lifts a row inside its own section (pinned or active); the drop plans
fractional order-key writes from the post-drop displayed order — the same
input web passes to planPinnedReorder. A stationary hold still opens the
context menu.

- UICollectionView drag/drop delegates + diffable reorderingHandlers;
  drop proposals clamp to the dragged row's section so pinned rows can't
  cross the divider and shelf rows/search results/headers never lift.
- planDrop() replaces movePlanner(): arbitrary-position drops with the
  same writability rules (reorder-capable + connected environments only;
  disconnected rows anchor but are never written).
- pendingReorder holds the dropped order against mid-flight stream
  updates and converges on the common rows; a failed or partial write
  snaps back to the true model state. New lifts are blocked while a
  write is outstanding and no-op drops skip the write.
- Stream updates defer while a drag is active (hasActiveDrag) so the
  lifted row can't shift under the finger.
- FeatureThreadMoveDirection/MoveOptions removed; lift gating is a plain
  writable+connected check.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@t3dotgg
t3dotgg force-pushed the pr/swiftui-pinned-reorder-20260912 branch from 524f5bd to 6e5f135 Compare September 14, 2026 04:25
@t3dotgg t3dotgg changed the title feat(swift-ios): hold-and-drag thread reordering feat(swift-ios): arrange threads with drag handles Sep 14, 2026
Comment thread apps/swift-ios/Features/Workspace/ThreadArrangementView.swift Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

Incoming navigation requests leave showingThreadArrangement set, so the workspace changes behind the still-present ThreadArrangementView and .newTask requests cannot present NewThreadView until the unrelated sheet is dismissed. Reset showingThreadArrangement in dismissTransientPresentations() along with the other transient presentation state.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/swift-ios/Features/Workspace/WorkspaceView.swift around line 641:

Incoming navigation requests leave `showingThreadArrangement` set, so the workspace changes behind the still-present `ThreadArrangementView` and `.newTask` requests cannot present `NewThreadView` until the unrelated sheet is dismissed. Reset `showingThreadArrangement` in `dismissTransientPresentations()` along with the other transient presentation state.

Comment thread apps/swift-ios/Features/Workspace/DailyUXModels.swift
@t3dotgg
t3dotgg merged commit e999c32 into pingdotgg:t3code/rebuild-mobile-app-swift Sep 14, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants