Migrate snapshot to daemon client with magical reuse#102
Merged
obj-p merged 2 commits intocli-mcp-parityfrom Apr 15, 2026
Merged
Migrate snapshot to daemon client with magical reuse#102obj-p merged 2 commits intocli-mcp-parityfrom
obj-p merged 2 commits intocli-mcp-parityfrom
Conversation
Third PR in the CLI/MCP parity stack. `snapshot` becomes a thin daemon client that reuses an existing preview session when one is running for the target file, and falls back to an ephemeral session otherwise (create → capture → stop). No more in-process NSApplication + preview compile from `snapshot` itself — all of that moves into the daemon. The magical resolution: - `--session <uuid>` wins if passed. - `--file <path>` (or the positional file arg): if exactly one active session has that source file, reuse it; else error on ambiguity, or ephemeral on miss. - No flag: if exactly one session is running, reuse it. Changes: - New SessionResolver: uses the new `session_list` MCP tool to map CLI flags to a session ID. Clear error messages on ambiguity or missing session. - New `session_list` MCP tool: returns one tab-delimited line per active session (iOS + macOS). Used by SessionResolver; also useful for future discovery commands. - SnapshotCommand rewrite: AsyncParsableCommand using DaemonClient. Handles both reuse and ephemeral paths. Traits are applied only on ephemeral start; reusing a session that was configured differently prints a note and snapshots with the session's current traits. Format is inferred from the output file extension (`.png` → PNG, `.jpg`/`.jpeg` → JPEG) unless `--quality` is explicit. - Platform resolution moved client-side. Previously the daemon called `inferredPlatformAsync` when no `--platform` was passed, which could hang on xcodeproj sources: the walk-up for Package.swift falls through to the repo root, then `swift package describe` runs on a large package and can sit forever. New `findPackageDirectory` short-circuits when it crosses an xcodeproj/xcworkspace/WORKSPACE boundary before finding Package.swift, returning nil instead. SnapshotCommand resolves platform locally and always passes an explicit `platform` to the daemon. - Fix MCP tool arg names: `project` → `projectPath`, `device` → `deviceUDID`. RunCommand had the same bug; fixed there too. - PreviewsMacOS.PreviewHost exposes `allSessions` so the new `session_list` tool can enumerate macOS sessions alongside iOS. - DaemonTestLock wraps every SnapshotCommandTests case, matching the pattern used by RunCommandTests and DaemonLifecycleTests — without it, the daemon state races across suites when they run in parallel. - SnapshotCommand and RunCommand no longer live in the "needs NSApplication" branch of PreviewsMCPApp.main. They're pure daemon clients. Test plan: - 13 existing snapshot tests still pass (with daemon path instead of in-process). - 243/243 non-iOS tests pass in the full concurrent sweep. - Manual: `previewsmcp run --detach` then `previewsmcp snapshot <same file>` captures the live window in ~0.4s vs ~30s for a cold ephemeral. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a54cb0d to
72d9141
Compare
Five small improvements from the review: 1. Surface preview_stop failures on ephemeral teardown. Extract stopEphemeralSession helper so a failed stop logs a warning instead of being silently swallowed by `try?`. Leaked sessions in a long-lived daemon could otherwise confuse future reuse matching. 2. Remove the dead `if lines.isEmpty` branch in handleSessionList — `[].joined(separator:)` already produces "". 3. Extract SessionResolver.resolveAgainst as a pure (no MCP client) function expressing the resolution policy. Makes the three decision branches (file-matches / file-ambiguous / sole-session) explicit and testable in principle, even though the current test targets are structured such that we exercise them through the integration tests rather than import-based unit tests. 4. Regression tests for findPackageDirectory's non-SPM boundary detection: one for xcodeproj-sibling, one for Bazel WORKSPACE. Guards against reintroducing the hang we chased down in #102. 5. New integration test: "Snapshot reuses an already-running session instead of ephemeral". Starts a detached run, then snapshots the same file, and asserts the snapshot completes in under 2s — reuse is sub-second whereas ephemeral is 5+ seconds. Proves the magical resolution actually reuses sessions rather than silently falling through to ephemeral. 30/30 daemon-touching tests pass in the targeted sweep. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third PR in the CLI/MCP parity stack. `snapshot` becomes a thin daemon client that reuses an existing preview session when one is running for the target file, and falls back to an ephemeral session otherwise.
Observed behavior
Live-session reuse is ~80× faster than cold ephemeral:
Test plan
Related
🤖 Generated with Claude Code