Skip to content

fix: a palm on the glass no longer drives the pen's stroke (#338) - #361

Merged
difficcd merged 1 commit into
mainfrom
fix/palm-steals-the-pen
Oct 1, 2026
Merged

difficcd merged 1 commit into
mainfrom
fix/palm-steals-the-pen

Conversation

@difficcd

@difficcd difficcd commented Oct 1, 2026

Copy link
Copy Markdown
Owner

The other half of #338 — "펜이 뚝뚝 끊김 (태블릿 환경에서)". Found by reading the pointer handlers rather than on the device, and the asymmetry it turns on is specific enough to be worth stating plainly.

Cause

A PC has one pointer. So every pointer event arriving at the canvas belonged to the stroke being drawn, and no handler ever had to ask which pointer it came from. A tablet has several: the pen, and the palm resting on the glass beside it.

startDraw refuses to begin a gesture from a touch — the palm rejection that has always been there:

if (e.pointerType === 'touch') return;

But nothing stopped a touch from feeding the gesture already running:

handler guard it had what the palm could do
onDraw if (!gesture.drawing.current) return; its moves were appended to the pen's stroke as points — a jump across the drawing
stopDraw () => { — took no event, so it could check nothing lifting off committed the pen's stroke mid-line

After that early commit the pen kept moving over a gesture that had ended, drawing nothing until it was lifted and put back down. A palm that rests and shifts while you write does this repeatedly: a line in pieces, on a tablet and nowhere else.

Change

The gesture already knew whose pointer it was — pointerId was set by begin() and read by nobody. It is now read, through one predicate in useGesture.ts that both handlers share:

export function pointerIsOwner(active, ownerId, eventId) {
    if (!active || ownerId === null || eventId === null || eventId === undefined) return true;
    return eventId === ownerId;
}

Two things deliberately pass through:

  • Events with no pointer to name. onPointerLeaveCanvas ends a stroke without an event; refusing those would stop leaving the canvas from stopping the stroke.
  • Everything while nothing is in flight. onDraw does its hover work through the same guard, and refusing a pointer there would leave the cursor stale over a selection's resize handles.

stopDraw's parameter is optional for the same reason.

Tests

test/hooks/gesturePointer.test.js, four cases — the owner check, the hover passthrough, the no-event passthrough, and:

  • pointer id 0 is a real pointer, not a missing one — the guard cannot test eventId for truthiness, because a first pointer is legitimately id 0 on some browsers and !0 would hand every stroke to whichever pointer came second. That is the shape this fix would most plausibly come back in.

1284 tests pass; npm run check green (460 helpers indexed).

To try

On the tablet, draw a long line with the pen while resting your hand on the screen — which is the natural way to draw and the condition this needs. The line should stay one unbroken stroke. Dragging a finger across the canvas mid-stroke should now do nothing to it.

This is the stroke break-up half of #338. The pencil-pressure half is #360.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
easy-mv-maker Ready Ready Preview Oct 1, 2026 6:26am UTC

A PC has one pointer, so every event on the canvas belonged to the stroke being drawn and no
handler had to ask which pointer it came from. A tablet has several: the pen, and the palm
resting on the glass beside it.

`startDraw` refused to *begin* a gesture from a touch - the palm rejection that has always
been there - but nothing stopped a touch from feeding the gesture already running:

- `onDraw` checked only `gesture.drawing.current`, so the palm's moves were appended to the
  pen's stroke as points, putting a jump across the drawing.
- `stopDraw` took no event at all, so it could not check anything: the palm coming off the
  glass committed the pen's stroke mid-line. The pen then went on moving over a gesture that
  had ended, drawing nothing until it was lifted and put down again.

Which is a line in pieces, on a tablet and nowhere else - #338's "strokes break up".

The gesture already knew whose pointer it was; `pointerId` was set by begin() and read by
nobody. It is now read, through one predicate both handlers share. Events with no pointer to
name still pass, because onPointerLeaveCanvas ends a stroke without one, and so does every
event while nothing is in flight, because onDraw does its hover work through the same guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@difficcd
difficcd force-pushed the fix/palm-steals-the-pen branch from 40bcd20 to 8d7c2bb Compare October 1, 2026 06:25
@difficcd
difficcd merged commit 8bcf7cd into main Oct 1, 2026
6 checks passed
@difficcd
difficcd deleted the fix/palm-steals-the-pen branch October 1, 2026 06:28

This branch was successfully deployed

1 active deployment
Preview — 8d7c2bb0 Deployed Oct 1, 2026 by vercel[bot]
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