Bloomscroll Next

YOUR OWN COPY. ONE SOURCE AT A TIME.

From download
to your first feed.

Start with public reading. Add your own accounts and saved material when you’re ready.

The full reader is a technical alpha for individual use on macOS or Linux. It needs Node.js 22.5 or newer. The download includes the built app; you do not need npm install, a compiler, or access to the private repository.

01 / RUN THE READER

Download, unzip, start.

Download Bloomscroll Next v0.10.2 ↓
  1. Install Node.js if needed. Use a supported release with version 22.5 or newer. Run node --version in Terminal to check.
  2. Unzip the download. Open Terminal in the extracted bloomscroll-next folder.
  3. Fetch the starter reading source, then start your reader:
node --experimental-sqlite dist/cli.js sync reading-psyche
node --experimental-sqlite dist/cli.js serve

The server prints a port between 7000 and 9999. On that same computer, open the browser address made from http://127.0.0.1: followed by that port. The exact 127.0.0.1 spelling matters for write requests. Keep Terminal open while using the app; Control-C stops it.

Choose Discover in the Balance controls initially. Your saved library starts empty. Save a discovery to bring it into reflection, or use the independent Read later queue.

Check the refresh result. Look for ready or partial, and a positive fetched count. A failed fetch may still exit normally. If the publisher is unreachable or has no eligible recent entries, an empty feed can be correct. In the app, Sources shows the attempt, source state, counts, and freshness.

No paid model API, social account, or Bloomscroll sign-in is required for public RSS reading. Your copy starts with no personal data. Optional sources marked unavailable do not prevent reading.

02 / CHOOSE YOUR READING

Connect RSS & Atom feeds.

Create data/next/feeds.json inside your extracted app folder. The first run creates data/next/. Here is an example configuration:

[
  {
    "id": "reading-psyche",
    "label": "Psyche",
    "url": "https://psyche.co/feed.rss"
  },
  {
    "id": "reading-quanta",
    "label": "Quanta",
    "url": "https://www.quantamagazine.org/feed/"
  }
]
  1. Use up to eight RSS/Atom feeds. Give each a distinct ID beginning with reading-. Use the publisher’s canonical HTTPS feed URL; redirects are not followed.
  2. Stop and restart the app after editing the file.
  3. Open Sources and refresh the selected feed. Alternatively, stop the app and run node --experimental-sqlite dist/cli.js sync reading-quanta, then start it again.

Discovery uses dated items from the past 30 days. Undated and older entries are not fresh discovery. Once saved, old material still belongs in reflection. Refresh is explicit; no background polling is installed.

03 / OPTIONAL, ADVANCED SETUP

Add X & Instagram.

These sources use bounded snapshots from your own authenticated browser. There is no one-click account connection and no continuous streaming. The helpers are included in the download, but Bird and Gramclaw are external tools and are not bundled or installed automatically.

Run the reader helper on the computer where you are already signed in. You need Python 3 and Node.js. For X, use an existing bookmark-capable Bird installation (the integration was verified with Bird 0.8). For Instagram, use a compatible Gramclaw src/live.js module (verified with the 1.1 browser transport). Browser/Keychain approval may be required; these tools’ availability and provider authentication can change.

Replace every uppercase placeholder below with your own account name and actual absolute path. No provider password or cookie goes into these commands.

X bookmarks

python3 scripts/refresh-social-snapshots.py --provider x \
  --x-transport bird --bird-bin /YOUR/PATH/TO/bird \
  --bird-account YOUR_X_HANDLE \
  --chrome-profile '/YOUR/CHROME/PROFILE' \
  --limit 20 --max-pages 1 && \
node --experimental-sqlite dist/cli.js import x-saved

For Following or For You, add --feed following or --feed for-you to the helper. On success, import x-following or x-for-you, respectively, instead of x-saved.

Instagram saved posts

python3 scripts/refresh-social-snapshots.py --provider instagram \
  --gramclaw-module /YOUR/PATH/TO/gramclaw/src/live.js \
  --instagram-account YOUR_INSTAGRAM_USERNAME \
  --chrome-profile '/YOUR/CHROME/PROFILE' \
  --limit 20 --max-pages 1 && \
node --experimental-sqlite dist/cli.js import instagram

For the Instagram home feed, add --feed home and import instagram-home on success. Saved posts enter reflection; home feeds enter discovery. Refresh Sources after importing to see current status.

Import only after a successful acquisition. The && in these commands enforces that. Look for live_read_published_as_snapshot from the helper, then cached-snapshot from the app import. A successful import is not a new provider read. The helper always writes into this app folder’s data/next/; use the default data directory for this workflow.

