What is SvelteReader?¶
⚠️ Coding agents must ALWAYS read their instructions.
SvelteReader is a nostr-native ebook reader with AI-powered annotations and ecash micropayments and is a proof-of-concept "freedom tech" web application that demonstrates:
-
Nostr Protocol:
- Users are just simply public/private key pairs - no usernames/passwords. Usage of a NIP-07 browser extention elevates this experience even further.
- Just works - Nostr protocol is used "under the hood" and should be largely invisible to the user. We aim for an experience that "just works" with only the "advanced user" suspecting usage of the nostr protocol.
- Decentralized Sync — Book "announcements" and book annotations are sent via user-specified relays.
- No central databases - relays > central databases
-
Client-Side Storage:
- Local Browser Storage: All application data is stored in the user's browser, you own your data.
- Avoid "app store" ecosystem lock-in - we don't need approval to publish freedom tech apps
- Core experience is ran on user's device.
-
eCash Payments:
- Bitcoin payments: any human can pay for this service to enhance their learning without risk of government "money" regulations locking them out.
- Pay-per-use AI features avoid subscriptions therefore the over-charging of users who don't maximize their monthly usage.
- Self-custody prevents risk of centralized theft of user funds - private keys are stored on user's devices.
-
CopyFuck: - The Free sharing of Knowledge is core to technological progress.
Design FAQ¶
Why don't you migrate your databases?¶
We are very early in development and can't be bothered to migrate - that's a feature for a mature product. We break things and move fast using a "clean slate" approach. Adapt or die.
Why Nostr instead of traditional auth (email/password, OAuth)?¶
Decision: Users authenticate via Nostr public keys (npubs) using the CypherTap component.
Reasoning: - Sovereignty: Users own their identity (private key), not the platform - Portability: Same identity works across all Nostr-compatible apps - No email: No PII collection, no password reset flows, no email verification - Censorship resistance: No central authority can ban a user
Trade-offs: - Key management is user's responsibility (can be complex) - No account recovery if private key is lost - Less familiar UX for mainstream users
Why no server-side user accounts?¶
Decision: All user data is stored client-side (browser) with optional sync to Nostr relays.
Reasoning: - Privacy: User data never touches our servers - Simplicity: No user database to manage, secure, or GDPR-comply with - Offline-first: App works without internet after initial load - Cost: No database hosting costs that scale with users
Trade-offs: - Data lost if browser storage cleared (unless synced to Nostr) - No server-side features (email notifications, etc.) - Multi-device requires Nostr sync (not instant)
Why ecash (Cashu) instead of Stripe or subscriptions?¶
Decision: Users pay per AI message using Cashu ecash tokens.
Reasoning: - Micropayments: Pay 1 sat per message (fractions of a cent) - No accounts: No Stripe account, credit card, or identity required - Privacy: Bearer tokens, no payment history linked to identity - Instant: No payment processing delays - Global: Works anywhere Bitcoin/Lightning works
Trade-offs: - Users must acquire Bitcoin/Lightning sats first - Ecash is less familiar than credit cards - Wallet balance management UX is new
Why pay-per-message instead of subscriptions?¶
Decision: Each AI message costs a fixed amount (default: 1 sat).
Reasoning: - Fairness: Pay only for what you use - Low barrier: Try for 1 sat, no monthly commitment - Scalability: Heavy users pay more, light users pay less - Simplicity: No subscription tiers, no billing cycles
Trade-offs: - Less predictable revenue for operator - Friction on every message (mitigated by wallet balance) - No "unlimited" option for power users (yet)
Why validate-then-redeem instead of redeem-then-process?¶
Decision: Validate ecash tokens before LLM processing, redeem only on success.
Reasoning: - Zero fund loss: If LLM fails, user keeps their sats - Trust: Users don't pay for failed requests - Recovery: Client can self-redeem unredeemed tokens
Trade-offs: - Server does work before payment (minor DoS vector) - More complex payment flow - Requires NUT-07 state checking
Why SHA-256 for book identity?¶
Decision: Books are identified by the SHA-256 hash of their EPUB file, not by title or local ID.
Reasoning: - Content-addressable: Same book = same hash, regardless of filename - Deduplication: Multiple users syncing same book share annotations - Verification: Can verify EPUB matches expected hash - Cross-device: Annotations link to book by hash, not device-specific ID
Trade-offs: - Different EPUB editions = different hashes (even minor changes) - Hash computation adds import latency (~500ms for large files) - No fuzzy matching for "same book, different edition"
Why IndexedDB + localStorage instead of just one?¶
Decision: Large data (EPUBs, annotations) in IndexedDB; small state (theme, spectate) in localStorage.
Reasoning: - IndexedDB: Designed for large binary blobs, supports transactions - localStorage: Synchronous, simple API, good for small config - Separation: Keeps frequently-accessed config fast - Quotas: IndexedDB has higher storage limits
Trade-offs: - Two storage APIs to manage - Sync between them if needed - Different browser support characteristics
Why ghost books?¶
Decision: Annotations can exist for books that aren't downloaded locally.
Reasoning: - Sync-first: Download annotations from Nostr before downloading EPUBs - Storage saving: Don't need EPUB to see annotation list - Discovery: See what books others annotate before committing to download - Multi-device: Annotations sync immediately, EPUBs sync when needed
Trade-offs: - Can't read book content without EPUB - UI must handle "book without data" state - SHA-256 verification needed when uploading EPUB later
Sync & Protocol¶
Why Nostr instead of a custom sync protocol?¶
Decision: Use Nostr protocol (NIPs) for annotation and book sync.
Reasoning: - Decentralized: No single point of failure or control - Interoperable: Other Nostr apps can read/write same data - Relay diversity: User chooses which relays to use - Social potential: Follow other readers, discover books
Trade-offs: - Nostr relays may not store data forever - Eventual consistency (not real-time sync) - Custom event kinds (30800, 30801) may not be widely supported
Why addressable events (kind 30000+) instead of regular notes?¶
Decision: Use addressable events for annotations and books.
Reasoning: - Updatable: Edit annotations without creating duplicates - Deletable: Publish tombstone to "delete" an annotation - LWW: Last write wins, simple conflict resolution - Deduplication: Relays keep only latest event per d-tag
Trade-offs: - More complex event structure - Requires d-tag management - Some relays may not fully support NIP-33
Why Last Write Wins (LWW) for conflict resolution?¶
Decision: When annotations conflict, the one with later created_at wins.
Reasoning:
- Simplicity: No CRDT complexity
- Predictable: User understands "latest edit wins"
- Nostr-native: Uses existing created_at field
- No merge conflicts: Never need to resolve conflicting edits
Trade-offs: - Concurrent edits lose data (last one overwrites) - Clock skew can cause issues - No rich merge (both versions preserved)
Why self-hosted LangGraph instead of OpenAI API directly?¶
Decision: AI runs through a self-hosted LangGraph agent, not direct API calls.
Reasoning: - Flexibility: Use any OpenAI-compatible LLM (Ollama, vLLM, etc.) - Control: Run inference on own hardware - Privacy: Conversations don't go to OpenAI by default - Extensibility: Add tools, RAG, multi-step reasoning - Cost: Self-hosted inference can be cheaper at scale
Trade-offs: - More infrastructure to manage - Need GPU for good performance - LangGraph learning curve
Why not default to OpenAI?¶
Decision: No hardcoded OpenAI defaults. Users must configure LLM endpoint.
Reasoning: - Philosophy: Avoid vendor lock-in, support open models - Privacy: User controls where their data goes - Cost transparency: User pays their own inference costs - Local-first: Encourage local LLM experimentation
Trade-offs: - More setup required - No "just works" experience - Users must understand LLM options
Frontend¶
Why Svelte 5 with runes instead of Svelte 4 stores?¶
Decision: Use Svelte 5 runes ($state, $derived, $effect) for new code.
Reasoning: - Future-proof: Runes are the future of Svelte - Fine-grained: Better reactivity control - TypeScript: Better type inference than stores - Simpler: No subscription boilerplate
Trade-offs: - Mixing patterns with existing Svelte 4 stores - Runes still evolving (potential API changes) - Learning curve for Svelte 4 developers
Why epub.js instead of alternatives?¶
Decision: Use epub.js for EPUB rendering.
Reasoning: - Mature: Well-established library with active community - Features: CFI support, pagination, search - Iframe isolation: Secure content rendering - Customizable: Themes, fonts, layout options
Trade-offs: - Large bundle size - Types are incomplete/missing - Some APIs are callback-based (not async/await) - Requires browser environment (no SSR)
Why disable SSR for the reader page?¶
Decision: export const ssr = false on /book/[id] route.
Reasoning: - epub.js requirement: Needs DOM APIs (window, document) - IndexedDB: Can't access browser storage server-side - Performance: No benefit to SSR for authenticated content
Trade-offs: - Slightly slower initial load - No SEO (but reader content shouldn't be indexed anyway) - Must handle loading state client-side
Why FastAPI instead of Node.js?¶
Decision: Python backend with FastAPI.
Reasoning: - LangChain ecosystem: LangGraph, LangChain are Python-first - AI tooling: Python has best ML/AI library support - Type hints: FastAPI's Pydantic models are excellent - Performance: Async support, uvicorn is fast
Trade-offs: - Different language than frontend - Python packaging can be complex - Less isomorphic code sharing
Why Cashu wallet in backend instead of direct redemption?¶
Decision: Backend has a Cashu wallet for token redemption.
Reasoning: - Separation: Agent doesn't need wallet dependencies - Flexibility: Backend can aggregate tokens, withdraw to Lightning - Security: Wallet keys only on backend, not in agent - Abstraction: API hides ecash complexity
Trade-offs: - Additional service to run - Backend must be trusted with funds - More complex deployment
Why store ecash tokens in logs during development?¶
Decision: Full tokens logged for recovery during POC phase.
Reasoning: - Recovery: If funds are lost due to bugs, can manually recover - Debugging: See exact token flow through system - POC phase: Will be removed before production
Trade-offs: - Tokens visible in logs (security risk) - Log storage requirements - Must disable before production