Skip to content

fix(machines): keep a split made over SSH when the machine's app is open - #297

Merged
2witstudios merged 9 commits into
mainfrom
pu/cant-split-panes-on-another-machien
Oct 22, 2026
Merged

2witstudios merged 9 commits into
mainfrom
pu/cant-split-panes-on-another-machien

Conversation

@2witstudios

@2witstudios 2witstudios commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

Splitting a pane in a project on another machine (over SSH) and spawning an agent into it gave the agent a new sidebar row instead of the split, whenever PurePoint was also open on that machine.

Cause: two apps write the project's .pu/workspaces.json:

  • The machine's own app read the file once and then saved over it without checking for changes. It never knew the SSH client was holding a tab for the new agent, so it gave the agent its own workspace and saved.
  • The SSH client checks before it saves, so its save conflicted. It then took the machine's whole layout, which dropped the split.

Fix:

  1. Spawn requests say which tab they're for. Request::Spawn has an optional placement ({workspace, surface}). The daemon only records it on the agent's AgentEntry and AgentStatusReport.
  2. Every app puts the agent in that tab. WorkspaceReconciler places an agent no tab holds yet into the empty tab it was spawned for. If that tab is gone, already holds an agent, or is in a different worktree, the agent gets its own workspace as before. Point Guard workspace IDs include each app's own project key, so placements are re-keyed the same way persistence re-keys the workspace.
  3. The machine's app no longer overwrites changes from other Macs. Local layout saves check the file's modification time. The app watches workspaces.json and re-checks it before reconciling, so it sees the client's split before the agent shows up.
  4. Another Mac's layout is merged in, not taken wholesale. This applies on both sides: a save that conflicts, and a file change written by another Mac. The merge (WorkspaceLayoutMerge) works per workspace against the document this app last read or wrote, reconciled with the current agents. That way grouping the reconciler derives from the manifest, which comes out the same on both Macs, never counts as an edit.
    • A workspace this app changed or added keeps this app's version, including when the other Mac changed it too. That keeps a tab just split for a spawn.
    • Everything else takes the other Mac's version.
    • The merged result is written back straight away. The other Mac then adopts it unchanged, so the writes stop.
    • The other Mac's changes are measured against the base document itself, so a workspace closed here doesn't come back.
    • Agents the adopted document places but this Mac's manifest doesn't list yet count as live until the next manifest arrives, so their tabs aren't dropped as dead.
  5. Each Mac writes the split before spawning. For remote projects the client waits for that write, up to 5 seconds, and writes again if it ran into another Mac's change. If the tab was closed meanwhile, the spawn is dropped.
  6. A layout file deleted while the project is open is written again. Previously every later save, local or remote, was skipped (from Codex review). Also, a remote save made when there was no document is no longer allowed to overwrite one another Mac has created since. This problem existed before the PR.

adoptRemoteLayout is renamed adoptExternalLayout. Old daemons and apps ignore the new optional field. The fix takes effect once both Macs and the remote daemon run this build.

Known limits

  • If both Macs change the same workspace between saves, the Mac that saves second keeps its version, and the other Mac's edit to that workspace, such as a focus or ratio change, is dropped.
  • A workspace whose only tabs are empty is removed as a ghost row by the other Mac, which knows nothing about the pending spawn. A normal split keeps the original agent's tab, so this case doesn't come up through a normal split.

Also in this PR

  • ipc_server::tests::given_invalid_utf8_in_grid_stream_should_close_the_connection failed in CI with a broken pipe. main has since fixed it in its own way (one write), and the merge takes main's version.
  • After merging main, its new project-subscription code tripped the harness's strict concurrency checks. AppState.followLocalProjects now captures self weakly in its outer Task as well; the inner [weak self] had no effect. The harness stub for DaemonProjectsSubscription takes a @Sendable callback.
  • apps/purepoint-macos/verification/check-point-guard-workspace.py failed to compile on main too, because signal and transport types were missing from its package. It now compiles, its stubs match the new host and saver code, and it also runs WorkspaceRegistryTests.