The expected account is checked before reads. The helper performs no posts, follows, messages, or bookmark changes. A failed acquisition retains the previous snapshot. Your account might have more content than a bounded read returns. Media links can expire; saved posts are not a durable video archive.

Already running the reader on another computer? Keep authentication on your browser computer. Move only the normalized data/next/snapshots/SOURCE-ID.json file to the same relative location on the app computer, privately, then run node --experimental-sqlite dist/cli.js import SOURCE-ID there. Do not copy cookies, provider caches, or someone else’s library.

If you do not already have compatible Bird or Gramclaw installed, start with RSS. Social setup currently needs technical assistance; this guide does not promise a general social installer.

04 / MAKE ROOM FOR WHAT YOU SAVED

Notes & recordings.

NoteStream

Use your own NoteStream account/API key in a private file. Point the reader to it before starting or syncing:

export BLOOMSCROLL_NEXT_NOTESTREAM_KEY_FILE='/YOUR/PRIVATE/key-file'
node --experimental-sqlite dist/cli.js sync notestream

The adapter collects selected owner notes tagged #sr, #in-green, #read, #memorize, #engrain, #enforce, #beautiful, #best, or #thinkabout. It excludes #remember and uses bounded pagination. Selected review tags feed spaced review; several learning-oriented tags receive a small ranking preference. NoteStream is distinct from Thoughtstream / Ideaflow.

Thoughtstream / Ideaflow and other saved material

These use normalized snapshots rather than a general live account connection. Your exporter should produce an envelope with the correct source ID, original acquisition time, completeness, and normalized rows. This illustrative row is not personal content:

{
  "source": "thoughtstream",
  "mode": "snapshot",
  "observedAt": 1792800000000,
  "complete": false,
  "rows": [{
    "source": "thoughtstream",
    "sourceId": "your-stable-note-id",
    "lane": "saved",
    "kind": "text",
    "title": null,
    "body": "An idea worth returning to",
    "url": null,
    "tags": ["read"],
    "createdAt": 1792700000000
  }]
}

Use real acquisition/content timestamps in milliseconds and a stable ID from your source. Exclude credentials and #remember material. Store privately as data/next/snapshots/thoughtstream.json, then run node --experimental-sqlite dist/cli.js import thoughtstream. There is no bundled generic Ideaflow exporter or account-connect wizard.

Otter

Optional Otter access needs your own private credentials file via BLOOMSCROLL_NEXT_OTTER_CREDENTIALS_FILE. The current adapter expects OTTER_EMAIL and OTTER_PASSWORD entries and selects retreat/practice-themed recordings, not the entire library. Then run node --experimental-sqlite dist/cli.js sync otter. It imports excerpts and original links; it does not archive all your audio.

Keep credential files private and outside the download/share package. Missing optional credentials leave a source unavailable, not a broken RSS reader.

05 / ONE PLACE FOR WHAT'S NEXT

Up next & Read later.

Up next, beside Library in the top bar, is one page with short sections, each one tap to open or play. With no extra setup it shows your Read later queue (oldest first). Put any post there with the clock icon; it is separate from Save, and opening a link never marks it done. All Read later in Library › opens the whole queue.

Two more sections appear when you set them up, and are labelled Optional until then: YouTube Watch Later and a practice set. Nothing is imported automatically, and nothing breaks if you never set them up.

06 / ADVANCED · OPTIONAL

YouTube Watch Later.

YouTube does not offer an API for Watch Later, so the app reads your own list with yt-dlp from the browser where you are signed in to YouTube. It is a snapshot you refresh when you like, not a live connection.

  1. Install yt-dlp (for example brew install yt-dlp or pipx install yt-dlp) and sign in to YouTube in Chrome, Brave, Edge, Firefox or Safari.
  2. On macOS, open Terminal on the desktop (not an SSH session): the browser's cookie key in Keychain is only readable from your logged-in session, and macOS may ask you to allow access. In the extracted bloomscroll-next folder run the commands below.
  3. Check the JSON: snapshot_published with a row count, then cached-snapshot from the import.
python3 -I scripts/youtube-watch-later.py acquire --root . --browser chrome
node --experimental-sqlite dist/cli.js import youtube-watch-later

Only the video ID, title, channel, duration, a YouTube thumbnail, the youtube.com link and the list position are kept. No cookies or account data are copied. Videos play only after you tap Play here (no autoplay), or open on YouTube. Re-run both commands to refresh; videos you removed from Watch Later leave the section but keep anything you saved.

Browser on a different computer? Run acquire there without --root . (it writes under ~/.local/share/bloomscroll-next-source-reader), then on the app computer run python3 -I scripts/youtube-watch-later.py pull --ssh-host HOST --remote-root /absolute/path/on/that/computer within an hour, then the import command.

07 / ADVANCED · OPTIONAL

A practice set.

The Practicing now and Quick references sections, the Practice lane in the feed (at most two items a session, oldest practiced first) and the Practice this button read from a separate, Install-compatible practice endpoint that you run yourself. Install is the name of a separate personal app that keeps a practice list; it is not distributed here, and any HTTP service that implements the contract below works. No such service ships with this download, and the integration is off unless you configure it.

  1. Provide an HTTP endpoint (the contract below; any service that implements it works) with GET /api/practicing returning {"version":1,"cap":20,"items":[…]} ordered oldest-practiced first. Each item: {"id":"item:…","title":"…","collection":{"title":"…"},"kind":"prescribed|course|lineage|self","restricted":false,"media":{"type":"video|audio","url":"https://…"} or {"type":"video","embed":{"provider":"youtube","id":"…","start":0}},"last_practiced_at":null,"added_at":"ISO time","practice_count":0,"href":"https://… page for the item"}. IDs must start with item: or ext:.
  2. POST /api/practicing (JSON) receives {"action":"practiced","id":"…","operationId":"…","via":"bloomscroll-next"}, and for Practice this {"action":"add","url":"…","title":"…","provider":"…","media":{"kind":"video","cachedUrl":…,"posterUrl":…,"youtubeId":…},"origin":{"service":"bloomscroll-next","itemId":"…"},"replaceId"?:"…"} and {"action":"remove","id":"…"}. It must respond with the whole updated set in the GET shape. Treat a repeated operationId as a no-op. Errors are a 409 with {"code":"practice_full","cap":20,"items":[…]}, "practice_removed" or "operation_id_reused", and a 404 for an unknown id.
  3. Optional, for Quick references: GET /api/references returning {"collections":[{"id":"…"}],"entries":[{"type":"item","id":"…","title":"…","media":"Local video","href":"https://…","collection":{"id":"…","title":"…","self_filed":true}}]}. Only self_filed, non-restricted item entries are shown.
  4. Start the app with BLOOMSCROLL_NEXT_INSTALL_URL set to that endpoint's base URL (the app calls it server-to-server). If its media should play in your browser, also set BLOOMSCROLL_NEXT_INSTALL_PUBLIC_URL to the browser-facing address (only its origin is used) so its media and an All references link at /references are allowed.
  5. Run node --experimental-sqlite dist/cli.js sync install-practice, or refresh it in Sources.

Practice items stay private: they are never sent to save/sync destinations or share links.

08 / OPTIONAL PRIVATE DEVICE ACCESS

Read your full library on a phone.

Run the app on an always-on computer. It listens on loopback only. Use your own Tailscale Serve setup for private HTTPS access. Use the actual port printed by Bloomscroll, and your own tailnet hostname.

  1. With the app running, point Tailscale Serve at http://127.0.0.1:PORT using an available HTTPS port.
  2. Set BLOOMSCROLL_NEXT_ORIGINS to the exact HTTPS origin Tailscale gives you, including any nondefault port. Restart the app with that environment variable.
  3. On your phone, connected to your tailnet, open that HTTPS address. Keep the app’s host computer awake.

Use private Serve rather than public Funnel. Access-key mode needs HTTPS because its cookie is Secure. The reader is not a shared multi-user hosted service.

If you only want notes and saved links on a phone, the separate phone beta runs without your own server. It does not import RSS/social feeds or share the full reader’s library.

KEEP YOUR READER YOURS

Backup, update, troubleshoot.

Backup: stop the app before copying data/next/ to a private backup. It contains your database, imported source snapshots, feed configuration, reflections, and review state. Never upload it as a public bug report.

Update: stop the app, back up your data, unzip the new release into a separate folder, and privately copy your existing data/next/ into it. Preserve private file permissions and credential environment settings. Then run the new app.

Empty discovery: choose Discover, inspect Sources, check the refresh result and publisher availability. Only dated recent material qualifies. Port conflict: set BLOOMSCROLL_NEXT_PORT to an available port between 7000–9999, excluding 7068, and restart. Unavailable social source: recheck your own signed-in browser, reader tool compatibility, expected account, and any OS authentication prompt.

Repository contributors: the repository remains private. With access, use the source setup guide. The runnable download above does not require repository access.

Alpha limitations: technical setup, optional sources (YouTube Watch Later, practice set) need your own tools or endpoint, no native installer or App Store release, no one-click social authentication, explicit refreshes, no automated rage-bait classifier, and no general multi-user hosting.