Skip to content

refactor(content-view): extract harness launch + lifecycle coordinators#271

Merged
eyelock merged 1 commit into
developfrom
refactor/content-view-split
May 5, 2026
Merged

refactor(content-view): extract harness launch + lifecycle coordinators#271
eyelock merged 1 commit into
developfrom
refactor/content-view-split

Conversation

@eyelock
Copy link
Copy Markdown
Owner

@eyelock eyelock commented May 4, 2026

Summary

Splits the launch and lifecycle flows out of ContentView (974 lines pre-PR — the highest-churn file in the audit's god-object list at §2G) into two @Observable coordinators. Lane C item from .claude/plans/remediation-sequencing-2026-05-03.md.

What moves

HarnessLaunchCoordinator owns:

  • State: pendingLaunch, launchSheetTarget, launchWorkingDirectory, launchWorktreeBranch, cardBeforeHarness
  • Types: PendingLaunch, LaunchSheetTarget (moved out of the deleted extension file)
  • Functions: requestLaunch, tryResolvePendingLaunch, launchHarness, dismissLaunchSheet, dismissHarnessDetail, clearAllSelection, captureCardBeforeHarness

HarnessLifecycleCoordinator owns:

  • State: installCardIDs, uninstallCardNames, harnessNameToFork/Update, showForkSheet/UpdateSheet/InstallSheet
  • Functions: installHarness, uninstallHarness, updateHarness, exportHarness, forkHarness, handleTransientSessionExit, handleForkCompleted

Pattern

Both are @Observable final class, held by ContentView via @State, with service dependencies (HarnessRepository, BoardViewModel, YNHDetector, VendorService, YNHPersistence) constructor-injected with .shared defaults. Sub-views participate by reading the same coordinator instance.

Not singletons. The audit (§2A) flagged singleton-style services as load-bearing bug class #1; adding HarnessLaunchCoordinator.shared would regress. @Observable + @State is the same pattern SettingsStore and the testability seams use — testable directly from XCTest with mocked dependencies, no global state to reset between tests.

Constraints honoured

  1. No half-migration. ContentView has zero direct references to any of the extracted state or functions. Every line goes through launchCoordinator. or lifecycleCoordinator.. Verified by grep.
  2. Extension files deleted. ContentView+HarnessLaunch.swift (78 lines) and ContentView+HarnessFork.swift (64 lines) are gone. Their @ViewBuilder helpers (harnessDetailView, updateSheet, forkSheet) inlined into ContentView; their logic moved into coordinators.
  3. Scope cap held. Did not refactor BoardViewModel.shared dependencies, did not touch TerminalActions / handlePaletteAction, did not move sheets into sub-views, did not touch handlePendingTerminal (URL-driven, separate concern).

Numbers

  • ContentView: 974 → 792 lines (−182)
  • Coordinators: 486 lines of pure logic, now testable in isolation
  • Tests: 1692 → 1705 (+13 coordinator tests)

Test plan

🤖 Generated with Claude Code

Splits the launch and lifecycle flows out of ContentView (974 lines
before, the highest-churn file in the audit's god-object list at §2G)
into two @observable coordinators held via @State.

- HarnessLaunchCoordinator owns: pendingLaunch, launchSheetTarget,
  launchWorkingDirectory, launchWorktreeBranch, cardBeforeHarness state,
  the PendingLaunch + LaunchSheetTarget types, and the requestLaunch /
  tryResolvePendingLaunch / launchHarness / dismiss / clearAllSelection
  / captureCardBeforeHarness functions.
- HarnessLifecycleCoordinator owns: installCardIDs, uninstallCardNames,
  harnessNameToFork/Update, showForkSheet/UpdateSheet/InstallSheet
  state, and the installHarness / uninstallHarness / updateHarness /
  exportHarness / forkHarness / handleTransientSessionExit /
  handleForkCompleted functions.

Both take their service dependencies via constructor injection (default
to the existing .shared instances) so tests can drive them directly.
Pattern follows the SettingsStore + Lane A approach: @observable +
@State, not new singletons. The audit (§2A) flagged singleton-style
services as load-bearing bug class #1; adding more would regress.

ContentView is now 792 lines (-182). Every reference to the extracted
state goes through a coordinator — no half-migration. Sub-views
participate via the @State-held coordinator instances. The
ContentView+HarnessLaunch.swift and ContentView+HarnessFork.swift
extension files are deleted; their content moved into the coordinators
or inlined into ContentView's harness sub-view extension.

handlePendingTerminal (URL-driven) stays on ContentView — different
entry point from the coordinator-driven flows.

Adds 13 tests covering both coordinators (initial state, fork/update
sheet state, transient-session-exit dispatch, dismiss clears state,
captureCardBeforeHarness, clearAllSelection).

Tests: 1705 (was 1692). Lint, format, build all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@eyelock eyelock merged commit 50ed6ee into develop May 5, 2026
7 checks passed
@eyelock eyelock deleted the refactor/content-view-split branch May 5, 2026 06:00
@eyelock eyelock added refactor Something could be better structured and removed refactor Something could be better structured labels May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant