Skip to content

frontend: map server code-point offsets to editor positions (#11) - #13

Merged
thorwhalen merged 2 commits into
mainfrom
fix/codepoint-offsets-11
Sep 22, 2026
Merged

thorwhalen merged 2 commits into
mainfrom
fix/codepoint-offsets-11

Conversation

@thorwhalen

@thorwhalen thorwhalen commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Closes #11

ductus reports span offsets in Python code points; the editor (ProseMirror, JavaScript strings) counts UTF-16 units. toPos = offset + 1 treated one as the other, so every highlight after an emoji or other astral character was shifted by one per such character and could split a surrogate pair. Paste and file-open were both affected.

Change

  • frontend/src/offsets.ts — positionsIn(text): a code-point-offset → ProseMirror-position function built from the exact text the report was computed against. Fast path (no high surrogate in the text) is the old offset + 1; otherwise one pass records each code point's UTF-16 start. Out-of-range offsets clamp to the end.
  • main.ts — track(report, text, positionsIn(text)). text is the text sent to the server, and read() already drops a report whose text is no longer in the editor, so the table always describes the document it maps into. After that instant every position moves through ProseMirror's own Mapping, which is UTF-16 already — nothing else converts.
  • editor.ts — toPos's doc now says it is for UTF-16 offsets and points at positionsIn for server offsets.

Verification

  • vitest: 26 passed (5 new in offsets.test.ts: BMP fast path, the Highlights shift after any emoji: server offsets are code points, the editor counts UTF-16 units #11 repro, exhaustive no-surrogate-split over a mixed string checked against Python-style code-point slicing, end-of-text, clamp).

  • Headless Chromium against ductus-http: a file of 30 × 😀 + the sample's first line now highlights exactly Great question, !, delve, tapestry (before: '�😀😀😀😀😀😀�' and every later finding shifted).

  • Server side, same symptom (found in review): rhetoric() matched concede-pivot against s.lower(), and str.lower() can change length ('İ'.lower() is two code points), so its offsets pointed at the wrong characters. Now matched with re.IGNORECASE against the original text; the reported value stays lowercase. Pinned by test_every_offset_indexes_the_text_even_when_lowercasing_changes_its_length (fails on main). Side effect, negligible: re.IGNORECASE also lets a leading İ/ı stand for the pronoun "I" in this one pattern.

  • Span docstring now states offsets are code points; editor.ts's module docstring no longer claims offset + 1; the now-unused toPos/toOffset helpers are removed so they cannot be reused by mistake.

Python: 299 passed, 13 skipped (doctests included).

Review

Independent adversarial sub-agent review (browser-driven: emoji, 𝒳, U+2000B, CRLF, em dash — all 9 painted highlights equal their server quotes). It confirmed track() is the only server-offset→position conversion and that Python emits code points everywhere except the concede-pivot bug above, which is fixed here along with its doc/cleanup nits.

Gate: hosted CI (Python + frontend workflows).

🤖 Generated with Claude Code

ductus reports Python code-point offsets; the editor counts UTF-16 units,
so every highlight after an emoji or other astral character was shifted
and could split a surrogate pair. track() now gets positionsIn(text), a
mapper built from the exact text the report was computed against.
…owercase

Review of #11 found the same symptom server-side: rhetoric() matched
concede-pivot against s.lower(), and str.lower() can change length
('İ' becomes two code points), so offsets pointed at the wrong characters.
Now matched case-insensitively against the original text; the reported
value stays lowercase. Also: Span documents that offsets are code points;
the editor's module docstring no longer claims offset + 1; the unused
toPos/toOffset helpers are removed so they cannot be reused by mistake.
@thorwhalen
thorwhalen merged commit 494e4d6 into main Sep 22, 2026
14 checks passed
@thorwhalen
thorwhalen deleted the fix/codepoint-offsets-11 branch September 22, 2026 14:47
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.

Highlights shift after any emoji: server offsets are code points, the editor counts UTF-16 units

1 participant