Bound clipboard sync retries and unify Windows subprocess flags - #31
Merged
Conversation
Review findings on the v0.11.6/v0.11.7 range. Five fixes; one reported
finding was wrong and is explained below.
transfers/clipboard:
- synchronizePcClipboard recorded #lastSentClipboard only on ACK, which
is deliberate (test_failed_pc_clipboard_sync_remains_retryable wants a
failed sync retried). But pollPcClipboard runs every 1800ms and skips
only text recorded as sent, and the ACK wait is 1500ms, so an
unacknowledged sync re-sent the same text every tick for as long as it
stayed on the PC clipboard -- an unbounded stream of control messages
with a status line flapping over it. Retries are now capped at three
attempts per text, after which the text is recorded as sent and the
status says so. Retryability is preserved; the loop is not.
- pasteText always followed the clipboard sync with a full
textInjectionMessages() injection. That chunks at 300 UTF-8 bytes and
awaits each chunk, so a paste at the 256 KiB limit was roughly 875
sequential round trips. Injection is now capped at 8 KiB; above that
the clipboard is still synchronized and the status tells the user to
paste on the device. pasteTypedText rejects oversized input with the
same explanation, since typing is all it does.
- resetClipboardSessionState cleared the visible fallback text box on
every connect, discarding text the user had typed while disconnected.
The two cached device-clipboard fields are still cleared; the textarea
is user input, not cached state.
Windows subprocess flags:
- network_access.apply() runs PowerShell New-NetFirewallRule on Windows
with no creation flags, so a console window appeared every time LAN
access was applied or removed. This is the same defect the new adb
helper was added to fix, and the most visible instance of it.
- That helper was the fourth expression of the same decision, and the
three existing ones disagreed: adb/client.py fell back to the real
constant, desktop/controller.py and desktop/support.py fell back to 0
(no flag), and network_access.py had nothing. All four now use
droid_web_display/process_utils.subprocess_creation_kwargs.
adb/client._subprocess_creation_kwargs is kept as a thin delegate
because the Windows regression test monkeypatches it by name.
Retracted finding: I reported that copyAndroidClipboard had lost the
transient user activation needed for navigator.clipboard.writeText by
moving the write into the device-message loop. That is wrong.
bindAndroidCopyWriteThrough in main.ts already performs the write inside
the originating gesture, polling the DOM for the reply and falling back
to document.execCommand("copy"). I had not read main.ts when I filed it.
No change was made there.
Tests: the session-state contract asserted the textarea is cleared; it
now asserts the opposite, with the reason. Added coverage for the retry
bound and the injection cap, against both src and dist.
No node/npm or pytest here. dist/assets/controller.js was hand-applied
and the manifest regenerated; verify_static_client passes over all 92
files. All 8 clipboard contract tests, both clipboard-copy server-mode
tests, the toolbar layout tests and the Windows creation-kwargs test
were executed directly and pass. The fixture-based subprocess tests and
anything needing a browser were not run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XfT5Z3Z24bjC8qtqERGpMQ
CI rebuilds dist/ with tsc, which splits a single-statement `if` across two lines. The hand-applied dist used a one-liner, so the new oversized-text test failed against a real build even though it passed against the hand-applied file. dist now uses the compiler's form, and the assertion matches either style so a future rebuild cannot fail on whitespace alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XfT5Z3Z24bjC8qtqERGpMQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes from a review of the v0.11.6/v0.11.7 range. Five fixes, and one reported finding that was wrong — detailed at the bottom.
Clipboard
Unbounded re-send on a missing ACK.
synchronizePcClipboardrecords#lastSentClipboardonly on ACK, which is deliberate —test_failed_pc_clipboard_sync_remains_retryablewants a failed sync retried. ButpollPcClipboardruns every 1800ms and skips only text recorded as sent, and the ACK wait is 1500ms. So an unacknowledged sync re-sent the same text every tick, forever, for as long as it stayed on the PC clipboard — an unbounded stream of control messages with a status line flapping over whatever the user was reading.Retries are now capped at three attempts per text, after which the text is recorded as sent and the status says so. Retryability is preserved; the loop is not.
~875 round trips for a large paste.
pasteTextalways followed the clipboard sync with a fulltextInjectionMessages()injection, which chunks at 300 UTF-8 bytes and awaits each chunk. At the 256 KiB clipboard limit that is roughly 875 sequential control messages. Injection is now capped at 8 KiB; above that the clipboard is still synchronized and the status tells the user to paste on the device.pasteTypedTextrejects oversized input with the same explanation, since typing is all it does.The ADR's reasoning is untouched — an ACK genuinely does not prove
KEYCODE_PASTEinserted anything, so deterministic injection stays the default. This only stops the pathological size.Connect wiped the fallback text box.
resetClipboardSessionStateclearedclipboardText.valueon every connect, discarding text the user had typed while disconnected. The two cached device-clipboard fields are still cleared — leaking those across devices is the bug that method exists to fix — but the textarea is user input, not cached state.Windows subprocess flags
The firewall call still flashed a console.
network_access.apply()runs PowerShellNew-NetFirewallRuleon Windows with no creation flags, so a window appeared every time LAN access was applied or removed. Same defect class the new adb helper was added to fix, and the most visible instance of it — a full PowerShell window during an interactive settings change.Four expressions of one decision, three of them different.
adb/client.pyfell back to the real constant,desktop/controller.pyanddesktop/support.pyfell back to0(no flag at all), andnetwork_access.pyhad nothing. All four now usedroid_web_display/process_utils.subprocess_creation_kwargs.adb/client._subprocess_creation_kwargsis kept as a thin delegate because the Windows regression test monkeypatches it by name.Retracted finding
I reported that
copyAndroidClipboardhad lost the transient user activation needed fornavigator.clipboard.writeTextby moving the write into the device-message loop, and that Copy would fail on Firefox.That is wrong.
bindAndroidCopyWriteThroughinmain.tsalready performs the write inside the originating gesture — it polls the DOM for the reply and falls back todocument.execCommand("copy"). I had not readmain.tswhen I filed the finding. No change was made there, and I reverted the refactor I had started.Verification
No node/npm or pytest in the authoring environment.
dist/assets/controller.jswas hand-applied and the manifest regenerated;verify_static_clientpasses over all 92 files.Executed directly and passing: all 8 clipboard contract tests, both clipboard-copy server-mode tests, the toolbar layout tests, and the Windows creation-kwargs test. Not run: the fixture-based subprocess tests, and anything needing a browser. The retry cap and the injection threshold have not been exercised against a real device.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XfT5Z3Z24bjC8qtqERGpMQ