LearnNewsExamplesServices
Frontmatter
id15477
titleDashboard popup admission ignores blocked-window failure
stateClosed
labels
bugaitestingregressioncore
assigneesneo-gpt-emmy
createdAtJul 18, 2026, 5:09 PM
updatedAtJul 18, 2026, 5:41 PM
githubUrlhttps://github.com/neomjs/neo/issues/15477
authorneo-gpt-emmy
commentsCount0
parentIssue15239
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 18, 2026, 5:41 PM

Dashboard popup admission ignores blocked-window failure

Closed Backlog/active-chunk-7 bugaitestingregressioncore
neo-gpt-emmy
neo-gpt-emmy commented on Jul 18, 2026, 5:09 PM

Context

The headed portability work in #15243 audits the landed generic dashboard tear-out lineage before QT-docking defaults consume it. Its acquisition contract is explicit: Neo.Main.windowOpen() returns a Boolean, and a blocked popup returns false rather than throwing. During the row-4/row-7 source audit, the generic dashboard path was found to violate that contract.

An exact executable probe against current dev stubbed Neo.Main.windowOpen() to resolve false and invoked Neo.dashboard.Container#openWidgetInPopup. The method still returned a success descriptor and left detachedItems.size === 1 with the widget recorded at index 0. No popup existed.

The Problem

src/dashboard/Container.mjs stages the widget in detachedItems before awaiting Neo.Main.windowOpen(), then discards the Boolean result and always returns popup geometry. Container#onDragBoundaryExit treats that descriptor as successful admission and starts the OS-window pointer-follow embodiment.

A blocked popup therefore creates three coupled false truths:

  1. detachedItems claims a vessel owns the live widget although no window exists;
  2. the sort zone enters window-drag mode and hides/reflows the in-window embodiment;
  3. the caller receives popup geometry and windowName as if admission succeeded.

The catch path cannot catch the primary failure because src/Main.mjs#windowOpen normalizes window.open() to a strict Boolean. The helper is also called by the legacy resumeWindowDrag() path. That mid-gesture close/re-acquire design is being retired by #15396 (park/re-show, zero reacquisition); until that composition lands, the helper must at least fail honestly, preserve any pre-existing detached entry, and never dereference a missing descriptor.

The Architectural Reality

  • src/Main.mjs:544-566 owns popup materialization and returns true only when window.open() produced a Window.
  • src/dashboard/Container.mjs:271-283 owns the generic dashboard's provisional detachedItems bookkeeping and popup request.
  • Container#onDragBoundaryExit owns initial boundary admission.
  • src/draggable/dashboard/SortZone.mjs#startWindowDrag is an effect that must run only after vessel admission. It hides the proxy/placeholder, expands the source layout, and arms Neo.main.addon.DragDrop.
  • The provisional detachedItems write must remain before the remote await to preserve the fast window-connect race, but it must roll back when the Boolean result is false.
  • A helper call may replace a pre-existing detached entry (the legacy resume path). Rollback therefore means restore the prior value when one existed; delete only a newly staged entry.
  • #15396 owns the architectural fix for mid-gesture out-conversion: park and re-show the same vessel, never reacquire. This leaf does not compete with that owner.

This is a production bug in the existing generic dashboard admission owner, not matrix work. #15243 records portability truth; this leaf repairs the falsified initial-admission invariant and makes the legacy caller fail honestly while #15396 removes its premise.

The Fix

Make the dashboard popup admission helper transactional around the Boolean result:

  1. openWidgetInPopup snapshots any prior detachedItems entry, stages the provisional value before the await, captures Neo.Main.windowOpen()'s Boolean, and restores the prior value (or deletes a newly staged entry) when the result is false or the call throws. It returns a popup descriptor only on true and a fail-closed value otherwise.
  2. onDragBoundaryExit recognizes failed admission, clears the container and sort-zone window-drag state, and leaves the live drag in its in-window embodiment. It must not call startWindowDrag.
  3. The legacy resumeWindowDrag caller guards the fail-closed result so it cannot dereference a missing descriptor or arm pointer-follow without a vessel. This is containment only; #15396 remains the owner of eliminating mid-gesture reacquisition.
  4. Add a focused dashboard Container unit witness with false and true controls, provisional-entry rollback, and the guarded legacy caller. The false control must model Boolean false, not throw.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Neo.dashboard.Container#openWidgetInPopup src/Main.mjs#windowOpen Boolean contract Return popup geometry only after true; restore prior bookkeeping on false/throw Fail-closed result, source ownership remains honest JSDoc names Boolean admission Unit false/true + pre-existing-entry controls
Neo.dashboard.Container#onDragBoundaryExit dashboard Container gesture owner Start window drag only after admitted vessel Retain in-window drag with no source mutation Existing method JSDoc extended Sort-zone spy: zero startWindowDrag on false
legacy Container#resumeWindowDrag #15396 successor authority Guard failed helper result; do not claim reacquisition succeeded Preserve prior detached entry; do not arm DragDrop JSDoc points to park/re-show successor False result does not throw or arm

Decision Record impact

Aligned with ADR 0029 §2.8.3's Boolean vessel-admission and fail-closed invariants. Depends on #15396 for the successor park/re-show lifecycle; no ADR amendment in this leaf.

Acceptance Criteria

  • Boolean false from Neo.Main.windowOpen causes openWidgetInPopup to return fail-closed and removes a newly provisional detachedItems entry.
  • When a pre-existing detached entry was present, failed admission restores that exact entry rather than deleting it.
  • Initial boundary exit on Boolean false does not call SortZone#startWindowDrag and restores both container-owned and sort-zone-owned window-drag state.
  • The legacy resume caller does not dereference a failed result or arm Neo.main.addon.DragDrop; #15396 remains the path that removes reacquisition entirely.
  • Boolean true preserves the existing fast-connect-safe ordering and returns the current popup geometry/windowName descriptor.
  • A focused test/playwright/unit/dashboard/Container.spec.mjs suite proves the false/true controls at the real Container method seam.
  • #15243 can cite the repaired leaf when classifying acquisition, object-permanence, and cleanup cells; the matrix itself remains test/document-only.

Out of Scope

QT dock-specific DockTearOut choreography; #15396 park/re-show implementation; popup-over-popup arbitration; headed Windows/Linux receipts; redesigning detachedItems; changing Neo.Main.windowOpen's Boolean API; cloud or external-client validation.

Avoided Traps

  • Catch-only rollback — rejected because blocked popups return false and do not throw.
  • Write detachedItems only after the await — rejected because a successfully opened window can connect before the App-Worker response returns; provisional-before-await plus rollback preserves both race orders.
  • Repair legacy resume by making reacquisition more elaborate — rejected because #15396 already owns the correct park/re-show successor.
  • Absorb the production fix into #15243 — rejected because the matrix ticket explicitly excludes implementation defaults and must remain an honest measurement authority.

Related

Parent epic #15239; discovered by #15243; successor lifecycle #15396; contract authority D#15204 and ADR 0029; generic lineage #7204 / #8160.

Structural pre-flight: the production owner remains src/dashboard/Container.mjs. The new witness follows the existing test/playwright/unit/dashboard sibling suite; no new production module or directory is introduced.

Live latest-open sweep: refreshed immediately before creation; no equivalent found. A2A claim sweep: refreshed immediately before creation; no competing claim found.

Origin Session ID: 019f6981-3a8c-7530-a68b-50a2788698d0

Retrieval Hint: "dashboard Container windowOpen false detachedItems blocked popup fail closed"