@omercnet/paseo-shared-browser 1.2.2-next.232.1 → 1.2.2-next.237.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +312 -25
  2. package/REMOTE_BROWSER_RESEARCH.md +395 -0
  3. package/STREAMING_DESIGN.md +191 -0
  4. package/client/browser-auto-capture-density.test.ts +102 -0
  5. package/client/browser-auto-capture-density.ts +30 -0
  6. package/client/browser-canvas-input.test.ts +198 -0
  7. package/client/browser-canvas-input.ts +202 -0
  8. package/client/browser-canvas-keyboard-web.test.ts +113 -0
  9. package/client/browser-canvas-keyboard-web.ts +230 -0
  10. package/client/browser-canvas-keyboard.test.ts +123 -0
  11. package/client/browser-canvas-keyboard.ts +126 -0
  12. package/client/browser-canvas-layout.test.ts +109 -0
  13. package/client/browser-canvas-layout.ts +51 -0
  14. package/client/browser-canvas-viewport-web.test.ts +77 -0
  15. package/client/browser-canvas-viewport-web.ts +63 -0
  16. package/client/browser-canvas-viewport.tsx +152 -0
  17. package/client/browser-capture-cadence.test.ts +9 -0
  18. package/client/browser-capture-cadence.ts +6 -0
  19. package/client/browser-capture-density-controls.tsx +75 -0
  20. package/client/browser-chrome.tsx +234 -0
  21. package/client/browser-client-presence.ts +184 -0
  22. package/client/browser-compose-controls.test.tsx +275 -0
  23. package/client/browser-compose-controls.tsx +174 -0
  24. package/client/browser-control-button.tsx +109 -0
  25. package/client/browser-control-dialog.tsx +71 -0
  26. package/client/browser-control-request.test.ts +27 -0
  27. package/client/browser-control-request.ts +57 -0
  28. package/client/browser-control-tooltip-web.ts +10 -0
  29. package/client/browser-dialog-body.tsx +22 -0
  30. package/client/browser-emulation-mode.test.ts +75 -0
  31. package/client/browser-emulation-mode.ts +98 -0
  32. package/client/browser-fill-viewport.test.ts +45 -0
  33. package/client/browser-fill-viewport.ts +28 -0
  34. package/client/browser-frame-buffer.test.ts +157 -0
  35. package/client/browser-frame-buffer.ts +148 -0
  36. package/client/browser-frame-image.tsx +62 -0
  37. package/client/browser-image-load.test.ts +23 -0
  38. package/client/browser-image-load.ts +26 -0
  39. package/client/browser-input-queue.test.ts +1201 -0
  40. package/client/browser-input-queue.ts +505 -0
  41. package/client/browser-menu-placement.test.ts +90 -0
  42. package/client/browser-menu-placement.ts +78 -0
  43. package/client/browser-panel-styles.ts +384 -0
  44. package/client/browser-quality-controls.test.ts +26 -0
  45. package/client/browser-quality-controls.tsx +139 -0
  46. package/client/browser-resolution-picker.tsx +189 -0
  47. package/client/browser-tabs.tsx +117 -0
  48. package/client/browser-toolbar-menu-web.test.ts +185 -0
  49. package/client/browser-toolbar-menu-web.ts +159 -0
  50. package/client/browser-toolbar-menu.tsx +255 -0
  51. package/client/browser-toolbar-submenu.tsx +93 -0
  52. package/client/browser-video-bytes.test.ts +44 -0
  53. package/client/browser-video-decoder.test.ts +473 -0
  54. package/client/browser-video-decoder.ts +311 -0
  55. package/client/browser-video-surface.tsx +21 -0
  56. package/client/browser-video-visibility.test.ts +45 -0
  57. package/client/browser-viewer-recovery.test.ts +111 -0
  58. package/client/browser-viewer-recovery.ts +78 -0
  59. package/client/browser.tsx +1642 -1569
  60. package/client/favorite-star.tsx +23 -0
  61. package/client/frame-lifecycle.ts +62 -59
  62. package/client/use-browser-auto-capture-density.test.ts +104 -0
  63. package/client/use-browser-auto-capture-density.ts +43 -0
  64. package/client/use-browser-canvas-input.ts +370 -0
  65. package/client/use-browser-capture-density.ts +87 -0
  66. package/client/use-browser-emulation-mode.ts +56 -0
  67. package/client/use-browser-fill-viewport.test.ts +108 -0
  68. package/client/use-browser-fill-viewport.ts +62 -0
  69. package/client/use-browser-frame-buffer.ts +66 -0
  70. package/client/use-browser-image-capture.ts +139 -0
  71. package/client/use-browser-scoped-mutation.test.ts +118 -0
  72. package/client/use-browser-scoped-mutation.ts +59 -0
  73. package/client/use-browser-video.test.ts +991 -0
  74. package/client/use-browser-video.ts +474 -0
  75. package/client/use-browser-viewer-recovery.ts +26 -0
  76. package/client/use-resolution-favorites.ts +90 -0
  77. package/client/web.test.ts +125 -0
  78. package/client/web.ts +405 -0
  79. package/index.client.tsx +0 -10
  80. package/index.server.ts +32 -0
  81. package/package.json +9 -5
  82. package/paseo-plugin.json +3 -1
  83. package/scripts/prepare-runtime.mjs +1 -1
  84. package/server/agent-browser-runtime.ts +1598 -314
  85. package/server/browser-cursor.test.ts +64 -0
  86. package/server/browser-cursor.ts +69 -0
  87. package/server/browser-frame-context.ts +22 -0
  88. package/server/browser-frame-epoch.test.ts +95 -0
  89. package/server/browser-frame-rejection.ts +17 -0
  90. package/server/browser-gesture.ts +120 -0
  91. package/server/browser-operation-diagnostics.test.ts +79 -0
  92. package/server/browser-operation-diagnostics.ts +103 -0
  93. package/server/browser-policy.ts +1313 -151
  94. package/server/browser-video-input-authority.test.ts +208 -0
  95. package/server/browser-video-policy.test.ts +564 -0
  96. package/server/browser-video-socket.test.ts +368 -0
  97. package/server/browser.ts +151 -16
  98. package/server/capture-transport-policy.test.ts +105 -0
  99. package/server/capture-transport-policy.ts +154 -0
  100. package/server/capture-transport-runtime.test.ts +213 -0
  101. package/server/daemon-launch-chain.test.ts +70 -0
  102. package/server/daemon-shutdown.test.ts +289 -0
  103. package/server/discrete-input-freshness.test.ts +364 -0
  104. package/server/discrete-input-native-lifetime.test.ts +150 -0
  105. package/server/display-loss-recovery.test.ts +294 -0
  106. package/server/emulation-restoration.test.ts +262 -0
  107. package/server/input-cleanup.ts +23 -0
  108. package/server/input-generation.test.ts +31 -0
  109. package/server/input-generation.ts +36 -0
  110. package/server/jpeg-capture-demand.test.ts +76 -0
  111. package/server/jpeg-capture-demand.ts +98 -0
  112. package/server/jpeg-frame.ts +29 -0
  113. package/server/keyboard-input.test.ts +61 -0
  114. package/server/keyboard-input.ts +104 -0
  115. package/server/live-input-policy.test.ts +958 -0
  116. package/server/live-input-runtime.test.ts +732 -0
  117. package/server/live-input-supervisor.test.ts +147 -0
  118. package/server/mcp-entry.ts +44 -2
  119. package/server/native-video-backpressure.test.ts +228 -0
  120. package/server/native-video-buffer.test.ts +141 -0
  121. package/server/native-video-buffer.ts +114 -0
  122. package/server/native-video-capture.test.ts +599 -0
  123. package/server/native-video-capture.ts +497 -0
  124. package/server/native-video-extension-source.test.ts +201 -0
  125. package/server/native-video-extension-source.ts +364 -0
  126. package/server/native-video-extension.test.ts +41 -0
  127. package/server/native-video-extension.ts +83 -0
  128. package/server/native-video-packet.ts +9 -0
  129. package/server/native-video-settings.test.ts +245 -0
  130. package/server/navigation-budget.ts +8 -0
  131. package/server/navigation-commit.ts +125 -0
  132. package/server/navigation-policy.test.ts +153 -0
  133. package/server/process-identity.ts +58 -0
  134. package/server/reattach-held-input.test.ts +143 -0
  135. package/server/runtime-frame-reuse.test.ts +295 -0
  136. package/server/runtime-jpeg-demand.test.ts +200 -0
  137. package/server/runtime-jpeg-shutdown.test.ts +210 -0
  138. package/server/runtime-jpeg-source-order.test.ts +201 -0
  139. package/server/runtime-launch-environment.test.ts +111 -0
  140. package/server/runtime-navigation.test.ts +327 -0
  141. package/server/runtime-owner-display.test.ts +152 -0
  142. package/server/runtime-owner.ts +305 -31
  143. package/server/runtime-page-state.test.ts +249 -0
  144. package/server/runtime-protocol.ts +31 -1
  145. package/server/runtime-target-retention.test.ts +181 -0
  146. package/server/runtime-video-opt-in.test.ts +82 -0
  147. package/server/runtime-video.test.ts +444 -0
  148. package/server/screencast-source-time.ts +46 -0
  149. package/server/stale-wheel-admission.test.ts +136 -0
  150. package/server/supervisor-client.ts +28 -4
  151. package/server/supervisor-heartbeat.test.ts +80 -0
  152. package/server/supervisor.ts +553 -55
  153. package/server/video-source-recovery.test.ts +20 -0
  154. package/server/video-source-recovery.ts +64 -0
  155. package/server/viewer-capture-lifetime.ts +51 -0
  156. package/server/virtual-display.test.ts +184 -0
  157. package/server/virtual-display.ts +236 -0
  158. package/shared/browser-display-preferences.ts +46 -0
  159. package/shared/browser-video.ts +80 -0
  160. package/shared/browser.ts +286 -65
  161. package/shared/capture-density.ts +16 -0
  162. package/shared/capture-settings.ts +17 -0
  163. package/shared/device-presets.ts +90 -0
  164. package/shared/resolution-menu.test.ts +120 -0
  165. package/shared/resolution-menu.ts +72 -0
  166. package/shared/video-settings.test.ts +38 -0
  167. package/shared/video-settings.ts +34 -0
  168. package/shared/viewport-limits.ts +5 -0
