LearnNewsExamplesServices
Frontmatter
id19243
titleA still pointer never gets the target popup's drop zones after the park
stateClosed
labels
bugaidashboard
assigneesneo-opus-ada
createdAtSep 26, 2026, 10:02 AM
updatedAtSep 26, 2026, 10:48 AM
githubUrlhttps://github.com/neomjs/neo/issues/19243
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 26, 2026, 10:48 AM

A still pointer never gets the target popup's drop zones after the park

neo-opus-ada
neo-opus-ada commented on Sep 26, 2026, 10:02 AM

Context

Operator goal for 2026-09-26, a v13.2 engine-release blocker owned by @neo-fable: a tab torn out and dragged over another popup must show the target's drop zones, as a native popup drag does. @neo-fable's #19241 fixes where over the target the vessel converts. That region probe also saw preview: null 160 ms after the park settled at every entry sample, with previews only during continuous movement.

ADR 0029 §2.8.6 binds the missing half: "one successful platform settlement must be enough to materialize or retire the target proxy even when the hand stops moving". This ticket is that half.

The Problem

Measured headed on real windows (Chrome, no emulation), on dev@40c1262496. The run used @neo-fable's staging: main at 2632,100 (1320×920), target popup 700×500 at 4300,300, torn-out commits vessel 320×240. The pointer entered from the left void, then stopped:

entry (fx, fy) +160 ms +500 ms +1 s +3 s one 1-px move, +160 ms
0.5, 0.3 converted, engaged, preview: null null null null tab-into, rendered
0.3, 0.3 converted, engaged, preview: null null null null tab-into, rendered
  • A still pointer never gets the drop zones.
  • A release there commits nothing, because DragTarget#onRemoteDrop commits currentPreview.
  • One pixel of motion is the entire difference.

The Architectural Reality

  1. Park settles. TabSortZone#scheduleVesselConversionReplay re-enters DragCoordinator#onDragMove exactly once with the latest frame.
  2. The target is cold when that frame arrives. While the park was transitioning, resolveRemoteDragTransition answered engage: false, so the coordinator never fed the target. Leaving the previous target ran onRemoteDragLeave → Participation#defaultClearPreview, which cleared the affordance geometry.
  3. The first answer is null. DragTarget#onRemoteDragMove → Participation#defaultPreviewFor calls DragAffordances#ensureGeometry() without awaiting it. Its JSDoc documents that the first frames resolve to null while geometry settles. It then reads the synchronous geometry mirror, which is still cold.
  4. Nothing asks again. getDomRect settles and geometry is set, but no later call to previewFor follows unless the pointer moves.
  5. Why no spec caught it. The existing arm "settled initial park re-enters the coordinator without a second external pointer frame" (DockTabSortZone.spec.mjs) stubs the coordinator. It pins that one frame is replayed, never that the frame yields a preview.

The Fix

DragTarget owns its hover state (currentDragPayload / currentPreview), so it owns the one re-drive.

  1. DragTarget.
    • Adds a seam, awaitPreviewable(payload): a thenable resolving true once the owner can answer a frame it could not yet answer; a non-thenable when nothing is pending.
    • Adds answerWhenPreviewable(payload). After a frame answers null, it re-runs previewFor(payload) once, only while that payload is still current: no newer frame, no leave, no terminal, not destroyed. No loop.
    • The error release is extracted to releasePreviewFailure(payload) for both paths.
  2. Participation forwards the seam, with a default defaultAwaitPreviewable(): while the affordance mirror is cold, ensureGeometry() resolves to Boolean(geometry); otherwise null.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
DragTarget config awaitPreviewable ADR 0029 §2.8.6 (one settlement is enough) optional owner seam, (payload) => Promise<Boolean> | non-thenable absent or non-thenable → no re-drive (today's behavior) member JSDoc AC-1–AC-3
Participation config awaitPreviewable + defaultAwaitPreviewable same forwarded to the target; the default waits on the cold affordance geometry host-supplied seam wins member JSDoc AC-1
DragTarget#onRemoteDragMove ADR 0029 §2.3 target contract unchanged synchronous answer, plus one fenced deferred answer after a null — method JSDoc AC-1–AC-3

Acceptance Criteria

  • AC-1 (unit, red-first): one frame on a cold target renders the preview once geometry settles, with no second frame. The drop path holds an accepted preview, and the drop zones render.
  • AC-2 (unit): a newer frame wins, so the settled answer is for the latest pointer (edge-top, not tab-into).
  • AC-3 (unit): a leave, or a destroyed target, before geometry settles leaves no preview and makes no deferred previewFor call.
  • AC-4 (headed receipt in the PR): the stopped-pointer probe at the fix head shows preview and rendered at +160 ms, holding to +3 s, for both entries.
  • AC-5: the dashboard, draggable, manager and workstation unit populations stay green.

Out of Scope

  • Where the vessel converts (#19241, @neo-fable).
  • The source-frame sampling (#19225, PR #19232).
  • The native title-bar path, which awaits its own embodiment settlement.

Avoided Traps

  • Awaiting geometry inside previewFor. It breaks the §2.3 synchronous answer the coordinator and the native path consume.
  • A second coordinator replay after the park. It would duplicate §2.8.6's one-frame contract to cover a target-tier gap.
  • Keeping geometry warm across leaves. It would stale-cache rects across window moves, which the geometry signature exists to prevent.

Decision Record impact

aligned-with ADR 0029 §2.8.6. This implements the "even when the hand stops moving" sentence at the target tier; no amendment.

Related

  • #19225 / PR #19232 and #19241 / PR #19242 are the dock-layout lane (@neo-fable).
  • The ADR 0029 §2.8.6 park replay: DockTabSortZone.spec.mjs, "settled initial park re-enters the coordinator…".

Live latest-open sweep: checked the latest 20 open neo issues at 2026-09-26T08:01:46Z; no equivalent found. A2A: no claim on this symptom. Measurement requested by @neo-fable (MESSAGE:2b872727).

Origin Session ID: 1b945fcf-1142-475f-8007-ac18d51c069a Retrieval Hint: "park replay one frame cold geometry mirror preview null stopped pointer re-drive"

tobiu referenced in commit a9abb6c - "fix(dock): a still pointer gets the target's drop zones after the park (#19243) (#19244) on Sep 26, 2026, 10:48 AM
tobiu closed this issue on Sep 26, 2026, 10:48 AM