Skip to content

fix(web): keep human panel focus local to the gesturing client - #570

Merged
thellert merged 1 commit into
mainfrom
fix/panel-focus-echo
Aug 12, 2026
Merged

fix(web): keep human panel focus local to the gesturing client#570
thellert merged 1 commit into
mainfrom
fix/panel-focus-echo

Conversation

@thellert

Copy link
Copy Markdown
Collaborator

Problem

With two windows of the same workspace open (or one window and a delayed echo), panel workspaces mutated themselves: a human tab switch was broadcast back over SSE and every client applied it with evicting 'replace' placement. Reproduced consequences:

  • Closing a tile emitted an unsuppressed survivor-focus POST; its broadcast echo landed ~0.5-1s later and could evict or re-open tiles the operator had just arranged (a rail reopen inside that window was evicted again immediately — the click appeared dead).
  • A second window's ordinary clicks remote-controlled the first window's active panel and, when the focused panel had no tile there, took over a tile and evicted its occupant.

panel-commands.js already documented the intended contract — a user-initiated switch is reported "so the server mirrors the active panel (and does not echo a focus event back)" — and the collaborative-panels design states human gestures never rebroadcast. The server violated both.

Change

  • routes/panels.py: broadcast a panel_focus frame only for source: "agent" switches; a source-less (human) POST updates the active_panel mirror silently. Membership-adding visibility frames still broadcast (shared state).
  • dock-sync.js: new setTileFocusHandler seam — a human dock-tab focus applies locally (rail accent, active-tab state via panel-manager's activateTab) instead of riding the now-removed echo; the mirror POST is unchanged.
  • dock-tab.js: the tile close button runs its dockview removal inside the echo guard, so the survivor auto-activation is never reported as a human focus — parity with retireTile.

Tests

  • New: route-level report-vs-command contract pins; browser test that a tile close with two tiles open commands nothing; two-client browser test that one client's focus gestures leave the other client's active panel and tiles untouched.
  • Amended: suites that pinned the old broadcast behavior now assert the new contract; agent-simulating callsites tag source: "agent" as the MCP path does.
  • Green locally: web_terminal route suites, all JS unit suites (659), full test_panels_browser.py + test_panels_collab_browser.py browser runs.

A human tab switch is a report, not a command: the server now mirrors
active_panel silently for source-less focus POSTs and broadcasts a
panel_focus frame only for agent-attributed switches, so one operator's
clicks can no longer rearrange every other window of the same workspace
or bounce back as a delayed echo that evicts tiles mid-gesture. The
gesturing client applies its own focus locally through a new dock-sync
focus-handler seam instead of riding the echo, and a tile close runs its
dockview removal inside the echo guard so the survivor auto-activation
is never reported as a human gesture.
@thellert
thellert merged commit 40cdad4 into main Aug 12, 2026
82 of 84 checks passed
@thellert
thellert deleted the fix/panel-focus-echo branch August 12, 2026 11:20
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