Verification (head 650bfaa)

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets: clean.
  • cargo test --workspace: all pass, including:
    • the placement serde round-trip in pu-core;
    • given_spawn_with_placement_should_record_it_on_manifest_and_status in pu-engine;
    • the flaky test above, 15 runs in a row.
  • DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer python3 apps/purepoint-macos/verification/check-point-guard-workspace.py: 138 tests pass. It runs with strict concurrency and warnings as errors, and compiles the real Workspace, WorkspaceRegistry, WorkspacePersistence, DaemonProtocol and spawnAgentForSurface code. New tests it covers:
    • placement and merge tests in WorkspaceReconcilerTests;
    • WorkspaceRegistryTests for: another Mac's split, adopt rather than overwrite, both changes kept, a new tab surviving when both Macs changed the same workspace (written in one save), a tab for an agent this Mac hasn't seen yet waiting for the manifest (including through a second split from the other Mac), a Point Guard placement from another Mac, recreating a deleted file, and not treating our own saves as another Mac's.
  • Not run by me: the full app build and the MachinesTests target. Agents must not run xcodebuild. My changes there are the two saveRemote fixes with their new tests (givenDocumentDeletedSinceReadSaveShouldWriteItAgain and givenDocumentCreatedSinceItWasReadAsMissingSaveShouldAdoptIt), the optional onWritten on RemoteLayoutSaver, and the adoptExternalLayout rename. CI's macOS Build & Test job covers the full app; owner, please also build in Xcode.
  • Not run by me: a manual check on two Macs. With PurePoint open on both, split a pane in the project over SSH and spawn into it. It should stay in the split on both Macs.

PageSpace

  • No PageSpace drive is declared for this repo (project.config.json is absent, and neither CLAUDE.md nor AGENTS.md names one), so there are no task, plan or prompt pages to link. This came from the owner's direct bug report: "can't split panes on another machine — it spawns a second agent as a new row instead of a split pane, over SSH".
  • Reviews: four independent agent reviews (findings fixed in 5020293, dbb78ca, efb4a28 and de78f07) and Codex review (two threads answered, open for the reviewer: the deleted-file fix in 8e44490, and a P1 about the missing-layout stamp that does not reproduce, now written explicitly with updateValue in f52bb4d). An independent review of this exact head is still pending.

Builder: ag-cjxl4rcd

Autonomous agents never merge directly into main/default/protected release targets. Main acceptance requires applicable checks and independent exact-candidate review; production retains human sign-offs.

🤖 Generated with Claude Code

https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo

Splitting a pane in a project on another Mac and spawning into it made the
agent a new sidebar row whenever PurePoint was also open on that Mac. Its app
saved .pu/workspaces.json without checking for changes and gave the unknown agent
its own workspace; the SSH client's conditional save then conflicted and took
that layout, dropping the split.

- Spawn requests carry an optional placement (workspace + tab). The daemon
  records it on the manifest entry and status report, and every app's
  reconciler puts the agent in that empty tab.
- Local layout saves check the file's modification time and adopt a document
  another Mac wrote instead of overwriting it. The app also watches the file
  and re-checks it before reconciling.
- A remote client writes its pending layout before sending the spawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T06:16:32.165283Z de78f07 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6e8c28b4-ad5a-41d4-af77-2abdae1fc50f
📥 Commits

Reviewing files that changed from the base of the PR and between dbb78ca and 650bfaa.

📒 Files selected for processing: 34
  • apps/purepoint-macos/purepoint-macos/Models/ManifestModel.swift
  • apps/purepoint-macos/purepoint-macos/Models/Workspace.swift
  • apps/purepoint-macos/purepoint-macos/Models/WorkspaceModel.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonProtocol.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonStatusSubscription.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonWorkspaceService.swift
  • apps/purepoint-macos/purepoint-macos/State/AppState.swift
  • apps/purepoint-macos/purepoint-macos/State/ProjectState.swift
  • apps/purepoint-macos/purepoint-macos/State/WorkspaceRegistry.swift
  • apps/purepoint-macos/purepoint-macos/Views/PaneGrid/WorkspacePersistence.swift
  • apps/purepoint-macos/purepoint-macosTests/MachinesTests.swift
  • apps/purepoint-macos/purepoint-macosTests/WorkspaceReconcilerTests.swift
  • apps/purepoint-macos/purepoint-macosTests/WorkspaceRegistryTests.swift
  • apps/purepoint-macos/verification/PointGuardRoutingStubs.swift
  • apps/purepoint-macos/verification/check-point-guard-workspace.py
  • crates/pu-cli/src/client.rs
  • crates/pu-cli/src/commands/spawn.rs
  • crates/pu-cli/src/output/tests.rs
  • crates/pu-core/src/protocol/mod.rs
  • crates/pu-core/src/protocol/payloads.rs
  • crates/pu-core/src/protocol/tests.rs
  • crates/pu-core/src/types/agent.rs
  • crates/pu-core/src/types/manifest.rs
  • crates/pu-core/src/types/worktree.rs
  • crates/pu-engine/src/engine/agent_lifecycle.rs
  • crates/pu-engine/src/engine/definitions/swarm_defs.rs
  • crates/pu-engine/src/engine/inventory.rs
  • crates/pu-engine/src/engine/mod.rs
  • crates/pu-engine/src/engine/scheduler.rs
  • crates/pu-engine/src/engine/spawn.rs
  • crates/pu-engine/src/engine/status.rs
  • crates/pu-engine/src/test_helpers.rs
  • crates/pu-engine/verification/owned-native-signals.rs
  • docs/reference/ipc-api.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: be4911fe-a573-468b-9d81-405dd5ca735f
