Fix Live View clipboard sync under implicit hosting (#310)#318
Conversation
Copying inside the remote browser never updated the viewer's local clipboard in an embedded Live View. Under neko's implicit_hosting, the client treated the viewer as a host (`remote.hosting` true) but never registered one server-side, so `remote.request()` short-circuited and no `control/request` was ever sent. neko delivers clipboard updates only to the registered host (`sessions.GetHost()`), so with no host they were dropped. - store/remote.ts: add a `controlling` getter that is true only when the viewer is the actual registered host, and guard `request()` on it instead of the broad `hosting`. The `id !== ''` check avoids treating the pre-connection empty-id state as controlling. - components/video.vue: on the first interaction under implicit hosting, call `remote.request()` so the session registers as host, while still forwarding the original click (the pinned neko server accepts input via LegacyIsHost() under implicit hosting, so no buffer/replay is needed). - neko/index.ts: stop surfacing the "you took the controls" toast to the viewer. Control is now registered automatically on first interaction, so it would otherwise fire on essentially every session's first click. Co-Authored-By: Claude <noreply@anthropic.com>
masnwilliams
left a comment
There was a problem hiding this comment.
reviewed — approve. clean, well-scoped fix that correctly isolates the root cause (client-side control registration under implicit hosting, not clipboard perms). the controlling getter + re-gating request() on it is the right mechanism, the empty-id guard is a nice touch, and the first-click path forwards the click immediately with no buffering. all notes below are minor — nothing blocking.
Nits / minor
components/video.vue:814— no in-flight guard onrequest().controllingonly flips after the server'scontrol/lockedround-trips, so rapid clicks before that response can send multiplecontrol/requestframes. benign (server re-locks to the same host id, idempotent), but the "second click sends no duplicate" property only holds once the response has landed. a local "request sent" flag would make it strictly one.neko/index.ts:242— the comment says notifications for other members are left intact, but the removed$notifywas self-only (this.id === id); the intact path for others is the chat event message, not a toast. wording nit.neko/index.ts:233— suppressing the self-toast also drops the confirmation for explicit control-takes in the full (non-embedded) neko UI. intended for the embed use case, just noting the shared-component side effect.
Question (non-blocking)
store/remote.ts:82—toggle()still gates onhosting, so under implicit hosting it only ever sendsRELEASE; any UI still wired to it (e.g.controls.vue:311) can't acquire control. pre-existing, not introduced here — flagging since you're in the area.
- remote.ts: add a `requesting` flag so rapid clicks before control/locked round-trips can't emit multiple control/request frames. Cleared on setHost (host resolved) and reset (disconnect/release). - neko/index.ts: clarify the toast-suppression comment — the removed $notify was self-only; the surviving other-member path is the chat event, and the suppression is a shared-component side effect intended for the embedded live view. Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks @masnwilliams! Addressed the two actionable notes in c15d5d5:
On the non-blocking |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.
Reviewed by Cursor Bugbot for commit c15d5d5. Configure here.
| return | ||
| } | ||
|
|
||
| accessor.remote.setRequesting(true) |
There was a problem hiding this comment.
Stuck requesting blocks host registration
Medium Severity
The request action sets requesting to true before confirming the message can be sent. If $client.connected is false (e.g., during network reconnects), sendMessage no-ops, leaving requesting latched true. This incorrectly blocks subsequent control requests, breaking host registration and clipboard sync until a full reset.
Reviewed by Cursor Bugbot for commit c15d5d5. Configure here.


Problem
Fixes #310. In an embedded Live View, copying text inside the remote browser never updates the viewer's local clipboard.
Root cause is client-side control registration, not browser clipboard permissions:
session.implicit_hosting: true.remote.hostinggetter istruewhenever implicit hosting is on, even though the viewer was never registered as a host server-side.remote.request()short-circuits whenhostingis true, so the client never sendscontrol/request.sessions.GetHost()); with no host registered,control/clipboardis never sent, sonavigator.clipboard.writeText()never runs.Sending a manual
control/requestin a live session producescontrol/locked, after which the very next copy deliverscontrol/clipboardand the paste works — isolating the defect to the missing client request. This matches upstreamm1k1o/neko#540.Change
store/remote.ts— add acontrollinggetter (true only when the viewer is the actual registered host) and guardrequest()on it instead of the broadhosting. Theid !== ''check prevents the pre-connection empty-id state from reading as controlling.components/video.vue— on the first interaction under implicit hosting, callremote.request()so the session registers as host, while still forwarding the original click immediately. The pinned neko server accepts input viaLegacyIsHost()under implicit hosting, so no mousedown/mouseup buffering + replay is required.neko/index.ts— stop surfacing the "you took the controls" toast to the viewer. Control now registers automatically on the first click, so the toast would otherwise fire on essentially every session's first interaction. Notifications for other members taking/stealing control are left intact.Relationship to #311
Same diagnosis and the same store-level fix as #311 (both port upstream
m1k1o/neko#540). This PR intentionally diverges invideo.vue: instead of buffering the first mouse down/up and replaying them via a@Watch('controlling'), it forwards the first click immediately and relies on the pinned server accepting pre-registration input. This avoids the stuck-remote-button edge cases raised in #311's review (replaying amousedownwith no matchingmouseup; a singleisMouseDownboolean mishandling multi-button chords). It also adds the "you took the controls" toast suppression, which #311 does not.Verification
Built the patched
chromium-headfulimage and embedded its Live View in a cross-origin iframe (allow="clipboard-read; clipboard-write"), driven with Playwright while capturing/wscontrol frames:control/request; remote Ctrl+C yields nocontrol/clipboard; paste stays stale.control/request→control/locked→ remote Ctrl+C →control/clipboardwith the exact text → paste matches. Second click sends no duplicate request; the first click is still delivered once (no dropped/stuck button); no "you took the controls" toast appears.macOS Cmd+C remains a separate, pre-existing gap noted in #310 (the client special-cases paste, not copy) and is out of scope here.
🤖 Generated with Claude Code
Note
Medium Risk
Changes WebSocket control registration and clipboard delivery for embedded sessions; behavior is localized to the neko client but affects first-interaction control state.
Overview
Fixes embedded Live View clipboard sync when implicit hosting is enabled: the client treated the viewer as “hosting” without ever sending
control/request, so the server never deliveredcontrol/clipboardto the viewer.store/remote.tsadds acontrollinggetter (viewer is the registered host) and arequestingflag sorequest()is no longer blocked by broadhostingand rapid clicks cannot spam duplicate requests beforecontrol/locked.video.vuecallsremote.request()on the first mouse down when implicit hosting is on and the viewer is not yet controlling, while still forwarding that click to the remote (no buffered replay).neko/index.tsremoves the self-only “you took the controls” toast oncontrol/locked, since auto-registration on first click would show it on every session.Reviewed by Cursor Bugbot for commit c15d5d5. Bugbot is set up for automated code reviews on this repo. Configure here.