Build Resurf
KINDAback to the verdict
A reading capture tool whose real problem is the return path: saving is easy and everyone does it, but a bookmark folder nobody opens is not a memory aid, so the engineering is in surfacing the right thing at a moment when it is useful rather than at a scheduled time nobody wants.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the local application and builds the extension.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Capturing the whole page markup stores navigation and advertisements, which pollutes search and wastes space.
Get it A maintained readability extraction library, tested against pages you actually read.
Verify
A news article, a documentation page and a long blog post all extract cleanly - have readyfree, and it takes a month
Why Resurfacing quality cannot be evaluated on a synthetic set, since the whole question is whether an old item is genuinely relevant to a new one, and only you can judge that.
Get it Use the capture for a month before building phase 4, so there is something real to surface.
Verify
At least two hundred captures with some highlights exist - decidefree
Why Phase 4 is the whole product and a wrong answer produces an annoying tool, so decide what triggers you will support before implementing any of them.
Get it Note the last five times you wished you could find something you had read, and what you were doing at the time. Those are your triggers.
Verify
A written note lists at least three trigger situations from your own experience
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- captures(id, url, title, text, excerpt, captured_at, source, reading_time_ms) · text stored at capture, not fetched later - highlights(id, capture_id, text, note, at) · what you actually marked, which is a much stronger signal than the capture - topics(id, label) and capture_topics(capture_id, topic_id, confidence) · derived, never asked for at capture - surfaces(id, capture_id, trigger_kind, triggered_at, acted_on, dismissed) · what was shown, why, and what you did - dismissals(capture_id, count, last_at) · dismissing the same thing three times means stop - Everything findable is derived after capture, because any field asked for at save time is a decision that slows saving down until saving stops.
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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DB_PATH | required | ~/.local/share/resurf/captures.db | Everything you have read. A record of what you were thinking about. |
CAPTURE_PROMPT | required | never | Present as a constant so the answer is visible. A prompt is a decision and decisions end habits. |
DISMISS_LIMIT | required | 3 | How many dismissals before an item stops resurfacing. Three is an answer. |
SCHEDULED_DIGEST | required | never | Present as a constant. A scheduled digest of old links is unread within a week. |
COMPRESS_AFTER_DAYS | required | 180 | Old text is compressed rather than deleted, since deleting removes what resurfacing runs on. |
SYNC_REMOTE | optional | s3://my-reading/archive | Optional and off by default. Absent means one machine, which is a fine way to run. |
The build, in order
Capturing without deciding anything
Saving something takes one action and no thought.
No tags, no folder, no note. The absence is the design.
Files
extension/capture.tsLater means a page that changed or disappeared.
Files
src/extract.tsThe text is on screen. Capture should not need a network.
Files
src/capture.ts
done when · tick each as it passeswatch out- Any field offered at capture, even an optional one, is a decision, and a decision at capture time is what turns a fast habit into a slow one that stops within a month. Capture with no prompt at all, and derive everything findable afterwards.
Highlights, which mean far more than captures
What you marked is distinguished from what you merely saved.
With surrounding context, so a small change does not orphan them.
Files
src/highlights.tsPeople save everything and highlight only what landed.
Files
src/signals.tsLeaving the page to add a note is a decision, and decisions stop habits.
Files
extension/highlight.ts
done when · tick each as it passeswatch out- Treating a highlight the same as a capture wastes the strongest signal in the system, since people save far more than they read and highlight only what actually landed. Weight highlights heavily in everything downstream and count them separately.
Finding it when you go looking
Full text search across everything you read finds it instantly.
Titles and URLs find nothing. You remember phrases.
Files
src/search.tsNever ask at capture. That was phase one's whole point.
Files
src/topics.tsTen thousand captures. Slower than typing means it is not the default way in.
Files
src/search.ts
done when · tick each as it passeswatch out- Searching only titles and URLs finds almost nothing, because you remember a phrase from the middle of an article rather than its headline. Index the full captured text, which you have precisely because it was stored at capture time.
Bringing things back at a useful moment
Something you saved reappears when it is relevant, not on a schedule.
The related page is the moment the old one becomes interesting.
Files
src/surface.tsSo you can tell whether resurfacing works rather than assuming.
Files
src/surfaces.tsThree dismissals is an answer.
Files
src/dismissals.ts
done when · tick each as it passeswatch out- A daily digest of old saves is unread within a week, because a scheduled reminder arrives when you are doing something else and old links are not interesting out of context. Trigger on context: reading a related page is the moment the old one is worth seeing.
Keeping it, and being able to leaveproduct builder
Years of reading survive and can move somewhere else.
Round trip tested. A reading archive should not be trapped.
Files
src/export.tstests/roundtrip.test.tsDeleting old captures removes what resurfacing runs on.
Files
src/compact.tsOff by default, through storage you control if on.
Files
src/sync.ts
done when · tick each as it passeswatch out- Storing the full text of everything you read grows without bound, and the naive response is to delete old captures, which removes exactly the material that resurfacing depends on. Compress old text rather than deleting it, and keep highlights uncompressed since they are small and are the strongest signal.
That is the whole plan for Resurf. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Any account or hosted service.
- Mobile capture.
- Social features and shared collections.
- Full page appearance archiving.
- Sync across devices and browsers, so your phone reading never enters the index
- Capture on anything that is not desktop Chrome: Safari, mobile apps, in-app browsers
- Robust extraction on SPAs, infinite scrolls, PDFs, and anything behind a login wall
- Curation judgment: your version will resurface plenty of pricing pages and airline confirmations
- Someone else maintaining it against Chrome manifest changes that break extensions on a schedule
- Surfacing while you write rather than only while you read, which is the harder half
- Grouping captures into a thread when you return to a topic repeatedly
- A weekly view of what you actually read, which is different from what you saved
Need the files? The project pack on the verdict page hands your agent the whole brief · more bookmarks.