fix(editor): preserve tasks in cross-note drags#693
Conversation
WalkthroughAdds block-handle drag clipboard serialization that preserves ProseMirror slice metadata, integrates it into ChangesBlock drag clipboard flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
apps/desktop/src/editor/block-drag-clipboard.test.tsx (1)
63-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering mount-retry and cleanup paths.
Tests exercise the handled/unrelated dragstart and handled dragend cases well, but the RAF-based mount-retry (
editor.mounted === false) and listener cleanup on unmount aren't exercised, nor is a dragend on a non-handle target (to confirmdraggingis left untouched).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/editor/block-drag-clipboard.test.tsx` around lines 63 - 140, Extend the BlockDragClipboard tests to cover the missing lifecycle paths: verify that when editor.mounted is false, the dragstart handler retries via requestAnimationFrame and processes the handle drag after mounting; verify unmount removes the dragstart/dragend listeners so subsequent events have no effect; and verify dragend from a non-handle target leaves editor.view.dragging unchanged. Use the existing renderBridge setup and mock RAF behavior as needed, restoring mocks during cleanup.apps/desktop/src/editor/block-drag-clipboard.tsx (1)
45-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer a runtime type guard over the assertion.
event as DragEventbypasses type-checking; sincewrapper.addEventListeneris typed generically (Element, notHTMLElement), aninstanceof DragEventcheck would satisfy the same narrowing without an assertion.♻️ Proposed refactor
const handleDragStart = (event: Event): void => { - const dragEvent = event as DragEvent - if (dragEvent.dataTransfer === null) { + if (!(event instanceof DragEvent) || event.dataTransfer === null) { return } + const dragEvent = event if (!isBlockHandleEvent(event)) { return }As per coding guidelines, "Avoid type assertions unless they are genuinely necessary."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/editor/block-drag-clipboard.tsx` around lines 45 - 52, In handleDragStart, replace the event as DragEvent assertion with an instanceof DragEvent runtime check before accessing dataTransfer, returning early for non-drag events; retain the existing null check and isBlockHandleEvent validation.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/desktop/src/editor/block-drag-clipboard.test.tsx`:
- Around line 63-140: Extend the BlockDragClipboard tests to cover the missing
lifecycle paths: verify that when editor.mounted is false, the dragstart handler
retries via requestAnimationFrame and processes the handle drag after mounting;
verify unmount removes the dragstart/dragend listeners so subsequent events have
no effect; and verify dragend from a non-handle target leaves
editor.view.dragging unchanged. Use the existing renderBridge setup and mock RAF
behavior as needed, restoring mocks during cleanup.
In `@apps/desktop/src/editor/block-drag-clipboard.tsx`:
- Around line 45-52: In handleDragStart, replace the event as DragEvent
assertion with an instanceof DragEvent runtime check before accessing
dataTransfer, returning early for non-drag events; retain the existing null
check and isBlockHandleEvent validation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 365683a2-4466-40ac-8be2-a4659cc76e44
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/desktop/package.jsonapps/desktop/src/editor/block-drag-clipboard.test.tsxapps/desktop/src/editor/block-drag-clipboard.tsxapps/desktop/src/editor/note-editor.tsx
ocavue
left a comment
There was a problem hiding this comment.
Let me re-implement this in the meowdown side.
Problem
Each day in the daily stream owns a separate Meowdown/ProseMirror editor. ProseKit's six-dot block handle exports rendered block HTML, but not the
data-pm-sliceenvelope that a different editor needs to recognize it as native content. Dropping a round task such as+ [ ] Taskonto another day therefore ran the task DOM through Meowdown's foreign-HTML converter and produced a literal\[ ]paragraph followed by detached task text. The saved note no longer contained or indexed a task.This change preserves the existing drag semantics: reordering inside one editor is still a move, while a drop between editor views is still a copy. It fixes the reported content corruption without adding cross-note source deletion.
Before -> After
\[ ]; task text becomes a separate paragraph+marker surviveChanges
BlockDragClipboardinside the editor's ProseKit context. Its bubblingdragstartlistener runs after ProseKit has selected the block, reuses ProseMirror's transformed slice metadata, and writes lossless HTML plus Markdowntext/plainto the transfer.+marker; the normal native-list serializer would normalize it to a square- [ ]checklist.dragend. The handle lives outside the contenteditable, so ProseMirror's own cleanup does not see cross-editor or canceled handle drags.+ [ ] Tasksurvives exactly, isolate unrelated drags, and cover delayed mount plus listener cleanup.Verification
pnpm --filter @reflect/desktop test --run src/editor/block-drag-clipboard.test.tsx src/editor/note-editor.test.tsx— 38 tests passed.pnpm check— typecheck and lint passed. The existing max-lines warnings ingraph-provider.tsxandindexer.tsremain unchanged.Risk / Rollout
No migration or rollout step is required. The listener is scoped to ProseKit's block-handle custom element, and the regression covers the main isolation boundary. Cross-editor drops intentionally remain copies; implementing true cross-note moves would require destination acknowledgement before safely deleting the source.