Skip to content

T-12: Wire GitHubFeature into WorktreeFeature + badge in WorktreeRowView #229

@kirich1409

Description

@kirich1409

Description

Wire GitHubFeature into the main app flow.

(1) Scope in parent reducer (WorktreeFeature)

@ObservableState struct State {
    var github: GitHubFeature.State = .init()
}
enum Action {
    case github(GitHubFeature.Action)
}
var body: some ReducerOf<Self> {
    Scope(state: \.github, action: \.github) { GitHubFeature() }
}

(2) Forward actions

  • After .loadProject loads state.remotes (T-4) → emit .github(.remotesUpdated(map)) where map: [URL: (remote: GitRemote, branch: String)] is built from worktrees × remotes with GitRemote.parse(remote.url), filter nil and host != "github.com".
  • On selected worktree change → .github(.selectedWorktreeChanged(path)).
  • NSApp notifications: in MainFeature effects on .onAppear subscribe to:
    • NotificationCenter.default.notifications(named: NSApplication.didResignActiveNotification)
    • NotificationCenter.default.notifications(named: NSApplication.didBecomeActiveNotification)
      Emit .github(.windowActiveChanged(active)) accordingly.

(3) Badge in WorktreeRowView

HStack {
    // existing: branch name + SHA7 + status badge
    Spacer()
    if let branchState = githubState(for: worktree.path), isAuthorized {
        GitHubBadge(status: branchState.combinedStatus) { showPopover.toggle() }
            .popover(isPresented: $showPopover, arrowEdge: .trailing) {
                GitHubStatusPopover(
                    state: popoverState,
                    onOpenURL: { openURL($0) },
                    onRefresh: { store.send(.github(.refreshNowTapped(worktree.path))) },
                    onSignInTapped: { store.send(.github(.signInTapped)) })
            }
    }
}

@Environment(\.openURL) in App target; bootstrap: @Dependency(\.openURL) = { NSWorkspace.shared.open($0) }.

(4) Accessibility

  • Badge — separate focusable element (accessibilityElement(children: .combine) on row; badge as child with .isButton).
  • Popover creates new accessibility context; Esc closes.

(5) Bootstrap

At app launch, credentialStore.currentAccount() → if non-nil, emit bootstrap action calling authService.currentUser() → send .signInResult(.success(info)) on valid. Otherwise start .signedOut. Polling auto-starts from T-8b on .signInResult(.success).

Spec reference

See swarm-report/github-integration-decomposition.md#t-12.

Relationships

Acceptance criteria

  • Launch → Preferences Sign in → badge appears on GitHub worktrees, hidden on non-GitHub
  • Badge click → popover opens; job click → browser opens correct URL
  • Minimize/deactivate window → polling pauses (verified via log stream --subsystem com.relay.github --category polling)
  • Sign out → all badges disappear; state.github.perBranch == [:]
  • Integration smoke test (XCTest with mock GitHubClient): sign in → badge visible → 1 fetch → sign out → badge hidden
  • App relaunch after sign-in → badges appear without re-sign-in (bootstrap works)

Complexity

L

Suggested agent

developer-workflow:swift-engineer (integration) + optionally developer-workflow:swiftui-developer (row UI)

Module / Layer

App target + WorktreeManager + GitHubIntegrationFeature / Wiring

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions