docs: complete project research
Files: - STACK.md - SvelteKit + SQLite + TypeScript stack recommendation - FEATURES.md - Feature landscape with MVP definition - ARCHITECTURE.md - Modular monolith architecture with repository pattern - PITFALLS.md - Critical pitfalls and prevention strategies - SUMMARY.md - Executive synthesis with roadmap implications Key findings: - Stack: SvelteKit 2.50.x + Svelte 5.49.x with SQLite and better-sqlite3 for single-user simplicity - Architecture: Modular monolith with content-addressable image storage, FTS5 for search - Critical pitfall: Store images on filesystem (not DB) from Phase 1 to avoid painful migration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
515
.planning/research/ARCHITECTURE.md
Normal file
515
.planning/research/ARCHITECTURE.md
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# Architecture Research
|
||||||
|
|
||||||
|
**Domain:** Personal task/notes web application with image attachments
|
||||||
|
**Researched:** 2026-01-29
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Standard Architecture
|
||||||
|
|
||||||
|
### System Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| CLIENT LAYER |
|
||||||
|
| +-------------------+ +-------------------+ +----------------+ |
|
||||||
|
| | Desktop Browser | | Mobile Browser | | PWA (future) | |
|
||||||
|
| +--------+----------+ +--------+----------+ +-------+--------+ |
|
||||||
|
| | | | |
|
||||||
|
+-----------+----------------------+---------------------+----------+
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| PRESENTATION LAYER |
|
||||||
|
| +------------------------------------------------------------+ |
|
||||||
|
| | Web Frontend (SPA) | |
|
||||||
|
| | +--------+ +--------+ +--------+ +--------+ +--------+ | |
|
||||||
|
| | | Notes | | Tasks | | Search | | Tags | | Upload | | |
|
||||||
|
| | | View | | View | | View | | View | | View | | |
|
||||||
|
| | +--------+ +--------+ +--------+ +--------+ +--------+ | |
|
||||||
|
| +------------------------------+-----------------------------+ |
|
||||||
|
+---------------------------------|--------------------------------+
|
||||||
|
| HTTP/REST
|
||||||
|
v
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| APPLICATION LAYER |
|
||||||
|
| +------------------------------------------------------------+ |
|
||||||
|
| | REST API (Monolith) | |
|
||||||
|
| | +------------+ +------------+ +------------+ | |
|
||||||
|
| | | Notes API | | Tasks API | | Search API | | |
|
||||||
|
| | +------------+ +------------+ +------------+ | |
|
||||||
|
| | +------------+ +------------+ +------------+ | |
|
||||||
|
| | | Tags API | | Upload API | | Auth API | | |
|
||||||
|
| | +------------+ +------------+ +------------+ | |
|
||||||
|
| +------------------------------+-----------------------------+ |
|
||||||
|
+---------------------------------|--------------------------------+
|
||||||
|
|
|
||||||
|
+---------------------+---------------------+
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
| DATA LAYER |
|
||||||
|
| +----------------+ +----------------+ +------------------+ |
|
||||||
|
| | SQLite | | File Storage | | FTS5 Index | |
|
||||||
|
| | (primary) | | (images) | | (full-text) | |
|
||||||
|
| +----------------+ +----------------+ +------------------+ |
|
||||||
|
+------------------------------------------------------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Responsibilities
|
||||||
|
|
||||||
|
| Component | Responsibility | Typical Implementation |
|
||||||
|
|-----------|----------------|------------------------|
|
||||||
|
| Web Frontend | UI rendering, user interaction, client-side state | React/Vue/Svelte SPA |
|
||||||
|
| REST API | Business logic, validation, orchestration | Node.js/Go/Python monolith |
|
||||||
|
| Notes API | CRUD operations for thoughts/notes | API route handler |
|
||||||
|
| Tasks API | CRUD for tasks, status transitions | API route handler |
|
||||||
|
| Search API | Full-text search across notes/tasks | Wraps FTS5 queries |
|
||||||
|
| Tags API | Tag management, note-tag associations | API route handler |
|
||||||
|
| Upload API | Image upload, validation, storage | Handles multipart forms |
|
||||||
|
| Auth API | Session management (single user) | Simple token/session |
|
||||||
|
| SQLite | Primary data persistence | Single file database |
|
||||||
|
| File Storage | Binary file storage (images) | Docker volume mount |
|
||||||
|
| FTS5 Index | Full-text search capabilities | SQLite virtual table |
|
||||||
|
|
||||||
|
## Recommended Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
+-- docker/
|
||||||
|
| +-- Dockerfile # Multi-stage build for frontend + backend
|
||||||
|
| +-- docker-compose.yml # Service orchestration
|
||||||
|
| +-- nginx.conf # Reverse proxy config (optional)
|
||||||
|
+-- backend/
|
||||||
|
| +-- cmd/
|
||||||
|
| | +-- server/
|
||||||
|
| | +-- main.go # Entry point
|
||||||
|
| +-- internal/
|
||||||
|
| | +-- api/ # HTTP handlers
|
||||||
|
| | | +-- notes.go
|
||||||
|
| | | +-- tasks.go
|
||||||
|
| | | +-- tags.go
|
||||||
|
| | | +-- search.go
|
||||||
|
| | | +-- upload.go
|
||||||
|
| | +-- models/ # Domain entities
|
||||||
|
| | | +-- note.go
|
||||||
|
| | | +-- task.go
|
||||||
|
| | | +-- tag.go
|
||||||
|
| | | +-- attachment.go
|
||||||
|
| | +-- repository/ # Data access
|
||||||
|
| | | +-- sqlite.go
|
||||||
|
| | | +-- notes_repo.go
|
||||||
|
| | | +-- tasks_repo.go
|
||||||
|
| | +-- service/ # Business logic
|
||||||
|
| | | +-- notes_svc.go
|
||||||
|
| | | +-- search_svc.go
|
||||||
|
| | +-- storage/ # File storage abstraction
|
||||||
|
| | +-- local.go
|
||||||
|
| +-- migrations/ # Database migrations
|
||||||
|
| +-- go.mod
|
||||||
|
+-- frontend/
|
||||||
|
| +-- src/
|
||||||
|
| | +-- components/ # Reusable UI components
|
||||||
|
| | +-- pages/ # Route-level views
|
||||||
|
| | +-- stores/ # Client state management
|
||||||
|
| | +-- api/ # Backend API client
|
||||||
|
| | +-- utils/ # Helpers
|
||||||
|
| +-- public/
|
||||||
|
| +-- package.json
|
||||||
|
+-- data/ # Mounted volume (gitignored)
|
||||||
|
| +-- app.db # SQLite database
|
||||||
|
| +-- uploads/ # Image storage
|
||||||
|
+-- .planning/ # Project planning docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structure Rationale
|
||||||
|
|
||||||
|
- **Monorepo with backend/frontend split:** Keeps deployment simple (single container possible) while maintaining clear separation
|
||||||
|
- **internal/ in Go:** Prevents external packages from importing internals; enforces encapsulation
|
||||||
|
- **Repository pattern:** Abstracts SQLite access, enables future database swap if needed
|
||||||
|
- **Service layer:** Business logic separated from HTTP handlers for testability
|
||||||
|
- **data/ volume:** Single mount point for all persistent data (database + files)
|
||||||
|
|
||||||
|
## Architectural Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Modular Monolith
|
||||||
|
|
||||||
|
**What:** Single deployable unit with clear internal module boundaries. Each domain (notes, tasks, tags, search) has its own package but shares the same database and process.
|
||||||
|
|
||||||
|
**When to use:** Single-user or small-team applications where operational simplicity matters more than independent scaling.
|
||||||
|
|
||||||
|
**Trade-offs:**
|
||||||
|
- Pro: Simple deployment, easy debugging, no network overhead between modules
|
||||||
|
- Pro: Single database transaction across domains when needed
|
||||||
|
- Con: All modules must use same language/runtime
|
||||||
|
- Con: Cannot scale modules independently (not needed for single user)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```go
|
||||||
|
// internal/api/routes.go
|
||||||
|
func SetupRoutes(r *mux.Router, services *Services) {
|
||||||
|
// Each domain gets its own route group
|
||||||
|
notes := r.PathPrefix("/api/notes").Subrouter()
|
||||||
|
notes.HandleFunc("", services.Notes.List).Methods("GET")
|
||||||
|
notes.HandleFunc("", services.Notes.Create).Methods("POST")
|
||||||
|
|
||||||
|
tasks := r.PathPrefix("/api/tasks").Subrouter()
|
||||||
|
tasks.HandleFunc("", services.Tasks.List).Methods("GET")
|
||||||
|
// Clear boundaries, but same process
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Repository Pattern for Data Access
|
||||||
|
|
||||||
|
**What:** Abstract data access behind interfaces. Repositories handle all database queries; services call repositories, not raw SQL.
|
||||||
|
|
||||||
|
**When to use:** Always for anything beyond trivial apps. Enables testing with mocks and future database changes.
|
||||||
|
|
||||||
|
**Trade-offs:**
|
||||||
|
- Pro: Testable services (mock repositories)
|
||||||
|
- Pro: Database-agnostic business logic
|
||||||
|
- Pro: Query logic centralized
|
||||||
|
- Con: Additional abstraction layer
|
||||||
|
- Con: Can become overly complex if over-engineered
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```go
|
||||||
|
// internal/repository/notes_repo.go
|
||||||
|
type NotesRepository interface {
|
||||||
|
Create(ctx context.Context, note *models.Note) error
|
||||||
|
GetByID(ctx context.Context, id string) (*models.Note, error)
|
||||||
|
List(ctx context.Context, opts ListOptions) ([]*models.Note, error)
|
||||||
|
Search(ctx context.Context, query string) ([]*models.Note, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqliteNotesRepo struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *sqliteNotesRepo) Search(ctx context.Context, query string) ([]*models.Note, error) {
|
||||||
|
// FTS5 search query
|
||||||
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
|
SELECT n.id, n.title, n.body, n.created_at
|
||||||
|
FROM notes n
|
||||||
|
JOIN notes_fts ON notes_fts.rowid = n.id
|
||||||
|
WHERE notes_fts MATCH ?
|
||||||
|
ORDER BY rank
|
||||||
|
`, query)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Content-Addressable Image Storage
|
||||||
|
|
||||||
|
**What:** Store images using content hash (MD5/SHA256) as filename. Prevents duplicates and enables cache-forever headers.
|
||||||
|
|
||||||
|
**When to use:** Any app storing user-uploaded images where deduplication and caching matter.
|
||||||
|
|
||||||
|
**Trade-offs:**
|
||||||
|
- Pro: Automatic deduplication
|
||||||
|
- Pro: Cache-forever possible (hash changes if content changes)
|
||||||
|
- Pro: Simple to verify integrity
|
||||||
|
- Con: Need reference counting for deletion
|
||||||
|
- Con: Slightly more complex upload logic
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```go
|
||||||
|
// internal/storage/local.go
|
||||||
|
func (s *LocalStorage) Store(ctx context.Context, file io.Reader) (string, error) {
|
||||||
|
// Hash while copying to temp file
|
||||||
|
hasher := sha256.New()
|
||||||
|
tmp, _ := os.CreateTemp(s.uploadDir, "upload-*")
|
||||||
|
defer tmp.Close()
|
||||||
|
|
||||||
|
_, err := io.Copy(io.MultiWriter(tmp, hasher), file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
finalPath := filepath.Join(s.uploadDir, hash[:2], hash)
|
||||||
|
|
||||||
|
// Move to final location (subdirs by first 2 chars prevent too many files in one dir)
|
||||||
|
os.MkdirAll(filepath.Dir(finalPath), 0755)
|
||||||
|
os.Rename(tmp.Name(), finalPath)
|
||||||
|
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
[User Action: Create Note]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
[Frontend Component] --HTTP POST /api/notes--> [Notes Handler]
|
||||||
|
| |
|
||||||
|
| (optimistic UI update) v
|
||||||
|
| [Notes Service]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Notes Repository]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [SQLite INSERT]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [FTS5 trigger auto-updates index]
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
[UI shows new note] <--JSON response-- [Return created note]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Upload Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
[User: Attach Image]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
[Frontend: file input] --multipart POST /api/upload--> [Upload Handler]
|
||||||
|
| |
|
||||||
|
| (show progress) v
|
||||||
|
| [Validate: type, size]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Hash content]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Store to /data/uploads/{hash}]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Create attachment record in DB]
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
[Insert image into note] <--{attachment_id, url}-- [Return attachment metadata]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
[User: Type search query]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
[Frontend: debounced input] --GET /api/search?q=...--> [Search Handler]
|
||||||
|
| |
|
||||||
|
| (show loading) v
|
||||||
|
| [Search Service]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Query FTS5 virtual table]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [JOIN with notes/tasks tables]
|
||||||
|
| |
|
||||||
|
| v
|
||||||
|
| [Apply ranking (bm25)]
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
[Display ranked results] <--JSON array-- [Return ranked results with snippets]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Data Flows
|
||||||
|
|
||||||
|
1. **Note/Task CRUD:** Frontend -> API Handler -> Service -> Repository -> SQLite. FTS5 index auto-updates via triggers.
|
||||||
|
2. **Image Upload:** Frontend -> Upload Handler -> File Storage (hash-based) -> DB record. Returns URL for embedding.
|
||||||
|
3. **Full-Text Search:** Frontend -> Search Handler -> FTS5 Query -> Ranked results with snippets.
|
||||||
|
4. **Tag Association:** Many-to-many through junction table. Tag changes trigger re-index if needed.
|
||||||
|
|
||||||
|
## Scaling Considerations
|
||||||
|
|
||||||
|
| Scale | Architecture Adjustments |
|
||||||
|
|-------|--------------------------|
|
||||||
|
| 1 user (target) | Single SQLite file, local file storage, single container. Current design is perfect. |
|
||||||
|
| 2-10 users | Still works fine. SQLite handles concurrent reads well. May want WAL mode for better write concurrency. |
|
||||||
|
| 10-100 users | Consider PostgreSQL for better write concurrency. Move files to S3-compatible storage (MinIO or Garage for self-hosted). |
|
||||||
|
| 100+ users | Out of scope for personal app. Would need auth system, PostgreSQL, object storage, potentially message queue for uploads. |
|
||||||
|
|
||||||
|
### Scaling Priorities (For Future)
|
||||||
|
|
||||||
|
1. **First bottleneck:** SQLite write contention (if ever). Fix: WAL mode (simple) or PostgreSQL (more complex).
|
||||||
|
2. **Second bottleneck:** File storage if hosting many large images. Fix: Object storage with content-addressing.
|
||||||
|
|
||||||
|
**Note:** For a single-user personal app, these scaling considerations are theoretical. SQLite with WAL mode can handle thousands of notes and tasks without issue.
|
||||||
|
|
||||||
|
## Anti-Patterns
|
||||||
|
|
||||||
|
### Anti-Pattern 1: Storing Images in Database
|
||||||
|
|
||||||
|
**What people do:** Store image bytes directly in SQLite as BLOBs.
|
||||||
|
|
||||||
|
**Why it's wrong:**
|
||||||
|
- Bloats database file significantly
|
||||||
|
- Slows down backups (entire DB must be copied for any change)
|
||||||
|
- Cannot leverage filesystem caching
|
||||||
|
- Makes database migrations more complex
|
||||||
|
|
||||||
|
**Do this instead:** Store images on filesystem, store path/hash reference in database. Use content-addressable storage (hash as filename) for deduplication.
|
||||||
|
|
||||||
|
### Anti-Pattern 2: No Full-Text Search Index
|
||||||
|
|
||||||
|
**What people do:** Use `LIKE '%query%'` for search.
|
||||||
|
|
||||||
|
**Why it's wrong:**
|
||||||
|
- Full table scan for every search
|
||||||
|
- Cannot rank by relevance
|
||||||
|
- No word stemming or tokenization
|
||||||
|
- Gets unusably slow with a few thousand notes
|
||||||
|
|
||||||
|
**Do this instead:** Use SQLite FTS5 from the start. It's built-in, requires no external dependencies, and handles relevance ranking.
|
||||||
|
|
||||||
|
### Anti-Pattern 3: Microservices for Single User
|
||||||
|
|
||||||
|
**What people do:** Split notes, tasks, search, auth into separate services "for scalability."
|
||||||
|
|
||||||
|
**Why it's wrong:**
|
||||||
|
- Massive operational overhead for no benefit
|
||||||
|
- Network latency between services
|
||||||
|
- Distributed transactions become complex
|
||||||
|
- Debugging across services is painful
|
||||||
|
- 2024-2025 industry trend: many teams consolidating microservices back to monoliths
|
||||||
|
|
||||||
|
**Do this instead:** Build a well-structured modular monolith. Clear internal boundaries, single deployment. Extract services later only if needed (you won't need to for a personal app).
|
||||||
|
|
||||||
|
### Anti-Pattern 4: Overengineering Auth for Single User
|
||||||
|
|
||||||
|
**What people do:** Implement full OAuth2/OIDC, JWT refresh tokens, role-based access control.
|
||||||
|
|
||||||
|
**Why it's wrong:**
|
||||||
|
- Single user doesn't need roles
|
||||||
|
- Complexity adds attack surface
|
||||||
|
- More code to maintain
|
||||||
|
- Personal app accessible only on your network
|
||||||
|
|
||||||
|
**Do this instead:** Simple session-based auth with a password. Consider basic HTTP auth behind a reverse proxy, or even IP-based allowlisting if only accessible from home network.
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### External Services
|
||||||
|
|
||||||
|
| Service | Integration Pattern | Notes |
|
||||||
|
|---------|---------------------|-------|
|
||||||
|
| None required | N/A | Self-hosted, no external dependencies by design |
|
||||||
|
| Optional: Reverse Proxy | HTTP | Nginx/Traefik for HTTPS termination if exposed to internet |
|
||||||
|
| Optional: Backup | File copy | Simple rsync/backup of data/ directory contains everything |
|
||||||
|
|
||||||
|
### Internal Boundaries
|
||||||
|
|
||||||
|
| Boundary | Communication | Notes |
|
||||||
|
|----------|---------------|-------|
|
||||||
|
| Frontend <-> Backend | REST/JSON over HTTP | OpenAPI spec recommended for documentation |
|
||||||
|
| API Handlers <-> Services | Direct function calls | Same process, no serialization |
|
||||||
|
| Services <-> Repositories | Interface calls | Enables mocking in tests |
|
||||||
|
| Services <-> File Storage | Interface calls | Abstracts local vs future S3 |
|
||||||
|
|
||||||
|
## Database Schema Overview
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Core entities
|
||||||
|
CREATE TABLE notes (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
title TEXT,
|
||||||
|
body TEXT NOT NULL,
|
||||||
|
type TEXT CHECK(type IN ('note', 'task')) NOT NULL,
|
||||||
|
status TEXT CHECK(status IN ('open', 'done', 'archived')), -- for tasks
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Full-text search (virtual table synced via triggers)
|
||||||
|
CREATE VIRTUAL TABLE notes_fts USING fts5(
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
content='notes',
|
||||||
|
content_rowid='rowid'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Tags (many-to-many)
|
||||||
|
CREATE TABLE tags (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT UNIQUE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE note_tags (
|
||||||
|
note_id TEXT REFERENCES notes(id) ON DELETE CASCADE,
|
||||||
|
tag_id TEXT REFERENCES tags(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (note_id, tag_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Attachments (images)
|
||||||
|
CREATE TABLE attachments (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
note_id TEXT REFERENCES notes(id) ON DELETE CASCADE,
|
||||||
|
hash TEXT NOT NULL, -- content hash, also filename
|
||||||
|
filename TEXT, -- original filename
|
||||||
|
mime_type TEXT NOT NULL,
|
||||||
|
size_bytes INTEGER,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_notes_type ON notes(type);
|
||||||
|
CREATE INDEX idx_notes_created ON notes(created_at DESC);
|
||||||
|
CREATE INDEX idx_attachments_note ON attachments(note_id);
|
||||||
|
CREATE INDEX idx_attachments_hash ON attachments(hash);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Order Implications
|
||||||
|
|
||||||
|
Based on component dependencies, suggested implementation order:
|
||||||
|
|
||||||
|
1. **Phase 1: Data Foundation**
|
||||||
|
- SQLite setup with migrations
|
||||||
|
- Basic schema (notes table)
|
||||||
|
- Repository layer for notes
|
||||||
|
- No FTS5 yet (add later)
|
||||||
|
|
||||||
|
2. **Phase 2: Core API**
|
||||||
|
- REST API handlers for notes CRUD
|
||||||
|
- Service layer
|
||||||
|
- Basic frontend with note list/create/edit
|
||||||
|
|
||||||
|
3. **Phase 3: Tasks Differentiation**
|
||||||
|
- Add type column (note vs task)
|
||||||
|
- Task-specific status handling
|
||||||
|
- Frontend task views
|
||||||
|
|
||||||
|
4. **Phase 4: Tags**
|
||||||
|
- Tags table and junction table
|
||||||
|
- Tag CRUD API
|
||||||
|
- Tag filtering in frontend
|
||||||
|
|
||||||
|
5. **Phase 5: Image Attachments**
|
||||||
|
- File storage abstraction
|
||||||
|
- Upload API with validation
|
||||||
|
- Attachment records in DB
|
||||||
|
- Frontend image upload/display
|
||||||
|
|
||||||
|
6. **Phase 6: Search**
|
||||||
|
- FTS5 virtual table and triggers
|
||||||
|
- Search API with ranking
|
||||||
|
- Search UI with highlighting
|
||||||
|
|
||||||
|
7. **Phase 7: Containerization**
|
||||||
|
- Dockerfile (multi-stage)
|
||||||
|
- docker-compose.yml
|
||||||
|
- Volume mounts for data persistence
|
||||||
|
|
||||||
|
**Rationale:** This order ensures each phase builds on working foundation. Notes must work before tasks (which are just notes with extra fields). Tags and attachments can be added independently. Search comes later as it indexes existing content. Containerization last so development is simpler.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- [Standard Notes Self-Hosting Architecture](https://standardnotes.com/help/self-hosting/getting-started) - API Gateway, Syncing Server patterns
|
||||||
|
- [Flatnotes - Database-less Architecture](https://github.com/dullage/flatnotes) - Simple markdown file storage approach
|
||||||
|
- [Evernote Data Structure](https://dev.evernote.com/doc/articles/data_structure.php) - Note/Resource/Attachment model
|
||||||
|
- [Task Manager Database Schema](https://www.tutorials24x7.com/mysql/guide-to-design-database-for-task-manager-in-mysql) - Tags and task relationships
|
||||||
|
- [SQLite FTS5 Extension](https://www.sqlite.org/fts5.html) - Full-text search implementation (HIGH confidence - official docs)
|
||||||
|
- [Microservices vs Monoliths in 2026](https://www.javacodegeeks.com/2025/12/microservices-vs-monoliths-in-2026-when-each-architecture-wins.html) - Modular monolith recommendation
|
||||||
|
- [MinIO Alternatives](https://rmoff.net/2026/01/14/alternatives-to-minio-for-single-node-local-s3/) - Self-hosted storage options
|
||||||
|
- [Image Upload Architecture](https://medium.com/@jgefroh/software-architecture-image-uploading-67997101a034) - Content-addressable storage pattern
|
||||||
|
- [Modern Web Application Architecture 2026](https://tech-stack.com/blog/modern-application-development/) - Container and deployment patterns
|
||||||
|
|
||||||
|
---
|
||||||
|
*Architecture research for: Personal task/notes web application*
|
||||||
|
*Researched: 2026-01-29*
|
||||||
214
.planning/research/FEATURES.md
Normal file
214
.planning/research/FEATURES.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Feature Research
|
||||||
|
|
||||||
|
**Domain:** Personal Task/Notes Web App (self-hosted, single-user)
|
||||||
|
**Researched:** 2026-01-29
|
||||||
|
**Confidence:** MEDIUM (based on competitor analysis via WebFetch + domain knowledge)
|
||||||
|
|
||||||
|
## Feature Landscape
|
||||||
|
|
||||||
|
### Table Stakes (Users Expect These)
|
||||||
|
|
||||||
|
Features users assume exist. Missing these = product feels incomplete.
|
||||||
|
|
||||||
|
| Feature | Why Expected | Complexity | Notes |
|
||||||
|
|---------|--------------|------------|-------|
|
||||||
|
| Create/Edit/Delete items | Basic CRUD is fundamental | LOW | Text input with persistence |
|
||||||
|
| Distinguish tasks vs thoughts | Project requirement; users expect to filter by type | LOW | Boolean or enum field |
|
||||||
|
| Mark tasks complete | Core task management; every competitor has this | LOW | Status toggle |
|
||||||
|
| Cross-device access | Project requirement; web app enables this inherently | LOW | Responsive design |
|
||||||
|
| Search | Users expect to find content quickly; all competitors offer this | MEDIUM | Full-text search on title + content |
|
||||||
|
| Tags/labels | Standard organization pattern (Todoist, Bear, Simplenote all have) | MEDIUM | Many-to-many relationship |
|
||||||
|
| Image attachments | Project requirement for digitizing paper notes | MEDIUM | File upload, storage, display |
|
||||||
|
| Mobile-friendly UI | "Any device" access means mobile must work | MEDIUM | Responsive design, touch targets |
|
||||||
|
| Data persistence | Notes must survive restart | LOW | Database storage |
|
||||||
|
| Quick capture | Fast entry is table stakes (Todoist: "Capture...the moment they come to you") | LOW | Minimal friction input |
|
||||||
|
|
||||||
|
### Differentiators (Competitive Advantage)
|
||||||
|
|
||||||
|
Features that set the product apart. Not required, but valuable.
|
||||||
|
|
||||||
|
| Feature | Value Proposition | Complexity | Notes |
|
||||||
|
|---------|-------------------|------------|-------|
|
||||||
|
| OCR on images | Search text within uploaded images (Evernote, Bear Pro have this) | HIGH | Requires OCR library/service |
|
||||||
|
| Image annotation | Mark up photos of paper notes | HIGH | Canvas drawing, save state |
|
||||||
|
| Natural language dates | "tomorrow", "next Monday" (Todoist signature feature) | MEDIUM | Date parsing library |
|
||||||
|
| Recurring tasks | Habits and repeated items (Todoist core feature) | MEDIUM | RRULE or simple patterns |
|
||||||
|
| Offline support | Work without internet, sync later | HIGH | Service worker, conflict resolution |
|
||||||
|
| Keyboard shortcuts | Power user efficiency | LOW | Event handlers |
|
||||||
|
| Dark mode | User preference, reduces eye strain | LOW | CSS variables, theme toggle |
|
||||||
|
| Markdown support | Rich formatting without WYSIWYG complexity (Simplenote, Bear, Obsidian) | MEDIUM | Markdown parser + preview |
|
||||||
|
| Note linking | Connect related items (Obsidian's core feature) | MEDIUM | Internal link syntax, backlinks |
|
||||||
|
| Document scanning | Camera capture with perspective correction (Evernote) | HIGH | Camera API, image processing |
|
||||||
|
| Export/backup | Data portability, user owns data (Obsidian philosophy) | LOW | JSON/Markdown export |
|
||||||
|
| Drag-and-drop reorder | Intuitive organization (Todoist Upcoming view) | MEDIUM | Sortable library, persist order |
|
||||||
|
| Pin/favorite items | Quick access to important items (Bear) | LOW | Boolean field, UI section |
|
||||||
|
| Due dates with reminders | Time-sensitive tasks (all task apps have this) | MEDIUM | Date field + notification system |
|
||||||
|
|
||||||
|
### Anti-Features (Commonly Requested, Often Problematic)
|
||||||
|
|
||||||
|
Features that seem good but create problems for a personal, single-user app.
|
||||||
|
|
||||||
|
| Feature | Why Requested | Why Problematic | Alternative |
|
||||||
|
|---------|---------------|-----------------|-------------|
|
||||||
|
| Real-time collaboration | "Maybe I'll share with family" | Massive complexity (OT/CRDT), scope creep, conflicts with "personal" app | Export/share single notes manually |
|
||||||
|
| AI-powered categorization | Trendy, seems smart | Over-engineering for personal use; manual tags are clearer | Good tag UX + search |
|
||||||
|
| Complex folder hierarchies | "I want to organize everything" | Deep nesting causes friction; flat + tags is more flexible | Tags with hierarchy (nested tags) |
|
||||||
|
| Kanban boards | Looks nice, seems productive | Overhead for personal tasks; simple list often better | Optional board view later |
|
||||||
|
| Multiple note types | "Journals, wikis, tasks, etc." | Complicates data model, UI; blur the simple task/thought distinction | Two types (task/thought) with tags |
|
||||||
|
| Social features | Share achievements, collaborate | Out of scope for self-hosted personal app | None |
|
||||||
|
| Heavy WYSIWYG editor | "I want formatting" | Bloated, complex, mobile-unfriendly | Markdown with preview |
|
||||||
|
| Notifications/alerts on web | Keep me on track | Browser notifications are annoying, unreliable | Focus on capture, not nagging |
|
||||||
|
| Version history | "I might want to undo" | Storage overhead, complexity for personal use | Simple edit; consider soft-delete |
|
||||||
|
| Multi-user/auth | "Maybe I'll share the server" | Security complexity, out of scope | Single-user by design |
|
||||||
|
|
||||||
|
## Feature Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
[Search]
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
|
||||||
|
[Tags]
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
└──enhances──> [Search] (filter by tag)
|
||||||
|
|
||||||
|
[Image attachments]
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
└──requires──> [File storage]
|
||||||
|
|
||||||
|
[OCR on images]
|
||||||
|
└──requires──> [Image attachments]
|
||||||
|
|
||||||
|
[Image annotation]
|
||||||
|
└──requires──> [Image attachments]
|
||||||
|
|
||||||
|
[Markdown support]
|
||||||
|
└──enhances──> [Create/Edit items]
|
||||||
|
|
||||||
|
[Note linking]
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
└──enhances──> [Search] (backlink discovery)
|
||||||
|
|
||||||
|
[Recurring tasks]
|
||||||
|
└──requires──> [Mark tasks complete]
|
||||||
|
|
||||||
|
[Due dates with reminders]
|
||||||
|
└──requires──> [Distinguish tasks vs thoughts]
|
||||||
|
|
||||||
|
[Offline support]
|
||||||
|
└──conflicts──> [Real-time collaboration] (sync conflicts)
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
|
||||||
|
[Export/backup]
|
||||||
|
└──requires──> [Data persistence]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency Notes
|
||||||
|
|
||||||
|
- **OCR requires Image attachments:** Cannot search images if they don't exist
|
||||||
|
- **Tags enhance Search:** Tag filtering is a search feature
|
||||||
|
- **Offline conflicts with Real-time collab:** Sync conflict resolution is hard; single-user sidesteps this
|
||||||
|
- **Recurring tasks require completion status:** Need to know when to regenerate
|
||||||
|
|
||||||
|
## MVP Definition
|
||||||
|
|
||||||
|
### Launch With (v1)
|
||||||
|
|
||||||
|
Minimum viable product based on project requirements:
|
||||||
|
|
||||||
|
- [x] Create/Edit/Delete items (task or thought) -- core CRUD
|
||||||
|
- [x] Distinguish tasks vs thoughts -- project requirement
|
||||||
|
- [x] Mark tasks complete -- essential task management
|
||||||
|
- [x] Image attachments -- project requirement for digitizing paper notes
|
||||||
|
- [x] Tags for organization -- project requirement
|
||||||
|
- [x] Search -- project requirement
|
||||||
|
- [x] Mobile-responsive UI -- "any device" requirement
|
||||||
|
- [x] Containerized deployment -- project requirement
|
||||||
|
|
||||||
|
### Add After Validation (v1.x)
|
||||||
|
|
||||||
|
Features to add once core is working:
|
||||||
|
|
||||||
|
- [ ] Dark mode -- low complexity, high user satisfaction
|
||||||
|
- [ ] Keyboard shortcuts -- power user efficiency
|
||||||
|
- [ ] Pin/favorite items -- quick access to important items
|
||||||
|
- [ ] Export to JSON/Markdown -- data portability
|
||||||
|
- [ ] Due dates on tasks -- natural extension of task type
|
||||||
|
- [ ] Drag-and-drop reorder -- better organization UX
|
||||||
|
|
||||||
|
### Future Consideration (v2+)
|
||||||
|
|
||||||
|
Features to defer until product-market fit is established:
|
||||||
|
|
||||||
|
- [ ] Markdown support -- adds complexity to editing
|
||||||
|
- [ ] Natural language dates -- requires parsing library
|
||||||
|
- [ ] Recurring tasks -- adds state machine complexity
|
||||||
|
- [ ] OCR on images -- requires external service/library
|
||||||
|
- [ ] Note linking -- changes how users think about the app
|
||||||
|
- [ ] Offline support -- significant complexity (service worker, sync)
|
||||||
|
|
||||||
|
## Feature Prioritization Matrix
|
||||||
|
|
||||||
|
| Feature | User Value | Implementation Cost | Priority |
|
||||||
|
|---------|------------|---------------------|----------|
|
||||||
|
| Create/Edit/Delete | HIGH | LOW | P1 |
|
||||||
|
| Task vs Thought distinction | HIGH | LOW | P1 |
|
||||||
|
| Mark complete | HIGH | LOW | P1 |
|
||||||
|
| Image attachments | HIGH | MEDIUM | P1 |
|
||||||
|
| Tags | HIGH | MEDIUM | P1 |
|
||||||
|
| Search | HIGH | MEDIUM | P1 |
|
||||||
|
| Mobile-responsive | HIGH | MEDIUM | P1 |
|
||||||
|
| Dark mode | MEDIUM | LOW | P2 |
|
||||||
|
| Keyboard shortcuts | MEDIUM | LOW | P2 |
|
||||||
|
| Pin/favorite | MEDIUM | LOW | P2 |
|
||||||
|
| Export | MEDIUM | LOW | P2 |
|
||||||
|
| Due dates | MEDIUM | MEDIUM | P2 |
|
||||||
|
| Drag-and-drop | MEDIUM | MEDIUM | P2 |
|
||||||
|
| Markdown | MEDIUM | MEDIUM | P3 |
|
||||||
|
| Natural language dates | LOW | MEDIUM | P3 |
|
||||||
|
| Recurring tasks | MEDIUM | HIGH | P3 |
|
||||||
|
| OCR | LOW | HIGH | P3 |
|
||||||
|
| Note linking | LOW | MEDIUM | P3 |
|
||||||
|
| Offline support | LOW | HIGH | P3 |
|
||||||
|
|
||||||
|
**Priority key:**
|
||||||
|
- P1: Must have for launch (MVP)
|
||||||
|
- P2: Should have, add when possible (v1.x)
|
||||||
|
- P3: Nice to have, future consideration (v2+)
|
||||||
|
|
||||||
|
## Competitor Feature Analysis
|
||||||
|
|
||||||
|
| Feature | Todoist | Bear | Simplenote | Obsidian | Our Approach |
|
||||||
|
|---------|---------|------|------------|----------|--------------|
|
||||||
|
| Task management | Core focus | Checkbox in notes | Basic lists | Plugin | First-class task type |
|
||||||
|
| Notes/thoughts | Via descriptions | Core focus | Core focus | Core focus | First-class thought type |
|
||||||
|
| Image attachments | Yes | Yes (with sketching) | No | Yes | Yes, for paper note capture |
|
||||||
|
| Tags | Yes (labels) | Yes (with icons) | Yes | Yes | Yes, simple tags |
|
||||||
|
| Search | Advanced filters | OCR search (Pro) | Instant search | Full-text | Full-text + tag filter |
|
||||||
|
| Sync | Cloud (their servers) | iCloud | Cloud (their servers) | Local-first, optional sync | Self-hosted, cross-device via web |
|
||||||
|
| Markdown | Limited | Yes | Yes | Yes (core) | Defer to v2 |
|
||||||
|
| Offline | Yes | Yes | Yes | Yes (local-first) | Defer to v2 |
|
||||||
|
| Mobile | Native apps | Native (Apple only) | Native apps | Native apps | Responsive web |
|
||||||
|
|
||||||
|
**Our differentiation:** Self-hosted, single-user simplicity with explicit task/thought distinction and image attachment for digitizing paper notes. No account required, no cloud dependency, you own your data.
|
||||||
|
|
||||||
|
## Confidence Notes
|
||||||
|
|
||||||
|
| Section | Confidence | Rationale |
|
||||||
|
|---------|------------|-----------|
|
||||||
|
| Table Stakes | HIGH | Verified against Todoist, Bear, Simplenote, Evernote via WebFetch |
|
||||||
|
| Differentiators | MEDIUM | Based on competitor features; value proposition is hypothesis |
|
||||||
|
| Anti-Features | MEDIUM | Based on domain experience; specific to single-user context |
|
||||||
|
| Dependencies | HIGH | Logical dependencies from requirements |
|
||||||
|
| MVP Definition | HIGH | Derived directly from project requirements |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- Todoist features page (verified via WebFetch)
|
||||||
|
- Obsidian home page (verified via WebFetch)
|
||||||
|
- Bear app home page (verified via WebFetch)
|
||||||
|
- Simplenote home page (verified via WebFetch)
|
||||||
|
- Evernote features page (verified via WebFetch)
|
||||||
|
|
||||||
|
---
|
||||||
|
*Feature research for: Personal Task/Notes Web App*
|
||||||
|
*Researched: 2026-01-29*
|
||||||
312
.planning/research/PITFALLS.md
Normal file
312
.planning/research/PITFALLS.md
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
# Pitfalls Research
|
||||||
|
|
||||||
|
**Domain:** Personal Task/Notes Web App with Image Attachments
|
||||||
|
**Researched:** 2026-01-29
|
||||||
|
**Confidence:** MEDIUM (based on training data patterns; WebSearch unavailable for verification)
|
||||||
|
|
||||||
|
## Critical Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Image Storage Coupled to Database
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
Storing images as BLOBs in the database (e.g., SQLite, PostgreSQL). Database grows massive, backups become slow, and queries for non-image data get impacted. Migrations become painful when you have GBs of binary data.
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
It feels simpler to have "everything in one place." Developers avoid the complexity of file storage + database references.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
Store images on filesystem (or object storage like MinIO for container setups). Store only the file path/reference in the database. Use a consistent naming convention (e.g., `{uuid}.{ext}` or `{timestamp}_{hash}.{ext}`).
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Database file growing faster than expected
|
||||||
|
- Backup times increasing disproportionately
|
||||||
|
- "Let me just base64 encode this" appearing in code
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 1 (Core Data Model) — the storage strategy must be correct from the start; migrating images out of database later is painful.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 2: No Image Upload Size/Type Validation
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
Users (even yourself) accidentally upload 50MB RAW files or non-image files. Server crashes, storage fills up, or worse — malicious files get stored.
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
"It's just for me" thinking leads to skipping validation. Edge cases seem unlikely for personal tools.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
- Server-side validation of file type (magic bytes, not just extension)
|
||||||
|
- Reasonable size limits (e.g., 10MB per image)
|
||||||
|
- Image format conversion on upload (resize large images, convert HEIC to JPEG)
|
||||||
|
- Reject non-image MIME types
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- No file validation code in upload handler
|
||||||
|
- Trusting client-side file picker to filter types
|
||||||
|
- No max file size configuration
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 2 (Image Handling) — build validation into the upload pipeline from the start.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 3: Tagging System Too Complex or Too Rigid
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
Two failure modes:
|
||||||
|
1. **Over-engineered:** Hierarchical tags, tag colors, tag descriptions, tag merging... system becomes a maintenance burden
|
||||||
|
2. **Under-thought:** No autocomplete, no tag normalization, end up with "work", "Work", "WORK" as separate tags
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
Over-engineering: Feature creep before validating basic needs.
|
||||||
|
Under-thought: "Tags are simple" — but consistent UX requires attention.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
Start with flat tags (no hierarchy). Implement:
|
||||||
|
- Case-insensitive matching (store lowercase, display original)
|
||||||
|
- Autocomplete from existing tags
|
||||||
|
- Tag renaming capability (update all entries)
|
||||||
|
- NO: tag colors, descriptions, nesting, icons until proven needed
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Planning tag hierarchies before shipping basic tagging
|
||||||
|
- No autocomplete in tag input
|
||||||
|
- Multiple code paths for "exact match" vs "fuzzy match"
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 3 (Organization/Tags) — resist complexity; ship simple tags first, iterate based on actual usage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 4: Search That Doesn't Search Images' Context
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
User photographs a paper note, tags it "meeting notes", then searches for "budget" — the paper note discussing budget isn't found because search only checks text fields, not the context of why the image was captured.
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
Search implementation focuses on structured data (title, description, tags) but images are opaque binary blobs.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
For v1 without OCR:
|
||||||
|
- Encourage descriptive titles/notes when capturing images
|
||||||
|
- Make it easy to add context to image entries
|
||||||
|
- Consider a "description" field specifically for image content
|
||||||
|
|
||||||
|
For later:
|
||||||
|
- OCR on upload (Tesseract, cloud OCR)
|
||||||
|
- Store extracted text for search indexing
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Image entries have only tags, no description field
|
||||||
|
- Search implementation ignores entry body/notes
|
||||||
|
- User finds themselves re-photographing notes to find content
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 2 (Image Handling) — ensure data model supports rich metadata for images.
|
||||||
|
Phase 4 (Search) — index all text fields including descriptions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 5: Mobile Browser Capture UX Disaster
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
Camera capture works in desktop browser testing but fails or is clunky on mobile:
|
||||||
|
- File input doesn't trigger camera
|
||||||
|
- Captured images are wrong orientation (EXIF rotation ignored)
|
||||||
|
- Upload fails silently on mobile networks
|
||||||
|
- UI doesn't fit mobile viewport
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
Testing on desktop only. Mobile browser APIs have quirks. EXIF orientation handling is notoriously inconsistent.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
- Use `<input type="file" accept="image/*" capture="environment">` for mobile camera
|
||||||
|
- Handle EXIF orientation server-side (normalize on upload)
|
||||||
|
- Test on actual mobile devices early
|
||||||
|
- Implement upload progress indicator
|
||||||
|
- Design mobile-first (small viewport is the constraint)
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- No `capture` attribute on file input
|
||||||
|
- Images appearing rotated in UI
|
||||||
|
- "Works on desktop" but not tested on phone
|
||||||
|
- No upload progress feedback
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 2 (Image Handling) — mobile camera capture is a core requirement, not an afterthought.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 6: No Data Export/Backup Strategy
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
Months of notes and images, then:
|
||||||
|
- Database corruption
|
||||||
|
- Accidental deletion
|
||||||
|
- Want to migrate to different system
|
||||||
|
- Container volume disappears
|
||||||
|
|
||||||
|
No way to recover because data only exists in app's internal format.
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
"I'll add export later" — but later never comes. Personal projects lack the forcing function of user complaints.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
- Design export from day one (JSON + image files in a zip)
|
||||||
|
- Automated backup script (cron job or container health check)
|
||||||
|
- Document the data format so future-you can parse it
|
||||||
|
- Consider SQLite file-based backup if using SQLite (just copy the file)
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- No export endpoint in API
|
||||||
|
- No backup documentation
|
||||||
|
- Only way to access data is through the UI
|
||||||
|
- No volume mount strategy for container deployment
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 1 (Core Data Model) — export-friendly data model.
|
||||||
|
Phase 5 (Polish) — implement actual export functionality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pitfall 7: Task/Thought Distinction Becomes Confusing
|
||||||
|
|
||||||
|
**What goes wrong:**
|
||||||
|
The distinction between "task" (actionable) and "thought" (reference) seems clear initially but breaks down:
|
||||||
|
- Is a meeting note a task or thought?
|
||||||
|
- Is a reminder a task?
|
||||||
|
- User forgets which type they used and can't find things
|
||||||
|
- Some entries are both
|
||||||
|
|
||||||
|
**Why it happens:**
|
||||||
|
Taxonomies that seem obvious become fuzzy with real data. Users don't think in the developer's categories.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
- Keep distinction minimal (maybe just a boolean: "actionable?")
|
||||||
|
- Allow changing type after creation
|
||||||
|
- Don't create separate "task view" and "thought view" initially — unified view with filter
|
||||||
|
- Consider: is this distinction even needed? Tags might be enough ("action-needed" tag)
|
||||||
|
|
||||||
|
**Warning signs:**
|
||||||
|
- Planning elaborate workflows for tasks vs thoughts
|
||||||
|
- Separate database tables for tasks and thoughts
|
||||||
|
- Users hesitating at "Is this a task or thought?" during capture
|
||||||
|
- Building two separate UIs
|
||||||
|
|
||||||
|
**Phase to address:**
|
||||||
|
Phase 1 (Core Data Model) — model as unified "entries" with a type field, not separate entities.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Debt Patterns
|
||||||
|
|
||||||
|
Shortcuts that seem reasonable but create long-term problems.
|
||||||
|
|
||||||
|
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|
||||||
|
|----------|-------------------|----------------|-----------------|
|
||||||
|
| Storing images as base64 in JSON | Simple API design | 33% size increase, slow serialization | Never — use multipart/form-data |
|
||||||
|
| No pagination on list views | Simpler frontend code | UI freezes with 500+ entries | Only for MVP with <100 entries, add quickly |
|
||||||
|
| Hardcoded single-user auth | Skip auth complexity | Can't add users later, security theater | Acceptable for personal tool if network-isolated |
|
||||||
|
| SQLite without WAL mode | Default config "just works" | Concurrent access issues | Never — always enable WAL for web apps |
|
||||||
|
| No image thumbnails | Skip image processing setup | Slow page loads, excessive bandwidth | Only for MVP, add in first polish phase |
|
||||||
|
|
||||||
|
## Integration Gotchas
|
||||||
|
|
||||||
|
Common mistakes when connecting to external services.
|
||||||
|
|
||||||
|
| Integration | Common Mistake | Correct Approach |
|
||||||
|
|-------------|----------------|------------------|
|
||||||
|
| Container volumes | Using default Docker volumes | Named volumes with explicit backup strategy |
|
||||||
|
| Reverse proxy (nginx/traefik) | Missing client_max_body_size | Configure for max image upload size + margin |
|
||||||
|
| Mobile camera API | Assuming desktop file input behavior | Test capture attribute, handle EXIF |
|
||||||
|
| Browser localStorage | Storing auth tokens without expiry | Use httpOnly cookies or short-lived tokens |
|
||||||
|
|
||||||
|
## Performance Traps
|
||||||
|
|
||||||
|
Patterns that work at small scale but fail as usage grows.
|
||||||
|
|
||||||
|
| Trap | Symptoms | Prevention | When It Breaks |
|
||||||
|
|------|----------|------------|----------------|
|
||||||
|
| Loading all entries on page load | Page takes seconds to load | Pagination, virtual scrolling | 200+ entries |
|
||||||
|
| Full-text search without index | Search takes seconds | FTS5 in SQLite, or search index | 1000+ entries |
|
||||||
|
| No image lazy loading | Page loads all images | Intersection Observer, lazy src | 20+ images visible |
|
||||||
|
| Synchronous image processing | Upload hangs for large files | Background processing queue | 5MB+ images |
|
||||||
|
| No database connection pooling | Connection errors under load | Use connection pool (even for SQLite) | Concurrent requests |
|
||||||
|
|
||||||
|
## Security Mistakes
|
||||||
|
|
||||||
|
Domain-specific security issues beyond general web security.
|
||||||
|
|
||||||
|
| Mistake | Risk | Prevention |
|
||||||
|
|---------|------|------------|
|
||||||
|
| Predictable image URLs | Anyone with URL can view images | UUID-based paths, auth check on image fetch |
|
||||||
|
| No auth on API endpoints | Data exposed to network | At minimum, basic auth or token |
|
||||||
|
| Storing original filenames | Path traversal, XSS in filenames | Rename to UUID on upload |
|
||||||
|
| EXIF data preserved | Location data leaked in images | Strip EXIF on upload (except orientation) |
|
||||||
|
| Direct file path in database | Path traversal on retrieval | Store relative path, validate on read |
|
||||||
|
|
||||||
|
## UX Pitfalls
|
||||||
|
|
||||||
|
Common user experience mistakes in this domain.
|
||||||
|
|
||||||
|
| Pitfall | User Impact | Better Approach |
|
||||||
|
|---------|-------------|-----------------|
|
||||||
|
| No quick capture mode | Friction kills habit formation | One-click/tap to new entry, minimal required fields |
|
||||||
|
| Tags require exact typing | Frustrating to remember tag names | Autocomplete, recent tags shown |
|
||||||
|
| No undo for delete | Data loss anxiety | Soft delete with "recently deleted" view |
|
||||||
|
| Image-only entries need title | Can't capture quickly | Allow entries with just image, no required title |
|
||||||
|
| Desktop-first design | Unusable on primary capture device (phone) | Mobile-first, responsive |
|
||||||
|
|
||||||
|
## "Looks Done But Isn't" Checklist
|
||||||
|
|
||||||
|
Things that appear complete but are missing critical pieces.
|
||||||
|
|
||||||
|
- [ ] **Image upload:** Often missing server-side type validation — verify file magic bytes, not just extension
|
||||||
|
- [ ] **Image display:** Often missing EXIF rotation handling — verify portrait photos display correctly
|
||||||
|
- [ ] **Search:** Often missing full-text indexing — verify search is fast with 1000+ entries
|
||||||
|
- [ ] **Tags:** Often missing case normalization — verify "Work" and "work" are same tag
|
||||||
|
- [ ] **Mobile capture:** Often missing camera integration — verify can photograph directly in mobile browser
|
||||||
|
- [ ] **Data persistence:** Often missing volume mounts — verify data survives container restart
|
||||||
|
- [ ] **Delete:** Often missing soft delete — verify deleted items can be recovered
|
||||||
|
|
||||||
|
## Recovery Strategies
|
||||||
|
|
||||||
|
When pitfalls occur despite prevention, how to recover.
|
||||||
|
|
||||||
|
| Pitfall | Recovery Cost | Recovery Steps |
|
||||||
|
|---------|---------------|----------------|
|
||||||
|
| Images in database | HIGH | Write migration script, extract to filesystem, update references, retest all image features |
|
||||||
|
| No export capability | MEDIUM | Add export endpoint, document format, backfill historical data |
|
||||||
|
| Broken mobile capture | LOW | Fix input attributes, test on device, may need EXIF handling |
|
||||||
|
| Tag inconsistency (case issues) | MEDIUM | Write migration to normalize, update search/filter logic |
|
||||||
|
| Missing pagination | MEDIUM | Add pagination to API, update frontend, may need loading states |
|
||||||
|
| Corrupted database (no backup) | CRITICAL | Hope for partial recovery; rebuild from any image files; start fresh |
|
||||||
|
|
||||||
|
## Pitfall-to-Phase Mapping
|
||||||
|
|
||||||
|
How roadmap phases should address these pitfalls.
|
||||||
|
|
||||||
|
| Pitfall | Prevention Phase | Verification |
|
||||||
|
|---------|------------------|--------------|
|
||||||
|
| Images in database | Phase 1: Data Model | Verify images stored on filesystem with DB reference |
|
||||||
|
| No upload validation | Phase 2: Image Handling | Test with oversized file, wrong file type |
|
||||||
|
| Complex tagging | Phase 3: Tags | Ship simple tags, resist adding features |
|
||||||
|
| Search misses image context | Phase 2 + Phase 4 | Search for text that should be in image description |
|
||||||
|
| Mobile capture broken | Phase 2: Image Handling | Test on actual phone, verify orientation |
|
||||||
|
| No backup/export | Phase 1 (model) + Phase 5 (implement) | Export data, reimport to fresh instance |
|
||||||
|
| Task/thought confusion | Phase 1: Data Model | Unified entry model with type field |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- Training data patterns from todo/notes app development (MEDIUM confidence)
|
||||||
|
- Common SQLite web app patterns (HIGH confidence — well documented)
|
||||||
|
- Mobile browser API quirks (MEDIUM confidence — may have changed)
|
||||||
|
- Image handling best practices (HIGH confidence — fundamental patterns)
|
||||||
|
- Container deployment patterns (HIGH confidence — well documented)
|
||||||
|
|
||||||
|
**Note:** WebSearch was unavailable for verification. Pitfalls are based on common patterns observed in training data. Recommend validating specific technical claims (e.g., current mobile browser capture API behavior) during implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Pitfalls research for: Personal Task/Notes Web App*
|
||||||
|
*Researched: 2026-01-29*
|
||||||
243
.planning/research/STACK.md
Normal file
243
.planning/research/STACK.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# Stack Research
|
||||||
|
|
||||||
|
**Domain:** Personal Task/Notes Web App (self-hosted, containerized, single-user)
|
||||||
|
**Researched:** 2026-01-29
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Recommended Stack
|
||||||
|
|
||||||
|
### Core Technologies
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why Recommended |
|
||||||
|
|------------|---------|---------|-----------------|
|
||||||
|
| **SvelteKit** | 2.50.x | Full-stack framework | Best DX for solo/small projects. Svelte 5's runes provide explicit reactivity. No virtual DOM = smaller bundles and faster performance. Built-in routing, SSR, and API routes. Perfect for self-hosted single-user apps. |
|
||||||
|
| **Svelte** | 5.49.x | UI framework | Compiles to vanilla JS with minimal runtime. 30% faster load times vs React/Vue per 2026 benchmarks. Smaller bundle = faster on any device. |
|
||||||
|
| **SQLite** | 3.x | Database | Zero-config, serverless, single-file database. Ideal for single-user apps. FTS5 provides full-text search built-in. No separate database service to manage in Docker. |
|
||||||
|
| **better-sqlite3** | 12.x | SQLite driver | 20-39% faster than alternatives. Synchronous API is simpler for single-user apps. Native bindings outperform WASM alternatives. |
|
||||||
|
| **TypeScript** | 5.x | Language | Type safety catches bugs early. First-class Svelte/SvelteKit support. Industry standard for modern web development. |
|
||||||
|
|
||||||
|
### Supporting Libraries
|
||||||
|
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| **sharp** | 0.34.x | Image processing | Always - for thumbnail generation, image optimization. 4-5x faster than ImageMagick. Handles JPEG, PNG, WebP, AVIF. |
|
||||||
|
| **Drizzle ORM** | 0.45.x (stable) | Database ORM | Recommended for type-safe SQL. Code-first schema in TypeScript. Lightweight (~7.4kb), no runtime overhead. Excellent SQLite support. |
|
||||||
|
| **Tailwind CSS** | 4.1.x | Styling | Always - utility-first CSS with zero runtime overhead. Automatic purging keeps CSS tiny. Great for rapid prototyping. |
|
||||||
|
| **Zod** | 3.x | Validation | Form validation, API input validation. Integrates with SvelteKit's form actions. TypeScript-first schema validation. |
|
||||||
|
| **nanoid** | 5.x | ID generation | URL-safe unique IDs for tasks/notes. Smaller and faster than UUID. |
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
|
||||||
|
| Tool | Purpose | Notes |
|
||||||
|
|------|---------|-------|
|
||||||
|
| **Vite** | Build tool | Built into SvelteKit. Fast HMR, optimized builds. |
|
||||||
|
| **ESLint + Prettier** | Code quality | Use `eslint-plugin-svelte` for Svelte-specific rules. |
|
||||||
|
| **Vitest** | Testing | Vite-native testing. Fast, compatible with SvelteKit. |
|
||||||
|
| **Docker** | Containerization | Multi-stage builds for minimal production images. Use `node:22-alpine` base. |
|
||||||
|
|
||||||
|
### Infrastructure (Self-Hosted)
|
||||||
|
|
||||||
|
| Technology | Version | Purpose | Why Recommended |
|
||||||
|
|------------|---------|---------|-----------------|
|
||||||
|
| **Node.js** | 22 LTS | Runtime | Current LTS. Required for better-sqlite3 native bindings. |
|
||||||
|
| **Docker** | Latest | Container | Multi-stage builds reduce image size 10x. Alpine base keeps footprint small (~70MB). |
|
||||||
|
| **Caddy** | 2.x | Reverse proxy (optional) | Simple config, automatic HTTPS. Built-in basic auth if needed. |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create SvelteKit project
|
||||||
|
npx sv create taskplaner
|
||||||
|
cd taskplaner
|
||||||
|
|
||||||
|
# Core dependencies
|
||||||
|
npm install better-sqlite3 drizzle-orm sharp nanoid zod
|
||||||
|
|
||||||
|
# Dev dependencies
|
||||||
|
npm install -D drizzle-kit @types/better-sqlite3 tailwindcss @tailwindcss/vite
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Storage Strategy
|
||||||
|
|
||||||
|
For image attachments, use **local filesystem storage**:
|
||||||
|
|
||||||
|
```
|
||||||
|
/data/
|
||||||
|
database.sqlite # SQLite database
|
||||||
|
attachments/ # Image files
|
||||||
|
{year}/{month}/ # Organized by date
|
||||||
|
{nanoid}.webp # Optimized images
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why filesystem over object storage:**
|
||||||
|
- Single-user app doesn't need S3 complexity
|
||||||
|
- Easy to backup (just copy the /data folder)
|
||||||
|
- No external dependencies
|
||||||
|
- Docker volume mount makes it simple
|
||||||
|
|
||||||
|
**Image processing pipeline:**
|
||||||
|
1. Accept upload (validate file type server-side, not just Content-Type header)
|
||||||
|
2. Generate thumbnail with sharp (resize to max 1920px width, convert to WebP)
|
||||||
|
3. Store original and thumbnail
|
||||||
|
4. Save path reference in SQLite
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
| Recommended | Alternative | When to Use Alternative |
|
||||||
|
|-------------|-------------|-------------------------|
|
||||||
|
| SvelteKit | Next.js | If you need React ecosystem libraries or team already knows React. |
|
||||||
|
| SvelteKit | Hono + separate frontend | If building API-first architecture for mobile apps later. |
|
||||||
|
| SQLite | PostgreSQL | If you expect multiple concurrent users or need advanced features like JSONB operators. |
|
||||||
|
| better-sqlite3 | Drizzle with LibSQL | If you want Turso cloud sync in the future. |
|
||||||
|
| Drizzle ORM | Prisma | If you prefer schema-first approach and don't mind slower cold starts. |
|
||||||
|
| Tailwind CSS | CSS Modules | If you dislike utility classes and prefer scoped traditional CSS. |
|
||||||
|
| sharp | Thumbor | If you need a dedicated image service with caching CDN. Overkill for single-user. |
|
||||||
|
|
||||||
|
## What NOT to Use
|
||||||
|
|
||||||
|
| Avoid | Why | Use Instead |
|
||||||
|
|-------|-----|-------------|
|
||||||
|
| **Express.js** | Outdated, slow, development stalled. No need for separate backend with SvelteKit. | SvelteKit API routes (or Hono if you must separate) |
|
||||||
|
| **Prisma** | Heavier runtime, slower cold starts, schema-first adds build step. | Drizzle ORM - lighter, faster, code-first |
|
||||||
|
| **MongoDB** | Overkill for structured task/note data. No ACID by default. Adds container complexity. | SQLite - simpler, faster for this use case |
|
||||||
|
| **Redis** | Unnecessary for single-user app. SQLite is fast enough for session/cache. | SQLite in-memory or just application state |
|
||||||
|
| **CSS-in-JS (styled-components, Emotion)** | Runtime overhead, larger bundles, complexity. | Tailwind CSS - zero runtime, tiny output |
|
||||||
|
| **TypeORM** | Buggy, inconsistent TypeScript support, heavy. | Drizzle ORM |
|
||||||
|
| **Sequelize** | Old patterns, poor TypeScript support. | Drizzle ORM |
|
||||||
|
| **sql.js** | 50-60% slower than native SQLite. Only use if browser-side SQLite needed. | better-sqlite3 for Node.js server |
|
||||||
|
| **node-sqlite3** | Callback-based API, slower than better-sqlite3. | better-sqlite3 |
|
||||||
|
| **Full auth systems (Keycloak, Authentik)** | Massive overkill for single-user. Adds container complexity. | Simple session with hashed password in SQLite, or Caddy basic auth |
|
||||||
|
|
||||||
|
## Stack Patterns by Variant
|
||||||
|
|
||||||
|
**If you want maximum simplicity:**
|
||||||
|
- Skip Drizzle, use better-sqlite3 directly with raw SQL
|
||||||
|
- Use SvelteKit's built-in `$app/stores` for state instead of external state management
|
||||||
|
- Single Dockerfile with embedded SQLite
|
||||||
|
|
||||||
|
**If you might add mobile app later:**
|
||||||
|
- Consider separating API with Hono
|
||||||
|
- Add JWT-based auth from the start
|
||||||
|
- Design REST API endpoints alongside SvelteKit routes
|
||||||
|
|
||||||
|
**If you want offline-first/PWA:**
|
||||||
|
- Add service worker (SvelteKit supports this)
|
||||||
|
- Consider IndexedDB for client-side cache
|
||||||
|
- Implement sync strategy for when online
|
||||||
|
|
||||||
|
## Authentication Recommendation
|
||||||
|
|
||||||
|
For **single-user self-hosted**, keep it simple:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Simple password hash stored in SQLite or environment variable
|
||||||
|
// Use Argon2 or bcrypt for password hashing
|
||||||
|
import { hash, verify } from '@node-rs/argon2';
|
||||||
|
|
||||||
|
// Session stored as httpOnly cookie
|
||||||
|
// SvelteKit hooks handle session validation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do NOT:**
|
||||||
|
- Use full OAuth providers (Google, GitHub) - unnecessary for single-user
|
||||||
|
- Implement JWT tokens - session cookies are simpler and more secure for web-only
|
||||||
|
- Use external auth services - adds complexity and external dependencies
|
||||||
|
|
||||||
|
**Consider:**
|
||||||
|
- Caddy's built-in basic auth as the simplest option (one line of config)
|
||||||
|
- Environment variable for password hash (no database needed for auth)
|
||||||
|
|
||||||
|
## Version Compatibility
|
||||||
|
|
||||||
|
| Package | Compatible With | Notes |
|
||||||
|
|---------|-----------------|-------|
|
||||||
|
| better-sqlite3@12.x | Node.js 18.17+ or 20.3+ | Requires native compilation or prebuilt binaries |
|
||||||
|
| sharp@0.34.x | Node.js 18.17+ or 20.3+ | Uses libvips, prebuilts available for common platforms |
|
||||||
|
| Drizzle@0.45.x | better-sqlite3@11+ | Use `drizzle-orm/better-sqlite3` adapter |
|
||||||
|
| SvelteKit@2.50.x | Svelte 5.x | Svelte 5 is peer dependency |
|
||||||
|
| Tailwind@4.1.x | Vite (built into SvelteKit) | Use `@tailwindcss/vite` plugin |
|
||||||
|
|
||||||
|
## Docker Configuration
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Multi-stage build for minimal production image
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-alpine AS production
|
||||||
|
WORKDIR /app
|
||||||
|
# Install only production dependencies
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
# Copy built application
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
# Create non-root user
|
||||||
|
RUN adduser -D appuser && chown -R appuser /app
|
||||||
|
USER appuser
|
||||||
|
# Data volume for SQLite and attachments
|
||||||
|
VOLUME /app/data
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "build"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key practices:**
|
||||||
|
- Multi-stage build reduces image from ~500MB to ~70MB
|
||||||
|
- Non-root user for security
|
||||||
|
- Volume mount for persistent data
|
||||||
|
- Alpine base for minimal attack surface
|
||||||
|
- Use `npm ci` for reproducible builds
|
||||||
|
|
||||||
|
## Search Implementation
|
||||||
|
|
||||||
|
Use **SQLite FTS5** (built-in full-text search):
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create FTS5 virtual table
|
||||||
|
CREATE VIRTUAL TABLE notes_fts USING fts5(
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
content='notes',
|
||||||
|
content_rowid='id'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Search with ranking
|
||||||
|
SELECT * FROM notes_fts WHERE notes_fts MATCH 'search query' ORDER BY rank;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why FTS5 over Meilisearch/Typesense:**
|
||||||
|
- Zero dependencies - built into SQLite
|
||||||
|
- 6-8x smaller index size than Meilisearch
|
||||||
|
- Much lower memory usage (~75MB vs 800MB)
|
||||||
|
- Simpler architecture - just SQL queries
|
||||||
|
- Sufficient for personal notes (thousands, not millions of documents)
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### HIGH Confidence (Official Documentation)
|
||||||
|
- [Svelte 5.49.0 Release](https://github.com/sveltejs/svelte/releases) - Current stable version verified
|
||||||
|
- [SvelteKit 2.50.1 Release](https://github.com/sveltejs/kit/releases) - Current stable version verified
|
||||||
|
- [Hono 4.11.7 Release](https://github.com/honojs/hono/releases) - Current stable version verified
|
||||||
|
- [Tailwind CSS v4.1 Documentation](https://tailwindcss.com/docs/installation) - Current version verified
|
||||||
|
- [sharp Documentation](https://sharp.pixelplumbing.com/) - API and requirements verified
|
||||||
|
- [better-sqlite3 GitHub](https://github.com/WiseLibs/better-sqlite3) - Performance claims verified
|
||||||
|
- [SQLite FTS5 Documentation](https://www.sqlite.org/fts5.html) - Built-in full-text search
|
||||||
|
- [Docker Node.js Best Practices](https://docs.docker.com/guides/nodejs/containerize/) - Official guide
|
||||||
|
|
||||||
|
### MEDIUM Confidence (Multiple Sources Agree)
|
||||||
|
- [Drizzle vs Prisma Comparison](https://betterstack.com/community/guides/scaling-nodejs/drizzle-vs-prisma/) - Performance characteristics
|
||||||
|
- [React vs Vue vs Svelte 2026](https://www.frontendtools.tech/blog/best-frontend-frameworks-2025-comparison) - Framework comparison benchmarks
|
||||||
|
- [SQLite vs PostgreSQL Comparison](https://www.selecthub.com/relational-database-solutions/postgresql-vs-sqlite/) - Use case recommendations
|
||||||
|
- [Node.js Backend Framework Comparison](https://betterstack.com/community/guides/scaling-nodejs/hono-vs-fastify/) - Express vs Fastify vs Hono
|
||||||
|
- [SQLite Driver Benchmark](https://sqg.dev/blog/sqlite-driver-benchmark) - better-sqlite3 performance verified
|
||||||
|
|
||||||
|
### LOW Confidence (Single Source, Verify Before Using)
|
||||||
|
- Drizzle ORM 1.0 release timing - still in beta, stable is 0.45.x
|
||||||
|
- Sharp 0.35.0 - currently RC, not stable
|
||||||
|
|
||||||
|
---
|
||||||
|
*Stack research for: Personal Task/Notes Web App*
|
||||||
|
*Researched: 2026-01-29*
|
||||||
247
.planning/research/SUMMARY.md
Normal file
247
.planning/research/SUMMARY.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
# Project Research Summary
|
||||||
|
|
||||||
|
**Project:** TaskPlaner - Personal Task/Notes Web App
|
||||||
|
**Domain:** Single-user, self-hosted task and notes management with image attachment capabilities
|
||||||
|
**Researched:** 2026-01-29
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This is a personal task and notes management application designed for self-hosted, single-user deployment with a focus on capturing both typed entries and digitized paper notes via image attachments. Expert developers in this domain prioritize simplicity over scalability, leveraging SQLite for persistence, filesystem storage for images, and modern full-stack frameworks that minimize operational complexity. The recommended approach is a modular monolith using SvelteKit with SQLite, avoiding microservices and external dependencies that add unnecessary overhead for single-user scenarios.
|
||||||
|
|
||||||
|
The key success factors are: (1) keeping the stack minimal but modern (SvelteKit + SQLite + better-sqlite3), (2) designing the data model to support both tasks and thoughts as a unified entity type rather than separate systems, and (3) implementing image storage on the filesystem with database references from day one to avoid painful migrations later. The architecture should follow a modular monolith pattern with clear internal boundaries (repository pattern, service layer) while maintaining deployment simplicity through containerization.
|
||||||
|
|
||||||
|
Critical risks include storing images directly in the database (bloats database, slow backups), missing mobile camera capture support (primary use case for digitizing paper notes), and over-engineering the task/thought distinction into separate systems. These are mitigated by: filesystem-based content-addressable image storage, mobile-first responsive design with HTML5 camera API integration, and a unified data model with a simple type discriminator field.
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### Recommended Stack
|
||||||
|
|
||||||
|
The research strongly recommends SvelteKit as the full-stack framework due to its exceptional developer experience for solo projects, small bundle sizes (30% faster load times vs React/Vue), and built-in SSR with API routes that eliminate the need for a separate backend. Svelte 5's runes provide explicit reactivity without virtual DOM overhead, making it ideal for responsive single-user applications.
|
||||||
|
|
||||||
|
**Core technologies:**
|
||||||
|
- **SvelteKit 2.50.x + Svelte 5.49.x**: Full-stack framework with built-in routing, SSR, and API routes — best DX for solo/small projects with minimal runtime overhead
|
||||||
|
- **SQLite 3.x + better-sqlite3 12.x**: Zero-config, serverless database with FTS5 full-text search built-in — 20-39% faster than alternatives, perfect for single-user apps with no separate database service to manage
|
||||||
|
- **TypeScript 5.x**: Type safety with first-class Svelte/SvelteKit support — catches bugs early and provides excellent IDE integration
|
||||||
|
- **Drizzle ORM 0.45.x**: Lightweight, type-safe SQL abstraction — code-first schema in TypeScript with ~7.4kb footprint and no runtime overhead
|
||||||
|
- **Tailwind CSS 4.1.x**: Utility-first CSS with zero runtime overhead — automatic purging keeps CSS tiny, great for rapid prototyping
|
||||||
|
- **sharp 0.34.x**: High-performance image processing — 4-5x faster than ImageMagick for thumbnail generation and optimization
|
||||||
|
|
||||||
|
**Supporting tools:**
|
||||||
|
- Node.js 22 LTS runtime (required for better-sqlite3 native bindings)
|
||||||
|
- Docker multi-stage builds for minimal production images (~70MB with Alpine base)
|
||||||
|
- Zod for form and API input validation
|
||||||
|
- nanoid for URL-safe unique ID generation
|
||||||
|
|
||||||
|
### Expected Features
|
||||||
|
|
||||||
|
Based on competitor analysis (Todoist, Bear, Simplenote, Evernote, Obsidian), the feature landscape is well-defined with clear table stakes and differentiators.
|
||||||
|
|
||||||
|
**Must have (table stakes):**
|
||||||
|
- Create/Edit/Delete items with task vs thought distinction — fundamental CRUD with type differentiation
|
||||||
|
- Mark tasks complete — core task management; every competitor has this
|
||||||
|
- Image attachments — project requirement for digitizing paper notes
|
||||||
|
- Tags for organization — standard pattern across all competitors
|
||||||
|
- Full-text search — users expect to find content quickly
|
||||||
|
- Mobile-responsive UI — "any device" access means mobile must work well
|
||||||
|
- Cross-device access via web — inherently provided by web app architecture
|
||||||
|
- Quick capture mode — minimal friction input is essential for habit formation
|
||||||
|
|
||||||
|
**Should have (competitive advantage):**
|
||||||
|
- Dark mode — low complexity, high user satisfaction
|
||||||
|
- Keyboard shortcuts — power user efficiency
|
||||||
|
- Pin/favorite items — quick access to important entries
|
||||||
|
- Export to JSON/Markdown — data portability and user ownership
|
||||||
|
- Due dates on tasks — natural extension of task management
|
||||||
|
- Drag-and-drop reorder — intuitive organization UX
|
||||||
|
|
||||||
|
**Defer to v2+ (nice to have):**
|
||||||
|
- Markdown support — adds complexity to editing
|
||||||
|
- Natural language date parsing ("tomorrow", "next Monday")
|
||||||
|
- Recurring tasks — adds state machine complexity
|
||||||
|
- OCR on images — requires external service/library
|
||||||
|
- Note linking with backlinks — changes user mental model
|
||||||
|
- Offline support with PWA — significant complexity (service worker, sync conflict resolution)
|
||||||
|
|
||||||
|
### Architecture Approach
|
||||||
|
|
||||||
|
The recommended architecture is a modular monolith with clear internal boundaries, avoiding microservices complexity while maintaining clean separation of concerns. This provides simple deployment (single container) with easy debugging and no network overhead between components, while enabling testability through repository pattern abstraction.
|
||||||
|
|
||||||
|
**Major components:**
|
||||||
|
1. **Web Frontend (SPA)** — Svelte 5 components with client-side routing, handles UI rendering and user interaction with optimistic updates
|
||||||
|
2. **REST API (SvelteKit routes)** — Business logic, validation, and orchestration with separate route handlers for notes, tasks, tags, search, and upload
|
||||||
|
3. **Repository Layer** — Data access abstraction using repository pattern to enable testing with mocks and future database changes if needed
|
||||||
|
4. **SQLite Database** — Primary data persistence with FTS5 virtual tables for full-text search (auto-updated via triggers)
|
||||||
|
5. **File Storage** — Content-addressable filesystem storage for images using SHA256 hash as filename for automatic deduplication
|
||||||
|
|
||||||
|
**Key architectural patterns:**
|
||||||
|
- Modular monolith with clear domain boundaries but single deployment
|
||||||
|
- Repository pattern for testable, database-agnostic business logic
|
||||||
|
- Content-addressable storage for images (hash as filename enables cache-forever and deduplication)
|
||||||
|
- Unified data model for notes/tasks (single table with type discriminator, not separate tables)
|
||||||
|
- FTS5 triggers for automatic search index updates on data changes
|
||||||
|
|
||||||
|
### Critical Pitfalls
|
||||||
|
|
||||||
|
1. **Storing images in database as BLOBs** — Database grows massive, backups become slow, migrations become painful. Solution: Store images on filesystem (content-addressable with hash-based naming), store only file path/hash in database. This must be correct from Phase 1 as migrating images out of database later is a high-cost recovery operation.
|
||||||
|
|
||||||
|
2. **No server-side image upload validation** — Users accidentally upload 50MB RAW files, server crashes, storage fills up, or malicious files get stored. Solution: Validate file type using magic bytes (not just extension), enforce reasonable size limits (e.g., 10MB), reject non-image MIME types, and convert/resize large images on upload.
|
||||||
|
|
||||||
|
3. **Mobile camera capture doesn't work** — Desktop testing passes but mobile fails with wrong orientation (EXIF rotation ignored), camera doesn't trigger, or UI doesn't fit viewport. Solution: Use `<input type="file" accept="image/*" capture="environment">` for mobile camera, handle EXIF orientation server-side, test on actual mobile devices early, design mobile-first.
|
||||||
|
|
||||||
|
4. **Tagging system becomes complex or inconsistent** — Either over-engineered (hierarchical tags, colors, descriptions) or under-thought (no autocomplete, "work" vs "Work" vs "WORK" as separate tags). Solution: Start with flat tags, implement case-insensitive matching, provide autocomplete from existing tags, resist adding features until proven needed.
|
||||||
|
|
||||||
|
5. **No data export or backup strategy** — Database corruption, accidental deletion, or wanting to migrate to different system with no recovery path. Solution: Design export from day one (JSON + image files), document data format, use named Docker volumes with explicit backup strategy.
|
||||||
|
|
||||||
|
## Implications for Roadmap
|
||||||
|
|
||||||
|
Based on research findings, the project should be structured in 7 phases following component dependencies and architectural layering patterns:
|
||||||
|
|
||||||
|
### Phase 1: Foundation & Data Model
|
||||||
|
**Rationale:** Must establish correct storage patterns before building features. Image storage strategy and unified entry model are critical architectural decisions that are painful to change later (PITFALLS.md: "high cost recovery" for images in database).
|
||||||
|
|
||||||
|
**Delivers:** SQLite database with migrations, basic schema (unified notes table with type field), repository layer for data access, filesystem storage structure for future images.
|
||||||
|
|
||||||
|
**Addresses:** Unified task/thought distinction (FEATURES.md: table stakes), prevents database storage pitfall (PITFALLS.md: critical), enables export-friendly data model (PITFALLS.md).
|
||||||
|
|
||||||
|
**Avoids:** Separate tables for tasks vs thoughts (PITFALLS.md: task/thought confusion), images in database (PITFALLS.md: critical pitfall #1).
|
||||||
|
|
||||||
|
### Phase 2: Core CRUD & Basic UI
|
||||||
|
**Rationale:** Get basic functionality working before adding complexity. Repository pattern established in Phase 1 enables rapid API development. User validation comes from being able to create and view entries.
|
||||||
|
|
||||||
|
**Delivers:** REST API handlers for notes CRUD, service layer with business logic, basic SvelteKit frontend with note list/create/edit views, task completion status toggle.
|
||||||
|
|
||||||
|
**Uses:** SvelteKit API routes (STACK.md), Drizzle ORM for type-safe queries (STACK.md), Svelte 5 components (STACK.md).
|
||||||
|
|
||||||
|
**Implements:** REST API layer and Web Frontend components (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Addresses:** Create/Edit/Delete items (FEATURES.md: P1), mark tasks complete (FEATURES.md: P1), quick capture mode (FEATURES.md: table stakes).
|
||||||
|
|
||||||
|
### Phase 3: Image Attachments
|
||||||
|
**Rationale:** Core project requirement for digitizing paper notes. Depends on data model from Phase 1. Must include mobile camera support as primary use case, not an afterthought.
|
||||||
|
|
||||||
|
**Delivers:** File storage abstraction with content-addressable storage, upload API with validation and EXIF handling, attachment records in database, frontend image upload/display with mobile camera integration, image processing with sharp for optimization.
|
||||||
|
|
||||||
|
**Uses:** sharp for image processing (STACK.md: 4-5x faster than ImageMagick), content-addressable storage pattern (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Implements:** Upload API and File Storage components (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Addresses:** Image attachments (FEATURES.md: P1), mobile camera capture (FEATURES.md: table stakes), mobile-responsive UI (FEATURES.md: P1).
|
||||||
|
|
||||||
|
**Avoids:** Images in database (PITFALLS.md: critical #1), no upload validation (PITFALLS.md: critical #2), mobile capture UX disaster (PITFALLS.md: critical #5).
|
||||||
|
|
||||||
|
### Phase 4: Tags & Organization
|
||||||
|
**Rationale:** Tags enhance search (implemented in Phase 5) and provide organization without complex hierarchies. Must keep simple to avoid over-engineering pitfall.
|
||||||
|
|
||||||
|
**Delivers:** Tags table and many-to-many junction table, tag CRUD API, autocomplete from existing tags, case-insensitive tag matching, tag filtering in frontend.
|
||||||
|
|
||||||
|
**Addresses:** Tags for organization (FEATURES.md: P1).
|
||||||
|
|
||||||
|
**Avoids:** Overly complex tagging (PITFALLS.md: critical #3) — resist hierarchies, colors, descriptions until proven needed.
|
||||||
|
|
||||||
|
### Phase 5: Search
|
||||||
|
**Rationale:** Depends on existing content from Phases 2-4 to index. FTS5 must index all text fields including image descriptions to avoid "search doesn't find images" pitfall.
|
||||||
|
|
||||||
|
**Delivers:** FTS5 virtual table and triggers for auto-indexing, search API with relevance ranking (BM25), search UI with result highlighting, tag filter integration.
|
||||||
|
|
||||||
|
**Uses:** SQLite FTS5 (STACK.md: built-in, zero dependencies), FTS5 Index component (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Implements:** Search API and FTS5 Index components (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Addresses:** Full-text search (FEATURES.md: P1), search on image context (PITFALLS.md: critical #4).
|
||||||
|
|
||||||
|
**Avoids:** LIKE '%query%' anti-pattern (ARCHITECTURE.md: no ranking, full table scan), missing image context in search (PITFALLS.md).
|
||||||
|
|
||||||
|
### Phase 6: Polish & UX Enhancements
|
||||||
|
**Rationale:** Core functionality complete, now improve user experience with low-complexity, high-impact features. These are proven patterns from competitors.
|
||||||
|
|
||||||
|
**Delivers:** Dark mode with theme toggle, keyboard shortcuts for common actions, pin/favorite functionality, drag-and-drop reorder, export to JSON/Markdown, due dates on tasks.
|
||||||
|
|
||||||
|
**Addresses:** Dark mode (FEATURES.md: P2), keyboard shortcuts (FEATURES.md: P2), pin/favorite (FEATURES.md: P2), export (FEATURES.md: P2), due dates (FEATURES.md: P2).
|
||||||
|
|
||||||
|
**Avoids:** No backup strategy (PITFALLS.md: critical #6) — export implements data portability.
|
||||||
|
|
||||||
|
### Phase 7: Containerization & Deployment
|
||||||
|
**Rationale:** Build and test locally first, containerize last. Docker configuration depends on knowing exact runtime requirements from Phases 1-6.
|
||||||
|
|
||||||
|
**Delivers:** Multi-stage Dockerfile (builder + production), docker-compose.yml with volume mounts, environment variable configuration, deployment documentation, backup strategy documentation.
|
||||||
|
|
||||||
|
**Uses:** Docker multi-stage builds (STACK.md: reduces image from ~500MB to ~70MB), Node.js 22 Alpine base (STACK.md), named volumes for data persistence (ARCHITECTURE.md).
|
||||||
|
|
||||||
|
**Addresses:** Containerized deployment (project requirement), cross-device access (FEATURES.md: table stakes).
|
||||||
|
|
||||||
|
**Avoids:** Data loss on container restart (PITFALLS.md: volume mount strategy), missing backup documentation (PITFALLS.md: critical #6).
|
||||||
|
|
||||||
|
### Phase Ordering Rationale
|
||||||
|
|
||||||
|
- **Foundation first (Phase 1):** Storage patterns must be correct from the start; changing image storage or data model later is high-cost recovery
|
||||||
|
- **Basic CRUD before features (Phase 2):** Validates architecture decisions with working software before adding complexity
|
||||||
|
- **Images early (Phase 3):** Core project requirement and primary differentiator; mobile testing provides early validation of responsive design
|
||||||
|
- **Tags before search (Phase 4):** Search enhancement; simpler to implement tags first then integrate into search
|
||||||
|
- **Search requires content (Phase 5):** FTS5 indexes existing data from Phases 2-4; no point indexing empty tables
|
||||||
|
- **Polish after core (Phase 6):** User validation of core features informs which polish features provide most value
|
||||||
|
- **Containerize last (Phase 7):** Simplifies development; knowing exact runtime dependencies enables optimal Docker configuration
|
||||||
|
|
||||||
|
This ordering follows ARCHITECTURE.md's recommended build order and addresses critical pitfalls at the earliest possible phase.
|
||||||
|
|
||||||
|
### Research Flags
|
||||||
|
|
||||||
|
**Phases likely needing deeper research during planning:**
|
||||||
|
- **Phase 3 (Images):** Mobile browser camera API quirks, EXIF handling libraries, image optimization parameters — while patterns are known, mobile browser behavior varies and needs verification during implementation
|
||||||
|
- **Phase 5 (Search):** FTS5 query syntax for ranking, snippet generation, advanced search features — basic usage is documented but optimal configuration may need research
|
||||||
|
|
||||||
|
**Phases with standard patterns (skip research-phase):**
|
||||||
|
- **Phase 1 (Foundation):** SQLite setup and repository pattern are well-documented with established best practices
|
||||||
|
- **Phase 2 (CRUD):** SvelteKit CRUD is standard framework usage with excellent documentation
|
||||||
|
- **Phase 4 (Tags):** Many-to-many relationships are fundamental database patterns with clear implementation
|
||||||
|
- **Phase 6 (Polish):** Dark mode, keyboard shortcuts, export are well-documented patterns with abundant examples
|
||||||
|
- **Phase 7 (Docker):** Containerization follows official Node.js Docker best practices with clear multi-stage build patterns
|
||||||
|
|
||||||
|
## Confidence Assessment
|
||||||
|
|
||||||
|
| Area | Confidence | Notes |
|
||||||
|
|------|------------|-------|
|
||||||
|
| Stack | HIGH | Verified against official releases and documentation. Version numbers confirmed current as of 2026-01-29. Svelte 5.49.0, SvelteKit 2.50.1, Tailwind 4.1, better-sqlite3 benchmarks verified. |
|
||||||
|
| Features | MEDIUM | Based on competitor analysis via WebFetch (Todoist, Bear, Simplenote, Obsidian, Evernote). Feature categorization is hypothesis until user validation. Table stakes are clearly established across competitors. |
|
||||||
|
| Architecture | HIGH | Modular monolith, repository pattern, and content-addressable storage are proven patterns with strong documentation. SQLite FTS5 is official feature with comprehensive docs. |
|
||||||
|
| Pitfalls | MEDIUM | Based on domain experience patterns from training data. Specific issues (images in DB, mobile EXIF, tag complexity) are well-documented problems. WebSearch unavailable for verification of 2026-specific mobile browser quirks. |
|
||||||
|
|
||||||
|
**Overall confidence:** HIGH
|
||||||
|
|
||||||
|
The stack and architecture recommendations are based on verified current versions and official documentation. Feature expectations are validated against multiple competitors with consistent patterns. Pitfalls are informed by established domain knowledge, though some specifics (particularly mobile browser behavior) should be validated during Phase 3 implementation.
|
||||||
|
|
||||||
|
### Gaps to Address
|
||||||
|
|
||||||
|
**Mobile browser camera API behavior (2026):** Research based on training data patterns; verify `capture="environment"` attribute support and EXIF rotation handling on target mobile browsers (Safari iOS, Chrome Android) during Phase 3 implementation.
|
||||||
|
|
||||||
|
**FTS5 performance at scale:** Research indicates FTS5 is sufficient for "thousands of documents" but doesn't provide specific benchmarks for single-user scenarios. Monitor search performance during Phase 5; if issues arise with >10,000 entries, consider external search engine.
|
||||||
|
|
||||||
|
**Image optimization parameters:** Research recommends sharp with WebP conversion and 1920px max width, but optimal quality/size tradeoff should be validated with real user photos during Phase 3. May need tuning based on actual paper note photo characteristics.
|
||||||
|
|
||||||
|
**Export format specification:** Research identifies export as critical but doesn't specify exact format. During Phase 6, design export format that balances human readability (JSON) with reimport capability (include schema version, handle image references).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- [Svelte 5.49.0 Release](https://github.com/sveltejs/svelte/releases) — Current stable version verified
|
||||||
|
- [SvelteKit 2.50.1 Release](https://github.com/sveltejs/kit/releases) — Current stable version verified
|
||||||
|
- [SQLite FTS5 Documentation](https://www.sqlite.org/fts5.html) — Built-in full-text search implementation
|
||||||
|
- [Docker Node.js Best Practices](https://docs.docker.com/guides/nodejs/containerize/) — Official containerization guide
|
||||||
|
- [better-sqlite3 GitHub](https://github.com/WiseLibs/better-sqlite3) — Performance claims verified
|
||||||
|
- [sharp Documentation](https://sharp.pixelplumbing.com/) — Image processing API and requirements
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- Todoist, Bear, Simplenote, Obsidian, Evernote features pages — Verified via WebFetch for competitor analysis
|
||||||
|
- [Drizzle vs Prisma Comparison](https://betterstack.com/community/guides/scaling-nodejs/drizzle-vs-prisma/) — ORM performance characteristics
|
||||||
|
- [SQLite Driver Benchmark](https://sqg.dev/blog/sqlite-driver-benchmark) — better-sqlite3 20-39% performance advantage verified
|
||||||
|
- [Microservices vs Monoliths in 2026](https://www.javacodegeeks.com/2025/12/microservices-vs-monoliths-in-2026-when-each-architecture-wins.html) — Modular monolith recommendation
|
||||||
|
- Standard Notes, Flatnotes, Evernote architecture documentation — Self-hosting patterns
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence, verify during implementation)
|
||||||
|
- Training data patterns for mobile browser camera API behavior — May have changed since training cutoff
|
||||||
|
- Image upload architecture patterns from Medium articles — General patterns, not version-specific
|
||||||
|
- Task manager database schema tutorials — Generic patterns, need adaptation
|
||||||
|
|
||||||
|
---
|
||||||
|
*Research completed: 2026-01-29*
|
||||||
|
*Ready for roadmap: yes*
|
||||||
Reference in New Issue
Block a user