Vibecode Pocket
track this build6 phases, 12 steps, beginner friendly0%🪦 Pocket shut down on July 8, 2025. The verdict below is now a post-mortem.
Save a URL, fetch and clean the article, store it, sync a list. Readability parsing is a solved library problem and the rest is a table. Pocket dying is the argument for owning this: the code was never the hard part, keeping the service alive was.
You are building a lean indie version of Pocket. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Pocket · indie build A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | save, extract, list | | Extraction | @mozilla/readability with jsdom | the Firefox Reader View engine | | Search | SQLite FTS5 | full text over everything you saved | | Hosting | A VPS behind Caddy | the phone share target needs a public HTTPS URL | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your Pocket CSV export, if you made one** (optional) · free - Why: Phase 5 imports it. The export window closed in November 2025. - Get it: The CSV Pocket emailed you, if you exported before the shutdown. - [ ] **A phone to install the PWA and use the share sheet** · free - Why: Phase 2 saves from the share sheet. - Get it: Android Chrome supports share targets fully; iOS Safari with the site added to the home screen. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: read.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Native apps, the recommendation feed, text-to-speech. - the mobile apps with offline sync - the recommendation feed - text-to-speech - someone keeping it online (though that turned out not to be guaranteed) If one of those is essential to you, that is the reason to keep paying for Pocket, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Pocket The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a read-it-later service to replace Pocket, which no longer exists. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. @mozilla/readability with jsdom for extraction. Server-rendered HTML, a PWA manifest so a phone can share to it. No framework. ### Data model (create this before Phase 1) - articles: id, url (unique), title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite - tags and article_tags - FTS5 index over title, excerpt and content. ### Phase 1 · Save and extract Build: POST /save with a URL fetches the page with a timeout and a size cap, refuses private addresses, extracts the article with Readability, sanitizes the HTML against an allowlist, rewrites relative URLs, and stores it. Duplicate URLs update rather than duplicate. Done when: a news article saves with clean readable HTML and no scripts, a paywalled page saves whatever is public with a note, and saving the same URL twice yields one row. Do not build yet: UI, import. ### Phase 2 · Capture from anywhere Build: a bookmarklet, a PWA share target so a phone's share sheet posts to /save, and an email-in address if you have one. Auth is a token in the bookmarklet and a session cookie for the PWA. Done when: a page saves from a desktop bookmarklet and from a phone share sheet. ### Phase 3 · Read Build: a list with unread, archive and favorites views, a reading page with a measured line length, dark mode, font size controls, and keyboard shortcuts. Mark read on open. Done when: a long article reads comfortably on a phone and the list updates without a reload after archiving. ### Phase 4 · Search and tags Build: FTS5 search over saved content, tags with autocomplete, and a tag view. Done when: a phrase inside an article body is found and tagging updates the view. ### Phase 5 · Import and export Build: import the Pocket CSV export (url, title, time_added, tags, status) re-fetching each article with a polite delay, and a full JSON export of everything you own. Done when: a real Pocket export imports with tags and read state and the JSON export round-trips. ### Phase 6 · Offline and deploy Build: a service worker caching the last 50 unread articles for offline reading, a nightly backup, a systemd unit, the README. Done when: airplane mode still opens recent articles. ### Out of scope (and why) - Native apps, the recommendation feed, text-to-speech. ### README must contain - The import path from the Pocket export, and the reminder that the export window has closed for anyone who did not act. - Where the database lives · this time the reading list is yours. ===== AGENTS.md ===== # Agent instructions · Pocket indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, @mozilla/readability with jsdom, SQLite FTS5, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Pocket A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Save and extract A URL becomes a clean article row; private addresses refused; duplicates update. ### Steps 1. Tables and POST /save with a timeout, size cap and private-address refusal articles (id, url unique, title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite), tags, article_tags, FTS5. ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` 2. Extract with Readability, sanitize against an allowlist, absolutize URLs ### Done when - [ ] A news article saves clean with no scripts - [ ] A paywalled page saves what is public with a note - [ ] Saving the same URL twice yields one row ## Phase 2 · Capture from anywhere Bookmarklet, PWA share target, session for the phone. ### Steps 1. A bookmarklet carrying SAVE_TOKEN 2. A web app manifest with a share_target posting to /save; a session cookie login ### Done when - [ ] A page saves from the desktop bookmarklet - [ ] A page saves from a phone share sheet ## Phase 3 · Read Unread, archive, favorites; a reading page worth reading on. ### Steps 1. List views and a reading page with measured width, dark mode, font controls 2. Keyboard shortcuts; mark read on open ### Done when - [ ] A long article reads comfortably on a phone - [ ] The list updates without reload after archiving ## Phase 4 · Search and tags FTS5 and tags with autocomplete. ### Steps 1. FTS5 over content synced by triggers 2. Tags with autocomplete and a tag view ### Done when - [ ] A phrase inside a body is found - [ ] Tagging updates the view ## Phase 5 · Import and export The Pocket CSV in, all of it out as JSON. ### Steps 1. Import the Pocket CSV re-fetching each article with a polite delay 2. A full JSON export that round-trips ### Done when - [ ] A real export imports with tags and read state - [ ] The JSON export round-trips ## Phase 6 · Offline and deploy Recent articles readable in airplane mode; backups; service. ### Steps 1. A service worker caching the last 50 unread 2. Nightly backup, systemd, Caddy, README Files: `README.md` ### Done when - [ ] Airplane mode opens recent articles ## Not in this build - Native apps, the recommendation feed, text-to-speech. ## After v1, if you want it - Highlights and notes per article - An RSS feed of your favorites ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/reader.db # Required. Public base URL. SITE_URL=https://read.yourdomain.com # Required · secret. openssl rand -base64 32; carried by the bookmarklet. SAVE_TOKEN=base64 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a lean indie version of Pocket. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Pocket · indie build A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | save, extract, list | | Extraction | @mozilla/readability with jsdom | the Firefox Reader View engine | | Search | SQLite FTS5 | full text over everything you saved | | Hosting | A VPS behind Caddy | the phone share target needs a public HTTPS URL | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your Pocket CSV export, if you made one** (optional) · free - Why: Phase 5 imports it. The export window closed in November 2025. - Get it: The CSV Pocket emailed you, if you exported before the shutdown. - [ ] **A phone to install the PWA and use the share sheet** · free - Why: Phase 2 saves from the share sheet. - Get it: Android Chrome supports share targets fully; iOS Safari with the site added to the home screen. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: read.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Native apps, the recommendation feed, text-to-speech. - the mobile apps with offline sync - the recommendation feed - text-to-speech - someone keeping it online (though that turned out not to be guaranteed) If one of those is essential to you, that is the reason to keep paying for Pocket, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Pocket The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a read-it-later service to replace Pocket, which no longer exists. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. @mozilla/readability with jsdom for extraction. Server-rendered HTML, a PWA manifest so a phone can share to it. No framework. ### Data model (create this before Phase 1) - articles: id, url (unique), title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite - tags and article_tags - FTS5 index over title, excerpt and content. ### Phase 1 · Save and extract Build: POST /save with a URL fetches the page with a timeout and a size cap, refuses private addresses, extracts the article with Readability, sanitizes the HTML against an allowlist, rewrites relative URLs, and stores it. Duplicate URLs update rather than duplicate. Done when: a news article saves with clean readable HTML and no scripts, a paywalled page saves whatever is public with a note, and saving the same URL twice yields one row. Do not build yet: UI, import. ### Phase 2 · Capture from anywhere Build: a bookmarklet, a PWA share target so a phone's share sheet posts to /save, and an email-in address if you have one. Auth is a token in the bookmarklet and a session cookie for the PWA. Done when: a page saves from a desktop bookmarklet and from a phone share sheet. ### Phase 3 · Read Build: a list with unread, archive and favorites views, a reading page with a measured line length, dark mode, font size controls, and keyboard shortcuts. Mark read on open. Done when: a long article reads comfortably on a phone and the list updates without a reload after archiving. ### Phase 4 · Search and tags Build: FTS5 search over saved content, tags with autocomplete, and a tag view. Done when: a phrase inside an article body is found and tagging updates the view. ### Phase 5 · Import and export Build: import the Pocket CSV export (url, title, time_added, tags, status) re-fetching each article with a polite delay, and a full JSON export of everything you own. Done when: a real Pocket export imports with tags and read state and the JSON export round-trips. ### Phase 6 · Offline and deploy Build: a service worker caching the last 50 unread articles for offline reading, a nightly backup, a systemd unit, the README. Done when: airplane mode still opens recent articles. ### Out of scope (and why) - Native apps, the recommendation feed, text-to-speech. ### README must contain - The import path from the Pocket export, and the reminder that the export window has closed for anyone who did not act. - Where the database lives · this time the reading list is yours. ===== AGENTS.md ===== # Agent instructions · Pocket indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, @mozilla/readability with jsdom, SQLite FTS5, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Pocket A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Save and extract A URL becomes a clean article row; private addresses refused; duplicates update. ### Steps 1. Tables and POST /save with a timeout, size cap and private-address refusal articles (id, url unique, title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite), tags, article_tags, FTS5. ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` 2. Extract with Readability, sanitize against an allowlist, absolutize URLs ### Done when - [ ] A news article saves clean with no scripts - [ ] A paywalled page saves what is public with a note - [ ] Saving the same URL twice yields one row ## Phase 2 · Capture from anywhere Bookmarklet, PWA share target, session for the phone. ### Steps 1. A bookmarklet carrying SAVE_TOKEN 2. A web app manifest with a share_target posting to /save; a session cookie login ### Done when - [ ] A page saves from the desktop bookmarklet - [ ] A page saves from a phone share sheet ## Phase 3 · Read Unread, archive, favorites; a reading page worth reading on. ### Steps 1. List views and a reading page with measured width, dark mode, font controls 2. Keyboard shortcuts; mark read on open ### Done when - [ ] A long article reads comfortably on a phone - [ ] The list updates without reload after archiving ## Phase 4 · Search and tags FTS5 and tags with autocomplete. ### Steps 1. FTS5 over content synced by triggers 2. Tags with autocomplete and a tag view ### Done when - [ ] A phrase inside a body is found - [ ] Tagging updates the view ## Phase 5 · Import and export The Pocket CSV in, all of it out as JSON. ### Steps 1. Import the Pocket CSV re-fetching each article with a polite delay 2. A full JSON export that round-trips ### Done when - [ ] A real export imports with tags and read state - [ ] The JSON export round-trips ## Phase 6 · Offline and deploy Recent articles readable in airplane mode; backups; service. ### Steps 1. A service worker caching the last 50 unread 2. Nightly backup, systemd, Caddy, README Files: `README.md` ### Done when - [ ] Airplane mode opens recent articles ## Not in this build - Native apps, the recommendation feed, text-to-speech. ## After v1, if you want it - Highlights and notes per article - An RSS feed of your favorites ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/reader.db # Required. Public base URL. SITE_URL=https://read.yourdomain.com # Required · secret. openssl rand -base64 32; carried by the bookmarklet. SAVE_TOKEN=base64 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a production product version of Pocket. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Pocket · product brief ## Problem Save a URL, fetch and clean the article, store it, sync a list. Readability parsing is a solved library problem and the rest is a table. Pocket dying is the argument for owning this: the code was never the hard part, keeping the service alive was. ## Product outcome A reading list that survives any company's strategy shift, with a phone path and offline reading. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small always-on host - a browser bookmarklet or PWA share target ## Explicit non-goals for v1 - Native apps, the recommendation feed, text-to-speech. - the mobile apps with offline sync - the recommendation feed - text-to-speech - someone keeping it online (though that turned out not to be guaranteed) ## Success criteria - Share-sheet save verified on a phone - Import verified on a real export - One restore drill performed ===== BRIEF.md ===== # Build brief · Pocket The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a read-it-later service to replace Pocket, which no longer exists. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. @mozilla/readability with jsdom for extraction. Server-rendered HTML, a PWA manifest so a phone can share to it. No framework. ### Data model (create this before Phase 1) - articles: id, url (unique), title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite - tags and article_tags - FTS5 index over title, excerpt and content. ### Phase 1 · Save and extract Build: POST /save with a URL fetches the page with a timeout and a size cap, refuses private addresses, extracts the article with Readability, sanitizes the HTML against an allowlist, rewrites relative URLs, and stores it. Duplicate URLs update rather than duplicate. Done when: a news article saves with clean readable HTML and no scripts, a paywalled page saves whatever is public with a note, and saving the same URL twice yields one row. Do not build yet: UI, import. ### Phase 2 · Capture from anywhere Build: a bookmarklet, a PWA share target so a phone's share sheet posts to /save, and an email-in address if you have one. Auth is a token in the bookmarklet and a session cookie for the PWA. Done when: a page saves from a desktop bookmarklet and from a phone share sheet. ### Phase 3 · Read Build: a list with unread, archive and favorites views, a reading page with a measured line length, dark mode, font size controls, and keyboard shortcuts. Mark read on open. Done when: a long article reads comfortably on a phone and the list updates without a reload after archiving. ### Phase 4 · Search and tags Build: FTS5 search over saved content, tags with autocomplete, and a tag view. Done when: a phrase inside an article body is found and tagging updates the view. ### Phase 5 · Import and export Build: import the Pocket CSV export (url, title, time_added, tags, status) re-fetching each article with a polite delay, and a full JSON export of everything you own. Done when: a real Pocket export imports with tags and read state and the JSON export round-trips. ### Phase 6 · Offline and deploy Build: a service worker caching the last 50 unread articles for offline reading, a nightly backup, a systemd unit, the README. Done when: airplane mode still opens recent articles. ### Out of scope (and why) - Native apps, the recommendation feed, text-to-speech. ### README must contain - The import path from the Pocket export, and the reminder that the export window has closed for anyone who did not act. - Where the database lives · this time the reading list is yours. ===== ARCHITECTURE.md ===== # Architecture · Pocket ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | save, extract, list | | Extraction | @mozilla/readability with jsdom | the Firefox Reader View engine | | Search | SQLite FTS5 | full text over everything you saved | | Hosting | A VPS behind Caddy | the phone share target needs a public HTTPS URL | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Saver | /save, extraction, sanitizing | A different extractor behind the same function | | Library | articles, tags, FTS | The core | | Reader | views and offline cache | Any UI | | Transfer | import and export | Add wallabag import | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite file. - `SITE_URL` · required · Public base URL. - `SAVE_TOKEN` · required, secret · openssl rand -base64 32; carried by the bookmarklet. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Pocket product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, @mozilla/readability with jsdom, SQLite FTS5, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Pocket Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Save and extract A URL becomes a clean article row; private addresses refused; duplicates update. ### Steps 1. Tables and POST /save with a timeout, size cap and private-address refusal articles (id, url unique, title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite), tags, article_tags, FTS5. ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` 2. Extract with Readability, sanitize against an allowlist, absolutize URLs ### Done when - [ ] A news article saves clean with no scripts - [ ] A paywalled page saves what is public with a note - [ ] Saving the same URL twice yields one row ## M2 · Capture from anywhere Bookmarklet, PWA share target, session for the phone. ### Steps 1. A bookmarklet carrying SAVE_TOKEN 2. A web app manifest with a share_target posting to /save; a session cookie login ### Done when - [ ] A page saves from the desktop bookmarklet - [ ] A page saves from a phone share sheet ## M3 · Read Unread, archive, favorites; a reading page worth reading on. ### Steps 1. List views and a reading page with measured width, dark mode, font controls 2. Keyboard shortcuts; mark read on open ### Done when - [ ] A long article reads comfortably on a phone - [ ] The list updates without reload after archiving ## M4 · Search and tags FTS5 and tags with autocomplete. ### Steps 1. FTS5 over content synced by triggers 2. Tags with autocomplete and a tag view ### Done when - [ ] A phrase inside a body is found - [ ] Tagging updates the view ## M5 · Import and export The Pocket CSV in, all of it out as JSON. ### Steps 1. Import the Pocket CSV re-fetching each article with a polite delay 2. A full JSON export that round-trips ### Done when - [ ] A real export imports with tags and read state - [ ] The JSON export round-trips ## M6 · Offline and deploy Recent articles readable in airplane mode; backups; service. ### Steps 1. A service worker caching the last 50 unread 2. Nightly backup, systemd, Caddy, README Files: `README.md` ### Done when - [ ] Airplane mode opens recent articles ## M7 · Operate it like a product (production only) Only for the product-builder path: know when the reader is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443 ===== OPERATIONS.md ===== # Operations · Pocket ## Backup SQLite nightly off the box. ## Restore Copy back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist Rotate SAVE_TOKEN if the bookmarklet leaks. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Share-sheet save verified on a phone - [ ] Import verified on a real export - [ ] One restore drill performed ## Launch constraint Do not market omitted Pocket capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/reader.db # Required. Public base URL. SITE_URL=https://read.yourdomain.com # Required · secret. openssl rand -base64 32; carried by the bookmarklet. SAVE_TOKEN=base64 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Pocket · indie build A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | save, extract, list | | Extraction | @mozilla/readability with jsdom | the Firefox Reader View engine | | Search | SQLite FTS5 | full text over everything you saved | | Hosting | A VPS behind Caddy | the phone share target needs a public HTTPS URL | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **Your Pocket CSV export, if you made one** (optional) · free - Why: Phase 5 imports it. The export window closed in November 2025. - Get it: The CSV Pocket emailed you, if you exported before the shutdown. - [ ] **A phone to install the PWA and use the share sheet** · free - Why: Phase 2 saves from the share sheet. - Get it: Android Chrome supports share targets fully; iOS Safari with the site added to the home screen. - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: read.yourdomain.com - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server ## Quick start ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Native apps, the recommendation feed, text-to-speech. - the mobile apps with offline sync - the recommendation feed - text-to-speech - someone keeping it online (though that turned out not to be guaranteed) If one of those is essential to you, that is the reason to keep paying for Pocket, and the README should say so rather than pretend.
# Build brief · Pocket The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a read-it-later service to replace Pocket, which no longer exists. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. @mozilla/readability with jsdom for extraction. Server-rendered HTML, a PWA manifest so a phone can share to it. No framework. ### Data model (create this before Phase 1) - articles: id, url (unique), title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite - tags and article_tags - FTS5 index over title, excerpt and content. ### Phase 1 · Save and extract Build: POST /save with a URL fetches the page with a timeout and a size cap, refuses private addresses, extracts the article with Readability, sanitizes the HTML against an allowlist, rewrites relative URLs, and stores it. Duplicate URLs update rather than duplicate. Done when: a news article saves with clean readable HTML and no scripts, a paywalled page saves whatever is public with a note, and saving the same URL twice yields one row. Do not build yet: UI, import. ### Phase 2 · Capture from anywhere Build: a bookmarklet, a PWA share target so a phone's share sheet posts to /save, and an email-in address if you have one. Auth is a token in the bookmarklet and a session cookie for the PWA. Done when: a page saves from a desktop bookmarklet and from a phone share sheet. ### Phase 3 · Read Build: a list with unread, archive and favorites views, a reading page with a measured line length, dark mode, font size controls, and keyboard shortcuts. Mark read on open. Done when: a long article reads comfortably on a phone and the list updates without a reload after archiving. ### Phase 4 · Search and tags Build: FTS5 search over saved content, tags with autocomplete, and a tag view. Done when: a phrase inside an article body is found and tagging updates the view. ### Phase 5 · Import and export Build: import the Pocket CSV export (url, title, time_added, tags, status) re-fetching each article with a polite delay, and a full JSON export of everything you own. Done when: a real Pocket export imports with tags and read state and the JSON export round-trips. ### Phase 6 · Offline and deploy Build: a service worker caching the last 50 unread articles for offline reading, a nightly backup, a systemd unit, the README. Done when: airplane mode still opens recent articles. ### Out of scope (and why) - Native apps, the recommendation feed, text-to-speech. ### README must contain - The import path from the Pocket export, and the reminder that the export window has closed for anyone who did not act. - Where the database lives · this time the reading list is yours.
# Agent instructions · Pocket indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, @mozilla/readability with jsdom, SQLite FTS5, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Pocket A read-it-later service to replace the Pocket that no longer exists: save from a bookmarklet or your phone's share sheet, extract the article with Readability, read it cleanly, tag and search it, import your Pocket export, export everything as JSON, and read offline. This time the reading list is yours. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Save and extract A URL becomes a clean article row; private addresses refused; duplicates update. ### Steps 1. Tables and POST /save with a timeout, size cap and private-address refusal articles (id, url unique, title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite), tags, article_tags, FTS5. ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` 2. Extract with Readability, sanitize against an allowlist, absolutize URLs ### Done when - [ ] A news article saves clean with no scripts - [ ] A paywalled page saves what is public with a note - [ ] Saving the same URL twice yields one row ## Phase 2 · Capture from anywhere Bookmarklet, PWA share target, session for the phone. ### Steps 1. A bookmarklet carrying SAVE_TOKEN 2. A web app manifest with a share_target posting to /save; a session cookie login ### Done when - [ ] A page saves from the desktop bookmarklet - [ ] A page saves from a phone share sheet ## Phase 3 · Read Unread, archive, favorites; a reading page worth reading on. ### Steps 1. List views and a reading page with measured width, dark mode, font controls 2. Keyboard shortcuts; mark read on open ### Done when - [ ] A long article reads comfortably on a phone - [ ] The list updates without reload after archiving ## Phase 4 · Search and tags FTS5 and tags with autocomplete. ### Steps 1. FTS5 over content synced by triggers 2. Tags with autocomplete and a tag view ### Done when - [ ] A phrase inside a body is found - [ ] Tagging updates the view ## Phase 5 · Import and export The Pocket CSV in, all of it out as JSON. ### Steps 1. Import the Pocket CSV re-fetching each article with a polite delay 2. A full JSON export that round-trips ### Done when - [ ] A real export imports with tags and read state - [ ] The JSON export round-trips ## Phase 6 · Offline and deploy Recent articles readable in airplane mode; backups; service. ### Steps 1. A service worker caching the last 50 unread 2. Nightly backup, systemd, Caddy, README Files: `README.md` ### Done when - [ ] Airplane mode opens recent articles ## Not in this build - Native apps, the recommendation feed, text-to-speech. ## After v1, if you want it - Highlights and notes per article - An RSS feed of your favorites
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/reader.db # Required. Public base URL. SITE_URL=https://read.yourdomain.com # Required · secret. openssl rand -base64 32; carried by the bookmarklet. SAVE_TOKEN=base64 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Pocket · product brief ## Problem Save a URL, fetch and clean the article, store it, sync a list. Readability parsing is a solved library problem and the rest is a table. Pocket dying is the argument for owning this: the code was never the hard part, keeping the service alive was. ## Product outcome A reading list that survives any company's strategy shift, with a phone path and offline reading. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small always-on host - a browser bookmarklet or PWA share target ## Explicit non-goals for v1 - Native apps, the recommendation feed, text-to-speech. - the mobile apps with offline sync - the recommendation feed - text-to-speech - someone keeping it online (though that turned out not to be guaranteed) ## Success criteria - Share-sheet save verified on a phone - Import verified on a real export - One restore drill performed
# Build brief · Pocket The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a read-it-later service to replace Pocket, which no longer exists. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. @mozilla/readability with jsdom for extraction. Server-rendered HTML, a PWA manifest so a phone can share to it. No framework. ### Data model (create this before Phase 1) - articles: id, url (unique), title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite - tags and article_tags - FTS5 index over title, excerpt and content. ### Phase 1 · Save and extract Build: POST /save with a URL fetches the page with a timeout and a size cap, refuses private addresses, extracts the article with Readability, sanitizes the HTML against an allowlist, rewrites relative URLs, and stores it. Duplicate URLs update rather than duplicate. Done when: a news article saves with clean readable HTML and no scripts, a paywalled page saves whatever is public with a note, and saving the same URL twice yields one row. Do not build yet: UI, import. ### Phase 2 · Capture from anywhere Build: a bookmarklet, a PWA share target so a phone's share sheet posts to /save, and an email-in address if you have one. Auth is a token in the bookmarklet and a session cookie for the PWA. Done when: a page saves from a desktop bookmarklet and from a phone share sheet. ### Phase 3 · Read Build: a list with unread, archive and favorites views, a reading page with a measured line length, dark mode, font size controls, and keyboard shortcuts. Mark read on open. Done when: a long article reads comfortably on a phone and the list updates without a reload after archiving. ### Phase 4 · Search and tags Build: FTS5 search over saved content, tags with autocomplete, and a tag view. Done when: a phrase inside an article body is found and tagging updates the view. ### Phase 5 · Import and export Build: import the Pocket CSV export (url, title, time_added, tags, status) re-fetching each article with a polite delay, and a full JSON export of everything you own. Done when: a real Pocket export imports with tags and read state and the JSON export round-trips. ### Phase 6 · Offline and deploy Build: a service worker caching the last 50 unread articles for offline reading, a nightly backup, a systemd unit, the README. Done when: airplane mode still opens recent articles. ### Out of scope (and why) - Native apps, the recommendation feed, text-to-speech. ### README must contain - The import path from the Pocket export, and the reminder that the export window has closed for anyone who did not act. - Where the database lives · this time the reading list is yours.
# Architecture · Pocket ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | save, extract, list | | Extraction | @mozilla/readability with jsdom | the Firefox Reader View engine | | Search | SQLite FTS5 | full text over everything you saved | | Hosting | A VPS behind Caddy | the phone share target needs a public HTTPS URL | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Saver | /save, extraction, sanitizing | A different extractor behind the same function | | Library | articles, tags, FTS | The core | | Reader | views and offline cache | Any UI | | Transfer | import and export | Add wallabag import | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite file. - `SITE_URL` · required · Public base URL. - `SAVE_TOKEN` · required, secret · openssl rand -base64 32; carried by the bookmarklet. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Pocket product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, @mozilla/readability with jsdom, SQLite FTS5, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Pocket Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Save and extract A URL becomes a clean article row; private addresses refused; duplicates update. ### Steps 1. Tables and POST /save with a timeout, size cap and private-address refusal articles (id, url unique, title, site, author, published_at, content_html, excerpt, word_count, saved_at, read_at, archived, favorite), tags, article_tags, FTS5. ```sh mkdir reader && cd reader && git init && npm init -y && npm pkg set type=module && npm install @mozilla/readability@0.5 jsdom@25 sanitize-html@2 mkdir -p data && cp .env.example .env ``` 2. Extract with Readability, sanitize against an allowlist, absolutize URLs ### Done when - [ ] A news article saves clean with no scripts - [ ] A paywalled page saves what is public with a note - [ ] Saving the same URL twice yields one row ## M2 · Capture from anywhere Bookmarklet, PWA share target, session for the phone. ### Steps 1. A bookmarklet carrying SAVE_TOKEN 2. A web app manifest with a share_target posting to /save; a session cookie login ### Done when - [ ] A page saves from the desktop bookmarklet - [ ] A page saves from a phone share sheet ## M3 · Read Unread, archive, favorites; a reading page worth reading on. ### Steps 1. List views and a reading page with measured width, dark mode, font controls 2. Keyboard shortcuts; mark read on open ### Done when - [ ] A long article reads comfortably on a phone - [ ] The list updates without reload after archiving ## M4 · Search and tags FTS5 and tags with autocomplete. ### Steps 1. FTS5 over content synced by triggers 2. Tags with autocomplete and a tag view ### Done when - [ ] A phrase inside a body is found - [ ] Tagging updates the view ## M5 · Import and export The Pocket CSV in, all of it out as JSON. ### Steps 1. Import the Pocket CSV re-fetching each article with a polite delay 2. A full JSON export that round-trips ### Done when - [ ] A real export imports with tags and read state - [ ] The JSON export round-trips ## M6 · Offline and deploy Recent articles readable in airplane mode; backups; service. ### Steps 1. A service worker caching the last 50 unread 2. Nightly backup, systemd, Caddy, README Files: `README.md` ### Done when - [ ] Airplane mode opens recent articles ## M7 · Operate it like a product (production only) Only for the product-builder path: know when the reader is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443
# Operations · Pocket ## Backup SQLite nightly off the box. ## Restore Copy back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist Rotate SAVE_TOKEN if the bookmarklet leaks. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Share-sheet save verified on a phone - [ ] Import verified on a real export - [ ] One restore drill performed ## Launch constraint Do not market omitted Pocket capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/reader.db # Required. Public base URL. SITE_URL=https://read.yourdomain.com # Required · secret. openssl rand -base64 32; carried by the bookmarklet. SAVE_TOKEN=base64 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
$ choose a build depth, inspect the files, then open the complete pack in your agent
They no longer can. Wallabag, Readwise Reader and self-hosted options absorbed the users; the lesson is that a reading list is a file you should own.
xthe mobile apps with offline sync
xthe recommendation feed
xtext-to-speech
xsomeone keeping it online (though that turned out not to be guaranteed)
Vibecode Pocket
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Pocket replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Pocket cost?
Nothing anymore: Pocket shut down on July 8, 2025. Before that it ran about $4.99/month.
What do I lose by replacing Pocket?
Honestly: the mobile apps with offline sync; the recommendation feed; text-to-speech; someone keeping it online (though that turned out not to be guaranteed). Since the product is gone, these gaps are now everyone's problem.
Is there an open-source alternative to Pocket?
Yes: wallabag (self-hosted read-it-later, imports Pocket exports), Readability (article extraction library from Firefox Reader View). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.