mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
- Update FEATURES.md, ARCHITECTURE.md, DEPLOYMENT.md with MCP sections - Add MCP endpoints to OpenAPI spec and regenerate API.md - Add rebase-before-PR best practice to CLAUDE.md git conventions - Update README with MCP/API feature mention Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
Architecture
Overview
Crumbs follows a local-first architecture where the client (browser) is the primary data store and the server acts as a sync target and backup.
┌─────────────────────────────┐
│ Browser (PWA) │
│ ┌───────────┐ ┌──────────┐ │
│ │ Svelte │ │ IndexedDB│ │
│ │ Stores │◄│ (idb) │ │
│ └─────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌─────▼─────────────▼─────┐ │
│ │ Sync Engine │ │
│ │ (LWW CRDT + Queue) │ │
│ └──────────┬──────────────┘ │
└─────────────┼────────────────┘
│ HTTP (JSON)
┌─────────────▼────────────────┐
│ Server (Node.js) │
│ ┌──────────┐ ┌────────────┐ │
│ │ SvelteKit│ │ SQLite │ │
│ │ API │◄│ (Drizzle) │ │
│ └──────────┘ └────────────┘ │
└──────────────────────────────┘
Data Flow
Read Path
- User opens app
- Svelte stores read from IndexedDB (instant)
- Background sync pulls latest from server
- If newer data found, IDB is updated, stores react
Write Path
- User creates/edits note
- Change written to IndexedDB immediately
- Change added to sync queue
- Background sync pushes queue to server
- Server applies changes with LWW resolution
Conflict Resolution (LWW)
For a single-user app with multiple devices:
- Each note has an
updatedAttimestamp andversionnumber - When merging: newer timestamp wins
- If timestamps match: higher version wins
- If both match: local version is preferred
Attachment Data Flow
- User drops/picks image in NoteEditor
- Client optimizes: resize ≤1920px, compress to WebP, generate 200px thumbnail
- Online: POST optimized + thumbnail to
/api/notes/{id}/attachments→ server saves todata/attachments/+ DB - Offline: store blobs in IDB
pendingAttachmentsstore → display viaURL.createObjectURL()→ on reconnect, sync pushes to server - Display: NoteCard uses
&thumb=1URL (200px WebP), editor shows full-size - SW Caching:
CacheFirstrule caches attachment URLs — previously-viewed images available offline
Tech Stack Rationale
SvelteKit
- Smallest bundle size of major frameworks
- Built-in SSR, routing, API routes
- Svelte 5 runes for reactive state
- Native service worker support via @vite-pwa/sveltekit
SQLite + Drizzle
- Zero-config database (single file)
- WAL mode for concurrent reads
- Drizzle provides type-safe queries without ORM overhead
- Perfect for single-user self-hosted apps
IndexedDB (idb)
- Browser-native storage (no size limits like localStorage)
- Async API doesn't block main thread
idblibrary provides Promise-based wrapper- Survives browser restarts
Tailwind CSS 4
- Utility-first, tree-shakeable
- Built-in dark mode support (
dark:variants) - New v4 uses Vite plugin (faster builds)
Argon2
- Current best practice for password hashing
- Memory-hard (resists GPU attacks)
- Winner of the Password Hashing Competition
Database Schema
See src/lib/server/db/schema.ts for the complete Drizzle schema.
Key tables:
users- Single user (password hash)sessions- Auth sessions (30-day TTL)notes- Core note data with version trackingtags- Unique tag namesnote_tags- Many-to-many note ↔ tagattachments- Image file metadata (path + thumbnailPath)api_keys- MCP API keys (SHA-256 hashed, prefix for display)sync_log- Sync operation history
MCP Server
Crumbs includes a built-in Model Context Protocol server that allows AI assistants (Claude Code, Claude Desktop, etc.) to interact with notes programmatically.
Architecture
- Endpoint:
POST /api/mcp(Streamable HTTP transport) - Auth: Bearer token via API keys (generated in Settings)
- Sessions: Stateful per MCP spec — session ID in
mcp-session-idheader - Transport:
WebStandardStreamableHTTPServerTransport(works with SvelteKit's Web Standard Request/Response)
Shared Logic
MCP tool handlers and REST API routes both call the same service layer (src/lib/server/notes-service.ts). This avoids duplicating business logic:
REST API routes ──┐
├──► notes-service.ts ──► Drizzle DB
MCP tool handlers ┘
Tools (14)
list_notes, get_note, create_note, update_note, trash_note, restore_note, archive_note, unarchive_note, delete_note, search_notes, list_tags, pin_note, reorder_notes, upload_image
File Organization
src/lib/server/ # Server-only code (DB, auth, attachments, notes-service)
src/lib/server/mcp/ # MCP server + tool definitions
src/lib/sync/ # Sync engine (shared types, server + client implementations)
src/lib/stores/ # Svelte reactive stores (notes, theme, auth, search)
src/lib/components/ # Svelte UI components
src/lib/utils/ # Pure utility functions (markdown, tags, colors)
src/lib/types/ # TypeScript type definitions
src/routes/api/ # REST API + MCP endpoints
src/routes/ # Page routes (SvelteKit file-based routing)