Skip to content

[PARKED] annotations refactoring - #3728

Open
panaC wants to merge 21 commits into
developfrom
panac/annotations-refactoring
Open

[PARKED] annotations refactoring#3728
panaC wants to merge 21 commits into
developfrom
panac/annotations-refactoring

Conversation

@panaC

@panaC panaC commented Jul 29, 2026

Copy link
Copy Markdown
Member

Fixes #3643
Fixes #3726
Fixes #3613

This PR refactors annotation and bookmark handling into a unified publication notes model. It introduces a shared publication-notes domain layer used across persistence, import/export, reader state, filtering, pagination, and display.

Feature Changes

  • Replaces the previous annotation/bookmark-specific state flow with a unified publication notes structure.
  • Adds a shared notes list experience for annotations and bookmarks, including sorting, filtering, pagination, and selection behavior.
  • Introduces a publication notes controller/repository layer for centralized save, update, remove, delete-by-publication, and snapshot hydration flows.
  • Refactors annotation import to support richer preview/reporting before applying changes.
  • Adds import diagnostics for unresolved annotations, unsupported selectors, ambiguous matches, conflicts, and already-imported items.
  • Preserves unresolved imported annotations with metadata so they can still be reported and surfaced meaningfully.
  • Updates Readium Annotation Set export to use the new publication notes view/state.
  • Adds HTML export flow support for publication notes.
  • Updates reader/main Redux actions and sagas to route note commands through the new publication notes command model.
  • Updates localization strings for the new import/export reporting and notes UI behavior.
  • Adds tests around the new publication notes models, reducers, controllers, selectors, import handling, and list behavior.

Change Analysis

The refactor moves annotation/bookmark behavior away from scattered reader-specific note state and toward a more explicit publication-scoped notes domain. This makes import/export, persistence, and UI listing behavior more consistent, while keeping the reader-facing workflows intact.

The biggest functional improvement is the import pipeline: imported annotation sets now produce a structured preview and report before changes are applied, making conflict and unresolved-selector handling more transparent. The UI can distinguish clean imports, conflicts, already-imported notes, unsupported selectors, and annotations that could not be resolved against the current publication content.

@panaC

panaC commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

Manual Test Procedure

Use the files in:

thorium-reader\test\annotations

Start from a clean Thorium state for accessible_epub_3.epub unless the test says otherwise.

Setup

  1. Open Thorium.
  2. Import accessible_epub_3.epub.
  3. Open the book.
  4. Make sure there are no existing annotations/bookmarks for this publication.
  5. Use the publication menu option to import annotations.

Test Cases

  1. Import accessible_epub_3-valid-basic.annotation
    Expected: import dialog shows 3 importable annotations. Import succeeds. Annotation list contains 3 notes with different styles: solid, underline, outline.

  2. Import accessible_epub_3-bookmark.annotation
    Expected: import succeeds. A bookmark is added, not a regular highlight note.

  3. Import accessible_epub_3-source-normalization.annotation
    Expected: import succeeds. The annotation source URL is accepted even though it is absolute and contains query/fragment data.

  4. Import accessible_epub_3-source-mismatch.annotation
    Expected: import is not blocked, but the dialog reports 1 unresolved annotation because the source file is missing from the EPUB spine.

  5. Import accessible_epub_3-unsupported-selector.annotation
    Expected: import is not blocked, but the dialog reports 1 unresolved annotation because the selector type is unsupported.

  6. Conflict test
    Start from a clean state, then:

    1. Import accessible_epub_3-conflicts-seed.annotation
    2. Confirm the import.
    3. Import accessible_epub_3-conflicts.annotation

    Expected: the second import shows conflicts:

    • 1 incoming newer annotation
    • 1 incoming older annotation
    • 1 already imported annotation
  7. Import accessible_epub_3-empty.annotation
    Expected: Thorium reports that the annotation file is empty / contains nothing to import.

  8. Import accessible_epub_3-invalid-schema.annotation
    Expected: Thorium rejects the file as an invalid annotation set.

Cleanup

After each independent test, delete the imported annotations or remove/reimport the EPUB so the next test starts clean. For the conflict test, keep the seed annotations before importing the conflict file.

@panaC panaC self-assigned this Jul 30, 2026
@panaC
panaC marked this pull request as ready for review July 30, 2026 10:24
@panaC

panaC commented Jul 30, 2026

Copy link
Copy Markdown
Member Author

Readium Annotation Import: target vs originalTarget

During Readium annotation import, Thorium stores the imported annotation data under readiumAnnotation.import.

target is the operational target used by Thorium. It may be normalized during import so that target.source matches an actual spine item href in the current publication.

originalTarget is only set when Thorium changes target.source. It preserves the exact target from the imported annotation file before normalization. It is not used for resolution; it is kept as provenance/debug information.

Example:

// Imported file
target.source = "OPS/chapter.xhtml"

// Thorium spine
"chapter.xhtml"

// Stored note
readiumAnnotation.import.target.source = "chapter.xhtml"
readiumAnnotation.import.originalTarget.source = "OPS/chapter.xhtml"

If the source cannot be matched to the publication spine, the note is still imported, but marked unresolved:

readiumAnnotation.import.unresolved.reason = "source-mismatch"

Resolution Algorithm

After import, the reader tries to convert readiumAnnotation.import.target into a Thorium locatorExtended.

It uses target, not originalTarget.

Supported selectors are:

CfiSelector
FragmentSelector with EPUB CFI
CssSelector
TextPositionSelector
TextQuoteSelector

If multiple selectors resolve to the same range, Thorium chooses the highest-priority candidate:

CfiSelector / FragmentSelector: 50
CssSelector: 40
TextPositionSelector: 30
TextQuoteSelector: 20

If no usable match is found, the note remains unresolved with reasons such as:

source-mismatch
unsupported-selector
selector-not-found
ambiguous-match

Export Behavior For Unresolved Notes

On export, Thorium does not reuse:

readiumAnnotation.import.target.selector
readiumAnnotation.import.originalTarget.selector

The export converter only serializes:

readiumAnnotation.export.selector || []

The exported target.source also comes from:

locatorExtended.locator.href || ""

Therefore, for an unresolved imported note that has no locatorExtended and no generated export selectors, the exported annotation will contain:

target: {
  source: "",
  selector: []
}

If the imported note is later resolved into a locatorExtended, Thorium can generate readiumAnnotation.export.selector from the resolved range, and those generated selectors will be exported.

@panaC panaC changed the title annotations refactoring [PARKED] annotations refactoring Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant