LearnNewsExamplesServices
Frontmatter
id17985
titleOuter DockSplitter resizes only one pane in a nested column
stateClosed
labels
bugaitestingregressioncore
assigneesneo-gpt-emmy
createdAtAug 31, 2026, 11:37 PM
updatedAtSep 1, 2026, 2:25 PM
githubUrlhttps://github.com/neomjs/neo/issues/17985
authorneo-gpt-emmy
commentsCount0
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 18021 DockMaximize transition arm rejects an idempotent second apply
blocking[]
closedAtSep 1, 2026, 2:25 PM

Outer DockSplitter resizes only one pane in a nested column

Closed Backlog/active-chunk-19 bugaitestingregressioncore
neo-gpt-emmy
neo-gpt-emmy commented on Aug 31, 2026, 11:37 PM

Context

Severity: P0. On the Workstation flagship, create a nested column by dragging one dock item above or below another, then resize the enclosing column through the visually vertical divider of its outer horizontal split. Only one stacked pane follows the new width; its sibling keeps the old cross-axis extent, leaving a large empty region inside the column.

The operator supplied a rendered Workstation screenshot showing unequal right edges after this journey. The dock document remains structurally plausible; the rendered descendants do not agree on their parent column's width.

Live baseline: origin/dev@fa20136f29 (merged PRs #17974, #17973, and #17977).

Creation-time duplicate evidence:

  • Live latest-open sweep: checked the latest 20 open issues at 2026-08-31T21:34Z; no equivalent ticket exists.
  • Recent all-read-state A2A claim sweep: no claim owns the nested-column geometry failure. #17980 owns a different retained-chrome duplication race.
  • The exact issue/discussion corpus contained no equivalent. The Knowledge Base query returned only the #13158 umbrella and older geometry lineage, but its stale corpus is advisory and does not certify freshness; live GitHub plus the A2A claim sweep are the duplicate authority.

The Problem

Existing witnesses prove flat adjacent-pair splitter behavior and individual drag/drop behavior, but none composes the sequence that failed:

real tab drag to split-above/below
  -> retained tab container enters a nested vertical split
  -> outer horizontal split is resized
  -> every stacked descendant must track the column width

The red-first E2E confirms the committed model and settled projection are correct. The failure exists only when the outer splitter starts while the preceding structural DockFlip still owns retained descendants: DockFlip has hoisted those panes to fixed old-pixel geometry, so the direct outer pair grows while the visible panes remain frozen. Once DockFlip settles, the layout heals. The bug is therefore a real live-resize correctness defect at the presentation-to-interaction ownership handoff, not a reducer or nested-flex defect.

This is a successor to the resolved flat-pair contract in #17820 / PR #17849. Reopening that ticket would erase the fact that the missing composition is newly observed.

The Architectural Reality

  • model/Operations.splitNode() creates the expected nested tree, and resizeSplit() commits one normalized immediate-child vector. The E2E proves the model remains byte-identical during preview and changes only the outer size vector on release.
  • projection/LayoutAdapter.projectSplitNode() correctly projects the nested vertical split as one direct flex child; both tab shells stretch with it.
  • DockSplitter.getResizeConfig() correctly names the two direct outer layout participants. Recursively resizing descendants would duplicate flexbox authority.
  • Main-thread DockFlip.play() temporarily uses position: fixed plus the old pixel width to preserve structural motion across clipping boundaries. Those descendants are deliberately outside flex flow until its cleanup runs.
  • projection/MotionSignal remains the worker-owned, counted observability signal; its finite fail-safe cannot own physical admission. Main-thread DockFlip snapshot/pending/active registries are the actual presentation authority, and DragDrop.onDragStart() consults them on the native event path before Resize.start() or App delivery.
  • ADR 0029 §2.1 separates committed state, projection, runtime interaction pixels, and main-thread DOM geometry. Direct manipulation must reclaim geometry authority from presentation before it measures or resizes.

The Fix

  1. Make Neo.main.addon.DockFlip.land({hostId}) a remote, idempotent, strictly host-scoped settlement seam. Missing hostId is refused; destroy alone uses the private all-host path. Snapshot-only, pending-wait, and active-stage generations share one owner: pending rAF/timer waits are interrupted and cancelled immediately, same-host successor capture lands its predecessor first, active cleanup is defensive LIFO, committed inline geometry is restored exactly, and interrupted plays resolve false without an external carrier.
  2. In Main DragDrop.onDragStart(), call the same-window DockFlip.landFromPath(nativePath) synchronously before Resize.start() and before publishing drag:start to App. DockFlip's own registry identifies the first path-owned host, so both live-resize and proxy-mode splitter starts are covered without trusting an App descriptor or waiting for a worker acknowledgement. Missing addon, foreign path, or no active presentation is a cheap fail-open no-op.
  3. Keep DockSplitter.onDragStart() App-side and semantic: after Main admission it captures the direct pair, projects optional proxy paint, and opens the inherited logical gesture under its generation fence. Add the exact trusted-pointer Workstation journey plus focused Main/DockFlip/DockSplitter lifecycle and order arms.

Contract Ledger

Target surface Source of authority Required behavior Fallback / failure Evidence
Nested split as outer pair member dock document + LayoutAdapter.projectSplitNode() The nested subtree is one direct outer flex child; its tab shells stretch to the same cross-axis extent Layout shells diverging by >1px is failure Real Workstation E2E, worker topology + DOM rect matrix
Presentation-to-interaction handoff Main DragDrop + DockFlip host registry; MotionSignal observability only Path-owned snapshot/pending/active FLIP state lands synchronously before native Resize admission and App delivery; fixed-stage descendants release to committed layout Missing addon, foreign path, or no presentation is a synchronous no-op; proxy mode is covered without a resize descriptor Held-App/immediate-move Main unit + DockFlip settlement mutants + Workstation E2E
Outer splitter preview and terminal DockSplitter + main-thread Resize + ADR 0029 §2.1 Preview conserves the direct outer pair; tab shells stay within 1px and transform-inclusive panes within 2px; release commits only the outer resizeSplit Escape restores the full column and commits zero operations Mid-gesture, release, and reverse-Escape E2E receipts

Decision Record impact

Aligned with ADR 0029 §2.1. This restores the existing state-ownership boundary; it adds no persisted field, operation, or public API and does not require an ADR amendment.

Acceptance Criteria

  • A Workstation Whitebox E2E uses trusted pointer input to place Matrix right of Alerts, place Feed above Alerts, and prove the resulting nested vertical split in worker-owned dockModel.
  • The E2E positively enters the structural neo-dashboard-dock-animating / fixed-stage window before grabbing the enclosing outer splitter.
  • The live resize resolves the two direct outer layout participants; no descendant-array contract is introduced.
  • During preview, both stacked tab shells track the nested parent within 1px and transform-inclusive pane rects within 2px; the outer pair stays conserved.
  • The serialized document is byte-identical while held; release changes only the outer split size vector, preserves the nested vector, and retains pane/tab identities.
  • A reverse-direction Escape arm restores the whole column and commits zero operations.
  • DockFlip.land({hostId}) is remote, requires one host, is idempotent, retires captured First state, interrupts pending frame/timer waits without servicing either carrier, serializes same-host successor capture, restores active fixed stages and exact inline styles without destroying the addon, and resolves interrupted plays.
  • Main DragDrop uses DockFlip's own path-to-host registry to land presentation synchronously before Resize.start() and App drag:start delivery. A held App handler plus immediate native move cannot mutate while fixed-stage presentation remains; live and proxy modes are covered, while missing addon/foreign path/no motion stay fail-open and synchronous.
  • Existing flat-pair DockSplitter, Workstation drag-affordance, DockFlip, and splitter/grid geometry suites remain green.

Out of Scope

  • The separate duplicate-action/reconcile race in #17980.
  • Edge-zone extent semantics, cross-window transfer, Pointer Events migration, or the LayoutAdapter decomposition in #17978.
  • Recursive descendant sizing inside DockSplitter; flex projection owns descendant stretch.

Avoided Traps

  • Programmatic-only topology setup: rejected because it bypasses the retained-instance path created by a real tab drag.
  • First visible splitter selector: rejected because edge and nested splitters share orientation classes; dockNodeId is the semantic selector.
  • Reducer patch first: rejected because current model operations already produce normalized nested trees and would not explain divergent rendered edges.
  • Arrays of descendant resize targets: rejected because the verified direct participant is the nested container; recursive pixel writes would make Splitter fight DockFlip and duplicate flexbox authority.
  • Waiting or refusing the pointer: rejected because direct manipulation should respond immediately; landing presentation is deterministic and uses DockFlip's existing cleanup authority.

Related

Parent epic: #13158
Architecture: D#17818
Resolved predecessor: #17820 / PR #17849
Nested flex precedent: #14985 / PR #15018
Recent drop-boundary repair: #17926 / PR #17939

Origin Session ID: 4426fb43-4968-4084-832e-1830de2e8747

Retrieval Hint: Workstation nested column outer splitter retained tab stale cross-axis width
Retrieval Hint: fa20136f29 DockFlip fixed-stage MotionSignal DockSplitter land before capture

— Emmy (GPT-5.6 Sol Ultra, Codex)