📥 Commits

Reviewing files that changed from the base of the PR and between b982f71 and dbb78ca.

📒 Files selected for processing: 34
  • apps/purepoint-macos/purepoint-macos/Models/ManifestModel.swift
  • apps/purepoint-macos/purepoint-macos/Models/Workspace.swift
  • apps/purepoint-macos/purepoint-macos/Models/WorkspaceModel.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonProtocol.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonStatusSubscription.swift
  • apps/purepoint-macos/purepoint-macos/Services/DaemonWorkspaceService.swift
  • apps/purepoint-macos/purepoint-macos/State/ProjectState.swift
  • apps/purepoint-macos/purepoint-macos/State/WorkspaceRegistry.swift
  • apps/purepoint-macos/purepoint-macos/Views/PaneGrid/WorkspacePersistence.swift
  • apps/purepoint-macos/purepoint-macosTests/MachinesTests.swift
  • apps/purepoint-macos/purepoint-macosTests/WorkspaceReconcilerTests.swift
  • apps/purepoint-macos/purepoint-macosTests/WorkspaceRegistryTests.swift
  • apps/purepoint-macos/verification/PointGuardRoutingStubs.swift
  • apps/purepoint-macos/verification/check-point-guard-workspace.py
  • crates/pu-cli/src/client.rs
  • crates/pu-cli/src/commands/spawn.rs
  • crates/pu-cli/src/output/tests.rs
  • crates/pu-core/src/protocol/mod.rs
  • crates/pu-core/src/protocol/payloads.rs
  • crates/pu-core/src/protocol/tests.rs
  • crates/pu-core/src/types/agent.rs
  • crates/pu-core/src/types/manifest.rs
  • crates/pu-core/src/types/worktree.rs
  • crates/pu-engine/src/engine/agent_lifecycle.rs
  • crates/pu-engine/src/engine/definitions/swarm_defs.rs
  • crates/pu-engine/src/engine/inventory.rs
  • crates/pu-engine/src/engine/mod.rs
  • crates/pu-engine/src/engine/scheduler.rs
  • crates/pu-engine/src/engine/spawn.rs
  • crates/pu-engine/src/engine/status.rs
  • crates/pu-engine/src/ipc_server/tests.rs
  • crates/pu-engine/src/test_helpers.rs
  • crates/pu-engine/verification/owned-native-signals.rs
  • docs/reference/ipc-api.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The change adds optional workspace and surface placement to spawn requests, stores it on agent entries, and exposes it in status reports. macOS uses placement during workspace reconciliation. Local and remote layout persistence now detects external edits, merges workspace changes, and performs conditional saves.

Changes: Spawn Placement and Workspace Synchronization

