Description
Implement TCA reducer GitHubFeature in GitHubIntegrationFeature/GitHubFeature.swift — state model + basic reducer actions only. Polling coordinator → T-8b.
(1) Public state types
public enum CIStatusView: Equatable, Sendable {
case unknown
case fresh(GHCombinedStatus)
case stale(GHCombinedStatus, age: Duration)
case error(GitHubAPIError)
}
public struct Etags: Equatable, Sendable {
public var combinedStatus: String?
public var workflowRuns: String?
}
public struct BranchGitHubState: Equatable, Sendable {
public var remote: GitRemote
public var branch: String
public var combinedStatus: CIStatusView
public var workflowRuns: [GHWorkflowRun]
public var etags: Etags
public var lastFetchedAt: Date?
public var quarantined: Bool // true after .forbidden(.saml) — excluded from poll
}
public enum AuthState: Equatable, Sendable {
case signedOut
case signingIn
case signedIn(GHAuthInfo)
case error(GitHubAuthError)
}
(2) Reducer shape
@Reducer public struct GitHubFeature {
@ObservableState public struct State: Equatable, Sendable {
public var auth: AuthState = .signedOut
public var perBranch: [URL: BranchGitHubState] = [:]
public var selectedWorktreePath: URL? = nil
public var windowActive: Bool = true
}
public enum Action: Sendable {
case signInTapped
case signInResult(Result<GHAuthInfo, GitHubAuthError>)
case signOutTapped
case remotesUpdated([URL: (remote: GitRemote, branch: String)])
case selectedWorktreeChanged(URL?)
case refreshNowTapped(URL?)
case openInBrowser(URL)
}
public init()
}
(3) Reducer behavior
.signInTapped → state.auth = .signingIn; effect: authService.signIn(...) → .signInResult
.signInResult(.success(info)) → .signedIn(info); log GitHubLog.auth (no PII)
.signInResult(.failure(err)) → .error(err); log without sensitive fields
.signOutTapped → clear all: auth = .signedOut, perBranch = [:], selectedWorktreePath = nil; effect: authService.signOut(login) + credentialStore.delete(for: login) + credentialStore.setCurrentAccount(nil)
.remotesUpdated(map) → add/update BranchGitHubState for each github worktree (.unknown status if new); eviction: remove entries whose paths are absent in map (ETag cleanup)
.selectedWorktreeChanged(path) → state.selectedWorktreePath = path
.refreshNowTapped(path) → translated to immediate pollTick in T-8b
.openInBrowser(url) → effect @Dependency(\.openURL)(url)
(4) Dependencies
Register DependencyKey conformances in this task:
@Dependency(\.gitHubAuthService)
@Dependency(\.gitHubCredentialStore)
@Dependency(\.openURL)
Spec reference
See swarm-report/github-integration-decomposition.md#t-8a (split from original T-8 per BA critical-1).
Relationships
Acceptance criteria
TestStore tests:
Complexity
L
Suggested agent
developer-workflow:swift-engineer
Module / Layer
GitHubIntegrationFeature / State + basic Effects
Description
Implement TCA reducer
GitHubFeatureinGitHubIntegrationFeature/GitHubFeature.swift— state model + basic reducer actions only. Polling coordinator → T-8b.(1) Public state types
(2) Reducer shape
(3) Reducer behavior
.signInTapped→state.auth = .signingIn; effect:authService.signIn(...)→.signInResult.signInResult(.success(info))→.signedIn(info); logGitHubLog.auth(no PII).signInResult(.failure(err))→.error(err); log without sensitive fields.signOutTapped→ clear all:auth = .signedOut,perBranch = [:],selectedWorktreePath = nil; effect:authService.signOut(login)+credentialStore.delete(for: login)+credentialStore.setCurrentAccount(nil).remotesUpdated(map)→ add/updateBranchGitHubStatefor each github worktree (.unknownstatus if new); eviction: remove entries whose paths are absent inmap(ETag cleanup).selectedWorktreeChanged(path)→state.selectedWorktreePath = path.refreshNowTapped(path)→ translated to immediate pollTick in T-8b.openInBrowser(url)→ effect@Dependency(\.openURL)(url)(4) Dependencies
Register
DependencyKeyconformances in this task:@Dependency(\.gitHubAuthService)@Dependency(\.gitHubCredentialStore)@Dependency(\.openURL)Spec reference
See
swarm-report/github-integration-decomposition.md#t-8a(split from original T-8 per BA critical-1).Relationships
Acceptance criteria
TestStore tests:
.signInTapped→.signingIn; mock auth success →.signedIn(info).signInResult(.failure(err))→.error(err).signOutTapped→perBranch = [:],selectedWorktreePath = nil,auth = .signedOut.remotesUpdatedfor 2 github worktrees →perBranchhas 2 entries with.unknown.remotesUpdatedwhere pathA is absent in new map → entry for pathA removed (eviction).remotesUpdatedwith non-github host → entry NOT added (filtered)GitHubAPIError: Equatable(depends on T-5 complete)Complexity
L
Suggested agent
developer-workflow:swift-engineerModule / Layer
GitHubIntegrationFeature/ State + basic Effects