LearnNewsExamplesServices
Frontmatter
id18115
titleThe hold is the gesture: a native titlebar drop fills the drop area over the dwell, commits on completion, and closes the popup after
stateClosed
labels
enhancementaicoredashboard
assigneesneo-opus-vega
createdAtSep 2, 2026, 5:36 PM
updatedAtSep 2, 2026, 6:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/18115
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 2, 2026, 6:34 PM

The hold is the gesture: a native titlebar drop fills the drop area over the dwell, commits on completion, and closes the popup after

Closed v13.2.0/archive-v13-2-0-chunk-18 enhancementaicoredashboard
neo-opus-vega
neo-opus-vega commented on Sep 2, 2026, 5:36 PM

Context

The native-titlebar drop is geometry-only: no pointer event reaches the page while the OS drags a popup, so the coordinator infers intent from the popup's position stream (dwell 450 ms on one claimed target + settle 250 ms), and there is no release event at all. #18112 (PR #18114) repairs the main-window case, where the park's focus step could never be granted, by making the move the strict gate: the drop then lands when the user releases, because the owner-routed move is refused while the OS holds the window and succeeds the instant it lets go.

That makes the drop work. It does not make it feel right, and the operator chose a different interaction on 2026-09-02 (in session, after reading the live receipts together):

the hold is the gesture — the drop area fills up while the popup rests on it, the transfer commits when the fill completes, the popup closes afterwards; no popup placeholder text; if the platform refuses the close while the titlebar is still held, even better — the close that finally succeeds tells us indirectly when the user dropped, and nothing visible waited on it.

Two facts frame the design. Nothing in the target's landing needs the OS: the transfer is app-side and the embodiment renders in the target regardless of focus. And the only platform verb left, the close, runs after the pane is already on screen.

The Problem

  • During the hold the user gets no feedback that waiting does anything: the highlight says "you may drop here", nothing says "keep holding and it docks".
  • The landing is tied to the release, which we cannot see. With PR #18114 it arrives at the next park retry after release — up to the current backoff step late.
  • The main-target path still runs a park (move the popup behind main) that exists only so a rejected drop can restore the popup; the target has already accepted before the timer completes, so the park buys nothing there.

The Architectural Reality

  • src/manager/DragCoordinator.mjs — onWindowPositionChange arms/refreshes the candidate (firstSeenAt, dwellRemaining, one setTimeout for commitNativeWindowDrop); updateNativeHover calls the target's onRemoteDragMove({embodyProxy: false, …}) on every geometry update; commitNativeWindowDrop → suspendWindowDrag (true strict park, false refusal → retryNativeWindowPark, any other answer non-strict → straight to onRemoteDragMove({embodyProxy: true}) → awaitRemoteDragEmbodiment → handoff nativeWindowDropHandoffMs 180 → onRemoteDrop → settleNativeWindowDisposition(committed) → the source's onRemoteDropOut); settleNativeWindowDisposition retries close/restore with nativeWindowDispositionRetryMs doubling.
  • apps/workstation/view/Workspace.mjs — suspendNativeWindowDrag (main realm) → nativeVesselParkHandlers.onConversionIn → parkTearOutVessel; retireNativeWindowDrag → onGestureTerminal → Neo.Main.windowNativeClose.
  • src/Main.mjs#windowNativeClose — entry.win.close() then return true, unverified; #focusWindow is the verified-Boolean pattern (poll the platform, answer what happened).
  • src/dashboard/dock/interaction/Preview.mjs — the previewed drop region: neo-dock-preview-affordance, neo-dock-preview-region, neo-dock-preview-accepted; painted by resources/scss/src/dashboard/dock/interaction/Preview.scss with a border colour and a much lighter background. src/dashboard/dock/interaction/Rail.mjs#revealDwellMs is the precedent for a dwell as a named, overridable constant.
  • test/playwright/e2e/workstation/WorkstationNativeTitlebarDragNL.spec.mjs (popup → main) and WorkstationNativePopupOverPopupNL.spec.mjs (popup → popup) drive geometry with CDP and poll for the commit; headless grants focus and close freely, so neither can witness a human hold.

The Fix

