fix(editor): render NodeViewContent first so click hit-testing isn't offset (#146) #147

Merged
vvzvlad merged 4 commits from fix/146-nodeview-content-order into develop 2026-06-24 04:40:59 +03:00

4 Commits

Author SHA1 Message Date
claude_code
8bb441870a test(editor): address PR #147 review — reflow tests, code-block guard, a11y
Resolve the pre-merge review items for the #146 NodeView content-first fix:

- Export collectScrollAncestors/reflowAfterPaste and add editor-paste-handler
  unit tests covering ancestor selection (overlay included, non-overflowing
  auto excluded, X axis), the scrollHeight>clientHeight gate, scrollingElement
  dedup, the docEl==null branch, and the double-rAF nudge.
- Extend the structural guard with CodeBlockView and merge the two it.each
  blocks into one document-order assertion (handles the <pre> nesting where the
  contentDOM is not the literal first child).
- Simplify the post-paste nudge to a single scrollTo(scrollLeft, scrollTop).
- Document that the post-paste reflow runs on every paste path intentionally,
  and cross-reference the two #146 mitigations in both fixes.
- a11y: aria-hidden the decorative footnotes heading and number marker, and
  label the footnotes list via role="group" + aria-label so the visual reorder
  does not break screen-reader reading order (WCAG 1.3.2).
- CHANGELOG: add a Fixed entry noting the caret fix is macOS-verified manually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 04:36:39 +03:00
claude code agent 227
67057de214 test(editor): guard #146 contentDOM-first invariant + fix code-block comment (PR #147 review)
Addresses the #147 review (Approve with comments):
- Add footnote-views.structure.test.tsx: a structural regression guard asserting
  the editable NodeViewContent is the FIRST child of FootnotesListView and
  FootnoteDefinitionView, with no contenteditable=false chrome before it. The
  whole #146 fix rests on this DOM-order invariant; the macOS caret symptom needs
  a real browser, but the order proxy is testable in jsdom. Stubs @tiptap/react
  so the views render as plain DOM — the test passes on the fixed order and fails
  on the pre-fix chrome-first order.
- Reword the code-block-view comment: it claimed a "top-right overlay (the
  transclusion pattern)", but the menu stays fully in flow as a full-width row
  lifted via flex `order: -1` (the .codeBlock wrapper is a flex column). No
  overlay/absolute positioning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:47:29 +03:00
claude code agent 227
1c39a45bc5 fix(editor): reflow scroll containers after paste to refresh click hit-testing (#146)
Pasting markdown/code inserts React NodeViews that mount asynchronously; until
the next reflow the browser's hit-test geometry is stale, so ProseMirror's
posAtCoords/caretRangeFromPoint maps a click to the wrong (offset) line — which
users reported clears itself on any scroll. Reproduce that scroll's side effect
with a ZERO-delta nudge (re-assign scrollTop/scrollLeft to their current value)
on every scrollable ancestor + the document scrolling element, run across two
animation frames so it lands after the pasted content + NodeViews commit. The
nudge does not move the viewport.

Wired into editor-paste-handler's handlePaste, which ProseMirror's someProp runs
(as an editorProps handler) before the MarkdownClipboard plugin that performs the
markdown/code insert — so the nudge is scheduled on exactly the paste path that
triggers the bug. Complements the structural NodeViewContent-order fix in this
branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:37:56 +03:00
claude code agent 227
38544e2ddc fix(editor): render NodeViewContent first so click hit-testing isn't offset (#146)
Three editable NodeViews rendered a contentEditable=false "chrome" element IN
FLOW BEFORE NodeViewContent. On macOS the browser's click hit-testing
(posAtCoords → caretRangeFromPoint) then misses the contentDOM and snaps the
caret to the previous node — the caret/selection lands a line (code block) or
several lines (footnotes, into the body) above where the user clicked.

Fix (the transclusion pattern / issue #146 plan): make the editable
NodeViewContent the FIRST child in the DOM and move the non-editable chrome
AFTER it, restoring its visual position with CSS:

- code-block-view: <pre><NodeViewContent/></pre> first; the language/copy menu
  follows and is lifted above via flex `order` (.codeBlock is now a flex column).
- footnotes-list-view: NodeViewContent first; the "Footnotes" heading follows and
  is lifted above via flex `order` (.list is a flex column; the separator border
  stays on the container).
- footnote-definition-view: NodeViewContent first; the "N." marker follows with
  `order:-1` to stay on the left; the ↩ back-link stays on the right.

Layout is visually unchanged. Verified in a real browser (Chromium): the
contentDOM is now the first child of every editable NodeView wrapper (no
contentEditable=false element precedes it), and the menu/heading/marker still
render in their original positions.

NOTE: the caret-offset itself is macOS-specific text hit-testing and does not
reproduce in headless Chromium/WebKit on Linux (verified extensively), so the
visible fix is best confirmed on macOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:48:21 +03:00