feat(swift-ios): make settle the primary thread swipe action - #5974
feat(swift-ios): make settle the primary thread swipe action#5974saphid wants to merge 14 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. New feature introducing thread swipe action changes with new settlement eligibility logic. Multiple unresolved comments (including a 🟡 Medium finding) identify correctness issues where Settle may be offered for threads the server would reject. You can customize Macroscope's approvability policy. Learn more. |
| } | ||
|
|
||
| var kinds: [HomeThreadSwipeActionKind] = [] | ||
| if thread.canToggleSettlement { |
There was a problem hiding this comment.
canToggleSettlement only describes capability and reversibility, not whether this thread can settle now. queued, working/monitoring, waiting-for-approval, and waiting-for-input threads can therefore get Settle as the full-swipe action even though the server rejects settling active sessions, pending requests, and queued turn starts. please mirror the server/client-runtime canSettle eligibility or disable full swipe for blocked states, with tests for those states.
|
Addressed the settlement authority findings and completed focused native verification on exact head The candidate now:
Verification:
The first native pass found a test argument-order compile error and a real automatic-settlement regression. Both were fixed before the passing run. DerivedData cleanup is deferred because another native test process acquired the shared environment after this run. A fresh direct Claude Opus 5 high review is unavailable because the current session quota remains exhausted until 04:50 AEST. No final-head independent review is claimed. |
|
Follow-up for the hosted native failure on exact head The failure was the shared provider-catalog merge-build incompatibility. This branch now uses the environment catalog when present and falls back to the legacy catalog when absent. The conflict resolution preserves the settlement projection and automatic-settlement state blockers. Verification:
A branch-local provider rerun could not start because the app-flow harness owns the single native lease. Hosted native CI will verify the merge result. No local branch-specific native pass is claimed for this follow-up. |
…pingdotgg/t3code into agent/pr5974-address-review-20260813-223500 # Conflicts: # apps/swift-ios/Features/Workspace/DailyUXModels.swift # apps/swift-ios/Features/Workspace/WorkspaceView.swift
| if input.hasPendingApprovals || input.hasPendingUserInput { return false } | ||
| if input.sessionStatus == "starting" || input.sessionStatus == "running" { return false } | ||
| if input.sessionStatus == "error" { return true } | ||
| guard let rawMessageAt = input.latestUserMessageAt, |
There was a problem hiding this comment.
🟡 Medium Shared/FeatureModels.swift:359
canSettle returns true for a fresh message whose turn has only been requested, allowing Settle while the turn is still waiting to start. latestTurnRequestedAt being newer than latestUserMessageAt makes allSatisfy false, and abs(now.timeIntervalSince(messageAt)) also treats a sufficiently future server timestamp as stale and returns true; keep future timestamps blocked and treat a request without started/completed timestamps as queued.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/swift-ios/Features/Shared/FeatureModels.swift around line 359:
`canSettle` returns `true` for a fresh message whose turn has only been requested, allowing Settle while the turn is still waiting to start. `latestTurnRequestedAt` being newer than `latestUserMessageAt` makes `allSatisfy` false, and `abs(now.timeIntervalSince(messageAt))` also treats a sufficiently future server timestamp as stale and returns `true`; keep future timestamps blocked and treat a request without started/completed timestamps as queued.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb748e6. Configure here.
| return true | ||
| } | ||
| return false | ||
| } |
There was a problem hiding this comment.
Settlement projection misses turn adoption
Medium Severity
FeatureSettlementProjection.canSettle does not match the shared hasQueuedTurnStart contract. Within the grace window it only clears the block via latestTurnCompletedAt, leaving latestTurnRequestedAt and latestTurnStartedAt unused, so an adopted turn still blocks Settle. Future-stamped messages also always fail (messageAge < 0) instead of using the shared absolute grace bound, so clock skew can hide Settle long after the server would accept it.
Reviewed by Cursor Bugbot for commit bb748e6. Configure here.
| case .idle, .failed, .completed: | ||
| break | ||
| } | ||
| guard canSettle else { return false } |
There was a problem hiding this comment.
Settled rows ignore queued-turn blocker
Medium Severity
canSettle short-circuits to true whenever isSettled is set, and isEffectivelySettled gates on that value before applying settle state. A thread that is still marked settled can therefore stay on the Settled shelf and offer Reopen even while a newer user message is inside the queued-turn grace window. The shared effectiveSettled path only forgives that blocker when settledAt is at least as new as the message.
Reviewed by Cursor Bugbot for commit bb748e6. Configure here.
|
Closing this version while I rebuild the upstream contribution set from the latest base. Clean versions are coming soon. |




Summary
Adds Settle/Reopen to pinned-thread swipe actions and enables full swipe. Reversible lifecycle actions are always first; Delete is structurally last and covered by an invariant test.
Verification
Scope
Targets the active native SwiftUI owner branch (#5178).
Note
Make Settle the primary swipe action for eligible threads in the home thread list
HomeThreadSwipeActionsto compute ordered swipe actions and full-swipe eligibility based on thread state, pin status, archive state, and settlement eligibility; Settle is now the primary (first) action for eligible threads.FeatureSettlementProjectionInputandFeatureSettlementProjectionto compute settlement eligibility from pending approvals, user input, session status, and turn/message timestamps.FeatureThread.canSettleand gatesisEffectivelySettledon it, so threads with pending requests or recent unprocessed messages are not treated as settled.removeCachedApprovalandremoveCachedInputinNativeFeatureClientto reconcile thread state against shell-reported pending flags before publishing, preventing premature idle/settled flips.Macroscope summarized bb748e6.
Note
Medium Risk
Touches thread lifecycle UX and native client state reconciliation for approvals, settlement, and swipe defaults; behavior is heavily tested but wrong gating could block settle or allow premature full-swipe actions.
Overview
Makes Settle/Reopen the primary trailing swipe on home thread rows (including pinned threads), with full swipe enabled only for reversible actions—Delete stays last and is never the full-swipe target.
Adds
FeatureSettlementProjectionandsettlementInputonFeatureThreadso settle/reopen and “effectively settled” UI respect blockers: pending approvals/input, live sessions, recent user messages (2‑minute grace), and turn timing.NativeFeatureClientmerges shellhasPendingApprovals/hasPendingUserInputwith cached detail when updating state and after resolving approvals/inputs (including when thread refresh fails).Introduces
HomeThreadSwipeActionsto centralize swipe/menu ordering; context menus hide settle when blocked. Provider label resolution no longer falls back to legacysnapshot.providerswhen an environment’s catalog is missing.Reviewed by Cursor Bugbot for commit bb748e6. Bugbot is set up for automated code reviews on this repo. Configure here.
Delivery: direct
Validated against Theo commit: f98cab5
Depends on: none
Merge order: this PR only
Validation status: Mergeable with runnable checks green; requested review changes remain. The unrelated Vercel authorization failure is a maintainer-side gate.