1. Dwell is the commit, and it is visible.

  • nativeWindowDropDwellMs 450 → 1200, configurable; the rehearsal tunes it. nativeWindowDropSettleMs 250 stays: the timer runs only on a still popup over a claimed zone, so passing over a zone never triggers anything.
  • The coordinator hands the target the timer with the hover: onRemoteDragMove gains dwell: {armedAt, durationMs} (from firstSeenAt and the constant) while a native candidate is armed, and onRemoteDragLeave ends it.
  • Affordance (operator's choice): the previewed drop area fills — its light background rises toward the border colour over exactly the dwell duration. Implemented as a CSS transition on the region driven by a --dock-native-dwell-ms custom property the preview publishes from the payload, so paint and commit share one number. Leaving the zone drains the fill in ~150 ms.
  • The fill is a SHAPE, not only a colour — a growing area — so the cue survives colour-vision deficits without a second channel (the hover contract keeps the popup visible during the hold, embodyProxy: false, so there is no proxy to ramp). prefers-reduced-motion shows the armed state as a static tint. Other ideas stay open for the rehearsal: a border sweep around the region’s perimeter, a fill that starts at the edge nearest the popup.

2. Main-window target: commit, then close. No park.

  • For a main-window target the Workstation’s strict park answers satisfied without a platform effect: parkTearOutVessel (nativeTitlebar && targetIsMain) returns true after the source-route authority check, writes parked: true, physical: false to the receipt, and never asks for focus or a move. The conversion bookkeeping around the park handler (VesselPark.onConversionIn) runs exactly as for a parked vessel, so the coordinator’s existing strict branch embodies, hands off and commits transferItem into main while the user may still hold the titlebar: the pane appears at the previewed slot the moment the fill completes. (Measured first with a null "no park needed" answer to the coordinator: that skipped the bookkeeping and the embodiment never settled — the NL spec ended with an empty candidate map and no receipt.)
  • The popup is retired through the owner handle. windowNativeClose becomes verified-Boolean like #focusWindow (poll win.closed briefly; answer what happened). If the platform defers the close while the titlebar is held, the disposition retry re-attempts at a fixed ~250 ms cadence (no backoff — the release is expected within seconds), bounded at 20 s; the close that succeeds is the indirect release signal. The popup shows its empty vessel meanwhile — no placeholder text (operator).
  • A lastNativeRetireReceipt records closeAttempts, closedAt, deferredWhileHeld, so a hand test is readable through the Neural Link.
  • PR #18114’s best-effort-focus branch was scoped to the same conjunction (targetIsMain && nativeTitlebar) that the satisfied park now returns on, so it became unreachable and is REMOVED by this ticket’s PR rather than left dead (Grace’s and Mnemosyne’s reading); #18114’s tearOutParkAttempts accounting stays, and the satisfied park resets it like a landed park. Popup targets keep the physical park.

3. Popup target: mechanics unchanged, same affordance and dwell constant. The strict park (windowNativeFocus on the handle) keeps working there.

4. Witnesses.

  • Unit, Workstation: a native-titlebar park onto the main window resolves true with zero platform calls (focus and move stubs refuse and are never reached), receipt {parked: true, physical: false}, source route still checked — red on dev (the park dies at focus); a popup target keeps the physical park (existing arm, retargeted to a popup). Unit, coordinator: the hover payload carries dwell with the constant and a stable armedAt across frames — red on dev.
  • Unit, Main.mjs: windowNativeClose returns false while win.closed stays false, true once it flips — red-first (today it returns true unconditionally).
  • Component (dock preview fixture): an armed native candidate stamps the fill on the previewed region with the dwell duration; leaving removes it; prefers-reduced-motion yields the stepped variant.
  • Both NL specs green under the Brain root at the new dwell (their polls allow it).
  • Hand test, operator-owned Post-Merge, three runs: hold still over main — the area fills over ~1.2 s, the pane lands, the popup vanishes (at once, or at release); release before the fill completes — the pane still lands ~1 s after the popup stops; pass over main without stopping — nothing triggers.

Acceptance Criteria

  • AC-1 A still popup over a claimed zone fills the drop area (a rising fill) over the dwell duration and drains on leave; reduced motion shows a static armed tint. Component witness.
  • AC-2 With the main window as target, dwell completion commits transferItem into main with no release, focus or move required; the park answers satisfied without a platform effect and the receipt records it. Unit witness red-first on dev.
  • AC-3 The popup retires through the owner handle with a verified close; a deferred close is retried at a fixed cadence and succeeds at release; the retire receipt records attempts. Unit witness for the verified close red-first.
  • AC-4 Popup-over-popup keeps its strict park path; WorkstationNativePopupOverPopupNL and WorkstationNativeTitlebarDragNL green under the Brain root at the new dwell.
  • AC-5 Dwell 1200 ms and the fill duration come from one published constant; unit/manager + component/dashboard green.
  • AC-6 Operator hand check (three runs above) passes on the Workstation. Post-Merge, operator-owned.

Out of Scope

  • The keyboard-command focus return path — unchanged.
  • Pointer-path cross-window drags (#17578).
  • A mid-drag cancel after the commit; the user tears out again.
  • Reverting PR #18114’s parkAttempts accounting — it stays; its main-target focus relaxation is superseded here, not layered.

Avoided Traps

  • Gating the visible result on a platform verb. Focus is never granted on this path and the move is refused while held. The transfer needs neither; only the close touches the OS, and it runs after the result is already on screen.
  • Two clocks. The paint and the commit read one constant; a CSS duration typed by hand drifts from the coordinator's timer the first time someone tunes one of them.
  • A colour-only timer. The fill is a rising AREA, not only a hue, so the cue survives colour-vision deficits (WCAG 1.4.1); the hover contract keeps the popup visible during the hold (embodyProxy: false), so there is no proxy to ramp alongside it.
  • Backoff on the close retry. After release the user expects the popup gone within a frame or two; exponential backoff would leave it hanging for seconds. Fixed cadence, bounded.
  • Reading a green rig as proof of the human path. Headless grants focus and close freely; the witnesses stub the refusals, and the hand test stays owned.

Related

  • #18112 / PR #18114 — the main-target park fix this layers on (Mnemosyne).
  • #18054 / PR #18055 — the park retry.
  • #18059 — popup-over-popup native drop (kept as is).
  • #17578 — the pointer-path coordinator defect.

Live latest-open sweep: checked the latest open issues at 2026-09-02T15:14Z and the queue again at 15:30Z (newest #18112, PR #18114); keyword sweeps dwell spring drop affordance, native titlebar drop commit close, drop area fill hold — no equivalent. A2A: Mnemosyne holds #18112 with PR #18114; this ticket is announced to her as the layer above it.

Origin Session ID: 87c91db1-7fcd-4987-9932-2bf78d3dda25

Retrieval Hint: "hold is the gesture native titlebar drop dwell fill commit then close spring-loaded dock"

tobiu referenced in commit 7402506 - "feat(dock): the hold is the gesture: a native titlebar drop fills the drop area over the dwell, commits, then closes the popup (#18115) (#18119)" on Sep 2, 2026, 6:34 PM
tobiu closed this issue on Sep 2, 2026, 6:34 PM