Skip to content

fix(board): stop phantom text node when dbl-clicking custom node title#146

Merged
winlp4ever merged 2 commits into
mainfrom
fix/title-dblclick-text-node
Jun 21, 2026
Merged

fix(board): stop phantom text node when dbl-clicking custom node title#146
winlp4ever merged 2 commits into
mainfrom
fix/title-dblclick-text-node

Conversation

@winlp4ever

@winlp4ever winlp4ever commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Dbl-clicking a custom node's title (sheet, code-sandbox, widget, mini-app, folder, document, icon, image) spawned a stray text note on the canvas. The title is positioned absolute below the node — outside canvas-harness's hit-test rect — so the native dblclick saw it as empty space and fell through harness-canvas's "create text node" branch.

What's in the diff

  • New hook `useStopCanvasDblClick` in use-stop-canvas-gesture.ts — sibling to the existing pointerdown stop. Stops native dblclick on the given element so the canvas-harness wrap never sees it.
  • NodeTitleCaption — added a stable `wrapperRef` on the outer `
    ` and bound both stops there. Replaces the previous calls bound to `buttonRef` and `inputRef` (those refs swap on `editing`, and `useEffect` deps don't track `ref.current` mutations, so binding to a conditionally-rendered ref only caught one of the two states).
  • Removed dead `buttonRef` and updated stale docstring.

Why `useStopCanvasDblClick` is a SEPARATE hook

React 17+ delegates events at the root container. A native `stopPropagation` below the root also blocks React's synthetic `onDoubleClick` on any descendant. The sheet body relies on its React `onDoubleClick` to open the inline editor (sheet/view.tsx:155) — so we can't blanket-stop dblclick everywhere.

The split lets each callsite pick its level of stopping:

  • Bodies (code-sandbox, sheet, etc.) — pointerdown only. React onDoubleClick still fires.
  • Title caption — pointerdown + dblclick. The descendant button/input have only `stopPropagation` in their React onDoubleClick — nothing useful is lost.

The new hook's docstring spells out when it's safe to use.

Why the wrapper, not the button/input

`editing` flips between `` and ``. The buttonRef is set on first render; the inputRef is null at first render (since the input isn't mounted yet), and `useEffect` deps `[ref]` don't re-trigger when the input later mounts. So the input never got a listener — and the user's double-click resolves on the input by the time the second click lands. The wrapper `

` is mounted for the component's lifetime, so events from either child bubble through it reliably.

Test plan

  • `npm run type-check` clean
  • `npm test --run` — 320/320 green
  • Dbl-click a custom node's title (try sheet, code-sandbox, widget, mini-app, folder, icon, image) → no phantom text node spawns
  • Single-click the title → enters edit mode (unchanged)
  • Dbl-click inside the title's input (word-select) → browser default still works
  • Dbl-click the sheet body → inline markdown editor still opens (this is the regression the refactor specifically protects against)
  • Dbl-click code-sandbox body → opens the panel (unchanged; uses onClick, not onDoubleClick)
  • Dbl-click empty canvas → still creates a text node (unchanged)

Dbl-clicking a custom node's title (sheet, code-sandbox, widget, etc.)
spawned a stray text note on the canvas. The title sits OUTSIDE the
node's canvas-harness hit-test rect (positioned absolute below the
body), so canvas-harness's native dblclick listener saw the click as
empty-space and fell through harness-canvas's "create text node" branch.

Stop the native dblclick at the title's wrapper div so it never reaches
canvas-harness. The wrapper is chosen specifically because it's the only
parent that stays mounted across edit-mode transitions — `useEffect`
deps don't track `ref.current` mutations, so binding to the button or
input (which swap on `editing`) would only catch one of them.

A new `useStopCanvasDblClick` hook holds the native stop, mirrored from
the existing `useStopCanvasGesture` (pointerdown). It's a separate hook
on purpose: blanket-stopping dblclick on a body ref would also block
React's synthetic onDoubleClick on the same element (React 17+ root
delegation), which would break sheet's inline editor entry. The new
hook is only safe on subtrees with no meaningful React onDoubleClick —
the docstring spells this out.
@winlp4ever winlp4ever merged commit bcdb0ea into main Jun 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant