Skip to content

Cloud presence: teammates' pointers and highlights on shared terminals - #12300

Open
lawrencecchen wants to merge 4 commits into
mainfrom
feat-presence-hub
Open

Cloud presence: teammates' pointers and highlights on shared terminals#12300
lawrencecchen wants to merge 4 commits into
mainfrom
feat-presence-hub

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Lets people attached to one cmux-tui session point at a cell and highlight a line in a shared terminal while they talk, from the Mac app.

Daemon (cmux-tui): a presence hub with commands presence-update, presence-clear, presence-list, a coalesced presence-changed subscribe event, and subscribe.presence_only so a presence link never receives tree or output traffic. Capability presence-v1. Anchors are surface coordinates (cell {row,col,scroll_offset} or point {x,y}); highlights are laser (fade) or pin (stay). Presence is never journaled and never touches the registry lock; the hub keeps only the latest state per connection, rate limits at 240/s, and clears on disconnect, presence-clear, or surface exit. Identity is the connection plus its set-client-info labels; authenticated actor identity is a later contract. Spec: cmux-tui/spec/presence.md. Raw bindings regenerated (109 commands, 49 events).

Mac: one presence-only link per cloud machine (CloudPresenceLink, CloudPresenceStore). The Ghostty view publishes the hovered cell; Cmd+Shift-drag publishes a laser highlight without making a local selection. A click-through overlay above each cloud pane (CloudPresenceOverlayView) draws other clients' arrow, name pill, cell frame, and highlights, shifting rows by scrollback offset difference.

Tests: hub unit tests; in-process server tests (coalescing, disconnect clear, surface-exit clear); a WebSocket end-to-end test with a presence_only viewer, a pointer, and a late joiner; Swift Testing coverage for decoding, command shapes, and row mapping (cmuxTests/CloudPresenceTests.swift).

Verified live on tag prsnc: the branch daemon swapped into a dev-stack Freestyle VM (baseline CPU build), a synthetic second client inside the VM pointed and pinned a highlight on the pane's surface, and the tagged Mac app drew it at the right cells; list-clients showed the Mac presence link as kind mac. Testbox: presence tests, clippy -D warnings on cmux-tui-core and cmux-tui, schema and inventory checks, Go/Rust/Python/TypeScript binding tests. workspace_registry::tests::startup_repairs_legacy_terminal_close_dangling_resource_rows fails identically on main 4aa03bc and is unrelated.

Known limits: terminal panes only (browser panes have no daemon surface id yet); a late joiner sees a pointer on its next move rather than through presence-list; names are self-asserted until actor identity lands.

Found on the way: #12308 (vm push chunk exceeds the Freestyle command cap).

https://claude.ai/code/session_012DpfpRex9MYKKZBz3Lv5sq


Note

Medium Risk
Touches terminal mouse routing and a second socket per cloud machine; mistakes could steal input or leak stale overlays, but scope is limited to presence-enabled cloud panes and optional presence-v1 capability.

Overview
Adds collaboration presence on the Mac app for cloud manual-mirror panes: teammates see each other’s cell pointers and highlights over the same remote terminal surface.

The Mac side introduces models and wire decoding for presence-changed, a presence-only CloudPresenceLink per cloud machine (subscribe with presence_only, throttled presence-update / presence-clear), and CloudPresenceStore to register panes, fan in remote entries, and publish local state. Manual-I/O gains presence-v1 commands and frame decoding (including clears with surface: null); the mirror byte stream ignores presence frames.

Ghostty publishes the hovered grid cell (with scroll offset) for registered panes; Cmd+Shift-drag starts a fading laser highlight without taking over normal selection. A click-through CloudPresenceOverlayView draws remote arrows, name pills, and selection-style highlights with scroll-offset correction. Cloud surface providers register/unregister panes and keep surface/socket IDs in sync on reconnect.

cmux-tui SDK bindings (C++/Go) are regenerated for presence-update, presence-clear, presence-list, presence-changed, and subscribe.presence_only.

Reviewed by Cursor Bugbot for commit 41195f4. Bugbot is set up for automated code reviews on this repo. Configure here.

… the daemon

