fix(maestro): mobile drawer access, focus preservation, stale rAF focus (#140)

Three more fixes from acceptance review:

- structured.ts's header omitted the .sessions-btn attach.ts's terminal
  header carries. The sidebar is off-canvas on mobile, so a structured
  session had no way to reach it — add the same button and
  requestToggleDrawer wiring.

- ConnectionPool.reconcileViews always remounted a repaired connection
  with focus:false. If the mistyped pane was the one actually focused,
  disposing it dropped DOM focus while `focusedId` still named it. Pass
  `focus: this.focusedId === id` instead — `focusedId` only ever names a
  real focus (multiview.ts skips setFocus while previewing), so a
  preview or a merely-visible split neighbour still comes back
  unfocused.

- onMount's deferred requestAnimationFrame focus had no way to tell it
  had been superseded by an unmount (or a remount elsewhere) before it
  fired, so a parked pane could steal focus a frame later. A token
  bumped on every mount/unmount lets the callback recognize it's stale
  and no-op.

Regression tests: 7 new (structured.test.ts, connections.test.ts). 495
tests total.
This commit is contained in:
Miguel Palhas
2026-09-05 06:41:40 +01:00
parent 42ffc1a352
commit b11784e8e3
4 changed files with 167 additions and 5 deletions
+9 -2
View File
@@ -171,8 +171,15 @@ export class ConnectionPool {
// siblings — append, never replaceChildren, or they'd all vanish.
this.hidden.appendChild(fresh.el);
}
if (wasMounted) fresh.onMount({ focus: false });
else fresh.onUnmount();
if (wasMounted) {
// `focusedId` only ever names a session the operator actually
// focused — never a merely-visible split neighbour or a preview
// (multiview.ts skips `setFocus` while previewing) — so this is
// exactly "was the keyboard actually in the pane being replaced."
fresh.onMount({ focus: this.focusedId === id });
} else {
fresh.onUnmount();
}
}
}