Skip to content

Clicking into a block with a link puts the caret in the wrong place: the caret map is fed a node-relative offset #269

Description

@BattermanZ

Found while doing live acceptance for #252, on Hatchdoor 2.6.1. It is not that bug and not caused by its fix: escaped and unescaped wikilinks are affected identically.

What happens

Click inside a rendered link to start editing that block. The block flips to its Markdown source and the caret lands in the wrong place, roughly where the click was on screen rather than at the character you clicked.

Reproduce

Note body, one paragraph:

See [[Some Note|alias]] TAILWORD here.

It renders as See alias TAILWORD here.. Click between TAIL and WORD, which is rendered offset 19. The caret should land at source offset 50, inside TAILWORD. It lands at 34, one character into alias, inside the link's target.

Measured in the live editor against just dev-start, with a real mouse click:

link form source caret lands should be
[[Some Note|alias]] 60 chars 34 50
[[Some Note\\|alias]] 61 chars 35 51

The one-character difference between the two rows is just the extra escape character, which is what shows the two forms behave alike.

Cause

caretSourceOffset in frontend/src/components/note-page/EditableBlock.tsx (around line 45) does:

const position = doc.caretPositionFromPoint?.(clientX, clientY);
if (position) {
  return sourceOffsetForRenderedOffset(source, position.offset);
}

caretPositionFromPoint returns an offset within the text node under the pointer, but sourceOffsetForRenderedOffset documents and expects an offset within the whole rendered block. In the example the click is inside the TAILWORD here. text node, so position.offset is 5, not 19, and the map faithfully converts the wrong number. The same applies to the WebKit caretRangeFromPoint branch just below it.

sourceOffsetForRenderedOffset itself is correct: given 19 it returns 50.

Scope

Any block whose rendered output splits into more than one node is affected, so links, bold, italic and inline code, not only wikilinks. A block of plain prose is a single text node, where the node-relative and block-relative offsets coincide, which is why this has gone unnoticed.

Suggested fix

Walk from the block root to the returned node and add up the text length in front of it, then pass that block-relative offset to sourceOffsetForRenderedOffset. Worth a test per inline construct, since the current unit tests exercise the map with correct input and so never catch the caller.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingready-for-agentImplementation-ready for an engineering agent

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions