Skip to content

Commit 580e52f

Browse files
authored
fix(agent): make Inbox recovery choices visible and unambiguous (#1498)
* fix(agent): make Inbox recovery choices explicit * fix(agent): invalidate stale recovery actions * fix(test): preserve agent recovery launch chronology * fix(sidebar): repair stale accessibility focus * fix(sidebar): route AX row keyboard navigation * fix(sidebar): preserve semantic focus for AX rows * fix(sidebar): sync SwiftUI keyboard focus * revert(sidebar): drop unrelated accessibility focus changes These four commits were not part of #1496 (Agent Inbox recovery choices). They made SidebarAccessibilityRowView accept first responder status, which stole arrow keys from the sidebar's own key handler and broke Finder-style keyboard navigation: SidebarFolderClickTests.testKeyboardNavigationTypeSelectAndOutlineSemantics failed on all three CI retries while main stayed green. Sidebar files are now byte-identical to main; only the Agent Inbox work from #1496 remains.
1 parent e99dc99 commit 580e52f

8 files changed

Lines changed: 569 additions & 28 deletions

Pine/AccessibilityIdentifiers.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ nonisolated enum AccessibilityID {
223223
static let agentInboxEmpty = "agentInboxEmpty"
224224
static let agentInboxNavigationStatus = "agentInboxNavigationStatus"
225225
static let agentInboxHelpButton = "agentInboxHelpButton"
226+
static let agentInboxRecoveryActions = "agentInboxRecoveryActions"
227+
static let agentInboxResumeSession = "agentInboxResumeSession"
228+
static let agentInboxNewSession = "agentInboxNewSession"
229+
static let agentInboxMarkReviewed = "agentInboxMarkReviewed"
230+
static let agentInboxCopyObjective = "agentInboxCopyObjective"
231+
static let agentInboxDismissTask = "agentInboxDismissTask"
226232
static func agentInboxRow(_ id: UUID) -> String {
227233
"agentInboxRow_\(id.uuidString)"
228234
}

Pine/Agent/AgentInboxModels.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,67 @@ nonisolated struct AgentInboxRow: Identifiable, Equatable, Sendable {
4848
}
4949
}
5050

51+
/// Minimal state that controls whether an already-presented recovery surface
52+
/// may remain visible. The task ID alone is insufficient because liveness and
53+
/// lifecycle can change in place while the same durable task remains listed.
54+
nonisolated struct AgentInboxRecoveryState: Equatable, Sendable {
55+
let id: UUID
56+
let canRecover: Bool
57+
58+
init(row: AgentInboxRow) {
59+
id = row.id
60+
canRecover = row.canRecover
61+
}
62+
63+
init(id: UUID, canRecover: Bool) {
64+
self.id = id
65+
self.canRecover = canRecover
66+
}
67+
}
68+
69+
/// Pure presentation policy for activating an Inbox row with Return.
70+
///
71+
/// Recovery deliberately has two keyboard steps: the first Return exposes the
72+
/// available actions, and only a later Return can invoke the visible primary
73+
/// action. This prevents a restored task from silently opening a fresh session
74+
/// when a vendor resume path is available.
75+
nonisolated enum AgentInboxActivation: Equatable, Sendable {
76+
case navigate
77+
case presentRecoveryActions
78+
case recover(AgentTaskRecoveryAction)
79+
80+
static func resolve(
81+
row: AgentInboxRow,
82+
recoveryActionsArePresented: Bool,
83+
canResumeVendorSession: Bool
84+
) -> AgentInboxActivation {
85+
guard row.canRecover else { return .navigate }
86+
guard recoveryActionsArePresented else {
87+
return .presentRecoveryActions
88+
}
89+
return .recover(primaryRecoveryAction(
90+
canResumeVendorSession: canResumeVendorSession
91+
))
92+
}
93+
94+
static func primaryRecoveryAction(
95+
canResumeVendorSession: Bool
96+
) -> AgentTaskRecoveryAction {
97+
canResumeVendorSession ? .resumeVendorSession : .startNewSession
98+
}
99+
100+
static func normalizedPresentedTaskID(
101+
_ taskID: UUID?,
102+
states: [AgentInboxRecoveryState]
103+
) -> UUID? {
104+
guard let taskID,
105+
states.contains(where: {
106+
$0.id == taskID && $0.canRecover
107+
}) else { return nil }
108+
return taskID
109+
}
110+
}
111+
51112
nonisolated struct AgentInboxSection: Identifiable, Equatable, Sendable {
52113
let id: AgentInboxSectionID
53114
let rows: [AgentInboxRow]

0 commit comments

Comments
 (0)