LearnNewsExamplesServices
Frontmatter
id18291
titleA grid row forwards mounted and theme to its cell components, but not windowId
stateClosed
labels
bugaicore
assigneesneo-opus-vega
createdAtSep 4, 2026, 4:52 PM
updatedAtSep 4, 2026, 6:06 PM
githubUrlhttps://github.com/neomjs/neo/issues/18291
authorneo-fable
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 4, 2026, 6:06 PM

A grid row forwards mounted and theme to its cell components, but not windowId

neo-fable
neo-fable commented on Sep 4, 2026, 4:52 PM

Context

Measured live on 2026-09-04 by the popup-birth arm for D#18224 (apps/workstation at dev@2ee23d7801, Playwright e2e rig, scratch spec test/playwright/e2e/workstation/PopupBirthArm.spec.mjs, untracked). The Feed pane — Workstation.view.FeedPane, a grid.Container with a sparkline column — was popped out into a real popup window through the production tab action. The grid subtree moved as the same instance (20 descendants, 0 lost, consistency 4/4/4, same provider and store), every descendant in the items tree took the popup's windowId — and every sparkline canvas that travelled with the grid went blank.

Receipt: the five pre-existing Neo.component.Sparkline cells kept the opener's windowId and read offscreenRegistered: false for the whole 5 s settle poll; the three cells the grid created after the hop (row pool grown for the larger vessel) carry the popup's windowId and registered at once. Screenshots: before, the Live column draws a sparkline in every row; after, the Live column is empty in every row of the popup.

The Problem

Neo.grid.Row extends component.Base, not a container (src/grid/Row.mjs:39–42). It owns its cell components in this.components (:155–184) and forwards two lifecycle configs by hand: mounted (afterSetMounted, :84–92, added for #9107 so canvases re-register after a VDOM purge) and theme (afterSetTheme, :100–108). It has no afterSetWindowId. grid.Body#afterSetWindowId (src/grid/Body.mjs:671–681) forwards to its rows and stops there. After a cross-window move, grid.Container → grid.View → grid.Body → every row carries the new id, and every pooled cell component keeps the old one. column/Component#cellRenderer (src/grid/column/Component.mjs:159–171) stamps windowId only at creation, which is why the three new cells were right.

For a canvas cell the consequence is total. component/Canvas#afterSetMounted (src/component/Canvas.mjs:50–91) re-transfers the canvas on remount with transferCanvasToWorker({componentId, nodeId, windowId}); with the stale id the request goes to the opener's main thread, where the node no longer exists, so the promise never resolves and offscreenRegistered stays false. Canvas#afterSetWindowId (:102–108) already resets the flag on an id change so the remount can re-register — the hook exists, the cascade never reaches it. Any component cell with window-scoped main-thread state fails the same way.

The Architectural Reality

  • container.Base#afterSetWindowId (src/container/Base.mjs:319–338) cascades to items, layout and sort zone. Rows are not containers, so they forward by hand, as they already do for mounted and theme.
  • #9107 (2026-02) established the pattern for mounted; theme followed; windowId is the third config the row must relay.
  • D#18224 row 8 / Euclid's provider-topology correction: realm registrations are re-armed by the component that owns them through afterSetWindowId. This defect is the one missing link in that chain for grids.

The Fix

Neo.grid.Row#afterSetWindowId(value, oldValue): call super, then for every key in this.components set components[key].windowId = value when value is set — the exact twin of afterSetMounted. Fold the three loops into one small forwarding helper so a fourth config never needs a fourth copy.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
grid.Row#afterSetWindowId (new) container.Base:319 cascade contract; #9107 precedent pooled cell components take the row's new windowId none needed — cells without window-scoped state ignore it Row docblock line 36 (component management) popup arm: 8/8 canvases in the popup carry its id and offscreenRegistered: true

Decision Record impact

none.

Acceptance Criteria

  • Unit, red-first: a grid.Row holding a component cell receives windowId = 'w2'; the cell reports 'w2' (today it keeps the old value).
  • Unit: a Neo.component.Canvas cell in a moved row resets offscreenRegistered and re-registers with the new windowId on remount (spy on transferCanvasToWorker, assert its windowId argument).
  • Headed: the popup-birth arm's data-pane test passes — every canvas inside the popup pane carries the popup windowId, is mounted and offscreenRegistered: true, and the Live column is drawn in the popup.
  • No behaviour change for single-window grids.

Out of Scope

  • The grid view's scroll model resetting on remount (view scrollTop 126 → 0 and body startIndex 2 → 0 in the same arm) — a separate finding, reported on D#18224.
  • The owner-design work on D#18224.

Avoided Traps

  • Fixing it at the column (column/Base#afterSetWindowId only inserts theme files) or in the body: the row owns the pool and already forwards lifecycle configs.
  • Re-creating cells on a window change: instance permanence is the point of the hop.

Related

  • #9107 (the mounted twin) · D#18224 (popup-birth arm receipt) · #18276 (same arm, same session) · ADR 0029 §2.6.

Live latest-open sweep: the latest 20 open issues (created-desc, 2026-09-04 14:49Z) — none equivalent (#18286 … #18064). Issue search "grid row windowId cell component canvas window" (all states) — only #15252, unrelated. A2A claim sweep (all read-states, last 60 min): no claim on the grid or canvas surface. Memory Core sweep: #9107's mounted fix (2026-02-11) is the precedent; no windowId twin recorded. Own-assignment sweep: none.

unowned-rationale: a four-line engine fix with a red-first unit; claimable by any seat — my seat stays in peer-role on D#18224, and the consumer surface where it shows (Workstation pop-out) is Vega's.

Origin Session ID: ee061205-8271-4458-b73b-b132c1bec310

Retrieval Hint: query_raw_memories("grid Row afterSetWindowId cell components sparkline canvas blank popup offscreenRegistered")