···31313232```
3333AppFeature (root TCA store)
3434-├─ RepositoriesFeature (repos + worktrees)
3434+├─ RepositoriesFeature (repos, worktrees, PR state, archive/delete flows)
3535├─ CommandPaletteFeature
3636-├─ SettingsFeature (appearance, updates, repo settings)
3636+├─ SettingsFeature (general, notifications, coding agents, shortcuts, github, worktree, repo settings)
3737└─ UpdatesFeature (Sparkle auto-updates)
38383939WorktreeTerminalManager (global @Observable terminal state)
···4242 └─ TerminalTabManager (tab/split management)
4343 └─ GhosttySurfaceState[] (one per terminal surface)
44444545-GhosttyRuntime (shared singleton)
4545+WorktreeInfoWatcherManager (global worktree watcher state)
4646+├─ HEAD watchers per worktree
4747+└─ debounced branch / file / pull request refresh events
4848+4949+GhosttyRuntime (shared runtime)
4650└─ ghostty_app_t (single C instance)
4751 └─ ghostty_surface_t[] (independent terminal sessions)
4852```
···5761Reducer ← .terminalEvent(Event) ← AsyncStream<Event>
5862```
59636060-- **Commands**: `createTab`, `closeFocusedTab`, `prune`, `setSelectedWorktreeID`, etc.
6161-- **Events**: `notificationReceived`, `tabCreated`, `tabClosed`, `focusChanged`, `taskStatusChanged`
6262-- Wired in `supacodeApp.swift`, subscribed in `AppFeature.task`
6464+- **Commands**: tab creation, initial-tab setup, blocking scripts, search, Ghostty binding actions, tab/surface closing, notification toggles, and lifecycle management
6565+- **Events**: notifications, dock indicator count changes, tab/focus changes, task status changes, blocking-script completion, command palette requests, and setup-script consumption
6666+- Wired in `supacodeApp.swift`, subscribed in `AppFeature.appLaunched`
6767+6868+Worktree metadata refresh uses `WorktreeInfoWatcherClient` in parallel:
6969+7070+```
7171+Reducer → worktreeInfoWatcher.send(Command) → WorktreeInfoWatcherManager
7272+ ↓
7373+Reducer ← .repositories(.worktreeInfoEvent(Event)) ← AsyncStream<Event>
7474+```
7575+7676+- **Commands**: `setWorktrees`, `setSelectedWorktreeID`, `setPullRequestTrackingEnabled`, `stop`
7777+- **Events**: `branchChanged`, `filesChanged`, `repositoryPullRequestRefresh`
7878+- Wired in `supacodeApp.swift`, subscribed in `AppFeature.appLaunched`
63796480### Key Dependencies
6581···78947995## Code Guidelines
80968181-- Target macOS 26.0+, Swift 6.2+
9797+- Target macOS 26.0+, Swift 6.0
8298- Before doing a big feature or when planning, consult with pfw (pointfree) skills on TCA, Observable best practices first.
8399- Use `@ObservableState` for TCA feature state; use `@Observable` for non-TCA shared stores; never `ObservableObject`
84100- Always mark `@Observable` classes with `@MainActor`
+19-2
docs/ssh-notifications-over-ssh.md
···4455Show Supacode notifications when a coding agent is running on a remote machine over `ssh`.
6677+## Status
88+99+This is still a design doc, not shipped behavior.
1010+1111+The current codebase remains socket-only for agent hooks:
1212+1313+- `AgentHookSettingsCommand` only generates Unix-socket commands
1414+- `ClaudeHookSettings` and `CodexHookSettings` only install those socket commands
1515+- there is no `SUPACODE_REMOTE_NOTIFICATIONS`, `SSH_CONNECTION`, or `SSH_TTY` transport switch in the app
1616+717## Decision
818919This is feasible.
···53635464- `supacode/Features/Terminal/Models/WorktreeTerminalState.swift`
5565- `supacode/Features/Settings/BusinessLogic/AgentHookSettingsCommand.swift`
6666+- `supacode/Features/Settings/BusinessLogic/ClaudeHookSettings.swift`
6767+- `supacode/Features/Settings/BusinessLogic/CodexHookSettings.swift`
5668- `supacode/Infrastructure/AgentHookSocketServer.swift`
57695870The installed hook commands send either busy-state updates or raw JSON payloads to a local Unix domain socket under `/tmp/supacode-<uid>/pid-<pid>`.
···65776678## Existing Signals In The Repo
67796868-There is already a local helper for the terminal-notification path:
8080+There are already local helpers for the terminal-notification path:
69817082- `bins/osc9-notify.sh`
8383+- `bins/osc777-notify.sh`
71847272-There are already tests that verify the deduplication behavior between hook notifications and OSC notifications:
8585+There are already tests that verify command generation, desktop notification delivery, and deduplication behavior:
73868787+- `supacodeTests/GhosttySurfaceBridgeTests.swift`
7488- `supacodeTests/AgentBusyStateTests.swift`
8989+- `supacodeTests/AgentHookCommandTests.swift`
75907691This means the local app side is already prepared to accept terminal-originated notifications and coalesce them against richer hook notifications.
7792···103118### Implementation shape
104119105120Add a second hook command builder that emits terminal notifications instead of writing to the local socket.
121121+122122+The current command-generation choke point is `AgentHookSettingsCommand`. `ClaudeHookSettings` and `CodexHookSettings` only embed those generated commands into their respective settings files, so the remote path should be added there rather than duplicated per agent.
106123107124The hook command should:
108125