refactor(web): shared createDragReorder primitive owning DOM drag plumbing #126
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/deckle!126
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/124"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #124
What
The drop verdicts were already pure and unit-tested (
classifyDrop,resolveDrop); the DOM event plumbing around them was hand-wired per pane. This PR extracts that skeleton into one shared Solid primitive and adopts it in the three genuine reorder sites, plus the two cleanups folded into the issue:web/src/dragReorder.ts—createDragReorder<Drag, Spot>owns the drag + drop-spot signals, the dragover guard/preventDefault/dropEffect="move"invariant (spot and accept are independent, so PagePane's "forbidden" indicator still renders without accepting), keyed dragleave clearing with the real-browser child-crossing anti-flicker guard, and clear-then-act drop/dragend. Zone geometry and verdicts stay pane-side as callbacks. 12 unit tests.ownspredicates; read-only gating unchanged; theverdictFor/verdictFor2duplication dies because the primitive hands the drag value to the drop action.onClear) and drop-time zone recompute stay pane-owned; mark dedupe preserved viasameSpot.relatedTargetanti-flicker dragleave is absorbed by the primitive; the inline no-op guard and drop-time recompute survive.web/src/dropzone.ts— the single midpoint/25-50-25/mode-degradation band-math implementation (was duplicated asrowDropZonein elementcrud anddropZonein pagemanage). Thresholds unchanged; the only reconciliations are the exact-75%-boundary tie-break (adopting pagemanage's pinned>=) and keeping the degenerate zero-height-rect boundary in the middle zone (which the jsdom wiring suites rely on) — both spelled out in a comment.web/src/test/dragsim.ts— the one jsdom drag-simulation helper (incl.FakeDataTransfer+permittedDropEffects); the four per-file copies are deleted, all suites pass with unchanged assertions.MediaModule and MediaPicker are deliberately not converted (whole-target folder move / upload sinks — not the reorder pattern).
Verification
tsc --noEmitclean.go vet ./...+go test ./...(hugo on PATH,web/distbuilt): all green.effectAllowed/dropEffectonly in the primitive (+ out-of-scope MediaModule); band math only indropzone.ts; onefireDrag.harness serve, content created through the UI): drag indicators appear and moves land in all three panes (tree:[alpha, beta]→[beta, alpha]confirmed via/api/tree; page elements[Text, Quote]→[Quote, Text]confirmed in the content file; card items[First, Second]→[Second, First]). Zero console errors.The smoke test surfaced one pre-existing quirk (reproduced bit-for-bit on a
mainbuild, so not from this PR): PagePane drops landing in a row's before/after edge zone can silently no-op because the gap slots collapse when drag state clears and the drop-time zone recompute then reads the shifted rect. Gap-slot drops — the primary affordance — land correctly. Filed separately.🤖 Generated with Claude Code
Landing audit — #126
Verdict: CONCERNS (non-blocking), merged on maintainer go-ahead.
Verification signal relied on
ci / build-and-test— success (6m2s). Runs unconditionally on everypull_request; coversnpm ci && npm run build && npm test, thengo vet ./... && go test ./... && go build. Not re-run.tsc --noEmit— run during this landing, exit 0. CI does not typecheck:npm testis barevitest run, andvite buildstrips types without checking them. This was the one gate nothing vouched for, so it was closed here rather than assumed.Mechanical checks
Closes #124present and well-formed, no conflicts againstmain.dropzone.ts;dropEffect/effectAllowedonly in the primitive (plus out-of-scope MediaModule); exactly onefireDrag; no pane declares its own drag/spot signals.preventDefaultin drop handlers preserved one-for-one across all three panes.Behavior deltas found (all accepted)
into→after. OldrowDropZoneused strict>; the unifieddropZoneuses>=. For a container row attop=100, height=40,clientY=130previously dropped inside the block and now drops after it. Disclosed in the PR body and pinned in a code comment. No test covered PagePane's boundary (old cases were 105/120/135), so the flip was silent.rel === 0flippedafter→into, via the new&& rel > 0guard. Unreachable in a real browser — for any non-zero-height rectrel === 0resolves in thebeforebranch. jsdom-only.reorder.leaveextends FormPane's anti-flicker fix to them. An improvement — but the one delta not disclosed in the PR body.Deltas 1 and 3 fall under issue #124's stated out-of-scope ("zone thresholds, indicator visuals"). Accepted as the unavoidable cost of unifying two implementations that genuinely disagreed at one boundary pixel: consolidation requires picking a winner, and the choice was documented rather than hidden.
Verified clean (no divergence)
verdictFor/verdictFor2collapse (all six call sites; the old!fromfallback was dead code) · read-only gating, handler-for-handler · all fourownspredicates vs. the oldclear*helpers · TreePane's auto-expand timer, cancelled in all four cases (dragLeavestill cancels explicitly before the anti-flicker early-return) · TreePanesameSpotdedupe · FormPanerelatedTargethandling incl. the null case ·dropEffect/preventDefaultaccept-gating across all three panes.Note
The PR body states the pre-existing PagePane edge-zone no-op quirk was "Filed separately" — no such issue exists on the tracker.