@@ -0,0 +1,395 @@
1
+ # Shared Browser: established remote-control designs and dropped-click research
2
+
3
+ Research date: 2026-10-03 (America/Toronto).
4
+ Status: continuous human input implemented. Network and native-phone latency require separate measurement.
5
+
6
+ ## Recommendation
7
+
8
+ Use the established remote-control input model for the human pane: an ordered
9
+ input channel owned by the current controller and browser geometry, independent
10
+ of individual video frames. Retain explicit screenshot/frame targeting for
11
+ agent commands. Fix the demonstrated visual-frame/input-frame waiter mismatch
12
+ as part of that separation, rather than adding longer capture waits.
13
+
14
+ Evaluate native browser WebRTC media from the existing tab-capture track next.
15
+ Compare it against a binary stream carried through Paseo's authenticated relay.
16
+ Select by measured interaction latency, clarity and relay compatibility, not
17
+ by assuming WebRTC solves input policy or guarantees sharper text.
18
+
19
+ Continuous human input intentionally allows normal presses without requiring
20
+ pixels captured after the preceding press. A remote display can
21
+ lag the page, as in ordinary remote desktop. Keep controller, exact target,
22
+ geometry, visibility, replacement-page and disconnect fences. Do not silently
23
+ change agent screenshot-targeting semantics or replay clicks after timeouts.
24
+
25
+ ## Demonstrated failure path matching the reported error
26
+
27
+ The preceding human channel inherited frame-target rules from discrete browser
28
+ automation. Every new mouse/touch press needs a decoded targeting receipt.
29
+ After non-hover input the server clears recent receipts and imposes a
30
+ post-input capture-time floor. The client also invalidates its input revision.
31
+ This creates a required trip through capture, transport, decode and React
32
+ presentation between successive independent presses.
33
+
34
+ A second, narrower mismatch is demonstrated in the production panel:
35
+
36
+ 1. The rapid-input fix allows a delayed read to paint if its source, document
37
+ and geometry remain current. This preserves video continuity.
38
+ 2. That paint retains its old input revision and correctly cannot authorize a
39
+ new press.
40
+ 3. The canvas input hook's waiter watches a changed *visible* frame ID instead
41
+ of a changed actionable targeting receipt.
42
+ 4. It wakes, the queue finds no current targeting authority, and emits
43
+ `A current decoded frame is required for a new press.`
44
+
45
+ A disposable mounted React/Query reproduction used the real panel and a DOM
46
+ mouse-down. It produced that exact error, sent zero gesture updates, and kept
47
+ Video visible. This confirms a rejected unsent press, not proof of a lost
48
+ published native click. The reproduction did not access the live browser.
49
+ Fixing only its waiter would avoid premature rejection but retain deliberate
50
+ frame-per-press delays. Increasing the four-second wait would not remove that
51
+ coupling.
52
+
53
+ Relevant local owners:
54
+
55
+ - `client/browser.tsx`: passes visible decoded frame identity to the canvas hook.
56
+ - `client/use-browser-video.ts`: permits same-source visual continuity but
57
+ retains the original read revision for input authority.
58
+ - `client/use-browser-canvas-input.ts`: resolves waiters when visible frame ID changes.
59
+ - `client/browser-input-queue.ts`: awaits targeting receipts and each gesture RPC.
60
+ - `server/browser-policy.ts`: requires recent press targets, clears receipts
61
+ after input, and applies the source-clock floor.
62
+
63
+ The failure description records the behavior before continuous admission was
64
+ implemented. The current implementation separates presentation from initial
65
+ input admission and is covered by the mounted panel fixtures.
66
+
67
+ ## Established implementations
68
+
69
+ These are primary-source findings. They describe the reviewed paths, not a
70
+ claim that every project is free of race conditions or input bugs.
71
+
72
+ | System | Input and display relationship | What is reusable | Cost of adopting the whole system |
73
+ | --- | --- | --- | --- |
74
+ | Chrome DevTools screencast | CDP events use decoded screen scale/offset; ordinary input does not wait for a new image after each event. | Closest source reference for our CDP coordinate mapping, modifiers, click count and drag geometry. | Frontend depends on DevTools SDK and UI, so importing the whole panel is inappropriate. |
75
+ | noVNC | Connection/view-only gates and framebuffer coordinates govern pointer sends; render flushing is separate. | Mature keyboard, cursor, gesture and RFB client code. | Requires an RFB/VNC server and normally a WebSocket gateway, replacing our tab-capture/CDP backend. |
76
+ | KasmVNC | Connected/view-only input is separate from its bounded asynchronous display queue. | Browser fork and media queue patterns. | Full use adds an Xvnc backend and protocol extensions; browser core is MPL-2.0, server GPLv2. |
77
+ | Apache Guacamole | Input sends are tunnel/session based; display flush and sync acknowledge rendering separately. | Standalone JavaScript client and input/display primitives. | Full use adds guacd plus a supported remote desktop backend and authenticated tunnel integration. |
78
+ | Selkies | Current design defaults to WebSocket/WebCodecs; WebRTC is optional. Human input is separately sequenced and gated by controller/token state. | Closest architectural reference for bounded streaming, input ordering, workers and WebCodecs. | Full runtime adds Linux display capture/input, Python and Rust media components. |
79
+ | Neko | WebRTC browser streaming with current host ownership and overlay-to-remote coordinate mapping. | Strong reference for shared control, native cursor and media/data separation. | Complete system adds a containerized desktop, Go/GStreamer/X11 and its room/session model. |
80
+ | Epic Pixel Streaming | Mouse handlers check video readiness, then send structured events; no decoded-frame ID on each press. | MIT TypeScript input/coordinate and WebRTC player references. | Complete player and server speak Unreal-specific signaling/messages, not our CDP protocol. |
81
+ | Chromium remoting | Host event dispatch validates incoming event fields and injects them, with event timing separate from display rendering. | Reference for input ordering, held-state cleanup and transport backpressure. | Chromium remoting is a substantial native subsystem, not a small embeddable Node library. |
82
+
83
+ ### Closest reference: Chrome DevTools
84
+
85
+ In the inspected screencast, image load updates screen scale/offset. Mouse and
86
+ wheel handlers forward through `InputModel`; input does not invalidate that
87
+ mapping until another image arrives. A drag retains its starting vertical
88
+ mapping until release. Profiling/inactive-target overlays can block input.
89
+ This is readiness and mapping, not an after-every-input freshness protocol.
90
+
91
+ Reviewed pinned source:
92
+ [InputModel](https://github.com/ChromeDevTools/devtools-frontend/blob/ae714aac9f0a7c8683fa4dff5a7dcaf55c233ad4/front_end/panels/screencast/InputModel.ts),
93
+ [ScreencastView](https://github.com/ChromeDevTools/devtools-frontend/blob/ae714aac9f0a7c8683fa4dff5a7dcaf55c233ad4/front_end/panels/screencast/ScreencastView.ts).
94
+ Both carry Chromium's BSD-style source notice. Treat them as a reference or
95
+ carefully attributed small extraction, not an interchangeable frontend package.
96
+
97
+ ### Rendering acknowledgements are not input admission
98
+
99
+ In released noVNC 1.7.0, pointer sends check connection and view-only state;
100
+ button handlers flush pending motion before a transition. The inbound render
101
+ flush path is separate. In Guacamole 1.6.0, mouse/key/touch sends use session
102
+ readiness and display scaling, while display flush/sync acknowledges rendered
103
+ operations. Neither reviewed send path demands a decoded frame token per press.
104
+ Guacamole touch availability still depends on the remote backend.
105
+
106
+ Primary sources:
107
+ [noVNC 1.7.0 RFB](https://github.com/novnc/noVNC/blob/63107bd06d9e1f6136ff21aeda8cd62cbf0d433e/core/rfb.js#L1172-L1225),
108
+ [Guacamole 1.6.0 client](https://github.com/apache/guacamole-client/blob/0537c89cd783681b986ff8f8c4e0b97ec6873371/guacamole-common-js/src/main/webapp/modules/Client.js#L328-L430),
109
+ [Guacamole JavaScript integration](https://guacamole.apache.org/doc/gug/guacamole-common-js.html).
110
+ An RFB fence or display sync is not our exact browser-document receipt policy;
111
+ copying a protocol component would not preserve that policy automatically.
112
+
113
+ KasmVNC's exact browser submodule similarly separates mouse sends from its
114
+ three-slot asynchronous frame queue, which can discard incomplete older display
115
+ frames. Its [input path](https://github.com/kasmtech/noVNC/blob/1746dfe9ed8084aee57b55c720ef00d4d7c26513/core/rfb.js#L2484-L2508)
116
+ and [display drop handling](https://github.com/kasmtech/noVNC/blob/1746dfe9ed8084aee57b55c720ef00d4d7c26513/core/display.js#L1146-L1223)
117
+ are useful references. The browser [MPL-2.0 license](https://github.com/kasmtech/noVNC/blob/1746dfe9ed8084aee57b55c720ef00d4d7c26513/LICENSE.txt)
118
+ and server [GPLv2 license](https://github.com/kasmtech/KasmVNC/blob/39b94e68ddc302dd896008b04a71e6115eb62d06/LICENSE.TXT)
119
+ are separate; adopting the full stack is a packaging/licensing choice.
120
+
121
+ ### Input ordering is a separate contract
122
+
123
+ Selkies' reviewed WebRTC code uses an ordered input channel and a separate
124
+ unordered pointer channel, plus sequence checks. Its bounded 512-message input
125
+ queue can discard messages when overloaded. Neko's reviewed client sends
126
+ ordered reliable binary events, positions before button transitions, and checks
127
+ host ownership on the server. Neither path carries a per-click decoded-frame
128
+ ID. These are useful designs, not unconditional no-loss guarantees or proof of
129
+ our stronger document/geometry fencing.
130
+
131
+ Pinned primary sources:
132
+ [Selkies channels](https://github.com/selkies-project/selkies/blob/5927ca77b1fc31d43ebca096b2aa59e35bbb128f/src/selkies/webrtc_engine.py#L2536-L2564),
133
+ [Selkies input queue](https://github.com/selkies-project/selkies/blob/5927ca77b1fc31d43ebca096b2aa59e35bbb128f/src/selkies/webrtc_engine.py#L2030-L2075),
134
+ [Neko client transport](https://github.com/m1k1o/neko/blob/bdd0428a607ccfd22eebabc74b85ae17a4cb42d5/client/src/neko/base.ts#L187-L245),
135
+ [Neko controller gate](https://github.com/m1k1o/neko/blob/bdd0428a607ccfd22eebabc74b85ae17a4cb42d5/server/internal/webrtc/handler.go#L16-L100).
136
+
137
+ ### Video readiness is not press freshness
138
+
139
+ Pixel Streaming's `isVideoReady()` checks the media element's `readyState > 0`.
140
+ Mouse-down/up/wheel then send normalized messages, without a last-decoded-frame
141
+ token. Its default data channel is ordered. This supports an established stream
142
+ being interactive while rendering catches up, not the stronger promise that a
143
+ press targets the pixels most recently captured after another press.
144
+
145
+ Reviewed pinned source:
146
+ [VideoPlayer](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/85f15cb354c8ccbfa1e8f20d9600e4d75dbce492/Frontend/library/src/VideoPlayer/VideoPlayer.ts),
147
+ [hovering mouse controller](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/85f15cb354c8ccbfa1e8f20d9600e4d75dbce492/Frontend/library/src/Inputs/MouseControllerHovering.ts),
148
+ [data channel](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/85f15cb354c8ccbfa1e8f20d9600e4d75dbce492/Frontend/library/src/DataChannel/DataChannelController.ts).
149
+ Its [MIT infrastructure license](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/85f15cb354c8ccbfa1e8f20d9600e4d75dbce492/LICENSE.md)
150
+ is distinct from licensing the Unreal Engine application.
151
+
152
+ ## Design changes worth adopting
153
+
154
+ ### Separate control readiness from frame targeting
155
+
156
+ Initial human channel admission should require a decoded current source and
157
+ geometry basis, as it does today. The channel should establish exact workspace/viewer/controller,
158
+ browser incarnation, target, document policy and CSS viewport mapping once.
159
+ Subsequent pointer/key transitions use ordered sequence numbers and that control
160
+ context. Video decoding may lag without revoking every independent press.
161
+ A viewer without control still cannot inject events.
162
+
163
+ Agent click tools retain their explicit frame receipts. Server authorization
164
+ must enforce the distinction; do not add a caller-selected flag that turns an
165
+ agent request into unchecked human input. A human session can require a
166
+ controller-owned admission token issued for the pane and its displayed geometry.
167
+
168
+ Maintain separate values for visible frame, actionable agent receipt and
169
+ human channel readiness. A visible old-read frame must neither invent an agent
170
+ receipt nor falsely resolve a wait for one. Renderer state should report video
171
+ stall separately from control ownership. A genuinely disconnected/frozen source
172
+ can disable new input under an explicit session policy; an ordinary view update
173
+ must not be treated as a disconnect.
174
+
175
+ ### Preserve transitions; coalesce motion
176
+
177
+ Mouse down/up, key down/up, text commits, touch start/end and cancellation are
178
+ ordered transitions. Never drop one because the next video frame is pending.
179
+ Coalesce only compatible moves or wheel deltas within the same control context,
180
+ without crossing a press/release or document/viewport boundary. Flush preceding
181
+ motion before a transition so the press lands at the intended coordinates.
182
+
183
+ Held input needs pointer capture and a release path even when the pointer leaves
184
+ the pane. Blur, controller loss, teardown and rejected sequences release held
185
+ state. Double-click forwarding must not synthesize a duplicate press from both
186
+ DOM down/up and the `dblclick` callback.
187
+
188
+ ### Do not turn input into a chain of network round trips
189
+
190
+ Our queue awaits each update RPC before sending the next. That is an additional
191
+ throughput limit even after frame coupling is removed. For an event stream whose
192
+ individual request/response round trip is R, that loop cannot exceed roughly
193
+ 1/R acknowledged updates per second. This is a design inference, not a measured
194
+ relay RTT or observed throughput result.
195
+
196
+ Use a bounded ordered stream or bounded event batches, with sequence
197
+ acknowledgements, instead of waiting for a separate round trip after every key
198
+ and mouse transition. The server still serializes native injection. Disconnect
199
+ or uncertain publication terminates the channel; it never replays presses.
200
+ Queue pressure must release/cancel cleanly rather than accumulating seconds of
201
+ old clicks that later fire on a changed page.
202
+
203
+ ## Streaming choices and actual reuse candidates
204
+
205
+ ### A. Native WebRTC from the existing Chromium tab capture
206
+
207
+ The extension already owns an exact-tab `MediaStream`. A prototype can pass its
208
+ video track to `RTCPeerConnection`, display the receiver's media track in a
209
+ web-only adapter, and use existing authenticated Paseo RPC for signaling.
210
+ This would let the browser own media packetization, congestion response,
211
+ keyframe recovery and playout instead of our custom encoded packet reader.
212
+ This is a proposed fit, not a proved drop-in or a measured latency improvement.
213
+
214
+ The [tabCapture API](https://developer.chrome.com/docs/extensions/reference/api/tabCapture)
215
+ provides the capture primitive. WebRTC still needs authenticated negotiation,
216
+ viewer/controller fencing and source identity. Direct ICE paths may work on
217
+ LANs; remote relay users require TURN coverage for networks that cannot connect
218
+ peers. Paseo's current application relay is not automatically a TURN relay.
219
+ [WebRTC TURN guidance](https://webrtc.org/getting-started/turn-server).
220
+
221
+ Start with browser-to-browser peers so no Node media engine is needed merely
222
+ for forwarding an existing browser track. Keep native phone JPEG fallback as
223
+ previously agreed. Test the current capture quality ceiling: RTP cannot recover
224
+ colour detail already lost before transmission.
225
+
226
+ ### B. Binary WebSocket or SDK stream with WebCodecs
227
+
228
+ Selkies shows this is a viable primary design, not inherently a lesser fallback.
229
+ Its current [design](https://github.com/selkies-project/selkies/blob/5927ca77b1fc31d43ebca096b2aa59e35bbb128f/docs/design.md)
230
+ uses WebSockets by default and WebRTC optionally. Its [web client](https://docs.selkies.io/latest/components/web-client)
231
+ separates streaming/input and uses worker components. We should compare a binary
232
+ stream and bounded decode pipeline with native WebRTC under identical load.
233
+
234
+ Our current media is base64 in JSON RPC replies. Base64 represents three bytes
235
+ as four characters before JSON/envelope overhead. Removing that conversion and
236
+ request/response scheduling is a concrete optimization candidate; how much it
237
+ helps requires profiling. A binary stream needs Paseo authentication and remote
238
+ relay support, not an anonymously exposed localhost socket.
239
+
240
+ ### C. Existing whole remote-desktop stack
241
+
242
+ Adopting noVNC, Guacamole, Selkies or Neko can replace substantial custom code.
243
+ They also replace substantial backend behavior: OS-level desktop capture/input,
244
+ new server dependencies, authentication wiring and browser lifecycle ownership.
245
+ They do not transparently preserve our exact-tab CDP operations, independent
246
+ native emulation, or agent screenshot contracts.
247
+
248
+ Benchmark Selkies and Neko in disposable containers if considering this route.
249
+ Do not migrate the user's profile into a new backend as a research shortcut.
250
+ An isolated integration would need to show it can retain workspace ownership,
251
+ agent tools and mobile fallback before choosing the whole-stack option.
252
+
253
+ Selkies' [licensing inventory](https://github.com/selkies-project/selkies/blob/5927ca77b1fc31d43ebca096b2aa59e35bbb128f/docs/licensing.md)
254
+ identifies MPL-2.0 project code and encoder-dependent components. Neko's
255
+ [license](https://github.com/m1k1o/neko/blob/bdd0428a607ccfd22eebabc74b85ae17a4cb42d5/LICENSE)
256
+ is Apache-2.0. Dependency and distribution obligations need separate review.
257
+
258
+ ### Library shortlist
259
+
260
+ Current stable versions were read from the npm registry on the research date;
261
+ no dependency was installed. Recheck metadata before implementation.
262
+
263
+ | Candidate | Registry stable / license | Use and limitation |
264
+ | --- | --- | --- |
265
+ | `@novnc/novnc` | 1.7.0 / MPL-2.0 | Actual reusable RFB client; requires an RFB backend, not a CDP or WebRTC adapter. |
266
+ | Guacamole JavaScript client | Source module / Apache-2.0 | Reusable input/display/tunnel modules; full remote protocol needs a guacd/backend integration. |
267
+ | `simple-peer` | 9.11.1 / MIT | Browser peer/signaling convenience; does not implement remote input, capture, TURN deployment or our authorization. Its Node-style dependencies need SDK/bundling review. |
268
+ | `werift` | 0.24.4 / MIT | TypeScript Node WebRTC if a server terminates media; unnecessary for initial browser-to-browser capture. Published metadata says Node >=16; development README says >=22, so do not conflate them. |
269
+ | `node-datachannel` | 0.33.4 / MPL 2.0 | Native Node/libdatachannel media/data transport; adds native binary packaging and platform support work. |
270
+ | `@epicgames-ps/lib-pixelstreamingfrontend-ue5.8` | 0.1.2 / MIT | A real streaming frontend library, but its Unreal-specific message/signaling model makes it a reference rather than a natural whole import. |
271
+ | Pixelflux | Source/native component | Reusable Linux capture/encoder pipeline behind Selkies; Rust/Python and display capture differ from our current tab track. Encoder license depends on the build. |
272
+
273
+ Primary library sources:
274
+ [noVNC](https://github.com/novnc/noVNC),
275
+ [Guacamole client](https://github.com/apache/guacamole-client),
276
+ [simple-peer](https://github.com/feross/simple-peer),
277
+ [werift](https://github.com/shinyoshiaki/werift-webrtc),
278
+ [node-datachannel](https://github.com/murat-dogan/node-datachannel),
279
+ [Pixel Streaming](https://github.com/EpicGames/PixelStreamingInfrastructure),
280
+ [Pixelflux](https://github.com/selkies-project/pixelflux).
281
+
282
+ For Pixelflux, the default x264-enabled build carries GPL components; an
283
+ OpenH264 build can avoid that encoder dependency. Check the entire distribution
284
+ and license notices before redistribution, not just a top-level library name.
285
+
286
+ ## Gotchas the references make concrete
287
+
288
+ - **Ordered input is separate from video.** Use reliable ordering for
289
+ presses/releases and committed text. An unordered move channel would need
290
+ sequence-aware reconciliation before any transition; do not introduce one
291
+ merely because WebRTC permits it.
292
+ - **WebRTC still buffers.** `bufferedAmount`, channel size limits, ICE failure,
293
+ playout/jitter and codec support remain real. Chromium's
294
+ [data-stream adapter](https://chromium.googlesource.com/chromium/src/+/main/remoting/protocol/webrtc_data_stream_adapter.cc)
295
+ uses its own send queue to avoid overrunning lower-layer buffering.
296
+ - **Resize mapping can race.** Use current CSS geometry, not JPEG pixel size or
297
+ device pixel density. Reject old geometry on replacement, retain admitted
298
+ drag coordinates through release, and qualify the first new mapping.
299
+ - **Stateful input must be cleaned up.** The pinned Pixel Streaming hovering
300
+ controller retains window-level move/up handlers during drags, and documents
301
+ a double-click release fix. These are evidence against assuming a library
302
+ automatically makes all drag/press edge cases disappear.
303
+ - **Touch libraries differ.** noVNC's
304
+ [pinch path](https://github.com/novnc/noVNC/blob/63107bd06d9e1f6136ff21aeda8cd62cbf0d433e/core/rfb.js#L1421-L1441)
305
+ emulates Ctrl/wheel, rather than genuine CDP multi-contact touch. Guacamole
306
+ 1.6.0 has a [Touch module](https://github.com/apache/guacamole-client/blob/0537c89cd783681b986ff8f8c4e0b97ec6873371/guacamole-common-js/src/main/webapp/modules/Touch.js#L98-L185),
307
+ but backend support and explicit cancellation still need checks. Reuse must
308
+ preserve our required physical touch behavior, not substitute mouse emulation.
309
+ - **Phone text is not desktop keycodes.** Pixel Streaming's
310
+ [release notes](https://github.com/EpicGames/PixelStreamingInfrastructure/releases)
311
+ describe separate text-input/IME support. Preserve our agreed basic live text
312
+ relay and commit complex IME with Done; do not replace it with keycode-only
313
+ forwarding.
314
+ - **Native Paseo is a distinct client.** The current
315
+ [plugin reference](https://github.com/getpaseo/paseo/blob/main/public-docs/plugins/reference.md)
316
+ documents React Native primitives and host-provided imports, not a portable
317
+ native WebRTC/video surface. A new native media API would require host work;
318
+ web-only libraries must remain isolated from phone bundle evaluation.
319
+ - **A protocol is not an authorization policy.** Continuous human input accepts
320
+ visual latency. Frame-targeted automation has a different targeting contract.
321
+ Keep both explicit and test that neither can acquire the other's privileges.
322
+
323
+ ## Implementation and follow-up plan
324
+
325
+ 1. **Define input contracts.** Separate human controller-owned channels from
326
+ frame-targeted agent commands. Specify document/geometry changes, suspension,
327
+ queued transitions and cleanup in one shared policy, covering JPEG and video.
328
+ 2. **Fix the demonstrated mismatch.** Frame waiters watch actionable admission,
329
+ not merely newly painted pixels. Pin the production-panel reproduction even
330
+ if human mode no longer normally needs those waits.
331
+ 3. **Remove ordinary human press/frame coupling.** Establish geometry readiness
332
+ on admission; retain native exact-target and controller checks. Normal
333
+ same-document dialog/list changes and scrolling do not revoke the channel.
334
+ Real document/viewport replacement cancels unsent old-context actions rather
335
+ than deferring them onto new content.
336
+ 4. **Measure current transport.** Record event-to-native acknowledgement and
337
+ event-to-observed-pixel latency, queue depth, source/encode/read/decode/paint
338
+ timings, resets, dropped frames and bytes. Keep clocks labelled by domain;
339
+ one-way delay cannot be computed from unsynchronized host/client clocks.
340
+ 5. **Prototype WebRTC and binary streaming in isolation.** Same tab, sizes,
341
+ density, bitrate and interaction workload; include real large-view changes,
342
+ cameras, loss, jitter, multiple viewers, LAN and actual Paseo relay paths.
343
+ 6. **Choose reuse based on results.** Prefer native browser media APIs for the
344
+ smallest initial WebRTC prototype; use libraries where they remove a real
345
+ responsibility. Choose a whole remote-desktop stack only if the additional
346
+ backend/packaging work beats retaining exact-tab capture and CDP control.
347
+ 7. **Review and activate exact source.** Independent policy/ordering review,
348
+ mounted DOM plus real browser tests, actual SDK/Hermes compile, and human
349
+ desktop/phone acceptance. Leave changes uncommitted until requested.
350
+
351
+ ### Required acceptance evidence
352
+
353
+ - Rapid clicks during a large same-document refresh publish each admitted
354
+ down/up once, in order, without a decoded-frame error or duplicates.
355
+ - Typing, modifier combinations, double/right click, wheel, scrollbar drags and
356
+ touch continue while video catches up.
357
+ - Controller loss, new target, actual document/viewport replacement, hidden
358
+ client and disconnect release held state and fence old queued events.
359
+ - A denied/uncertain native press is never automatically retried on a new page.
360
+ - Agent stale-frame rejection still holds; agent requests cannot mint human
361
+ admission authority.
362
+ - Video and native JPEG fallback use the same human control semantics.
363
+ - Actual pane measurements include latency percentiles and byte/CPU load;
364
+ server captures alone are not evidence of host rendering or relay performance.
365
+ - Existing sign-in/workspace/profile ownership and current UI conventions remain
366
+ unchanged by a transport selection.
367
+
368
+ ## Research limits and handoff
369
+
370
+ Source review and a local production-panel reproduction were completed.
371
+ No candidate replacement system was installed, no live browser inputs were
372
+ sent, and no current streaming runtime was changed by this investigation.
373
+ The transport performance comparison remains work to perform, not a benchmark
374
+ result inferred from project descriptions. The current live improvement has
375
+ human confirmation, but the reported click rejection remains unresolved until
376
+ the approved input contract is implemented and verified.
377
+
378
+
379
+ ## Implemented follow-up
380
+
381
+ The approved human-input change admits decoded geometry once and continues ordered presses,
382
+ keyboard, touch and motion independently of replacement video frames. Normal
383
+ idle closure can reopen the exact acknowledged geometry grant. Native admission
384
+ compares expected attachment/document generation after asynchronous cleanup
385
+ and page attachment, before binding, so an intervening same-URL reload refuses
386
+ input. Controller, viewer, page, viewport and uncertain-publication changes still
387
+ cancel the channel. Agent input retains fresh frame receipt checks.
388
+
389
+ The mounted production panel verifies five clicks with video decoding blocked,
390
+ then another after idle closure using the same acknowledged geometry. A separate
391
+ fixture proves that visual-only delayed frames cannot wake initial admission,
392
+ while actually actionable decoded publication can. Native tests cover page and
393
+ attachment replacement during admission awaits. These checks do not measure
394
+ human-pane relay latency. No WebRTC, RFB, guacd or new dependency was added in
395
+ this policy change; transport batching and media replacement remain follow-ups.
@@ -0,0 +1,191 @@
1
+ # Shared Browser streaming design
2
+
3
+ This document describes the media and input contracts maintained by the plugin.
4
+ User-facing controls and installation are documented in [README](README.md).
5
+ [Remote-control research](REMOTE_BROWSER_RESEARCH.md) records the primary-source
6
+ comparisons behind the input design.
7
+
8
+ ## Transport and ownership
9
+
10
+ Encoded video is opt-in: the daemon must set `PASEO_SHARED_BROWSER_VIDEO=1`. Without it
11
+ the capture helper is never loaded and `video.read` returns a typed `video-disabled` reply,
12
+ after which the client stops reading and uses JPEG. When enabled, desktop and web clients
13
+ use Chromium tab capture, a realtime WebCodecs encoder, and bounded encoded-packet reads
14
+ over authenticated Paseo RPC. The receiver decodes into a retained canvas. Native clients
15
+ and clients without a usable decoder use JPEG; a native surface needs a host SDK
16
+ export that the published plugin SDK does not yet provide. This is encoded video, not WebRTC or a push subscription; it
17
+ shares the reliable connection and relay used by Paseo.
18
+
19
+ `server/native-video-extension.ts` materializes the immutable bundled capture
20
+ helper in the runtime's private IPC directory. The helper has tabCapture and
21
+ debugger permissions, but no content scripts, web-accessible resources, site
22
+ message handlers, or network host permissions. It captures the exact retained
23
+ workspace tab, never a title-selected window or arbitrary desktop.
24
+
25
+ `server/native-video-capture.ts` owns the helper target, track, encoder cohorts
26
+ and encoded buffers. `server/agent-browser-runtime.ts` binds that ownership to
27
+ the exact native page attachment and emulation. `server/browser-policy.ts`
28
+ checks viewer lifetime and the browser generations before admitting packets.
29
+ `client/use-browser-video.ts` owns viewing retries and presentation authority;
30
+ `client/browser-video-decoder.ts` owns codec queues and frame disposal.
31
+
32
+ Every acquisition waits for the complete target or viewport transition and any
33
+ pending native cleanup. Expired, detached or hidden viewers cannot revive media
34
+ ownership through a late reply. Encoder cohorts retire after two seconds without
35
+ active reads. JPEG capture follows actual image demand and retires after three
36
+ seconds without demand; attaching a viewer or reading video does not renew it.
37
+
38
+ ## Resource limits and backpressure
39
+
40
+ Encoded packets are limited to 2 MiB. A reply contains at most 32 packets and
41
+ 4 MiB of encoded payload. Source and decoder queues are independently bounded.
42
+ Video waits are at most 500 milliseconds and run outside ordered input and
43
+ workspace mutation lanes. Socket media admission leaves capacity for controls;
44
+ heartbeat uses a separately bounded lane so a slow page command cannot expire
45
+ the bridge. Buffered socket lines resume draining after admitted work completes.
46
+
47
+ At most three bitrate/frame-rate cohorts share a track. A new profile cannot
48
+ evict a healthy peer encoder. Capacity exhaustion returns an explicit reason
49
+ and uses JPEG while a bounded viewing retry waits for capacity. Source-image
50
+ skips do not break already encoded delta dependencies. Actual encoded-output
51
+ loss suppresses dependent deltas until a keyframe restores the chain.
52
+
53
+ The receiver feeds a bounded FIFO as the native decoder makes progress, rather
54
+ than resetting it because a normal reply contains more than four pictures. A
55
+ new retained keyframe allows a delayed viewer to skip obsolete encoded backlog.
56
+ Old decoder callbacks are generation-fenced and their frames closed. The last
57
+ paint remains visible during catch-up. Feature-detected native byte/base64
58
+ conversion avoids intermediate binary strings; exact fallback conversion is
59
+ retained for older clients.
60
+
61
+ ## Presentation and input are separate contracts
62
+
63
+ Human input requires decoded and painted geometry. A channel binds the controller,
64
+ viewer, workspace, runtime, bridge, native attachment, document and viewport. Every
65
+ independent press (mouse down, first touch contact) carries its own decoded frame
66
+ receipt, valid for five seconds and spent by acknowledged input; the next press waits
67
+ a bounded time for a newer decoded frame, and a stalled presentation therefore cannot
68
+ admit clicks indefinitely. Motion, releases and keys of a held gesture use the ordered
69
+ channel without waiting for new pixels. Agent commands retain strict fresh-frame
70
+ targeting.
71
+
72
+ A visible frame is not automatically an actionable receipt. Delayed reads may
73
+ continue painting within the same source and geometry while retaining their
74
+ original input revision. Initial-admission waiters wake only for actionable
75
+ pixels. A document or viewport transition, controller loss, cancellation,
76
+ disconnect, hidden panel or uncertain mutation invalidates admission. Native
77
+ binding checks its expected generation after attachment and cleanup awaits.
78
+
79
+ Press/release, key and text transitions remain ordered; only compatible motion
80
+ can coalesce without crossing a transition. Pointer capture and teardown release
81
+ held state. The four-second idle channel closure releases native keys/buttons; a reopen
82
+ needs a fresh decoded receipt. No published or uncertain input is replayed after
83
+ a timeout.
84
+
85
+ Video can retain its last painted canvas across an ordinary input or document
86
+ transition without retaining input authority. Viewer, runtime, bridge and
87
+ viewport replacement clear that source. The 2.5-second stalled-paint watchdog
88
+ requests image fallback but holds the canvas until a current JPEG actually
89
+ decodes. A revision guards that handoff against late images and React commits.
90
+
91
+ Source timestamps remain Chromium capture timestamps. Arrival time never makes
92
+ queued, duplicate, backwards or missing source timestamps fresh. JPEG admission
93
+ uses a one-second source age and bounded native clock tolerance. Navigation
94
+ clears cached JPEGs and queued video before admitting replacement pixels.
95
+
96
+ ## Quality and geometry
97
+
98
+ The menu separates JPEG quality (40, 65, 85 or 100), video bitrate (2, 5, 12 or
99
+ 24 Mbps), frame rate (15, 30 or 60 FPS), source density and local Fit/Actual size.
100
+ These are encoder targets, not measured bandwidth or guaranteed frame rates.
101
+ JPEG 100 remains lossy and its byte budget can lower actual quality. Video
102
+ keyframes repair decoder dependencies; they do not restore RGB detail reduced
103
+ by the native capture's colour sampling. No lossless idle overlay or static-page
104
+ comparison loop is used.
105
+
106
+ Capture density is independently controller-authorized at 1x or 2x. It preserves
107
+ CSS dimensions, input mode, user agent and preset identity. Physical dimensions
108
+ must each remain at most 3840, so 2x is unavailable above 1920 on either CSS axis.
109
+ Each viewer panel defaults to Auto, choosing the smallest supported density for
110
+ its displayed frame size and screen pixel ratio. Fit uses the contained frame;
111
+ Actual uses CSS dimensions. Manual selections disable automatic adjustment for
112
+ the panel. Adjustments are debounced, require visible/idle controller authority,
113
+ and pause on failure. Fill viewport carries the chosen density in its resize RPC
114
+ so growing beyond the 2x limit can reduce density and resize in one transition.
115
+ Rendering DPR is raised to at least the selected density while a genuine phone
116
+ DPR is retained. Explicit preset changes retain their established density reset
117
+ behavior; mode-only changes retain it. Returned state reports actual density.
118
+
119
+ Encoded and decoded dimensions match the requested viewport and density. Chromium
120
+ YUV capture rounds odd physical dimensions down to even sizes. Only that exact
121
+ one-pixel alignment is resampled by the encoder to the requested size, preserving
122
+ page layout, capture density and source timing. Other dimension mismatches remain
123
+ terminal. Odd-sized 1x captures can use VP8 when H.264 rejects those dimensions;
124
+ 2x Pixel 7 capture remains 824 by 1678.
125
+
126
+ ## Failure and navigation recovery
127
+
128
+ Source-wide startup, stopped-track and reset failures retire through the native
129
+ stop barrier even while reads continue. Acquisition has a three-second monotonic
130
+ cooldown starting when failure is admitted, and cleanup independently blocks
131
+ replacement. An encoder-cohort failure does not retire healthy peers. Dimension
132
+ failure is permanent for the current attachment and viewport configuration;
133
+ polling or navigation cannot repeatedly restart it. A configuration change can
134
+ recover without weakening exact geometry.
135
+
136
+ Navigation observes its matching main-frame commit before dispatch, then waits
137
+ for the command acknowledgement and document commit, not DOMContentLoaded. Full
138
+ navigation matches its loader; same-document navigation matches root frame and
139
+ URL. A download acknowledgement retains the document. One bounded metadata read
140
+ follows commit. Attachment recovery and held-state cleanup have separate budgets.
141
+ An uncertain navigation is not replayed.
142
+
143
+ Transient video read failures revoke input authority and use bounded viewing
144
+ backoff. Exhaustion stops reads. Ordinary pointer/image refresh cannot rearm that
145
+ retry; successful navigation may queue one recovery after cooldown. Decoder
146
+ failures are terminal for that playback attempt. Expired viewers reattach through
147
+ the existing viewing recovery, never by replaying a mutation or taking control.
148
+
149
+ Diagnostics record redacted operation categories, elapsed duration and safe CDP
150
+ method names, with rate limits. They must not record URLs, text, credentials,
151
+ viewer/control tokens, media payloads, SDP or per-frame successes. A screenshot
152
+ failure together with navigation-history/layout timeouts indicates a broader
153
+ runtime failure; successful cached metadata is not proof of page preservation.
154
+
155
+ ## Verification
156
+
157
+ Follow the isolated workflow in [README](README.md). Unit regressions cover
158
+ packet bounds, frame authority, viewer expiry, transition races, socket admission,
159
+ source cleanup, codec queues and geometry. The opt-in native video gate captures
160
+ an actual tab and decodes through the production receiver at desktop, sharp phone
161
+ and large square sizes, checking all four corners and batched final-frame paint.
162
+ It also distinguishes real 2x source rendering from rescaling a 1x image.
163
+
164
+ Mounted panel checks require the explicitly supplied tooling workspace described
165
+ in README. Handoff, rapid-input, continuous-input and admission fixtures exercise
166
+ React/React Query settlement and DOM events. They do not prove phone IME behavior,
167
+ physical-input latency, human-pane rendering or performance through a live relay.
168
+ A successful MCP image capture establishes server image delivery only.
169
+
170
+ ## WebRTC follow-up
171
+
172
+ WebRTC could move media off the ordered RPC connection and avoid base64 expansion.
173
+ Authenticated RPC can carry bounded offer/answer/ICE signaling. Native-phone image
174
+ fallback would remain. Local proof alone cannot qualify remote networking: Paseo's
175
+ relay is not a TURN service, and WSL/NAT clients may require configured TURN,
176
+ short-lived credentials, traffic limits and operational ownership.
177
+
178
+ An owned native-track proof produced correct full-frame geometry and local ICE
179
+ playback. Exact correlation between a presented RTP frame and its authenticated
180
+ source timestamp remains unqualified. Arrival time must not stand in for capture
181
+ time. An alternative data channel can retain existing source-stamped packets but
182
+ needs bounded fragmentation, incomplete-packet expiry, backpressure and keyframe
183
+ recovery. Neither alternative is shipped.
184
+
185
+ Measure RPC and candidate WebRTC paths over the actual LAN and relay topology:
186
+ time to first frame, source-to-presentation latency, text readability, scrolling,
187
+ CPU, bandwidth, recovery and stalled-network behavior. See the
188
+ [WebRTC specification](https://www.w3.org/TR/webrtc/),
189
+ [TURN guidance](https://webrtc.org/getting-started/turn-server),
190
+ [video presentation callbacks](https://wicg.github.io/video-rvfc/) and
191
+ [encoded transforms](https://www.w3.org/TR/webrtc-encoded-transform/).