Skip to content

feat: replace the highlights prop with a history the view keeps - #15

Merged
JoshuaPariona merged 10 commits into
mainfrom
feat/highlights-api-own
Sep 25, 2026
Merged

JoshuaPariona merged 10 commits into
mainfrom
feat/highlights-api-own

Conversation

@JoshuaPariona

Copy link
Copy Markdown
Collaborator

Summary

Splits the old highlights prop in two and gives the view a real undo history.
Cuts 1.2.0.

highlights was the value baked into the page at mount and a controlled
state prop that replayed whatever it was set to, while the ref mutated the same
state imperatively. Two writers, one value, so the component had to tell a
genuine restore from an echo of its own last report:

if (highlights === lastEmittedHighlights.current) return;

That guard was correct and it still leaked: building a clear → undo button on
top of it needed a stack in a ref, a flag to swallow the echo of a restore, and
reasoning about string identity, because React skips an effect when the prop
value has not changed. The Snack demo in this repo had all three. Nobody writes
that on the way to shipping a reader app.

Now:

  • initialHighlights is the value the view mounts with, read once.
  • setHighlights(payload) replaces the highlights of a mounted view; ""
    clears them.
  • undo(), redo(), clearHistory(), getHistory() and
    onHistoryChange expose a history the page keeps itself, recorded at the
    one place every content change already passed through.

Two behaviours changed along the way, both of them bugs found while testing this
on device:

  • A payload that is not a serialized highlights string is refused through
    onError without touching the highlights already on screen. It used to
    call removeAllHighlights() first and fail afterwards, so a bad string cost
    the reader their highlights and left the history pointing at a state that was
    no longer on screen.
  • Stepping through the history no longer replays a highlight's entrance
    animation. An undo restores something the reader has already seen; announcing
    it as new misreads the gesture.

The history lives in the WebView runtime, beside the highlighter that owns the
state, and is bounded at 50 entries — each one is a whole serialized payload.

Type of change

  • fix — bug fix (no public API change)
  • feat — new backward-compatible API
  • Breaking change to the public API or the RN ↔ WebView bridge contract
  • docs / chore / ci / test — no runtime change

highlights is removed, so code that passes it no longer compiles. The
changelog opens with the migration. This ships as 1.2.0 rather than 2.0.0 by
decision: the package is ten days old with ~19 downloads a month, and the
upgrade note carries the break.

How was this tested?

yarn lint, yarn typecheck and yarn test (156, up from 151) pass. The new
tests cover setHighlights, the three history commands posting their messages,
onHistoryChange mapping, a malformed history message falling back to defaults
instead of propagating NaN, and the getHistory round-trip.

On an Android emulator, through the example app: highlight twice, undo and redo
step through correctly and the buttons disable themselves at each end; a
deliberately invalid payload surfaces invalid_highlight and leaves the
highlights standing.

  • Android (example app)
  • iOS — not exercised by hand

Two things landed after that session and have not been run on a device: the
entrance-animation guard and clearHistory().

Worth knowing: the history logic itself has no automated coverage. It lives in
the WebView runtime, which is a template string, so the tests can only assert
the messages that cross the bridge. That is where a ReferenceError slipped
past TypeScript during this work.

Checklist

  • yarn lint, yarn typecheck, and yarn test all pass.
  • Public API changes are documented — docs/REFERENCE.md gains Restoring,
    replacing and undoing
    , the props and callbacks tables, and the ref
    entries; the README quick start uses the new shape.
  • Bridge changes keep all three sides in sync: BridgingNames for undo,
    redo, clearHistory and getHistory, the handlers in utils.ts, and
    the ref methods in SelectableTextView.tsx.
  • CHANGELOG.md updated under ## [Unreleased] — N/A: this PR cuts
    the release, so the entries sit under ## [1.2.0] - 2026-09-24.
  • I did not bump the version — N/A: bump: v1.2.0 syncs the six
    places docs/VERSION-UPDATE.md lists (versionCode 2 → 3) plus the two
    cosmetic ones. Merging this arms git tag v1.2.0, which publishes to
    npm.

The Snack demo is updated in snack/, but the published Snack still runs 1.1.0
until its files and its pinned version are updated by hand after the release —
see snack/README.md.

@JoshuaPariona
JoshuaPariona merged commit c50ff73 into main Sep 25, 2026
3 checks passed
@JoshuaPariona
JoshuaPariona deleted the feat/highlights-api-own branch September 25, 2026 04:03
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.

1 participant