.article-card form had margin-left: auto applied to each form individually inside a flex row that also held the meta text (hostname, date, unread badge) — spacing shifted depending on whether the unread badge was present, since the auto-margin was competing with a variable number of sibling elements instead of anchoring against a fixed layout. Restructured into two explicit flex groups per row — meta info on the left, action buttons on the right, justify-content: space-between on the container — so the buttons land at a consistent x-position regardless of read/unread state or article title length. Same fix applied to both the list view and the reader view's meta row. Verified visually against both states (read and unread cards) in a screenshot — buttons now align consistently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
stash
A self-hosted read-later tool. Click "Send to stash" in the browser, the current page's article content is extracted and stored on your own server — full-text searchable, with a plain reader view. No third party ever sees your reading list.
Two pieces, neither useful alone:
- Server (
src/server) — Express + SQLite (viabetter-sqlite3), FTS5 full-text search, a minimal server-rendered reading list + reader view, and the/api/*endpoints the extension talks to. - Extension (
extension/) — Manifest V3, sideloaded (not published to any store). Runs@mozilla/readabilityagainst the current page to strip nav/ads/chrome and keep just the article, falling back to the raw rendered page if extraction comes back too thin to be useful.
Full design and the reasoning behind the scope boundaries: PROPOSAL.md.
Server setup
cp .env.example .env
npm install
npm run token -- my-laptop # prints a bearer token — paste it into the extension's options
npm run dev:server
Extension setup
chrome://extensions→ enable Developer mode → "Load unpacked" → selectextension/- Click the extension icon once, or use "Options" from its context menu, to open the options page
- Enter your server's URL and the token from
npm run token
Then either click the toolbar icon or right-click a page → "Send to stash".
Reading
Visit http://your-server:3070/?t=<token> — a plain list of what's been
captured, with search and an unread filter. Click through to /read/:id
for the cleaned reader view.
Status
Built and verified end-to-end: capture → server storage → full-text search → mark-read → reader view, driven through the actual extension (loaded in a real Chromium instance, not just unit-tested logic) against a live server. Not yet deployed anywhere.
License
MIT.