Adds a presence hub to cmux-tui-core: presence-update, presence-clear,
presence-list commands and a coalesced presence-changed subscribe event
under capability presence-v1. Presence is never journaled, is cleared on
disconnect or surface exit, and is rate limited per connection. Spec in
spec/presence.md; raw bindings regenerated.

Claude-Session: https://claude.ai/code/session_012DpfpRex9MYKKZBz3Lv5sq
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cmux166 Ready Ready Preview Sep 11, 2026 4:11am UTC
cmux41 Ready Ready Preview Sep 11, 2026 4:11am UTC

@github-actions

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 8 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 1b7bb5d2-53bd-4397-9bb6-f93a72ddfcf5

📥 Commits

Reviewing files that changed from the base of the PR and between 897bb7a and 41195f4.

⛔ Files ignored due to path filters (25)
  • cmux-tui/bindings/cpp/include/cmux/raw/generated/commands.hpp is excluded by !**/generated/**
  • cmux-tui/bindings/cpp/include/cmux/raw/generated/events.hpp is excluded by !**/generated/**
  • cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp is excluded by !**/generated/**
  • cmux-tui/bindings/cpp/src/raw/generated/protocol.cpp is excluded by !**/generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/.cmux-sdk-manifest.json is excluded by !**/_generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/_schema.py is excluded by !**/_generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/client.py is excluded by !**/_generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/codec.py is excluded by !**/_generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/metadata.py is excluded by !**/_generated/**
  • cmux-tui/bindings/python/cmux/raw/_generated/models.py is excluded by !**/_generated/**
  • cmux-tui/bindings/rust/src/generated/.cmux-sdk-manifest.json is excluded by !**/generated/**
  • cmux-tui/bindings/rust/src/generated/commands.rs is excluded by !**/generated/**
  • cmux-tui/bindings/rust/src/generated/events.rs is excluded by !**/generated/**
  • cmux-tui/bindings/rust/src/generated/metadata.rs is excluded by !**/generated/**
  • cmux-tui/bindings/rust/src/generated/mod.rs is excluded by !**/generated/**
  • cmux-tui/bindings/rust/src/generated/types.rs is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/.cmux-sdk-manifest.json is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/commands.ts is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/events.ts is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/index.ts is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/metadata.ts is excluded by !**/generated/**
  • cmux-tui/bindings/typescript/src/raw/generated/types.ts is excluded by !**/generated/**
  • cmux-tui/bindings/zig/src/raw/generated/.cmux-sdk-manifest.json is excluded by !**/generated/**
  • cmux-tui/bindings/zig/src/raw/generated/presence_test.zig is excluded by !**/generated/**
  • cmux-tui/bindings/zig/src/raw/generated/protocol.zig is excluded by !**/generated/**
📒 Files selected for processing (57)
  • Sources/Cloud/CloudPresenceEntry.swift
  • Sources/Cloud/CloudPresenceLink.swift
  • Sources/Cloud/CloudPresenceOverlayView.swift
  • Sources/Cloud/CloudPresenceStore.swift
  • Sources/Cloud/CloudTuiManualIOCommand.swift
  • Sources/Cloud/CloudTuiManualIOConnection.swift
  • Sources/Cloud/CloudTuiManualIOFrame.swift
  • Sources/Cloud/CloudTuiManualIOFrameDecoder.swift
  • Sources/Cloud/CloudTuiManualMirrorSession.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/Surfaces/CmuxTuiSurfaceProvider+ManualMirror.swift
  • Sources/Surfaces/CmuxTuiSurfaceProviders.swift
  • cmux-tui/bindings/cpp/.cmux-sdk-manifest.json
  • cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json
  • cmux-tui/bindings/go/raw/client_test.go
  • cmux-tui/bindings/go/raw/generated_commands.go
  • cmux-tui/bindings/go/raw/generated_events.go
  • cmux-tui/bindings/go/raw/generated_metadata.go
  • cmux-tui/bindings/go/raw/generated_presence_test.go
  • cmux-tui/bindings/go/raw/generated_types.go
  • cmux-tui/bindings/java/src/com/cmux/raw/.cmux-sdk-manifest.json
  • cmux-tui/bindings/java/src/com/cmux/raw/Commands.java
  • cmux-tui/bindings/java/src/com/cmux/raw/Events.java
  • cmux-tui/bindings/java/src/com/cmux/raw/GeneratedCmuxClient.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceAnchor.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceAnchorCell.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceAnchorPoint.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceChangedEvent.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceClearRequest.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceEntry.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceHighlight.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceHighlightMode.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceListRequest.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceListResult.java
  • cmux-tui/bindings/java/src/com/cmux/raw/PresenceUpdateRequest.java
  • cmux-tui/bindings/java/src/com/cmux/raw/Protocol.java
  • cmux-tui/bindings/java/src/com/cmux/raw/SubscribeRequest.java
  • cmux-tui/bindings/java/tests/com/cmux/raw/GeneratedCoverageTest.java
  • cmux-tui/bindings/python/tests/test_protocol.py
  • cmux-tui/bindings/typescript/test/generated.test.ts
  • cmux-tui/bindings/zig/examples/watch.zig
  • cmux-tui/bindings/zig/src/raw.zig
  • cmux-tui/crates/cmux-tui-core/src/event_bus.rs
  • cmux-tui/crates/cmux-tui-core/src/lib.rs
  • cmux-tui/crates/cmux-tui-core/src/mux.rs
  • cmux-tui/crates/cmux-tui-core/src/presence.rs
  • cmux-tui/crates/cmux-tui-core/src/server.rs
  • cmux-tui/crates/cmux-tui-core/tests/websocket_transport.rs
  • cmux-tui/scripts/check-sdk-schema.py
  • cmux-tui/spec/README.md
  • cmux-tui/spec/commands.md
  • cmux-tui/spec/events.md
  • cmux-tui/spec/inventory.json
  • cmux-tui/spec/presence.md
  • cmux-tui/spec/sdk-schema.json
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CloudPresenceTests.swift

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.

…terminal panes

One presence-only cmux-tui link per machine (identify, set-client-info with
presence-v1, subscribe presence_only). The Ghostty view publishes the hovered
cell as a presence-update and a Cmd+Shift drag as a laser highlight; a
click-through overlay above each cloud pane draws other clients' pointers,
name pills, and highlights, shifting rows by scrollback offset. Daemon gains
subscribe.presence_only so the link never receives tree or output traffic.

Claude-Session: https://claude.ai/code/session_012DpfpRex9MYKKZBz3Lv5sq
@blacksmith-sh

This comment has been minimized.

@lawrencecchen
lawrencecchen marked this pull request as ready for review September 11, 2026 04:11
@lawrencecchen lawrencecchen changed the title cmux-tui: ephemeral collaboration presence (pointer + highlight) Cloud presence: teammates' pointers and highlights on shared terminals Sep 11, 2026

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 41195f4. Configure here.

highlight: cloudPresenceHighlight
)
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Laser highlight never fades

High Severity

finishCloudPresenceHighlight leaves cloudPresenceHighlight set, and every later pointer publish resends that laser. The daemon then refreshes updated_at_ms on the whole entry, so viewers age the laser from the latest pointer move and it never fades while the publisher keeps hovering.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41195f4. Configure here.

)
NSBezierPath(roundedRect: rect.insetBy(dx: -1, dy: -0.5), xRadius: 2, yRadius: 2).fill()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Highlight ignores end scroll offset

Medium Severity

drawHighlight shifts both ends by the start cell’s scroll_offset and drops the end anchor’s offset. Start and end are captured at different times, so a mid-drag scroll or live output that changes rowsBelowViewport maps the range to the wrong rows.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41195f4. Configure here.

return machineEntries.values
.filter { $0.surface == pane.remoteSurfaceID }
.sorted { $0.client < $1.client }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Own pointer drawn on overlay

Medium Severity

entries(forPane:) returns every live entry on the pane’s surface, including this Mac’s own presence-changed echo. The overlay is meant to draw teammates only, so the local user gets a duplicate arrow and name pill on their cursor.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41195f4. Configure here.

if entries.removeValue(forKey: link.machineID) != nil {
post(machineID: link.machineID)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Presence link does not reconnect

Medium Severity

A dropped presence socket is left disconnected until the next publish. updateSocketPath no-ops when the path is unchanged, so after a machine blip or daemon restart a viewer who is not hovering never redials and teammates’ pointers stay gone.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 41195f4. Configure here.

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