feat(app): single-source the G1 capture dump onto the workspace snapshot (F-mcp-snapshot-g1, #216)#226
Merged
Merged
Conversation
…dler The DTO mirror of `WorkspaceSnapshot` was private to `mcp/handler.rs`, but the G1 capture dump is about to serialise the same model. Move it to `app::snapshot_dto` unchanged — same fields, same order, same omissions — so both readers share one wire form instead of drifting mirrors. Pure structural move: no behaviour change.
The dev-loop dump and the MCP `snapshot` tool described the same app with two models — `CaptureDump` (tabs + focused terminal) and `WorkspaceSnapshot` (config + sidebar + panes + scoped terminals). Two models means a field can mean one thing on disk and another on the wire, and every later rung has to teach both. Collapse them. `Event::Capture` now carries the adapter-injected `SnapshotInputs`, `Effect::Capture` a `WorkspaceSnapshot`, and `App::build_capture` is a call to the snapshot builder under a fixed `SnapshotFilter::capture()` — every section, the focused pane's screen kept whole (a file pays for the full picture where a call must not). `core::capture` and its two types are gone; the shell gathers a capture's inputs through the same seam a bridge snapshot uses. The dump's JSON is now `app::snapshot_dto`, shared with the MCP handler, so the capture gained config, the sidebar and per-pane detail (handle, kind, cwd, status) for free. Breaking dump-format change: `focused_pty` becomes `terminals` keyed by handle, and status words follow the MCP vocabulary (`ready` becomes `idle`), which retires `strings::status_label`. The newest-stamp discovery contract — the one an AI actually depends on — is untouched.
The note is the reference document for `capture-<ts>.json`, and every field it described was renamed or removed: the event/effect payloads, the CaptureDump field map, the sample JSON, the status vocabulary and the file map. A reader following it would key on `focused_pty` / `active_tab` / `ready` and match nothing this build writes.
bastien-gallay
force-pushed
the
worktree-216-mcp-snapshot-g1
branch
from
July 25, 2026 19:14
39978c7 to
9dfd37c
Compare
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.
What
The G1 dev-loop dump (
~/.termherd/captures/capture-<ts>.json) and the MCPsnapshottool described the same application with two models:CaptureDump(tabs + the focused terminal) andWorkspaceSnapshot(config +sidebar + per-pane tabs + scoped terminals). Two models means a field can mean
one thing on disk and another on the wire, and every later rung has to teach
both. This collapses them onto one.
SnapshotFilter::capture()is the fixed shape behind a capture: everystructural section, plus the focused pane's screen kept whole. A capture is
written to a file, not streamed into a call, so it pays for the full picture —
but it stays scoped to the pane the developer is looking at when they press the
key.
core::capture(CaptureDump/CaptureTab) is gone, and the shell gathers acapture's adapter inputs through the same seam a bridge
snapshotuses(
snapshot_inputs_for), so config and terminal text are collected exactly oneway.
Two commits
refactor(app)— tidy-first, no behaviour change: the JSON wire form ofWorkspaceSnapshotmoves out ofmcp/handler.rsintoapp::snapshot_dto,field-for-field, so both readers can share it.
feat(app)— the reframe above.Breaking dump-format change
Deliberate, and the point of the slice:
focused_pty: "<text>"→terminals: { "<handle>": "<text>" }focus,config,sidebar, and per-pane detail (stablehandle, kind, cwd, status)
"ready"becomes"idle"— whichretires
strings::status_label(it existed only for this dump)The contract an AI actually depends on — newest capture = highest-named
pair — is untouched.
AGENTS.md's capture note is refreshed.Tests
Written red first, then made green. 8 tests over the new behaviour:
core::snapshot— the capture filter is every section + the focused terminal,untruncated (10 000 lines survive).
core::app::capture— the dump carries every section; the focused text rideswhole; a background pane's injected text is not included; a tab's custom
title wins; empty workspace; split pane order (the last two are regressions
kept from the old suite).
app::capture— the on-disk JSON is the MCP shape (string handles,"idle").app::shell— end-to-end through theperform_capturedir seam: the writtenfile holds focus, tabs, config and the focused terminal.
Full suite green (
cargo test --workspace);fmt·clippy·machete·deny·check-deps·check-arch·markdownlintall pass. ROADMAP entryticked.
Closes #216.