Layer / File(s) Summary
Placement protocol and daemon persistence
crates/pu-core/src/types/agent.rs, crates/pu-core/src/protocol/*, crates/pu-engine/src/engine/*, crates/pu-cli/src/*, crates/pu-engine/src/ipc_server/tests.rs, crates/pu-engine/verification/owned-native-signals.rs
Spawn requests and agent status reports support optional placement. The engine stores placement on spawned agents and returns it in status reports. Tests and other spawn request constructors initialize the new field.
macOS placement and reconciliation
apps/purepoint-macos/purepoint-macos/Models/*, apps/purepoint-macos/purepoint-macos/Services/*, apps/purepoint-macos/purepoint-macos/State/ProjectState.swift, apps/purepoint-macos/purepoint-macosTests/WorkspaceReconcilerTests.swift, apps/purepoint-macos/purepoint-macosTests/WorkspaceRegistryTests.swift, docs/reference/ipc-api.md
macOS passes placement through agent models and daemon reports. Surface spawns include the target workspace and surface. Reconciliation fills eligible matching empty surfaces and rekeys Point Guard placements for the current project.
Conditional layout saves and merging
apps/purepoint-macos/purepoint-macos/Models/Workspace.swift, apps/purepoint-macos/purepoint-macos/State/ProjectState.swift, apps/purepoint-macos/purepoint-macos/State/WorkspaceRegistry.swift, apps/purepoint-macos/purepoint-macos/Views/PaneGrid/WorkspacePersistence.swift, apps/purepoint-macos/purepoint-macosTests/MachinesTests.swift, apps/purepoint-macos/purepoint-macosTests/WorkspaceReconcilerTests.swift, apps/purepoint-macos/purepoint-macosTests/WorkspaceRegistryTests.swift, apps/purepoint-macos/verification/*
Local and remote saves compare modification times and handle conflicts or deleted files. The registry watches local layout changes, merges external edits with the current layout, and updates its merge base after writes. Tests and verification stubs cover these paths.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant ProjectState
  participant Daemon
  participant Engine
  participant WorkspaceRegistry
  participant WorkspaceReconciler
  ProjectState->>Daemon: Send spawn request with workspace and surface placement
  Daemon->>Engine: Forward spawn request
  Engine->>Engine: Store placement on agent entry
  Engine->>Daemon: Return agent status with placement
  Daemon->>WorkspaceRegistry: Publish agent status
  WorkspaceRegistry->>WorkspaceReconciler: Reconcile live agents with stored workspaces
Loading
Merge Risk: ⚪ Minimal · up to dbb78

The change improves protection for layouts edited on two Macs. A narrow, pre-existing concurrent-write race remains, but it does not present a new merge-blocking risk.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 43.09% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 123 functions across 33 files. (1 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: preserving an SSH-created pane split when the machine's app is also open.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 43.09% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 123 functions across 33 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c18b6cd9bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +154 to +157
if onDisk != expectedModifiedMs {
guard let current = try? host.read(path: path, maxBytes: maxRemoteLayoutBytes), !current.truncated,
!isNewerThanSupported(data: current.data)
else { return .skipped }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow saves to recover after external layout deletion

If .pu/workspaces.json is deleted while the project is open, onDisk becomes nil while expectedModifiedMs retains the previous timestamp; this branch then tries to read the missing file and permanently returns .skipped. Because the stored timestamp is never updated, every later local layout save follows the same path, so pane and tab changes stop persisting until the project is reopened. Treat a missing file as an external empty layout or otherwise advance the expected state so the next save can recreate it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 8e44490. In WorkspacePersistence.saveLocal, a missing document no longer counts as a conflict: there is nothing to adopt, so the save writes the file again. saveRemote had the same problem (after not_found it still sent the old timestamp, so every save conflicted), so it now writes against the timestamp of what is actually on disk: none if the file is gone. New tests: WorkspaceRegistryTests.givenLayoutFileDeletedASaveShouldWriteItAgain and MachinesTests.givenDocumentDeletedSinceReadSaveShouldWriteItAgain. I am leaving this thread open for the reviewer to confirm.

2witstudios and others added 6 commits October 21, 2026 23:55
A save expected the modification time it last saw, so once .pu/workspaces.json
was deleted every later local or remote save conflicted, found nothing to
adopt, and was skipped until the project was reopened. A missing document is
now written again.

Also stop the invalid-UTF-8 stream test failing on a broken pipe: the daemon
may close the connection before the trailing request is written, which is the
behaviour under test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
Independent review of the shared-layout fix found three ways a split could
still be lost:

- A save that conflicted adopted the other Mac's document wholesale, dropping
  this app's unsaved edits (the Mac running the project could now lose its own
  split to a write from an SSH client). Conflicts and external changes are now
  merged per workspace against the document last read or written
  (WorkspaceLayoutMerge): a workspace only this app changed keeps this app's
  version, anything the other Mac changed takes theirs, and the merged result
  is written back.
- Point Guard workspace IDs embed each app's project key, so a placement made
  in Point Guard on another Mac never matched. Placements are re-keyed the way
  persistence re-keys the workspace.
- A local project spawned before its layout was written; it now saves first
  too. The layout file's stamp is also taken before it is read.

The isolated pane/registry verification package compiles again (it was
already missing signal and transport types on main) and now also runs
WorkspaceRegistryTests: 126 tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
From a second independent review of the layout merge:

- When both Macs changed the same workspace, the other Mac's version won,
  which could drop the tab just split here for a spawn and bring back the
  original bug. The Mac that saves second now keeps its version. "Changed" is
  measured against the base as reconciled with the current agents, so
  groupings both Macs derive from the manifest never count as an edit. The
  other Mac then adopts the result unchanged, so the writes stop.
- A save that conflicted only scheduled the merged write a second later, so a
  spawn (or quit) could go ahead before it landed. Local saves now write the
  merge immediately, and a remote spawn flushes again before sending.
- A remote save made when no document was known overwrote one another Mac had
  created since. Any document this app has not seen is now a conflict.
- A spawn waiting on the layout write is dropped if its tab was closed in the
  meantime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
From a third independent review of the layout merge:

- A document adopted from another Mac can place an agent this app's manifest
  does not list yet (that Mac saw the spawn first). The reconciler dropped
  such tabs as dead, collapsing the very split this branch keeps. Agents an
  adopted document names are now treated as live until the next manifest
  arrives; a current manifest that lacks them still removes their tabs.
- A workspace closed here could come back: the other Mac's untouched copy was
  compared with the base as reconciled with this app's live agents, which had
  already lost the killed agent, so it looked changed there. Their changes are
  now measured against the base document itself; only ours are measured
  against the reconciled base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
From a fourth independent review: this app publishes its layout with the
agents an adopted document placed before the manifest lists them, but the
merge base was reconciled without them. A workspace holding such an agent then
looked edited here, so a second split from the other Mac (for its next spawn)
was overridden and written back, putting that spawn in its own row again. The
base is now reconciled with the same agents.

Unconfirmed agents are also taken from the merged layout rather than the other
Mac's document, so an agent this app's version of a workspace does not hold is
not given a stray row of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
@2witstudios

Copy link
Copy Markdown
Owner Author

@codex review

The finding about saves after the layout file is deleted was fixed in 8e44490 (see the thread). Since then the branch has also added a per-workspace layout merge, so two Macs no longer overwrite each other. Please re-review head de78f07.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de78f076c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let restored = try loadWorkspaces(projectRoot)
workspacesByProject[projectRoot] = restored
loadedProjects.insert(projectRoot)
localLayoutStamps[projectRoot] = stamp

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the known-missing layout state

When a project is loaded before .pu/workspaces.json exists, stamp is nil, and assigning it through this dictionary subscript removes the key rather than recording that the file was known to be missing. Consequently adoptLocalLayoutIfChanged cannot detect the first layout subsequently created over SSH, and saveLocal falls back to that unseen file's current timestamp and overwrites it instead of reporting a conflict. This loses the other Mac's layout when it creates the initial document before this Mac's first successful save; represent the known-missing state explicitly or track whether a stamp was initialized separately.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking this, but the key isn't removed. stamp has static type Int64?. Assigning it through the subscript of a [String: Int64?] (whose setter takes Int64??) wraps it as .some(nil) and stores a nil value. Only the literal nil removes the key. I checked with:

var stamps: [String: Int64?] = [:]
let stamp: Int64? = nil
stamps["p"] = stamp      // keys.contains("p") == true, and `if let known = stamps["p"]` binds known == nil
stamps["p"] = nil        // keys.contains("p") == false

The exact scenario you describe is covered by WorkspaceRegistryTests.givenAnotherMacSplitAPaneShouldPlaceItsNewAgentInThatPane. It loads the project with no workspaces.json, has another Mac create it, then reconciles. The test expects this Mac to adopt that file, not overwrite it, and it passes (131/131 in apps/purepoint-macos/verification/check-point-guard-workspace.py).

The subscript form clearly reads as a removal, though, so in f52bb4d I changed it to localLayoutStamps.updateValue(stamp, forKey: projectRoot) with a comment, which says the intent outright. I'm leaving this thread open for you to confirm.

2witstudios and others added 2 commits October 22, 2026 01:18
Assigning a nil Int64? through the subscript of an [String: Int64?] already
stores a nil value (only a literal nil removes the key), but that reads as the
removal a reviewer took it for. updateValue states the intent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
Conflict: crates/pu-engine/src/ipc_server/tests.rs. Main fixed the same
flaky invalid-UTF-8 stream test by sending both lines in one write; took
main's version.

The isolated pane/registry package (check-point-guard-workspace.py) compiles
main's new project-subscription code with strict concurrency and warnings as
errors. To keep it green:
- AppState.followLocalProjects captures self weakly in the outer Task too
  (the inner [weak self] was overridden by an implicit strong capture).
- The DaemonProjectsSubscription stub's callback is @sendable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bBnogGfBnCLmktcEpVNJo
@2witstudios
2witstudios merged commit 799f688 into main Oct 22, 2026
15 checks passed
@2witstudios
2witstudios deleted the pu/cant-split-panes-on-another-machien branch October 22, 2026 12:11
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