Skip to content

fix(ci): precreate draft before parallel release builds - #25

Merged
fc221 merged 12 commits into
mainfrom
bate
Jul 16, 2026
Merged

fix(ci): precreate draft before parallel release builds#25
fc221 merged 12 commits into
mainfrom
bate

Conversation

@fc221

@fc221 fc221 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • create one draft release before the platform build matrix starts
  • make macOS, Windows, and Linux upload into the same release
  • prevent Generate latest.json from downloading a partial platform-only draft

Root cause

Parallel tauri-action jobs raced while looking up the draft release. Two jobs could create separate drafts for the same tag, splitting macOS/Windows and Linux assets across different release IDs.

Validation

  • reproduced on beta.77: two drafts for one tag; neither contained all platform assets
  • beta.79 release workflow passed end to end
  • beta.79 contains macOS .app.tar.gz + signature, Windows installers, Linux packages, and latest.json in one published prerelease
  • git diff --check

Related

Follow-up to #24.

fc221 and others added 12 commits July 13, 2026 23:47
- park the controlled mac cursor on a clipping edge near the server side
  (arrow hotspot renders top-left, only right/bottom edges clip it to a
  sliver; corners trip macOS hot corners) — windows keeps the far corner
- remote Caps Lock posts the ctrl+space input-source hotkey instead of a
  dead injected caps keycode, so the focused app's IME switches immediately
- injected arrow/nav keys carry SecondaryFn (+NumericPad) flags so system
  and app shortcut matching recognises them
- injected mouse buttons carry kCGMouseEventClickState via a click tracker
  (system doubleClickInterval, 8px chain distance) so double-clicks register

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The transport loop serially awaited every command: a dead peer's 2s connect
timeout (hit every ~3s by discovery warming and every 2s by clipboard
retries) or one 48MB stream write stalled every queued input datagram behind
it — the periodic input freezes and the 'QUIC send failed' warn line every
few seconds for hours.

- datagrams now send synchronously on established connections (quinn's
  send_datagram is not async) and never wait for a dial; a missing peer gets
  one background connect task, marked in the map so move bursts cannot spawn
  a connect storm
- stream sends run in spawned tasks, capped by a semaphore (8 in flight)
- consecutive-failure fast-fail per peer address (concept from PR #22):
  after 3 strikes the caller-facing handle returns an error immediately so
  the input layer releases the cursor, and dial attempts are muted to one
  probe per 3s window until the peer recovers
- failure logging collapses to first-failure + entering-mute + recovery
  instead of one warn per attempt
- drop the always-true ack_required plumbing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Windows hook thread drained PeekMessage then slept 10ms per iteration;
low-level hook callbacks are only dispatched while the installing thread
services its queue, so every mouse/keyboard event waited out the remainder
of the sleep — ~15.6ms real without timeBeginPeriod — batching a 1000Hz
mouse into ~64Hz bursts on the local machine and the forwarding path alike.
MsgWaitForMultipleObjects wakes on the first queued message (20ms idle
timeout keeps the desktop and switch-request checks running). Ported from
the bate line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
on_stream held the layout mutex through clipboard writes (retry sleeps,
pbcopy spawn, up to 32MB base64 decode) and file-transfer disk writes; the
input capture and receive hot paths block on the same mutex, so every
clipboard sync froze input for tens to hundreds of ms. Snapshot the layout
under the lock and run the handlers on the clone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EDGE_DROP_WINDOWS_ENABLED has been hardcoded false; everything behind it was
dead: the edge drop window sync + specs + geometry helpers, the pointer-drop
destination chain (destination_hint wire field, pointer receive root, Finder
insertion-directory probe, drop landing window, pointer position readers),
their consts, the runtime field, five tests and the test-only wrappers, and
the two orphaned HTML assets. destination_hint was serde-default/optional on
the wire, so removal stays compatible with older peers.

~720 lines removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every mouse event on both the capture and receive paths repeated work whose
answer changes on layout edits, not per event:

- capture: current_input_targets rebuilt the target list under a BLOCKING
  layout lock per local move — including a LanPeer build whose
  local_ip_address() opened a UDP socket (4 syscalls) inside the tap/hook
  callback. Targets are now cached behind a 250ms TTL (Arc, try_lock,
  stale-serve), local_ip_address caches for 5s, and the per-target
  target_is_online lock round-trip is gone (build_input_targets already
  filters online + input-ready)
- send: mouse packets use the pairing context cached on the target instead
  of re-locking the layout and re-deriving the origin peer per packet (key
  events still consult the live layout for the modifier remap), and the
  packet serializes through a borrowing mirror so the ~1KB of credential
  strings is no longer cloned per event
- receive: datagrams decode outside the layout lock and input-first
  (control packets were parsed and rejected first on every move); the lock
  now covers only authorization + coordinate mapping while injection
  syscalls run after release; the local peer id is cached for 5s instead of
  building a LanPeer per packet; the clipboard target is only rewritten
  when the controller actually changes; the remote mouse position/buttons
  mutex became two atomics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ~355 lines of dead CSS: 30 classes from an abandoned UI iteration with
  zero TSX references, incl. the unreachable mac custom chrome (App.tsx
  hard-codes windows-only chrome), plus their entries in shared selectors
  and media queries
- desktopApi browser-preview theatre (fake runtime state machine, fake
  Math.sin performance samples, fake peers) replaced with one static stub;
  browser dev mode still renders the layout editor
- 17 unused i18n keys in both languages, the unused sendSecureAttention
  export, the unread DiagnosticDevice/knownDevices types, the
  edgeSwitchHotkeyFromKeyboardEvent pass-through alias, and @types/node
  (nothing type-checks node code)

tsc -b and npm run build pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When a standard-user MyKVM injects a click or key into an elevated / uiAccess
foreground window (Task Manager ships uiAccess=true, UAC-elevated apps, etc.),
Windows drops the SendInput with ERROR_ACCESS_DENIED. Cursor MOVE still works
(SetCursorPos), so the symptom is 'remote control just stopped' with no
explanation while MyKVM is plainly still running.

The code was writing a per-event debug file to C:\ProgramData\MyKVM\*.txt on
every refusal and telling the user nothing. Replace both with one throttled
log line (once per 10s) that names the cause and the fix: restart MyKVM as
administrator to control elevated windows. Not a code-fixable limitation —
UIPI is a security boundary — but no longer a silent one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…id drift

Issue #23: keyboard/mouse worked but clipboard synced only one way
(Windows copy -> macOS paste worked; macOS copy -> Windows paste never did).

Root cause is an authorization asymmetry between input and clipboard on the
receiving CLIENT. Input packets (packet_authorized_fields) accept a paired
controller matched by its STABLE transport public key OR its id OR the legacy
local-device fallback. Clipboard (clipboard_packet_authorized) matched by the
origin id ALONE — and a controller's derived peer id drifts when its LAN IP
changes, so once the macOS server's id no longer equalled the id Windows
recorded at pairing, its clipboard was silently rejected while its input kept
flowing (key still matched). The reverse direction worked because the macOS
receiver is role=server and skips the paired-controller check entirely.

Carry the sender's transport public key on ClipboardPacket (serde-default, so
older peers still decode) and match it first, mirroring input auth. The key is
~700 bytes on a rare, stream-delivered packet that already carries the full
copied text/image, so the size cost is negligible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The borrowed InputPacketRef removed the per-event credential CLONE but every
datagram still carried the full ~0.8KB pairing block on the wire — ~0.5KB of
which (mostly the base64 transport certificate) is constant for a whole
control session. On a low-MTU / VPN path that leaves little headroom under the
QUIC datagram size limit, and it is pure repetition.

Send the credentials only ~once per 2s per destination; steady-state packets
omit them (serde skip on empty) and shrink to ~0.15KB. The receiver authorizes
a credentialled packet in full and caches the source address for 5s, then
admits the credential-less packets in between while that authorization is
fresh. A peer that never proved the pairing secret from an address never gets
a cache entry, so its credential-less packets are always rejected — same trust
as before, just amortized. The clipboard target is set by the credentialled
packets and persists, so credential-less packets need nothing extra.

Wire-compatible in decode (all credential fields already serde-default), but a
credential-less packet is rejected by an OLD receiver, so BOTH sides must run
this build — a one-sided update degrades that direction's input to ~0.5Hz
until the peer is updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The mouse tester showed 后退/前进 stuck at 0: MouseButton only had
Left/Right/Middle and the Windows capture hook ignored WM_XBUTTONDOWN/UP, so
the side navigation buttons were never captured or injected.

- MouseButton gains Back/Forward with their own mask bits
- Windows capture handles WM_XBUTTON* and reads XBUTTON1(back)/XBUTTON2(forward)
  from the high word of mouseData
- macOS injects them as OtherMouseDown/Up stamped with button number 3/4
  (no double-click chaining — they are navigation, not click targets)
- Windows injection (for the mac->win direction) uses MOUSEEVENTF_X* with the
  button in mouseData

Both sides must run this build; an old peer can't decode the new wire variants.
Caps->IME behaviour left as-is (⌃Space) per the user's choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fc221
fc221 merged commit a2ea416 into main Jul 16, 2026
6 checks passed
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