5.4 KiB
5.4 KiB
Annotator & Reader Fixes Reference
Annotation System Architecture
Key Components
Annotator.tsx- Annotation lifecycle, popup display, style/color managementAnnotationRangeEditor.tsx- Drag handles for adjusting selection rangeMagnifierLoupe.tsx- Magnifying glass during handle drag (mobile only)useTextSelector.ts- Text selection detection and processinguseAnnotationEditor.ts- Editing existing annotationsuseInstantAnnotation.ts- Creating new annotations on selection
Highlight Rendering
- Highlights rendered by foliate-js
Overlayer(SVG overlayer in paginator shadow DOM, not iframe) - Each view in multiview paginator has its own
Overlayerinstance with unique clipPath ID Overlayer.add()stores range + draw function;redraw()recalculates positions from stored ranges- Colors stored as color names mapped to custom hex via
globalReadSettings.customHighlightColors - Sidebar uses
color-mix()CSS function with custom colors, not Tailwind utility classes (#3273) - Rounded highlight style supported via
verticaloption passed to overlayer (#3208)
Multiview Overlayer Pitfalls
- Duplicate SVG IDs: Each overlayer creates
<clipPath>for loupe hole — IDs MUST be unique per instance orurl(#id)resolves to wrong element, clipping everything - docLoadHandler scope:
FoliateViewer.tsxre-adds annotations onloadevent — MUST filter bydetail.index(loaded section), not re-add ALL annotations (overwrites drag edits) - MagnifierLoupe lifecycle: Don't destroy/recreate loupe on every drag tick —
hideLoupe()should only run on unmount,showLoupe()fast path updates position only - Stale closures in useTextSelector:
getProgress()must be called inside callbacks, not captured at hook top-level (useFoliateEvents deps are[view]only)
Fix History
| Issue | Problem | Root Cause | Fix |
|---|---|---|---|
| #3286 | Selection stuck on first annotation | initializedRef guard blocked re-computation |
Remove guard, consolidate style/color effects |
| #3273 | Custom colors not in sidebar | Hardcoded Tailwind classes | Use inline style with color-mix() |
| #3234 | Letter-by-letter selection on mobile | No word boundary snapping | Add snapRangeToWords() using Intl.Segmenter |
| #3208 | Hard rectangular highlights | No border radius support | Pass vertical option, update foliate-js |
| #3002 | Can't see text under finger | No magnification UI | New MagnifierLoupe component using view.renderer.showLoupe() |
| #3082 | No page numbers on annotations | pageNumber field missing |
Add pageNumber to BookNote type, compute on create |
| #3225 | Android tools unresponsive | Premature makeSelection() call |
Remove premature re-selection in Android path |
Common Annotation Bugs
Selection Issues
- Word snapping: Uses
Intl.Segmenterwithgranularity: 'word'to snap selection to word boundaries - Android re-selection: Don't call
makeSelection(sel, index, true)immediately on pointer-up; let the popup flow complete - Range editor handles: Remove
initializedRefguards that prevent re-computation when switching annotations
Color/Style Issues
- Custom colors in sidebar: Use inline
style={{ backgroundColor: 'color-mix(...)' }}not Tailwind classes - Style synchronization: Consolidate
selectedStyleandselectedColorinto oneuseEffect - Switching annotations: Must call
setShowAnnotPopup(false)andsetEditingAnnotation(null)before setting up new annotation
Reader/Content Fixes
Progress Display
- Use physical
view.renderer.pageandview.renderer.pagesfor page counts (#3213, #3200) - Last page shows 100% by fixing boundary condition (#3383)
- FB2 subsections need special handling for progress (#3136)
Translation View (#3078)
- Problem: Page jumps back during full-text translation
- Root cause: DOM mutations from sequential translation insertions cause paginator relayout
- Fix: Batch DOM updates with 50ms timer, use bounded concurrent queue (max 5), show loading overlay
TOC Navigation (#3124)
- Problem: Expanding TOC chapter scrolls back to current chapter
- Fix: Only scroll-into-view on navigation, not on expand/collapse
Accessibility (a11y) Fixes
Screen Reader (TalkBack) Support
- Page indicator updates (#2276): Add focus handlers on
<p>elements that callview.goTo(cfi)to update position - Navigation buttons (#3036): Always show prev/next buttons when screen reader active;
PageNavigationButtons.tsx - Dropdown menus (#3035): Use
DropdownContextwith overlay dismiss instead of blur-based closing
Dropdown Architecture for a11y
DropdownContext(src/context/DropdownContext.tsx) manages which dropdown is open globally- Uses
useId()for unique identification - One dropdown open at a time
<Overlay>for dismissal (tap/click outside) instead ofonBlur<details>element withopen={isOpen}for semantic structure- No auto-focus-first-item (conflicts with TalkBack)
E-ink Readability
- Use
not-eink:Tailwind variant for colors and opacity (#3258) - Don't use
text-primary(blue) or low opacity on e-ink - Highlights use foreground color in dark mode e-ink (#3299)
Key Utility Functions
snapRangeToWords()insrc/utils/sel.ts- Word boundary snappinghandleAccessibilityEvents()insrc/utils/a11y.ts- Screen reader focus handlingcolor-mix()CSS function for custom highlight colors with opacity