LearnNewsExamplesServices
Frontmatter
id12895
titleMigrate tab/table/dashboard SortZones to processDragEnd override-point
stateClosed
labels
enhancementairefactoringarchitecture
assigneesneo-gpt
createdAtJun 11, 2026, 12:14 PM
updatedAtJun 21, 2026, 2:16 AM
githubUrlhttps://github.com/neomjs/neo/issues/12895
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 12, 2026, 2:45 PM

Migrate tab/table/dashboard SortZones to processDragEnd override-point

Closed v13.1.0/archive-v13-1-0-chunk-1 enhancementairefactoringarchitecture
neo-opus-grace
neo-opus-grace commented on Jun 11, 2026, 12:14 PM

Context

Surfaced by the cross-family review of PR #12892 (@neo-opus-grace) and confirmed + refined by @neo-fable (author/lead). #12892 renamed Neo.draggable.container.SortZone#onDragEndprocessDragEnd and made onDragEnd a synchronous re-entry latch routing exactly one drag:end delivery into processDragEnd. The grid header-toolbar SortZone was migrated to the new override-point; three sibling subclasses were not, leaving the base-class override-point contract followed by only 1 of 4 subclasses.

The Problem

Post-#12892 the base container/SortZone contract is: onDragEnd = the re-entry latch (a drag:end fans out across owner + child items → multiple deliveries; the latch admits one), processDragEnd = the drop logic subclasses extend. Three subclasses still override the legacy onDragEnd and call super.onDragEnd:

  • src/draggable/dashboard/SortZone.mjs:262 (+ self-invokes me.onDragEnd({}) at :312 / :349)
  • src/draggable/tab/header/toolbar/SortZone.mjs:40
  • src/draggable/table/header/toolbar/SortZone.mjs:58

Not a current regression (these were un-latched on dev before #12892 too, and super.onDragEnd → latch → processDragEnd still runs the base logic), but the consequences:

  • They lose re-entry-latch protection for their OWN override logic — the double-execution the latch exists to prevent remains possible for tab/table/dashboard.
  • The "subclasses extend processDragEnd" contract is violated by 3 of 4 subclasses → maintenance hazard (the next SortZone subclass copies a sibling's onDragEnd override and perpetuates the drift).

The migration is not uniform: tab/table are clean mechanical renames; dashboard is a redesign (see Architectural Reality).

The Architectural Reality

  • Base (src/draggable/container/SortZone.mjs, post-#12892): onDragEnd(data) = latch (dragEndActive flag + try/finally → me.processDragEnd(data)); processDragEnd(data) = the drop pipeline.
  • tab / table (tab/header/toolbar/SortZone.mjs:40, table/header/toolbar/SortZone.mjs:58): both override onDragEnd and await super.onDragEnd(data) — a clean mechanical rename (onDragEndprocessDragEnd, super.onDragEndsuper.processDragEnd), mirroring the rename already applied to grid/header/toolbar/SortZone.mjs in #12892.
  • dashboard (dashboard/SortZone.mjs): overrides onDragEnd (:262, super.onDragEnd at :273) and self-invokes me.onDragEnd({}) at :312 / :349 as window-drag CLEANUP. A naive rename would route those cleanup self-calls through the base latch, changing their semantics. The redesign question (per @neo-fable, latch author): should the cleanup route through the public entry at all? Three candidate shapes (fable's framing, prior = (c)):
    • (a) self-calls target processDragEnd directly — deliberate latch bypass, cleanup always runs. Risk: re-opens double-execution for cleanup racing a real drag-end.
    • (b) the latch carries an owner-token so the holder's own nested calls pass. Cost: machinery for a single caller.
    • (c) extract the cleanup subset the :312/:349 self-calls actually need into its own method and call that. Rationale: the self-calls likely never wanted the full drop pipeline, just the restore subset. Whoever executes MUST verify which subset :312/:349 depend on first.

The Fix

Two separable units (one ticket, split ACs — execution may split across PRs):

  1. Mechanical (tab + table): rename onDragEndprocessDragEnd, super.onDragEndsuper.processDragEnd. No behavior change; the base latch now wraps their drop logic.
  2. Dashboard redesign: resolve the self-call × latch interaction per shape (a)/(b)/(c), after verifying the :312/:349 cleanup dependency subset.

Context (#12894): the DeltaUpdates.moveNode Chromium-moveBefore heal now lives in the moveNode path dashboard's window-drag flows traverse — relevant when validating the dashboard cleanup behavior.

Decision Record impact

none — no ADR governs the SortZone override-point contract; this aligns the subclasses with the code-level contract #12892 established.

Acceptance Criteria

  • tab/header/toolbar/SortZone overrides processDragEnd (not onDragEnd) and calls super.processDragEnd; behavior unchanged.
  • table/header/toolbar/SortZone overrides processDragEnd (not onDragEnd) and calls super.processDragEnd; behavior unchanged.
  • dashboard SortZone: the :312 / :349 cleanup self-call dependency subset is identified (documented in the PR) before a shape is chosen.
  • dashboard SortZone: the latch × self-call interaction is resolved (shape a/b/c) such that (i) drag:end double-delivery is latched and (ii) the window-drag cleanup self-calls run their intended subset without racing a real drag-end.
  • No onDragEnd override remains outside the base latch across the 4 container/SortZone subclasses (grid already done).
  • Existing draggable SortZone specs green (test/playwright/unit/draggable/container/SortZone.spec.mjs + the tab/table/dashboard drag paths).

Out of Scope

  • The grid SortZone (already migrated in #12892).
  • The DeltaUpdates.moveNode Chromium-moveBefore heal hardening (that's #12894's own follow-up: scroll-preservation + gating).
  • Any change to the base latch mechanism itself.

Related

  • #12892 (introduced the override-point rename + latch; grid migrated) · #12894 (the #12883 reopen follow-up; moveNode context for dashboard) · #12883 (the grid DnD ticket the parent work resolves).
  • Review provenance: PR #12894 review PRR_kwDODSospM8AAAABCsYyng + #12892 review PRR_kwDODSospM8AAAABCr-KvQ.

Release classification: post-release (Approve+Follow-Up follow-up — non-blocking).

Live latest-open sweep: checked latest 20 open issues at 2026-06-11T10:12Z; no equivalent found.

Origin Session ID: 0bc22bf2-9ded-4cdb-b351-182c3ab1d16f

Retrieval Hint: query_raw_memories("SortZone onDragEnd processDragEnd override-point migration dashboard latch self-call")

Authored by Claude Opus 4.8 (Claude Code), @neo-opus-grace.

tobiu referenced in commit 56bdbdf - "refactor(draggable): align SortZone drag-end overrides (#12895) (#12989)" on Jun 12, 2026, 2:45 PM
tobiu closed this issue on Jun 12, 2026, 2:45 PM