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
+16 -1
View File
@@ -314,8 +314,21 @@ Object.assign(CodemanApp.prototype, {
}
},
/** SSE `remote:hostWaking` — a wake is running (ours or one started by typing). */
/**
* SSE `remote:hostWaking` — a wake is running (ours or one started by typing).
*
* ⚠️ The ONLY definition of this handler: `panels-ui.js` must not define it too.
* Both mix into `Codeman.prototype` and this file loads later, so a second copy
* would be silently shadowed (the guard in `sse-dispatch-table.test.ts` sees that a
* handler exists, not that two modules claim the same name). The toast is
* deliberately UNCONDITIONAL — a wake can start for a background session (input on
* a non-active tab) where there is no banner to update.
*/
_onRemoteHostWaking(data) {
const label = data && data.label ? data.label : 'Remote host';
// Long enough to cover the wake + attach (~10s measured on a warm S3), and it
// is replaced by `remote:sessionReconnected` the moment the pane is back.
this.showToast(`Waking ${label} … input is queued`, 'info', { duration: 12000 });
const state = this._hostWake;
if (!state || !data || state.sessionId !== data.sessionId) return;
state.waking = true;
@@ -326,6 +339,8 @@ Object.assign(CodemanApp.prototype, {
/** SSE `remote:hostWakeFailed` — the host did not come back in time. */
_onRemoteHostWakeFailed(data) {
const label = data && data.label ? data.label : 'Remote host';
this.showToast(`${label} did not wake up — queued input is still held`, 'error', { duration: 15000 });
const state = this._hostWake;
if (!state || !data || state.sessionId !== data.sessionId) return;
state.waking = false;
+7 -11
View File
@@ -120,17 +120,13 @@ Object.assign(CodemanApp.prototype, {
// Wake-on-LAN from user input on a sleeping remote host (see remote-wake.ts).
_onRemoteHostWaking(data) {
const label = data && data.label ? data.label : 'Remote host';
// Long enough to cover the wake + attach (~10s measured on a warm S3), and it
// is replaced by `remote:sessionReconnected` the moment the pane is back.
this.showToast(`Waking ${label} … input is queued`, 'info', { duration: 12000 });
},
_onRemoteHostWakeFailed(data) {
const label = data && data.label ? data.label : 'Remote host';
this.showToast(`${label} did not wake up — queued input is still held`, 'error', { duration: 15000 });
},
// ⚠️ The `remote:hostWaking` / `remote:hostWakeFailed` HANDLERS live in
// `host-wake-ui.js`, which owns the banner state. They are NOT redefined here:
// both files mix into `CodemanApp.prototype` and `host-wake-ui.js` is loaded
// later, so a second definition would silently shadow the banner update (and the
// toast would never fire — the exact silent no-op `sse-dispatch-table.test.ts`
// exists to prevent, which cannot see shadowing). The toasts are shown from the
// host-wake-ui handlers instead.
// Bash tools