A privacy-first article saving system that puts you in control of your data. Think Pocket or Instapaper, but your articles sync via Google Sheetsβno third-party data concerns. An optional backend server adds features like automatic metadata extraction.
The core system is working and deployed. Both the Chrome extension and PWA are functional with offline-first architecture.
Chrome Extension (Complete)
- One-click article saving from any webpage
- Metadata extraction (title, description, images, domain)
- Tags and notes support
- Direct Google Sheets sync with OAuth 2.0
- CRX packaged with stable extension ID
PWA Application (Core Complete - 85% Done)
- Full offline functionality with IndexedDB
- CRUD operations (create, read, update, delete)
- Google Sheets sync with OAuth 2.0
- Manual "Sync Now" with Last-Write-Wins conflict resolution
- Search with relevance scoring
- Advanced filtering (tags, domain, archived, favorite)
- Cursor-based pagination with "Load More" UI
- Dark mode support
- YouTube video support with embedded player
- Share links via "Share to ReadLater2" on Android (iOS not supported)
- Deployed to GitHub Pages with CI/CD
Sync Engine (Complete)
- Auto-creation of "ReadLater" spreadsheet
- Conflict-free merging with timestamp-based resolution
- Offline-first: all operations work without internet
- Background sync to Google Sheets
- Debounced auto-sync (infrastructure ready, not active)
- Filter controls in UI (filters work, not exposed yet)
- Sync progress indicators with pending change counts
- App lifecycle sync (on close/background)
- Background Sync API integration (service worker-based)
- Multiple spreadsheets/lists support
- Full article content caching for offline reading
- Text highlighting and annotations
- Chrome Web Store publication
- Backend: full page content extraction, AI-powered summarization
Motivation:
- User-owned data: Your articles live in your Google Sheetsβyou control access, backups, and retention
- Minimal infrastructure: Works with zero backend; optional server adds convenience features
- Privacy-first: Data never touches third-party servers (except Google Sheets)
- Offline-capable: Read and manage articles without internet connection
- Easy sharing: Share your reading list by sharing your Google Sheet
- React 19 with TypeScript and Tailwind CSS
- Offline-first storage using IndexedDB (Dexie.js)
- React Query for state management
- Service Worker with Web Share Target API
- Installable on any device (desktop, mobile, tablet)
- Manifest V3 with service worker
- One-click save from context menu or browser action
- Page metadata extraction
- Immediate sync to Google Sheets
- Shared library for both PWA and extension
- OAuth 2.0 authentication
- CRUD operations with error handling
- Conflict resolution (Last-Write-Wins)
- Fastify 5 with TypeScript
- URL metadata extraction (title, description, og:image)
- Enables auto-populated article fields in the PWA
- Runs independently β not part of the pnpm workspace
- See backend/README setup below
- Node.js 18+ and pnpm
- Google Cloud Project with Sheets API enabled (for OAuth)
# Install dependencies
pnpm install
# Start all packages in development mode
pnpm dev
# Or run individual packages
pnpm app dev # PWA only (http://localhost:3030)
pnpm ext dev # Extension only (load unpacked from dist/)The backend is a standalone project with its own dependencies, separate from the pnpm workspace.
cd backend
pnpm install # Separate install β not part of the workspace
pnpm dev # Starts on http://localhost:4080Then enable it in the PWA: Settings > Backend Server > Enable, set URL to http://localhost:4080.
When enabled, adding a URL in the PWA will auto-fetch title, description, and featured image from the page.
# Build everything
pnpm build
# Build extension CRX with stable ID
pnpm ext build:crxpnpm lint # ESLint all packages
pnpm typecheck # TypeScript checking
pnpm test # Run all tests with Vitest- Offline-First Architecture: All operations work immediately in IndexedDB, sync happens in background
- Cursor-Based Pagination: Consistent performance even with thousands of articles
- Advanced Search: Full-text search with relevance scoring across title, description, domain, tags
- Smart Filtering: Filter by tags, domain, archived status, favorites
- Conflict Resolution: Automatic Last-Write-Wins merging prevents sync conflicts
- Optimistic Updates: Instant UI feedback, sync happens asynchronously
- Dark Mode: System-aware theme with manual toggle
- YouTube Support: Direct video playback for saved YouTube links
Frontend: React 19, Tailwind CSS 4.x, shadcn/ui components, Lucide icons Backend: Fastify 5, cheerio, Pino (optional, standalone) Build: Vite 6, TypeScript 5.8, pnpm workspaces State: @tanstack/react-query, minimal Zustand Storage: Dexie.js (IndexedDB), Google Sheets API PWA: VitePWA plugin, Workbox, Service Workers Extension: Manifest V3, @crxjs/vite-plugin Testing: Vitest, React Testing Library, fake-indexeddb Deployment: GitHub Pages (PWA), manual CRX distribution (extension)
backend/ # Optional Fastify server (standalone)
βββ src/features/metadata/ # URL metadata extraction
βββ src/lib/ # Error handling
βββ src/utils/ # Logging
packages/ # pnpm workspace
βββ app/ # PWA (React + Vite + IndexedDB)
β βββ src/features/articles/ # Article management
β βββ src/features/settings/ # App preferences
β βββ src/components/ui/ # shadcn components
β
βββ extension/ # Chrome Extension (Manifest V3)
β βββ src/background.ts # Service worker
β βββ src/popup.tsx # Extension UI
β βββ manifest.json # Extension config
β
βββ google-sheets-sync/ # Shared sync engine
β βββ src/auth/ # OAuth providers
β βββ src/spreadsheet/ # Sheets API operations
β βββ src/sync/ # Sync engine
β
βββ core/ # Shared types/utilities
βββ src/types/ # TypeScript definitions
βββ src/utils/ # Shared utilities
- All user actions happen immediately in IndexedDB
- React Query treats IndexedDB as the source of truth
- Background sync to Google Sheets (non-blocking)
- Works completely offlineβsync when connectivity returns
- Extension: Save β Google Sheets (online-only, no local storage)
- PWA: Save β IndexedDB β Queue for sync β Background upload to Sheets
- Sync: Fetch from Sheets β Merge with LWW β Update IndexedDB
- Last-Write-Wins (LWW) using
editedAt || timestamp - Automatic resolutionβno user intervention
- Later timestamp wins; ties prefer remote version
- Simple and deterministic for single-user use case
- IndexedDB: Unix timestamps (ms) for performance
- Google Sheets: ISO 8601 strings for human readability
- Automatic conversion during sync operations
Comprehensive docs available in docs/:
- ARCHITECTURE.md - System design, data flow, architecture decisions
- OFFLINE_STORAGE.md - IndexedDB implementation, React Query patterns, repository layer
- testing-guide.md - Vitest best practices, testing patterns
- tasks.md - Development roadmap and progress tracking
- CLAUDE.md - Project instructions for AI assistants
6 test files covering critical functionality:
- Article repository (CRUD, pagination, search, filtering)
- Article list component (load more, filtering, CRUD operations)
- Sync service (conflict resolution, queue management)
- Google Sheets integration
- Component rendering
pnpm test # Run all tests (workspace)
pnpm app test # PWA tests only
pnpm ext test # Extension tests only
cd backend && pnpm test # Backend tests (separate)See docs/testing-guide.md for detailed testing guidance.
PWA: Auto-deploys to GitHub Pages on push to main branch
Extension: Manual build with pnpm ext build:crx creates readlater-extension.crx
If you're picking up this project:
- Start here: Read ARCHITECTURE.md for system design
- Understand storage: Review OFFLINE_STORAGE.md
- Check progress: See tasks.md for current state
- Next priorities:
- Enable debounced auto-sync (code ready, just needs activation)
- Expose filter controls in UI
- Implement app lifecycle sync
The foundation is solid. Most remaining work is UX polish and activation of already-built infrastructure.
MIT
For questions or issues, check the documentation first. Most answers are in docs/.