2026-09-23·by Sijie Wang#lucerna#software#architecture

raw-display-cursor

The dual-coordinate backbone (raw ↔ display)

Parent: key-designs

The single most load-bearing idea in the reader. Every persisted CachedSentence (core/src/entities/CachedSentence.ts) carries two coordinate systems:

  • rawStart / rawText — offsets into the source text (where tokenization, lemmatization, and stable identity live);
  • displayStart / displayText — offsets into the sanitized text the user actually reads (whitespace collapsed, soft-hyphens/markup removed);
  • displayToRaw[] — a per-sentence map converting a display offset back to a raw offset (sentenceDisplayToBookRaw / sentenceBookRawToDisplay with binary search).

The rule: every durable position — a bookmark, a highlight's startOffset/endOffset, a saved word's rawStart, the reading Anchor — is stored as a book-absolute raw offset; the display layer is reconstructed on render (Page.ts composes page-local ↔ book-raw via pageDisplayToRaw/pageRawToDisplay).

Why this is the backbone: a reader re-paginates constantly (font size, viewport, window resize), and the sanitizer changes the displayed characters. If positions were stored as display offsets or page numbers, every note/highlight/vocab anchor would drift the moment layout changed. Anchoring to raw source offsets makes them layout-independent — the whole point of a reading app where users mark things and come back. Anchor even stores a ≤200-char display prefix and re-resolves it to a sentenceIdx by prefix-match, so position survives even if offsets shift.

The (raw, display, displayToRaw) triple is built per sentence by services/sentenceSanitize.ts and persisted through the sentence cache (an in-memory building map committed atomically to sentences_real, version-gated by SANITIZER_VERSION + PAGINATION_VERSION so a logic bump invalidates stale rows). This is why it is a typed value-object boundary, not scattered offset arithmetic: the conversion is non-trivial and one-directionally lossy.

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →