LearnNewsExamplesServices
Frontmatter
id18216
titleAfter a pin on a programmatically tucked pane the dock''s projection seam stops delivering: every later commit lands in worker truth and never in the view
stateClosed
labels
bugaidashboard
assigneesneo-opus-grace
createdAtSep 3, 2026, 5:31 PM
updatedAtSep 4, 2026, 10:11 AM
githubUrlhttps://github.com/neomjs/neo/issues/18216
authorneo-opus-grace
commentsCount9
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 4, 2026, 10:11 AM

After a pin on a programmatically tucked pane the dock's projection seam stops delivering: every later commit lands in worker truth and never in the view

Closed Backlog/active-chunk-20 bugaidashboard
neo-opus-grace
neo-opus-grace commented on Sep 3, 2026, 5:31 PM

Context

Found while running the NL whitebox battery for #17844 — the first time that set has executed since the repo split, because every spec in it is config-ignored unless NEO_AGENTOS_RUNTIME_ROOT is set and nothing sets it.

Two of the four dock reds are one cause, and it is not the reducer.

Measured at dev ca83102346, N=2 (reproduced in two separate census runs)

DockAutoHideRevealNL.spec.mjs:90 and :293 both fail after a pin gesture on an auto-hidden pane.

The document is correct. The reducer commits exactly what it should:

[auto-hide] inspector: {…"autoHidden":true} -> {…"autoHidden":false,"pinned":true}

The rail does not retire. Worker truth still holds the affordance:

Error: no rail instance may survive the post-pin re-projection
- Expected  Array []
+ Received  Array [ { className: "Neo.dashboard.dock.interaction.Rail",
+                     id: "neo-dashboard-dock-rail-1" } ]

and so does the DOM:

Error: the retired rail tab must leave the DOM
Locator: locator('.neo-dashboard-dock-rail-tab')
Expected: 0   Received: 1   (14 × resolved to 1 element)

Nine util.VNode.getVnode: Component not found for id: neo-component-29 errors accompany the run — a vnode lookup against a component that no longer exists, which is the shape of a surviving affordance pointing at a torn-down pane.

Why this is a contract violation and not a stale expectation

I checked this before calling it a defect, because the other red in the same census is a stale expectation (DockLockActionNL:31 asserts ['alpha','beta'] against a fixture that gained a third item).

Rail.mjs:12 states the contract itself:

The rail is pure render projection (per-window, derived, never persisted)

So a Rail instance surviving a re-projection that rails nothing contradicts the class's own stated nature. The spec's expectation is right.

setItemPinned is classed topology in Operations.operationChangeClass, so this commit takes the full staged transaction — the slow path. This is not a fast-path escape: the rail fails to retire even when everything is restaged.

The engine already names the hazard, one guard over. Workspace#isDockRailedItem's JSDoc:

a railed pane is projected OUTSIDE the shell. reconcileStableTopology admits the fast path on "every structural dock node retains identity", which stays true while the rail — a separate surface it does not reconcile — still holds the old pane. The result is a stale rail copy beside a fresh one.

That guard stops the item-flag path from producing a stale rail. This ticket is the same stale rail arriving through the path the guard does not cover.

What I am NOT claiming

I cannot date the onset and I am not calling it a regression. #14911 ("Wholesale container refresh leaves destroyed child component DOM in place") closed 2026-07-10 and :293 cites it, so this is that symptom class returning — but the battery carrying the guard has been unrunnable since the split, so there is no CI history to bisect against. Dating it is cheap for whoever takes this: the spec exists, and NEO_AGENTOS_RUNTIME_ROOT plus a bisect over Rail.mjs / projection/ will band it.

I have not diagnosed the mechanism. Whether the projection fails to destroy the Rail, destroys it while leaving its DOM, or rebuilds it from stale railItems is unmeasured. The three candidates are distinguishable — worker-truth instance vs DOM node vs adapter input — and guessing between them is what this ticket exists to prevent.

Acceptance Criteria

  • AC-1 A pin gesture on an auto-hidden pane leaves no Rail instance in worker truth and no .neo-dashboard-dock-rail-tab in the DOM. Red-first: both arms of DockAutoHideRevealNL fail at ca83102346.
  • AC-2 The mechanism is named before the fix — which of instance teardown, DOM removal, or adapter input carries the failure, measured rather than inferred.
  • AC-3 The getVnode: Component not found errors either disappear or are shown to be an independent condition; a fix that leaves nine console errors has not finished.
  • AC-4 Onset is banded to a change, or the ticket records why it could not be and what was tried.
  • AC-5 Control — the last auto-hidden item on an edge retires the whole rail, and a rail still holding OTHER items keeps it. Rail teardown must not become unconditional.

Non-goals

  • DockLockActionNL:31 (a stale assertion) and DockSplitterProxyPaintNL:29 — separate reds from the same census, tracked on #17844.
  • The invocation gap that hid this (nothing sets NEO_AGENTOS_RUNTIME_ROOT, no workflow runs e2e) — #17844.
  • Any change to setItemPinned's change class. It is topology and correctly so; the fast path is not implicated.

Related

#17844 (the census that surfaced it) · #14911 (closed; :293 is its guard) · #18039, #17969 (closed, adjacent rail-return defects) · #18151 (the dock epic)

Sweep before filing: latest-20 open (no equivalent), open full-text rail in:title (none), closed-queue for the same symptom (#18039, #17969, #14911 — all closed, none this shape).

Authored by Grace (Claude Opus 5, Claude Code). Session 8d936ec9-b816-4ded-a91e-6e91ef6a3325.