Skip to content

feat(swift-ios): make settle the primary thread swipe action - #5974

Closed
saphid wants to merge 14 commits into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:saphid/swiftui-settle-swipe-v2
Closed

feat(swift-ios): make settle the primary thread swipe action#5974
saphid wants to merge 14 commits into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:saphid/swiftui-settle-swipe-v2

Conversation

@saphid

@saphid saphid commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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

  • apps/swift-ios/Scripts/ci-test.sh — 220 tests passed
  • Fresh independent Claude Opus 5 high review completed; all actionable findings were addressed and the final repair review found no blockers.
  • Simulator visual evidence will be attached before marking this PR ready for review.

Scope

Targets the active native SwiftUI owner branch (#5178).

Note

Make Settle the primary swipe action for eligible threads in the home thread list

  • Introduces HomeThreadSwipeActions to 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.
  • Adds FeatureSettlementProjectionInput and FeatureSettlementProjection to compute settlement eligibility from pending approvals, user input, session status, and turn/message timestamps.
  • Adds FeatureThread.canSettle and gates isEffectivelySettled on it, so threads with pending requests or recent unprocessed messages are not treated as settled.
  • Fixes removeCachedApproval and removeCachedInput in NativeFeatureClient to reconcile thread state against shell-reported pending flags before publishing, preventing premature idle/settled flips.
  • Behavioral Change: full-swipe now triggers a reversible action (Settle, Reopen, Unpin, Restore) rather than Delete; Delete is always last in the swipe list.

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 FeatureSettlementProjection and settlementInput on FeatureThread so settle/reopen and “effectively settled” UI respect blockers: pending approvals/input, live sessions, recent user messages (2‑minute grace), and turn timing. NativeFeatureClient merges shell hasPendingApprovals / hasPendingUserInput with cached detail when updating state and after resolving approvals/inputs (including when thread refresh fails).

Introduces HomeThreadSwipeActions to centralize swipe/menu ordering; context menus hide settle when blocked. Provider label resolution no longer falls back to legacy snapshot.providers when 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.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 575a80fd-33bf-4b9e-ad5f-385d0017332e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 10, 2026
@saphid

saphid commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Simulator evidence from the integrated approved Debug build:

Settle exposed as the first swipe action

The same thread moved into the Settled shelf

Verified on iOS 26.5 against an active disposable backend. The exact-head native invariant test also covers full-swipe choosing the first (Settle) action.

@saphid
saphid marked this pull request as ready for review August 10, 2026 09:27
@macroscopeapp

macroscopeapp Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

@t3-code t3-code Bot left a comment

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.

reviewed swipe action ordering, full-swipe safety, context-menu parity, and focused tests. no blocking issues found.

@t3-code t3-code Bot left a comment

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.

independent review found a blocking state-eligibility gap in the new full-swipe action.

}

var kinds: [HomeThreadSwipeActionKind] = []
if thread.canToggleSettlement {

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.

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.

@saphid

saphid commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the settlement authority findings and completed focused native verification on exact head e2ea292e1.

The candidate now:

  • preserves detail and shell pending-request blockers
  • retains shell-only background liveness during detail updates
  • allows explicit settle actions when the settlement projection permits them
  • prevents automatic age-based settlement while the shell state is queued, working, monitoring, or waiting
  • keeps the full-swipe action reversible

Verification:

  • focused DailyUXSidebarTests and NativeMultiEnvironmentTests: 39 passed, 0 failed
  • Swift parse and diff checks passed
  • result bundle: test_sim_2026-08-13T15-47-12-266Z_pid9862_5e792b07.xcresult

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.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Aug 13, 2026
Comment thread apps/swift-ios/Features/Workspace/HomeThreadCollectionView.swift
Comment thread apps/swift-ios/Features/Shared/FeatureModels.swift
@saphid

saphid commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up for the hosted native failure on exact head 7388b7839:

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.

Comment thread apps/swift-ios/Features/Workspace/WorkspaceView.swift
saphid added 2 commits August 14, 2026 02:39
…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,

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 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.

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ 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
}

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bb748e6. Configure here.

case .idle, .failed, .completed:
break
}
guard canSettle else { return false }

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.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bb748e6. Configure here.

@saphid

saphid commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Closing this version while I rebuild the upstream contribution set from the latest base. Clean versions are coming soon.

@saphid saphid closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 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.

1 participant