Build Readwise Reader

KINDAreplaces $12.99/mosaves $155.88/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

Save a page from anywhere, extract a clean reader view, highlight and annotate, and have those highlights still point at the right text when the extractor improves, because they are anchored by quoted text and its surroundings rather than by a character offset.

estimated effort multi-daythe files for this build are in the project pack

RuntimeNode 22 with SQLiteExtractionReadability plus a sanitiserAnchoringQuoted text with prefix and suffix context, not offsetsCaptureA bookmarklet and a share endpointExportMarkdown per document with highlights inline

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server, the extractor and the review job.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. have readyfree

    Why Phase 1's extraction and Phase 3's re-extraction are both only meaningful against real pages: a clean article, a JavaScript-only page, one with an aggressive content security policy, a very long piece and one that gets edited.

    Get it Collect them from your own reading and keep them as fixtures with their expected outcomes.

    Verify All five are saved with expected extraction results recorded

  3. have readyfree

    Why Phase 2's anchoring is only tested by a document where the same words appear twice, which is what the surrounding context exists to disambiguate.

    Get it Any long article will contain repeated phrases. Highlight one deliberately and check it resolves to the right occurrence.

    Verify A highlight on a repeated phrase resolves to the correct occurrence

  4. API keyfree tiers cover this

    Why Phase 5 only. Everything else works without delivery.

    Get it A transactional provider with a verified sending domain.

    Verify A digest arrives in an inbox

  5. have ready5 to 10 US dollars a month

    Why Saving from a phone needs a reachable endpoint, and the review digest needs something running on a schedule.

    Get it A small virtual server, or a machine at home behind a tunnel. Authenticate before exposing it.

    Verify A save from a phone reaches the endpoint over HTTPS

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- documents(id, url, canonical_url, title, author, saved_at, published_at, state, word_count) · state is inbox, later, archived
- extractions(id, document_id, extractor_version, text, html, extracted_at, content_hash)
- highlights(id, document_id, quote, prefix, suffix, note, colour, created_at, resolved_offset, resolve_state)
- tags(id, name), document_tags(document_id, tag_id)
- reviews(highlight_id, last_shown_at, next_due_at, times_shown)
- A highlight belongs to a document, not to an extraction. That is what lets the extraction be replaced.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DB_PATHrequired./data/reader.dbDocuments, extractions and highlights.
FETCH_TIMEOUT_MSrequired10000Per-page fetch timeout. A slow page should fail rather than hold up a save.
FETCH_MAX_BYTESrequired5000000Streaming byte cap, enforced during download.
ANCHOR_CONTEXT_CHARSrequired40How much prefix and suffix is stored with a quote. Enough to disambiguate a repeated phrase.
REVIEW_DAILY_CAPrequired5Highlights per daily review. Small enough that the review actually happens.
SAVE_TOKENsecretrequiredreplace-with-a-random-tokenUsed by the bookmarklet and the share endpoint. Scoped to saving only.

The build, in order

  1. Save and extract

    A saved page becomes readable text, and a page that cannot be extracted says so.

    1. Timeout, streaming byte cap, robots respected. You are a well-behaved client saving for yourself.

      Files src/fetch.js

    2. Readability for the article, then strip scripts and remote references so the copy is inert.

      Files src/extract.js

      terminal
      npm install @mozilla/readability jsdom dompurify
    3. So you know which extractor produced a stored copy, which matters in Phase 3.

      Files src/extract.js

    done when · tick each as it passes
    watch out
    • Storing raw fetched HTML and rendering it later means every time you open your own archive you load the publisher's scripts and trackers. Sanitise at capture: strip scripts, event handlers and remote references, so the stored copy is inert.
  2. Highlights anchored to text, not to positions

    A highlight knows what it selected, not where in a string it was.

    1. The selected text plus a short prefix and suffix. Enough context to disambiguate a repeated phrase.

      Files src/anchor.js

    2. Exact match first, then approximate. Record which strategy succeeded.

      Files src/anchor.js

    3. Marked, still readable as a quote, never silently attached to the wrong place.

      Files src/anchor.js

    done when · tick each as it passes
    watch out
    • Storing a character offset into the extracted text means every highlight breaks the moment the extraction changes by one character, which happens whenever the extractor improves or the publisher edits the page. Quote plus context resolves against new text and degrades gracefully when it cannot.
  3. Re-extraction, which is the point

    Improving the extractor does not lose a year of highlights.

    1. A document has several; one is current. Rollback is then trivial.

      Files src/extractions.js

    2. Counts by outcome, shown before the new extraction becomes current.

      Files src/reextract.js

    3. A re-extraction that loses highlights should be reversible in one action.

      Files src/extractions.js

    done when · tick each as it passes
    watch out
    • Replacing an extraction without re-resolving highlights leaves them pointing at text that has moved, and the damage is silent and cumulative. Re-resolve as part of re-extraction, report the counts, and keep the old extraction so a bad one can be reverted.
  4. A queue you can actually clear

    Saved articles are triaged rather than accumulating into a wall of guilt.

    1. Inbox, later, archive. One action between them, keyboard reachable.

      Files src/queue.js

    2. From word count. It is what lets you pick something that fits the time you have.

      Files src/queue.js

    3. Archive without reading, recorded as such. It is the action that keeps the queue survivable.

      Files src/queue.js

    done when · tick each as it passes
    watch out
    • A read-later queue with no triage becomes a list of a thousand articles nobody opens, and the tool stops being used rather than the queue being cleared. Make archiving without reading a first-class action, since deciding not to read something is a legitimate outcome and the queue depends on it.
  5. Highlight review

    What you highlighted comes back, spaced out, so it is not written once and never seen again.

    1. Recency and annotation as weights, with a daily cap.

      Files src/review.js

    2. Email with the highlights and their sources. This is where a review actually gets read.

      Files src/digest.js

    3. Marked not useful and never shown again. Without it, the review degrades over time.

      Files src/review.js

    done when · tick each as it passes
    watch out
    • Showing highlights at random from a large archive produces mostly forgettable ones and trains you to skip the review. Weight towards annotated highlights and recent reading, and cap the daily count so the review takes a minute rather than being abandoned.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more read it later.