fix(remote): stop the wake handlers shadowing each other; enforce the input cap

Two findings from a final review pass over the wake-on-LAN feature.

`_onRemoteHostWaking` / `_onRemoteHostWakeFailed` were defined in BOTH
`panels-ui.js` (toasts) and `host-wake-ui.js` (banner). Both files mix into
`CodemanApp.prototype` and `host-wake-ui.js` loads later, so the panels-ui copies
were silently shadowed: the toast never fired, and a wake started for a BACKGROUND
session (input on a non-active tab) produced no notification at all, since the
banner handler only acts on the active session. The handlers now live only in
`host-wake-ui.js`, show the toast unconditionally, and update the banner when the
woken session is the active one.

`appendBoundedPending` dropped only WHOLE chunks, so a single input value over the
cap (one large paste is one `input` value, up to the 100 KB input schema) was kept
in full: "bounded at 4 KB" held per chunk, not per session, and nothing was logged.
The surviving chunk's head is now trimmed too, code-point aware so a multi-byte
character is never split into a replacement char.

Adds the guard that would have caught the first one: every SSE dispatch handler must
be defined in exactly ONE frontend module. The existing test only asserts a handler
EXISTS somewhere, which two modules both satisfy while one is shadowed.
This commit is contained in:
Randalix
2026-09-15 21:01:53 +02:00
parent 1380b023e2
commit 8dfc965d13
6 changed files with 81 additions and 17 deletions
+5 -3
View File
@@ -404,7 +404,8 @@ The invariants worth keeping:
after the reattach, with a settle delay so bytes cannot land in a still-connecting
pane. The **send-and-wait** path blocks on the wake instead — its response is open
anyway, and buffering would break the wait contract.
- **The command runs without a shell** (`spawn(path, [], { shell: false })`), the schema
- **The command runs without a shell** (`spawn(path, [], { stdio: 'ignore' })` — `shell`
defaults to `false`), the schema
requires a single executable path (no arguments, no `$`/backtick), and `wakeMac` is a
structural hex-pair allowlist. A broken or missing wake target fails the wake, never the
input route.
@@ -418,8 +419,9 @@ The invariants worth keeping:
again). Other host-level fields deliberately stay as persisted, so neither path can
silently re-point an existing pane's SSH options.
- **UI/SSE**: `remote:hostWaking` and `remote:hostWakeFailed` (plus the reused
`remote:sessionReconnected`) drive the banner and toasts in `host-wake-ui.js` /
`panels-ui.js`.
`remote:sessionReconnected`) drive the banner and toasts, all from `host-wake-ui.js` —
its handlers are the ONLY definitions, since a second one in another mixin would be
silently shadowed by script order.
Tests: `test/remote-wake.test.ts` (decision/throttle table, single-flight registry,
buffering + flush order, MAC parsing/magic packet, live host-config resolution, and the wiring