LearnNewsExamplesServices
Frontmatter
id19986
titleA tab drag in a maximized dock pane targets the layout behind it
stateClosed
labels
bugaidashboard
assigneesneo-gemini-julia
createdAtOct 22, 2026, 11:38 AM
updatedAtOct 22, 2026, 5:10 PM
githubUrlhttps://github.com/neomjs/neo/issues/19986
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtOct 22, 2026, 5:10 PM

A tab drag in a maximized dock pane targets the layout behind it

Closed Backlog/active-chunk-10 bugaidashboard
neo-opus-ada
neo-opus-ada commented on Oct 22, 2026, 11:38 AM

Context

Found in manual testing of apps/workstation on dev (2026-10-22), in the same pass as #19982, #19984 and #19985. @tobiu's screenshot, which follows in a comment, shows a maximized pane mid tab-drag inside its own header. Two things in it are wrong:

  1. A teal frame that ends part-way down the pane. It looks like a cut-off focus outline. It is the dock's drop preview for the layout behind the maximized pane.
  2. The header actions render as × _ ⋯. ⋯ (More tabs) sits last, while the frozen header order puts it first.

Measurements

Headless Chromium at 8bb3574d0f (its src/, apps/ and resources/scss/ are identical to dev at 84b711878a). Gesture: maximize the shipped layout's 12-tab pane, activate a tab so the header re-partitions (#19982), then drag "Command and Control Console" 600 px left within the header and release. Docked control: a 2000×900 viewport, dragging "Priority Alert Observatory" 150 px right within its strip.

docked maximized (1440×900)
preview while dragging neo-dock-preview-tab-into over the pane itself [1125,121,1691,707] neo-dock-preview-edge-top neo-dock-preview-accepted neo-dock-preview-region neo-dock-preview-cut-bottom over the workspace's top edge band [0,109,1440,505], with a 2px teal border. This is the frame in the screenshot.
after release tabs reordered in place neo-dock-maximized is gone, and the pane is back at its docked rect [808,121,1188,707]. There are still 6 tabs nodes, and the dragged tab is the active tab of the same node.

So an in-strip reorder of a maximized pane targets the workspace edge behind the maximized plane, and releasing it un-maximizes the pane.

The ⋯ reversal is not reproduced headless. I replayed three paths: a fresh 1440 px page; first clicking ⋯ (#19984's path) and refocusing the body; and 2000 px, where all 12 tabs fit and no ⋯ exists. In all three the DOM and visual order stayed spacer, ⋯, [gated actions], restore, close. It is recorded here as operator-observed, mid-drag, with the edge preview active.

The Problem

Maximize's own input contract is not enforced on the dock's cross-zone path. It suppresses the maximized node's strip SortZone with allowOverdrag: false, boundaryContainerId: <bar> and enableProxyToPopup: false. Those configs bound the strip's proxy, and none of them reaches the dock's targeting.

Neo.dashboard.dock.interaction.TabSortZone#onDragMove engages the cross-window dragCoordinator, and after the base sort fires dockCrossZoneDragMove with the pointer, on every move. Nothing checks whether the source node is maximized. The workspace then resolves the pointer against the docked layout under the maximized plane.

A maximized header sits inside the dock host's top-edge activation band. Its own in-strip gesture therefore reads as "dock to the top edge", and that target is accepted. On release the commit is classified as an operation beyond the node, which clears maximize terminally.

The strip had its own drag logic before dock layouts existed. On this gesture it overlaps the dock's pane-drag logic, and a maximized pane exposes the overlap.

The Architectural Reality

  • src/dashboard/dock/plugin/Maximize.mjs: suppressDragSources() snapshots and flips the three strip-zone configs; the restore path puts them back.
  • src/dashboard/dock/interaction/TabSortZone.mjs onDragMove(): the coordinator engagement is gated on sortGroup and a live proxyRect, and dockCrossZoneDragMove is unconditional. Neither consults maximize.
  • Design authority: Maximize's @summary. "in-strip tab reordering stays live; cross-zone drag sources and tear-out affordances of the maximized node are suppressed (every drop target sits under the maximized plane)", and operations confined to the node, "reordering … within it" included, "defer to the re-projection rule instead, which re-applies onto the surviving node".

The Fix

Let Maximize own the whole gate, as its @summary says. While a node is maximized, drags that start in its strip produce no cross-zone targeting:

  • TabSortZone engages the coordinator and fires dockCrossZoneDragMove only while its zone allows cross-zone drags.
  • Maximize#suppressDragSources() turns that off next to the three configs it already flips, and its restore path restores it from the same snapshot.

The in-strip reorder then commits through the strip's own sort, a node-confined operation, so maximize survives under the existing re-projection rule.

Acceptance Criteria

  • While a pane is maximized, an in-strip tab drag shows no dock drop preview outside that pane: no neo-dock-preview-edge-*, and no other node's preview. The strip's own reorder feedback still works.
  • Releasing reorders the tab within the strip, and the pane stays maximized (neo-dock-maximized and the restore action stay).
  • Throughout the drag, the header's action order stays ⋯ → focus-gated engine set → restore/close. This is a guard for the reversal; on dev it is not red in our replays.
  • Docked panes are unchanged: an in-strip drag still shows the tab-into preview and reorders.
  • E2E: a new whitebox Neural Link spec, test/playwright/e2e/workstation/WorkstationMaximizeTabReorderNL.spec.mjs, maximizes the 12-tab pane, re-projects its header, and drags a header tab within the strip with a real pointer. Mid-drag it asserts no edge preview and the action order; after release it asserts maximize is kept and the reorder applied. It must be red on dev (edge-top preview present, pane un-maximized after release) and green with the fix. A docked arm stays green. It joins the hosted Workstation NL tier (#19655).

Out of Scope

  • Tearing a tab out of a maximized pane, which the contract already suppresses.
  • #19982's stale partition after maximize. The spec activates a tab first, as the operator did, and does not depend on #19982.
  • Other overlaps between the strip's drag logic and the dock's pane drags in docked panes. They are not observed here.

Related

Live latest-open sweep: checked latest 20 open issues at 2026-10-22T09:38:09Z; no equivalent found. Keyword sweep: "maximized tab drag preview", "dockCrossZoneDragMove", "maximize drag edge preview", "tab reorder maximized"; closed adjacent only (#18471, Group operations clearing maximize, fixed by #18472; #18027). A2A claim sweep: no overlapping lane-claim. MC sweep: "tab drag inside maximized dock pane shows drop preview behind it…", 6 results, no prior decision. Own-assignment sweep: none overlapping.

Origin Session ID: 70e59a84-efd6-4e7a-9262-c5478bd314ff Retrieval Hint: "maximized dock pane tab reorder shows edge-top drop preview, dockCrossZoneDragMove ignores maximize, Maximize suppressDragSources"

tobiu
tobiu Oct 22, 2026, 11:40 AM
Image
  • 2026-10-22T09:54:06Z @neo-opus-ada cross-referenced by #19987
  • 2026-10-22T09:54:07Z @neo-opus-ada cross-referenced by #19988
  • 2026-10-22T10:06:29Z @neo-opus-ada cross-referenced by #19989
  • 2026-10-22T10:18:24Z @neo-opus-ada cross-referenced by #19990
  • 2026-10-22T11:04:01Z @neo-opus-ada unassigned from @neo-opus-ada
  • 2026-10-22T12:09:12Z @neo-gemini-julia assigned to @neo-gemini-julia
  • 2026-10-22T12:38:24Z @neo-gemini-julia cross-referenced by PR #20000
  • 2026-10-22T14:58:16Z @neo-opus-vega cross-referenced by PR #20005
tobiu referenced in commit 97a21f6 - "fix(dashboard): suppress cross-zone tab drag while dock pane is maximized (#19986) (#20000)" on Oct 22, 2026, 5:10 PM
tobiu closed this issue on Oct 22, 2026, 5:10 PM