2026-03-15 Session Log
LinguaRAG: cross-page selection highlights, accurate page tracking for notes, pin annotation cleanup, TTS sync diagnosis
lingua-rag
PDF cross-page drag highlights, note page accuracy, pin annotation removal, TTS word-highlight sync investigation
What I Did
-
Fixed cross-page drag highlight not rendering — When scrolled so page 2 is partially visible and you drag-select text there, the blue selection overlay didn’t appear. Root cause: the render condition checked
n === pageNumber(the active page from scroll position), so highlights only drew on the current page. Added aselectionPagestate that tracks which page the selection actually occurred on, and passed the correct page number toensureTextContent(forPage?)so it loads the right page’s text data for rect computation. -
Fixed notes/vocab saving with wrong page number — When selecting text on a non-active page (e.g., page 2 while viewing page 1), notes and vocabulary were saved with the wrong page. Added a
pagefield to theSelectionPopupinterface so the popup carries the actual selected page. Updated note panel, vocab panel, and highlight-index calculation to usepopup.pageinstead of the scroll-basedpageNumber. -
Prevented popup from appearing on empty area drag — Dragging in blank space (no text) still triggered the selection popup. This happened because
caretRangeFromPointalways returns the nearest text node even when clicking empty areas. Added ane.target !== textLayerguard — if the click target is the text layer container itself (not a child<span>), it’s treated as empty space and the drag is ignored. -
Removed pin/sticky annotation system (~280 lines) — The old pin-based annotation system (📌 icons on PDF pages with sticky-note-style editing popovers) was fully superseded by the highlight notes system. Pins were being created at
x_pct: 0, y_pct: 0, causing stray 📌 icons in the top-left corner. Removed:STICKY_COLORSconstant, 6 state variables (isStickyMode,pendingSticky,editingSticky,stickyText,stickyColor,draggingPin), 2 refs (pinDragRef,pinWasDraggedRef), the pin-draguseEffect, sticky mode click handler on page containers, and all pin/sticky rendering JSX. Kept the annotation API functions since the highlight note system still uses them. -
Reverted compound word merging for pronunciation — Implemented adjacent-word merging to handle STT splitting compound words like “ChatGPT” → “chat” + “gpt”. The approach was to concatenate 1-2 adjacent unmatched STT words and compare against the expected word. Reverted per user request — needs further design consideration.
-
Diagnosed TTS word-highlight sync issue with numbers — When TTS reads “Microsoft 365”, the highlight chip jumps past “365” too quickly. Root cause: the timer-based highlighting estimates duration by character count (
w.length). “365” gets weight 3 (3 chars), but TTS pronounces it as “three hundred sixty-five” (~1-1.5 seconds). The 210ms allocation (3 × 70ms) is far too short. InvestigatedSpeechSynthesisUtterance.onboundaryas a solution — it provides exact word timing from the speech engine. However, Chrome’s Google network voices (localService: false) don’t fire boundary events because audio is streamed from Google servers without word-level metadata. Local voices do support it.
Key Decisions
- Pin annotations removed entirely — Highlight-based notes replaced this feature completely. The pin system had a side effect where annotations created via the note panel got
x_pct: 0, y_pct: 0, placing unwanted pins at the top-left corner. popup.pagepattern — Embedding the page number in the popup state decouples selection context from scroll position. This is more reliable than relying onpageNumberwhich updates asynchronously based on scroll events.
Next
- Improve TTS word highlighting — prefer local voices +
onboundaryevents, with timer-based fallback for network voices (hybrid approach) - Resolve compound word STT splitting (e.g., “ChatGPT” → “chat” + “gpt”) — revisit adjacent word merging design
- Test full guest→login flow with 2+ PDFs
- Commit all session changes