Canopy Reading
Canopy is a local-first, low-maintenance AI reading system that prioritizes reflection and long-term value over engagement metrics. Unlike traditional reading apps that provide generic recommendations based on your entire reading history, Canopy organizes reading around branches — themes, questions, or curiosities that guide what you read—and delivers recommendations that learn your preferences within each area of interest.
The system uses event sourcing to maintain an append-only log of all reading decisions, making every recommendation and reflection inspectable and reversible. Canopy integrates with e-reader apps via OPDS export, automatically enriches book metadata using OpenLibrary, and learns from user reflections to improve recommendations over time. It’s designed as personal software that works alongside platforms like Goodreads, focusing on specialized recommendation rather than social features.
Canopy Reading is available as a self-hostable web application that can be run locally or deployed to Cloudflare.
Find the source code on GitHub
Technology Stack
Framework:
- Astro with Server-Side Rendering (SSR)
- TypeScript
- Node.js filesystem adapter for standalone deployment
- Cloudflare Durable Objects adapter for storage
Frontend:
- Tailwind
- Astro components for UI
- Responsive design
Storage:
- Pluggable storage adapter architecture
- File-based event sourcing (default)
- Support for Cloudflare Durable Objects (via adapter)
- Human-readable JSON event files
AI & APIs:
- OpenAI GPT-4o for recommendation generation
- OpenLibrary API for book metadata enrichment
- Mock recommendation fallback for development
Data Formats:
- OPDS (Open Publication Distribution System) for e-reader integration
- Markdown for filesystem event storage and API responses (human-readable)
- JSON for Cloudflare Durable Objects event storage
Development Tools:
- Vitest for testing
- Biome for linting and formatting
- GitHub Actions for CI/CD
Architecture Patterns
Event Sourcing:
- All reading decisions stored as immutable events in chronological order
- Append-only log ensures complete history preservation
- State projection from event stream allows for time-travel debugging
Storage Adapter Pattern:
- Pluggable storage backends (filesystem, Cloudflare Durable Objects, memory)
- Clean interface abstraction (
StorageAdapter) for different deployment targets - Factory pattern for adapter instantiation based on environment configuration
Projection Pattern:
- Branch state computed from event stream rather than stored directly
- Inbox and library derived from events, ensuring consistency
- Context window built from history for AI recommendations
Branch-Based Organization:
- Each branch maintains independent recommendation context
- Category-specific learning prevents preference bleeding across genres
- Fork capability allows branching reading paths from existing branches
API-First Design:
- RESTful API endpoints for all operations
- OPDS-compatible export endpoints for e-reader integration
- Stateless API design with event sourcing providing persistence
Reflection-Driven Learning:
- User reflections inform future recommendations within each branch
- Book-level and branch-level reflections both contribute to context
- AI recommendations improve as reflection history grows
Challenges Faced
Storage Adapter Abstraction:
- Creating a unified interface that works for both file system and Cloudflare Durable Objects
- Handling different consistency models (file system vs. distributed storage)
- Maintaining adapter compatibility while adding new features
AI Context Management:
- Building effective context windows from event history for recommendations
- Balancing context size with API token limits
- Extracting meaningful signals from user actions (accept/reject/defer) and reflections
Local-First Philosophy:
- Ensuring all data remains accessible without cloud dependencies
- File-based storage that’s git-friendly and human-readable
- No database migrations or complex infrastructure requirements
Long-Lived Personal Software:
- Creating software that can be maintained over years without significant ongoing effort
- Minimizing dependencies to reduce security updates and breaking changes
- Ensuring file storage is useful in isolation—all event files are human-readable markdown that can be understood and processed without the application
- Designing for “pick up and put down” usage patterns where the software remains functional after months of inactivity