feat: v0.1.0 - geolocation capture, calendar, search, Starlight docs site

- Automatic browser geolocation capture on event creation
- Reverse geocoding via Nominatim API for place names
- Full-text search with SQLite FTS5
- Calendar view for browsing past entries
- DateNavigator component for day navigation
- SearchModal with Ctrl+K shortcut
- QuickAddWidget with Ctrl+J shortcut
- Starlight documentation site with GitHub Pages deployment
- Multiple AI provider support (Groq, OpenAI, Anthropic, Ollama, LM Studio)
- Multi-user registration support

BREAKING: Events now include latitude/longitude/placeName fields
This commit is contained in:
lotherk
2026-03-27 02:27:55 +00:00
parent deaf496a7d
commit 0bdd71a4ed
67 changed files with 15201 additions and 355 deletions

406
todo/ai_style.md Normal file
View File

@@ -0,0 +1,406 @@
# AI Diary Style Feature Specification
## Overview
This document outlines a feature allowing users to customize the writing style/voice of AI-generated diary pages. Currently, the journal generation uses a fixed system prompt stored in Settings. This feature would provide a more robust, user-friendly way to define and manage diary writing styles.
---
## Feature Description
Users can select from predefined style presets or create custom styles that influence how the AI writes their diary pages. Each style defines tone, voice, formatting preferences, and specific writing guidelines that get injected into the generation prompt.
### Core Capabilities
1. **Style Presets**: Pre-defined writing styles (formal, casual, poetic, etc.)
2. **Custom Styles**: User-defined styles with full control over prompt components
3. **Per-Diary Override**: Ability to use a different style for specific dates
4. **Style Preview**: Generate a sample diary to preview style before use
5. **Multiple Presets**: Create and switch between multiple saved styles
---
## Style Preset Definitions
### 1. Formal
**Voice**: Professional, polished, articulate
**Use Case**: Users who want their diary to read like well-crafted prose
```
System Prompt Component:
You write in a formal, articulate style. Use complete sentences and proper grammar.
Avoid contractions and colloquialisms. Maintain a reflective, thoughtful tone.
Structure your diary with clear paragraphs that flow logically.
```
**Sample Output**:
> Today began with considerable promise. The morning meeting concluded successfully, yielding clarity on the project timeline. Subsequent hours were devoted to deep work, punctuated by brief exchanges with colleagues. The afternoon brought an unexpected interruption, though it proved fruitful in unexpected ways. Tomorrow presents new challenges that require careful preparation.
---
### 2. Casual
**Voice**: Relaxed, friendly, conversational
**Use Case**: Users who want their diary to feel like chatting with a good friend
```
System Prompt Component:
Write in a casual, friendly tone as if talking to a close friend. Use contractions naturally.
Keep it relaxed and easygoing. Short sentences are fine. Include natural pauses and "ums."
Make it feel like you're debriefing the day with someone who gets you.
```
**Sample Output**:
> So here's how today went... The morning was pretty productive, got through most of my todo list before lunch which was nice. Had this weird interaction with a coworker around 2pm that threw me off for a bit, but I think we figured it out. The rest of the afternoon flew by. Anyway, tomorrow's another day!
---
### 3. Poetic
**Voice**: Literary, evocative, lyrical
**Use Case**: Users who appreciate beautiful, descriptive language
```
System Prompt Component:
Write in a poetic, literary style. Use sensory details, metaphors, and evocative language.
Let sentences breathe with varied rhythm. Paint scenes with words. Find beauty in ordinary moments.
Use occasional line breaks for emphasis. Let the writing have a lyrical quality.
```
**Sample Output**:
> The light crept through the window this morning like a slow, golden tide. Meetings blurred together—a symphony of voices and shifting priorities. There was a moment, somewhere between the third cup of coffee and the afternoon's weight, when the world seemed to hold its breath. Tonight, the stars are sharp and numerous. Tomorrow, another page turns.
---
### 4. Minimalist
**Voice**: Concise, direct, stripped-back
**Use Case**: Users who prefer brevity and clarity over elaboration
```
System Prompt Component:
Write in a minimalist style. Keep sentences short and direct. Cut unnecessary words.
No flourishes or embellishments. Get to the point. Use bullet points if helpful.
Focus on facts and essential observations only. Less is more.
```
**Sample Output**:
> - Morning: Team sync, focused work block
> - Afternoon: Client call, project review
> - Evening: Walk, read
> Notable: Finally resolved the bug that's been nagging me
> Tomorrow: Deadline for presentation
---
### 5. Conversational
**Voice**: Warm, personal, storytelling
**Use Case**: Users who want their diary to feel like telling a story
```
System Prompt Component:
Write as if telling a story to someone who cares. First person, warm and personal.
Use "I" naturally. Describe things the way you'd explain them to a friend.
Include small details that bring the day to life. Make it feel present and immediate.
```
**Sample Output**:
> So I have to tell you about today—it was one of those days where everything just clicked, you know? First thing this morning I sat down and finally cracked that problem I've been working on for weeks. The feeling was amazing. Then at lunch I ran into this old friend I hadn't seen in forever and we talked for an hour. The rest of the day was chill by comparison. I'm really grateful for days like this.
---
### 6. Reflective
**Voice**: Introspective, thoughtful, philosophical
**Use Case**: Users who want to explore the meaning behind their days
```
System Prompt Component:
Write with an introspective, reflective lens. Explore what events meant, not just what happened.
Ask yourself questions. Examine patterns and connections. Go deeper than surface events.
Consider what you learned, what surprised you, what you'll carry forward.
```
**Sample Output**:
> What strikes me most about today is how the small moments added up to something meaningful. The conversation with the new team member revealed that sometimes the most valuable insights come from unexpected places. I notice I've been carrying some anxiety about the project, yet today's progress reminded me that I've handled difficult things before. What does it mean to be building something that matters? These questions linger as the day closes.
---
## Prompt Engineering for Each Style
### Structure Comparison
All styles share this base structure:
1. **Style Directive**: How to write (tone, voice)
2. **Content Requirements**: What to include
3. **Structure Guidance**: How to organize
4. **Example Integration**: From user events
### Style-Specific Additions
| Style | Sentence Length | Emotion | Formatting | Vocabulary |
|-------|-----------------|---------|------------|------------|
| Formal | Long, complex | Moderate | Paragraphs | Sophisticated |
| Casual | Short-mixed | High | Mixed | Simple |
| Poetic | Varied | Very high | Line breaks | Rich |
| Minimalist | Very short | Low | Lists/bullets | Plain |
| Conversational | Short-medium | High | Paragraphs | Colloquial |
| Reflective | Medium-long | High | Open paragraphs | Thoughtful |
### JSON Response Handling
The AI returns JSON for title + content. Style affects:
- **Title style**: Formal (proper nouns), Casual (lowercase ok), Poetic (evocative)
- **Content style**: Applied through system prompt injection
---
## UI for Selecting/Creating Styles
### Settings Page Addition
```
┌─────────────────────────────────────────────────────┐
│ DIARY STYLE │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Current Style: [Casual ▼] [Preview] │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ STYLE PRESETS [+ Create New] │
│ ┌─────────────────────────────────────────────────┐ │
│ │ ● Formal │ │
│ │ ○ Casual │ │
│ │ ○ Poetic │ │
│ │ ○ Minimalist │ │
│ │ ○ Custom: Morning Pages │ │
│ └─────────────────────────────────────────────────┘ │
```
### Style Editor Modal
```
┌─────────────────────────────────────────────────────┐
│ CREATE / EDIT STYLE │
│ │
│ Name: [ ] │
│ │
│ Style Directive: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Write in a... │ │
│ │ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Content Requirements: │
│ ┌─────────────────────────────────────────────────┐ │
│ │ • Summarize key events │ │
│ │ • Include emotions and reflections │ │
│ │ • End with forward-looking thought │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Example Tone (optional): │
│ ┌─────────────────────────────────────────────────┐ │
│ │ "Today was a good day. I felt..." │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ [Cancel] [Save Style] │
└─────────────────────────────────────────────────────┘
```
### Per-Diary Style Override
On the journal generation page (`/journal/:date`):
```
┌─────────────────────────────────────────────────────┐
│ GENERATE DIARY [⚙ Style: Casual]│
└─────────────────────────────────────────────────────┘
```
Clicking style button opens dropdown to override for this single generation.
### Style Preview Feature
Generate a "test" diary using current events + selected style:
- Modal shows generated preview
- "Apply this style" button to save as default
- Useful for testing custom styles before committing
---
## Storage of Custom Styles
### Database Schema Addition
```prisma
model StylePreset {
id String @id @default(uuid())
userId String
name String
isDefault Boolean @default(false)
// Style components
styleDirective String // Core tone/voice instructions
contentRequirements String? // What to include
structureGuidance String? // How to organize
exampleText String? // Sample tone (optional)
// Metadata
isBuiltIn Boolean @default(false) // True for official presets
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
}
```
### Settings Model Update
```prisma
model Settings {
// ... existing fields
defaultStyleId String? // FK to StylePreset
defaultStyleId String?
defaultStyle StylePreset? @relation(fields: [defaultStyleId], references: [id])
}
```
### Journal Model Update (for per-diary override)
```prisma
model Journal {
// ... existing fields
stylePresetId String? // Override style for this specific journal
stylePreset StylePreset? @relation(fields: [stylePresetId], references: [id])
}
```
---
## API Design
### New Endpoints
```
GET /api/v1/styles // List user's styles
POST /api/v1/styles // Create custom style
GET /api/v1/styles/:id // Get specific style
PUT /api/v1/styles/:id // Update style
DELETE /api/v1/styles/:id // Delete custom style
POST /api/v1/styles/:id/preview // Generate preview with this style
PUT /api/v1/settings/default-style // Set default style
```
### Generation Integration
In `journal.ts`, modify the generation to:
1. Check `journal.stylePresetId` for per-diary override
2. Fall back to `settings.defaultStyleId`
3. Fall back to legacy `settings.journalPrompt` (for backwards compatibility)
4. Build style prompt from StylePreset or use journalPrompt directly
### Prompt Building Logic
```typescript
function buildStylePrompt(style: StylePreset | null, journalPrompt: string): string {
if (!style) return journalPrompt;
const parts = [
style.styleDirective,
style.contentRequirements && `\n\nRequirements:\n${style.contentRequirements}`,
style.structureGuidance && `\n\nStructure:\n${style.structureGuidance}`,
style.exampleText && `\n\nTone example: "${style.exampleText}"`,
].filter(Boolean);
return parts.join('\n\n') + '\n\n' + journalPrompt;
}
```
---
## Implementation Complexity
### Phase 1: Core Infrastructure (Medium)
- [ ] Database schema changes (StylePreset model)
- [ ] Settings model update
- [ ] Journal model update
- [ ] New API routes for CRUD operations
- [ ] Seed default presets on first run
### Phase 2: UI Components (Medium)
- [ ] Style selector in Settings
- [ ] Style editor modal (create/edit)
- [ ] Preview modal with generation
- [ ] Per-diary override UI
### Phase 3: Generation Integration (Low)
- [ ] Update journal generation to use style
- [ ] Handle migration from old journalPrompt
- [ ] Add style_id to generation task logging
### Complexity Breakdown
| Component | Complexity | Notes |
|-----------|------------|-------|
| Database Schema | Low | Simple additions, Prisma handles migration |
| API Routes | Medium | Standard CRUD, ~100 lines |
| Style Editor UI | Medium | Form with preview, ~200 lines |
| Preview Feature | Medium | Requires new generation endpoint |
| Per-Diary Override | Low | Simple FK field + dropdown |
| Migration Path | Low | Handle nulls gracefully |
### Estimated Time
- Backend: 2-3 hours
- Frontend: 3-4 hours
- Testing: 1 hour
---
## Priority Recommendation
### Recommended Priority: **Phase 2 (Medium-High)**
This feature adds significant user value with moderate implementation effort.
**Justification**:
1. **High Impact**: Users frequently want personalized output
2. **Low Risk**: Feature is additive, doesn't break existing flows
3. **Builds on Existing**: Leverages already-implemented journal generation
4. **Differentiated**: Few journaling apps offer this level of customization
### Implementation Order
1. **First**: Add StylePreset model + seed default styles
2. **Second**: Update journal generation to use style
3. **Third**: Basic style selector in Settings
4. **Fourth**: Style editor for custom styles
5. **Fifth**: Preview feature (optional, can ship later)
### Considerations
- **Migration**: Existing users with custom `journalPrompt` should be converted to a "Custom" style preset on first migration
- **Defaults**: Pre-build the 6 style presets so users have immediate options
- **Validation**: Ensure style prompts don't exceed model context limits
- **Preview**: Consider rate-limiting preview generation to prevent abuse
---
## Future Enhancements
1. **Style Transfer**: Copy another user's shared style (community feature)
2. **Learning from Edits**: After user edits generated diary, update style
3. **Multi-language Styles**: Different writing styles per language
4. **Style Analytics**: Show which styles user uses most
5. **Export/Import**: Share style configurations
---
## Summary
The AI Style feature transforms DearDiary from a basic AI diarist into a personalized writing assistant. By giving users control over tone, voice, and formatting, we create a more engaging journaling experience that matches individual preferences.
The implementation is straightforward given the existing architecture, and the feature delivers immediate value without requiring significant refactoring or risk.

327
todo/calendar.md Normal file
View File

@@ -0,0 +1,327 @@
# Calendar View Feature Research
## Overview
This document outlines research and implementation ideas for adding a calendar view to DearDiary, enabling users to visualize their journaling activity across days, weeks, and months at a glance.
## Current State Analysis
### Existing Navigation & Data Flow
- **Routes**: `/` (Dashboard), `/today`, `/history`, `/diary`, `/day/:date`, `/journal/:date`, `/settings`
- **API**: `getDays()` returns `Array<{ date, eventCount, hasJournal, journalTitle?, journalExcerpt? }>`
- **History page**: Currently a simple list showing all days chronologically
### Key Data Points Per Day
- `date`: ISO date string (YYYY-MM-DD)
- `eventCount`: Number of events captured
- `hasJournal`: Whether diary page has been generated
- `journalTitle`: Title from AI-generated diary
- `journalExcerpt`: Preview text from diary content
---
## Feature Description
### 1. Monthly Calendar View
**Primary use case**: Overview of journaling activity at a glance
- Grid layout (7 columns for weekdays)
- Each day cell shows:
- Date number
- Visual indicator(s) for presence of events/diary
- Event count (optional, on hover or expanded)
**Visual Indicators**
| State | Indicator |
|-------|-----------|
| No events | Empty cell, muted styling |
| Events only (draft) | Small blue/gray dot |
| Diary generated | Small purple dot |
| Both (events + diary) | Two dots or colored dot with indicator |
| Today | Highlighted cell (border/background) |
| Selected day | Different background/border |
**Event Density Visualization**
- Option to show density heat map (more events = darker shade)
- Scale: 1-2 events (light), 3-5 (medium), 6+ (dark)
### 2. Weekly Calendar View
**Primary use case**: Detailed look at recent activity
- Horizontal 7-day strip
- Each day shows expanded content:
- Event count
- Mini event list (first 2-3 events)
- Diary status badge
### 3. Daily Mini Calendar
- Fixed position in header or sidebar
- Shows current month
- Click to navigate to specific date
- Quick navigation (prev/next month arrows)
---
## UI Component Suggestions
### Recommended Libraries
| Library | Pros | Cons |
|---------|------|------|
| **react-big-calendar** | Full-featured, customizable, well-maintained | Requires styling, may be heavy |
| **react-calendar** | Lightweight, simple API, good theming | Limited customization |
| **react-datepicker** | Easy to integrate, accessible | More input-focused than display |
| **fullcalendar-react** | Enterprise-grade, many views | Complex, may be overkill |
| **Custom CSS Grid** | Full control, lightweight | More implementation work |
### Recommendation: Custom CSS Grid + react-calendar hybrid
For DearDiary's needs, a custom implementation using CSS Grid provides:
- Full Tailwind integration (matches existing style)
- Lightweight bundle
- Complete control over visual indicators
- No dependency on heavy calendar libraries
**Fallback**: If ready-made needed, `react-calendar` is lightweight (~50KB) and sufficient for monthly view.
### Tailwind CSS Implementation (Custom)
```tsx
// Monthly calendar grid
<div className="grid grid-cols-7 gap-1">
{days.map(day => (
<button className={cn(
"relative p-2 rounded-lg text-center",
day.isToday && "ring-2 ring-purple-500",
day.hasEvents && "bg-slate-800",
day.hasJournal && "bg-purple-900/30"
)}>
<span>{day.dateNumber}</span>
{day.hasEvents && (
<span className="absolute bottom-1 left-1/2 -translate-x-1/2 w-1.5 h-1.5 rounded-full bg-blue-400" />
)}
</button>
))}
</div>
```
---
## Integration with Existing Pages
### 1. Standalone Calendar Page (`/calendar`)
New route added to App.tsx:
- Full monthly calendar view
- Navigation to previous/next months
- Click on day navigates to `/day/:date` or `/journal/:date`
- Toggle between month/week views
### 2. Integration with Dashboard
Replace or supplement "Recent Diary Pages" with mini calendar:
- Show current month
- Click date to navigate
- Compact version (fewer details per cell)
### 3. Integration with History Page
Replace list view with calendar as default:
- Toggle between calendar and list views
- Calendar shows activity overview
- List remains available for detailed browsing
### 4. Navigation Updates
Update Navbar to include calendar link:
```tsx
// App.tsx Navbar
<a href="/calendar" className="text-slate-300 hover:text-white transition">Calendar</a>
```
### 5. Quick Date Navigation
Mini calendar in header (optional):
- Always visible month view
- Click date = navigate to `/day/:date`
---
## Implementation Complexity
### Phase 1: Basic Monthly Calendar (Priority: High)
- [ ] Create `Calendar.tsx` component
- [ ] Add `/calendar` route
- [ ] Fetch days data for current month
- [ ] Render grid with visual indicators
- [ ] Click to navigate
**Complexity**: ~2-3 hours
**Files**: `frontend/src/components/Calendar.tsx`, route in App.tsx
### Phase 2: Dashboard Integration (Priority: Medium)
- [ ] Add mini calendar to Dashboard
- [ ] Show current month
- [ ] Click date navigates to day
**Complexity**: ~1-2 hours
### Phase 3: Week View (Priority: Low)
- [ ] Add week view toggle
- [ ] Horizontal 7-day strip
- [ ] Expanded content per day
**Complexity**: ~2-3 hours
### Phase 4: Advanced Features (Priority: Low)
- [ ] Event density heat map
- [ ] Drag to select date range
- [ ] Export calendar as image
- [ ] iCal integration
**Complexity**: Varies
---
## API Requirements
### Current API Sufficiency
`getDays()` returns all days - filtering by month happens on client. For larger datasets:
**Potential enhancement**:
```typescript
// Get days for specific month (optional optimization)
async getDaysByMonth(year: number, month: number): Promise<DayInfo[]>
```
**Current workaround sufficient**: Fetch all days, filter in React (acceptable for < 365 entries).
---
## Accessibility Considerations
### WCAG 2.1 AA Compliance
1. **Keyboard Navigation**
- Arrow keys to move between days
- Enter/Space to select
- Escape to close calendar
2. **Screen Reader Support**
- `aria-label` on each day cell: "March 15, 2026 - 3 events, diary generated"
- `aria-current` for today
- `role="grid"` with proper row/cell structure
3. **Visual Indicators**
- Don't rely solely on color
- Use icons/shapes + color
- Sufficient contrast ratios (4.5:1 minimum)
4. **Focus Management**
- Focus visible on calendar open
- Return focus to trigger element on close
### Example ARIA Implementation
```tsx
<button
aria-label={`${dateString} - ${eventCount} events${hasJournal ? ', diary generated' : ''}`}
aria-current={isToday ? 'date' : undefined}
className="..."
>
<span aria-hidden="true">{dayNumber}</span>
{hasEvents && <span className="sr-only">Has events</span>}
</button>
```
---
## Mobile-Friendly Considerations
### Responsive Design
1. **Viewport Adaptation**
- Desktop: Full monthly grid (35 cells visible)
- Tablet: Scrollable or condensed
- Mobile: Week view default, swipe between weeks
2. **Touch Targets**
- Minimum 44x44px tap targets
- Adequate spacing between dates
3. **Interaction Patterns**
- Swipe left/right for month navigation
- Tap to select, long-press for context menu
### Library Considerations
If using `react-big-calendar`:
```tsx
<Calendar
views={['month', 'week', 'day']}
defaultView="week" // Default to week on mobile
popup
selectable
style={{ height: 'auto' }}
/>
```
---
## Recommended Implementation Plan
### Priority 1: Calendar Page
1. Create `frontend/src/components/Calendar.tsx`
- Monthly grid using CSS Grid
- Visual dots for event/diary status
- Click navigates to `/day/:date`
2. Add route `/calendar` in App.tsx
3. Add nav link in Navbar
4. Test with existing data
### Priority 2: Dashboard Integration
1. Add mini calendar above recent entries
2. Show current month
3. Highlight today, clickable dates
### Priority 3: History Enhancement
1. Add toggle between list/calendar views
2. Calendar as default
### Priority 4: Week View (Optional)
1. Add view switcher
2. Horizontal week strip with event previews
---
## Summary
| Aspect | Recommendation |
|--------|----------------|
| **Implementation** | Custom CSS Grid (Tailwind) |
| **First placement** | New `/calendar` page |
| **View types** | Monthly default, week as secondary |
| **Indicators** | Dots (blue=events, purple=diary) |
| **Navigation** | Click to `/day/:date` or `/journal/:date` |
| **Accessibility** | Full ARIA, keyboard nav |
| **Mobile** | Default to week view, touch-friendly |
### Estimated Timeline
- Phase 1 (Calendar page): 2-3 hours
- Phase 2 (Dashboard): 1-2 hours
- Phase 3 (Week view): 2-3 hours
- **Total**: ~5-8 hours
---
## Questions for Further Research
1. Should calendar support date range selection for bulk operations?
2. Export to iCal/Google Calendar desired?
3. Sync with external calendars (Google, Apple)?
4. Recurring event support?
5. Need to show time-of-day heat map?

598
todo/export.md Normal file
View File

@@ -0,0 +1,598 @@
# Data Export Feature - DearDiary
Comprehensive research document for implementing a data export feature.
---
## 1. Feature Overview
Allow users to export their diary data in multiple formats with flexible scope and options. This feature enables users to:
- Backup their data locally
- Migrate to other journaling platforms
- Create offline archives
- Share selected entries
---
## 2. Export Formats
### 2.1 Markdown (.md)
**Description**: Human-readable plain text format with frontmatter metadata.
**Technical Approach**:
- Single file: One `.md` file per day or combined
- Use YAML frontmatter for metadata (date, title, word count)
- Structure:
```markdown
---
date: 2024-01-15
title: A Quiet Morning
event_count: 5
generated_at: 2024-01-15T20:30:00Z
---
# January 15, 2024
## Events
[08:30] Had coffee and read news
[12:00] Team meeting about Q1 goals
## Diary Page
The morning started quietly...
```
**Complexity**: Low - straightforward string generation
**Priority**: High - most versatile, easy to implement
---
### 2.2 JSON (.json)
**Description**: Machine-readable structured format for programmatic use.
**Technical Approach**:
```json
{
"exported_at": "2024-01-15T20:30:00Z",
"user_id": "user-uuid",
"format_version": "1.0",
"entries": [
{
"date": "2024-01-15",
"journal": {
"title": "A Quiet Morning",
"content": "The morning started quietly...",
"generated_at": "2024-01-15T20:30:00Z"
},
"events": [
{
"id": "event-uuid",
"type": "text",
"content": "Had coffee and read news",
"created_at": "2024-01-15T08:30:00Z",
"metadata": {}
}
]
}
]
}
```
**Complexity**: Low - native Prisma JSON serialization
**Priority**: High - essential for backups/migrations
---
### 2.3 PDF (.pdf)
**Description**: Print-ready formatted document.
**Technical Approach**:
- Use `pdfkit` or `puppeteer` (headless Chrome) for generation
- Puppeteer recommended for complex layouts/CSS support
- Template options:
- Simple: Title + content (minimal styling)
- Full: Events listed with diary page formatted
- Page breaks handled for multi-day exports
**Complexity**: Medium - requires additional dependency
**Priority**: Medium - high user demand for print/export
---
### 2.4 HTML (.html)
**Description**: Web-viewable static pages.
**Technical Approach**:
- Single HTML file with embedded CSS
- Include basic navigation for multi-day exports
- Responsive design with print media queries
- Structure:
```html
<!DOCTYPE html>
<html>
<head>
<title>DearDiary Export</title>
<style>
body { font-family: system-ui; max-width: 800px; margin: 0 auto; padding: 2rem; }
.entry { margin-bottom: 2rem; }
.meta { color: #666; font-size: 0.9rem; }
</style>
</head>
<body>
<h1>January 2024</h1>
<div class="entry">
<h2>January 15, 2024</h2>
<div class="meta">5 events</div>
<p>Diary content...</p>
</div>
</body>
</html>
```
**Complexity**: Low-Medium - string generation with CSS
**Priority**: Medium - good for web publishing
---
### 2.5 ePub (.epub)
**Description**: Ebook format for e-readers.
**Technical Approach**:
- Use `epub-gen` or similar library
- Structure: One chapter per day or per month
- Include cover image with app branding
- Metadata: Title, author, generated date
**Complexity**: High - requires ebook-specific libraries
**Priority**: Low - niche use case, can be deprioritized
---
## 3. Export Scope
### 3.1 Single Diary
- Export one day's journal + events
- API: `GET /api/v1/export?date=2024-01-15`
- Returns single entry with all related data
### 3.2 Date Range
- Export events between start and end dates
- API: `GET /api/v1/export?start=2024-01-01&end=2024-01-31`
- Batch query: Prisma `where: { date: { gte: start, lte: end } }`
### 3.3 All Data
- Export entire user dataset
- Include settings, metadata
- Requires pagination for large datasets
---
## 4. Include/Exclude Options
### 4.1 Content Filters
| Option | Description | Implementation |
|--------|-------------|----------------|
| `events_only` | Raw events without AI-generated diaries | Filter journals from response |
| `diaries_only` | Only generated diary pages | Filter events from response |
| `with_media` | Include media file references | Include `mediaPath` field |
| `without_media` | Exclude media references | Omit `mediaPath` field |
### 4.2 Data Structure Options
```typescript
interface ExportOptions {
format: 'md' | 'json' | 'pdf' | 'html' | 'epub';
scope: 'single' | 'range' | 'all';
date?: string;
startDate?: string;
endDate?: string;
include: {
events: boolean;
journals: boolean;
media: boolean;
settings: boolean;
};
organization: 'single_file' | 'folder';
compress: boolean;
}
```
---
## 5. File Organization
### 5.1 Single File
- All content in one file (`.md`, `.json`, `.html`)
- Best for: small exports, JSON backups
- Simple to implement
### 5.2 Folder Structure
```
export-2024-01-15/
├── index.html # Main navigation
├── 2024-01-15/
│ ├── journal.md # Diary page
│ ├── events.md # Raw events
│ └── media/ # Photos, voice memos
├── 2024-01-14/
│ └── ...
└── manifest.json # Export metadata
```
- Best for: large exports with media
- Use ZIP compression for download
---
## 6. Compression Options
### 6.1 ZIP Archive
- Default for folder exports > 10MB
- Use `Bun.zip()` or `archiver` package
- Include manifest with export details
**Implementation**:
```typescript
// Example: ZIP export flow
async function exportZip(options: ExportOptions) {
const tempDir = await createTempDir();
await generateFiles(tempDir, options);
const zipPath = `${tempDir}.zip`;
await zip(tempDir, zipPath);
return serveFile(zipPath);
}
```
---
## 7. Streaming Large Exports
### 7.1 Problem
- Large exports (years of data) can exceed memory
- Need progressive loading and streaming response
### 7.2 Solution: Server-Sent Events (SSE)
**API Design**:
```
POST /api/v1/export
Content-Type: application/json
{
"format": "json",
"startDate": "2020-01-01",
"endDate": "2024-01-15"
}
```
**Response** (chunked):
```
event: progress
data: {"percent": 10, "stage": "loading_events"}
event: data
data: {"date": "2020-01-01", ...}
event: progress
data: {"percent": 20, "stage": "loading_journals"}
event: data
data: {"date": "2020-01-02", ...}
event: complete
data: {"total_entries": 1000, "export_size": "5MB"}
```
### 7.3 Implementation Notes
- Use Prisma cursor-based pagination for memory efficiency
- Stream directly to response without buffering
- Provide progress updates every N records
---
## 8. Privacy & Security
### 8.1 Authentication
- Require valid API key for all export endpoints
- User can only export their own data
### 8.2 Sensitive Data Handling
- **Option**: Password-protect exports
- Use AES-256 encryption for ZIP
- Prompt for password in UI
- **Option**: redact sensitive entries
- Tag certain events as "private"
- Exclude from export by default
### 8.3 Media Files
- Generate signed URLs for media export
- Set expiration (24h default)
- Don't include raw API keys in export
### 8.4 Audit Logging
- Log export requests (who, when, scope)
- Store in new `ExportLog` model
---
## 9. Database Schema Changes
### 9.1 New Models
```prisma
model ExportLog {
id String @id @default(uuid())
userId String
format String
scope String
startDate String?
endDate String?
recordCount Int
sizeBytes Int?
status String @default("pending")
createdAt DateTime @default(now())
completedAt DateTime?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model ScheduledExport {
id String @id @default(uuid())
userId String
name String
format String
scope String @default("all")
frequency String @default("weekly")
includeJson Json?
enabled Boolean @default(true)
lastRunAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
---
## 10. API Changes
### 10.1 New Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/export` | Create export job |
| GET | `/api/v1/export/:id` | Get export status |
| GET | `/api/v1/export/:id/download` | Download export file |
| GET | `/api/v1/exports` | List export history |
| DELETE | `/api/v1/export/:id` | Delete export |
| GET | `/api/v1/scheduled-exports` | List scheduled exports |
| POST | `/api/v1/scheduled-exports` | Create schedule |
| PUT | `/api/v1/scheduled-exports/:id` | Update schedule |
| DELETE | `/api/v1/scheduled-exports/:id` | Delete schedule |
### 10.2 Request/Response Examples
**Create Export**:
```typescript
// POST /api/v1/export
interface CreateExportRequest {
format: 'md' | 'json' | 'pdf' | 'html' | 'epub';
date?: string; // single day
startDate?: string; // range start
endDate?: string; // range end
include: {
events: boolean;
journals: boolean;
media: boolean;
settings: boolean;
};
organization: 'single_file' | 'folder';
compress: boolean;
password?: string; // optional ZIP password
}
interface ExportResponse {
id: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
progress: number;
downloadUrl?: string;
expiresAt?: string;
}
```
---
## 11. UI/UX Considerations
### 11.1 Export Page Location
- Add to Settings page as "Export Data" section
- Or create dedicated `/export` route
### 11.2 Export Modal
```
┌─────────────────────────────────────────┐
│ Export Your Data │
├─────────────────────────────────────────┤
│ │
│ Format: [Markdown ▼] │
│ ○ Markdown │
│ ○ JSON │
│ ○ PDF │
│ ○ HTML │
│ ○ ePub │
│ │
│ Scope: ○ This month │
│ ○ This year │
│ ○ All time │
│ ○ Custom range [____] │
│ │
│ Include: ☑ Generated diaries │
│ ☑ Raw events │
│ ☐ Media files │
│ ☐ Settings │
│ │
│ Options: ○ Single file │
│ ○ Folder (with ZIP) │
│ │
│ ☐ Password protect │
│ [________] │
│ │
│ [Cancel] [Export] │
└─────────────────────────────────────────┘
```
### 11.3 Progress View
- Show progress bar during export
- Estimated time remaining
- Cancel button for large exports
- Email notification option (future)
### 11.4 Export History
- List of past exports with:
- Date, format, scope
- Size, record count
- Download link (with expiration)
- Delete button
---
## 12. Scheduled Exports
### 12.1 Configuration Options
| Frequency | Description |
|-----------|-------------|
| `daily` | Every day at configured time |
| `weekly` | Every Sunday |
| `monthly` | First day of month |
| `quarterly` | Every 3 months |
### 12.2 Implementation
- Use cron-style scheduling
- Run as background job (Bun.setInterval or dedicated worker)
- Store exports in cloud storage (S3-compatible) or local
- Send notification when ready
### 12.3 Use Cases
- Automated weekly backups
- Monthly archive generation
- Quarterly review compilation
---
## 13. Implementation Roadmap
### Phase 1: Core Export (Week 1-2)
- [ ] Add `ExportLog` model to schema
- [ ] Implement JSON export endpoint
- [ ] Implement Markdown export endpoint
- [ ] Add single date/range query support
- [ ] Basic export UI in Settings
**Complexity**: 3/5
**Priority**: High
### Phase 2: Advanced Formats (Week 3)
- [ ] HTML export
- [ ] PDF export (using puppeteer)
- [ ] ePub export (optional)
**Complexity**: 4/5
**Priority**: Medium
### Phase 3: Large Exports (Week 4)
- [ ] Streaming with SSE
- [ ] ZIP compression
- [ ] Progress reporting
**Complexity**: 5/5
**Priority**: Medium
### Phase 4: Automation (Week 5)
- [ ] Scheduled exports model
- [ ] Background job scheduler
- [ ] Scheduled exports UI
**Complexity**: 4/5
**Priority**: Low
### Phase 5: Security & Polish (Week 6)
- [ ] Password-protected ZIPs
- [ ] Export audit logging
- [ ] Media file handling
- [ ] Edge cases and testing
**Complexity**: 3/5
**Priority**: Medium
---
## 14. Dependencies Required
| Package | Purpose | Version |
|---------|---------|---------|
| `pdfkit` | PDF generation | ^0.14.0 |
| `puppeteer` | HTML to PDF | ^21.0.0 |
| `archiver` | ZIP creation | ^6.0.0 |
| `epub-gen` | ePub creation | ^0.1.0 |
| `jszip` | Client-side ZIP | ^3.10.0 |
---
## 15. Testing Considerations
### 15.1 Unit Tests
- Export formatters (MD, JSON, HTML)
- Date range filtering
- Include/exclude logic
### 15.2 Integration Tests
- Full export workflow
- Large dataset performance
- Streaming response handling
### 15.3 Edge Cases
- Empty date range
- Missing media files
- Export during active generation
- Concurrent export requests
---
## 16. Priority Recommendation
| Feature | Priority | Rationale |
|---------|----------|-----------|
| JSON/Markdown export | P0 | Core requirement for backups |
| Single/range export | P0 | Essential scope control |
| Export UI | P0 | User-facing feature |
| PDF export | P1 | High user demand |
| HTML export | P1 | Good alternative to PDF |
| Streaming exports | P2 | Performance for large data |
| ZIP compression | P2 | Usability for folder exports |
| ePub export | P3 | Niche, can skip |
| Scheduled exports | P3 | Automation, lower urgency |
| Password protection | P4 | Advanced, security theater |
---
## 17. Open Questions
1. **Storage**: Should exports be stored temporarily or generated on-demand?
2. **Retention**: How long to keep export downloads available?
3. **Media handling**: Include actual files or just references?
4. **Third-party sync**: Export to Google Drive, Dropbox?
5. **Incremental exports**: Only export new data since last export?
---
## 18. Summary
This feature set provides comprehensive data export capabilities while maintaining security and user privacy. Starting with JSON/Markdown exports covers 80% of use cases (backups, migration). PDF and HTML add print/web options. Streaming and compression enable handling of large datasets. Scheduled exports provide automation for power users.
Recommend implementing Phase 1 first to establish core functionality, then iterate based on user feedback.

537
todo/gallery.md Normal file
View File

@@ -0,0 +1,537 @@
# Media Gallery Feature for DearDiary
## Overview
This document outlines the comprehensive feature specification for adding media gallery capabilities to DearDiary - an AI-powered daily journaling app. The gallery will allow users to view, manage, and organize all their photos and voice recordings in one centralized location.
## Feature Description
### Core Functionality
1. **Media Grid View**
- Responsive masonry or grid layout displaying all media items
- Support for both photos and voice recordings
- Visual distinction between media types (photo thumbnails vs audio waveform icons)
- Lazy loading for performance with large collections
- Infinite scroll pagination
2. **Audio Player**
- Inline audio player for voice recordings
- Play/pause controls
- Progress bar with seek functionality
- Duration display
- Waveform visualization (optional enhancement)
3. **Filtering & Organization**
- Filter by media type (photos, voice recordings, all)
- Filter by date range (today, this week, this month, custom range)
- Search by date
- Sort by date (newest/oldest)
4. **Thumbnail Generation**
- Automatic thumbnail generation for photos
- Multiple sizes: small (150px), medium (300px), large (600px)
- Server-side processing using sharp or similar library
- Cache thumbnails for performance
5. **Lightbox View**
- Full-screen photo viewer
- Navigation between photos (previous/next)
- Zoom functionality
- Photo metadata display (EXIF data)
- Close on escape key or click outside
6. **Storage Management**
- Display total storage used
- Show individual file sizes
- Bulk delete functionality
- Delete confirmation dialogs
- Storage quota warnings
7. **Media Metadata**
- EXIF data extraction for photos (camera, date taken, location, etc.)
- Audio duration and format info
- File size and dimensions
- Creation date
8. **Privacy Considerations**
- All media stored locally or on user's own object storage
- No third-party media processing
- Optional encryption at rest
- Clear deletion removes all related data
---
## Storage Architecture
### Option 1: Local File Storage (Recommended for Self-Hosted)
```
/data/media/
/{userId}/
/{date}/
/photos/
{eventId}.jpg # Original
{eventId}_thumb.jpg # Thumbnail
{eventId}_medium.jpg # Medium size
/voice/
{eventId}.webm # Voice recording
```
**Pros:**
- Simple to implement
- No additional costs
- Full control over data
- Easy backup
**Cons:**
- Requires server-side file management
- No CDN for fast delivery
- Storage limited to server capacity
**Implementation:**
- Use `sharp` library for image processing
- Generate thumbnails on upload or on-demand
- Serve via static file middleware
### Option 2: Object Storage (S3-compatible)
**Pros:**
- Scalable storage
- Built-in CDN integration
- High availability
- Offload bandwidth
**Cons:**
- Additional complexity and cost
- Requires S3-compatible service
- More complex authentication
**Recommendation:** Start with local storage for v1, design for S3 compatibility in v2.
---
## Database Schema Changes
### New Media Model
```prisma
model Media {
id String @id @default(uuid())
userId String
eventId String?
type String // "photo" | "voice"
fileName String
originalName String
mimeType String
fileSize Int // bytes
duration Int? // seconds (for audio)
width Int? // pixels
height Int? // pixels
thumbnailPath String?
mediumPath String?
exifData String? // JSON string
storageType String @default("local")
storageKey String? // S3 key if using object storage
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
event Event? @relation(fields: [eventId], references: [id], onDelete: SetNull)
@@index([userId, type])
@@index([userId, createdAt])
@@index([eventId])
}
```
### Updated Event Model
```prisma
model Event {
id String @id @default(uuid())
userId String
date String
type String // "event" | "text" | "photo" | "voice" | "health"
content String
metadata String?
mediaId String? // Link to Media
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
media Media? @relation(fields: [mediaId], references: [id], onDelete: SetNull)
@@index([userId, date])
@@index([date])
@@index([type])
}
```
### Storage Statistics (Optional)
```prisma
model StorageStats {
userId String @id
totalBytes Int @default(0)
photoBytes Int @default(0)
voiceBytes Int @default(0)
photoCount Int @default(0)
voiceCount Int @default(0)
lastUpdated DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
---
## API Endpoints Needed
### Media Management
```
// Upload media (photo or voice)
POST /api/v1/media/upload
Body: multipart/form-data
- file: File
- type: "photo" | "voice"
- date: string (YYYY-MM-DD)
- eventId?: string (optional link to existing event)
- metadata?: object (optional additional metadata)
// Get media item by ID
GET /api/v1/media/:id
// Delete media item
DELETE /api/v1/media/:id
// Bulk delete media
POST /api/v1/media/bulk-delete
Body: { ids: string[] }
// Get media thumbnail
GET /api/v1/media/:id/thumbnail/:size
- size: "small" | "medium" | "large"
// Stream media file
GET /api/v1/media/:id/stream
// Get EXIF data
GET /api/v1/media/:id/exif
// Get storage usage stats
GET /api/v1/media/stats
// List all media with filters
GET /api/v1/media
Query params:
- type?: "photo" | "voice" | "all"
- startDate?: string (YYYY-MM-DD)
- endDate?: string (YYYY-MM-DD)
- page?: number
- limit?: number
- sort?: "newest" | "oldest"
```
### Integration with Existing Events
```
// Create event with media
POST /api/v1/events
Body: {
date, type, content, metadata,
media: { type, file }
}
// Add media to existing event
POST /api/v1/events/:id/media
Body: multipart/form-data with file
// Get event with media
GET /api/v1/events/:id
Response includes linked media
```
---
## UI/UX Design Patterns
### Gallery Page Layout
```
┌─────────────────────────────────────────────────────┐
│ DearDiary [Settings] │
├─────────────────────────────────────────────────────┤
│ [Dashboard] [Today] [History] [Diary] [Gallery] │
├─────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ 📷 Photos 🎤 Voice | Jan 2026 ▼ │ │
│ │ Total: 2.3 GB | 156 photos | 42 voice │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 📷 │ │ 📷 │ │ 🎤 │ │ 📷 │ │ 📷 │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ 📷 │ │ 🎤 │ │ 📷 │ │ 📷 │ │ 🎤 │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ [Load More...] │
└─────────────────────────────────────────────────────┘
```
### Filter Bar Components
- **Type Toggle**: Segmented button control (All | Photos | Voice)
- **Date Picker**: Month/Year dropdown with custom range option
- **Search**: Date input or quick filters (Today, This Week, This Month, Year)
### Photo Card Component
```
┌─────────────────┐
│ │
│ [Thumbnail] │
│ │
├─────────────────┤
│ Jan 15, 2026 │
│ 2.4 MB • 4032×3024 │
└─────────────────┘
```
### Voice Card Component
```
┌─────────────────────────────┐
│ 🎤 Voice Recording │
│ ━━━━━━━━━●━━━━━━━━━━━━━ │
│ 0:45 / 2:30 │
│ Jan 15, 2026 • 1.2 MB │
└─────────────────────────────┘
```
### Lightbox Component
```
┌─────────────────────────────────────────────────────┐
│ ← │ 📷 Photo Title │ ⋮ │ → │
│ │ │ │
│ │ │ │
│ │ [Full Size Image] │ │
│ │ │ │
│ │ │ │
├─────┴──────────────────────────┴────────────────┤
│ 📅 Jan 15, 2026 📷 4032×3024 📏 2.4 MB │
│ 📷 iPhone 15 Pro 📍 San Francisco │
└─────────────────────────────────────────────────────┘
```
### Storage Management Panel (Settings or Modal)
```
┌─────────────────────────────────────────┐
│ Storage Usage │
│ │
│ ████████████████████░░░░░░░░░ 2.3/10GB│
│ │
│ 📷 Photos: 1.8 GB (156 files) │
│ 🎤 Voice: 500 MB (42 files) │
│ │
│ [Delete Old Media] │
│ [Download All] │
│ [Clear All Media] │
└─────────────────────────────────────────┘
```
### Design Tokens (Tailwind)
- **Background**: `bg-slate-900`
- **Cards**: `bg-slate-800`
- **Borders**: `border-slate-700`
- **Primary**: `text-purple-400`
- **Muted**: `text-slate-400`
- **Hover**: `hover:bg-slate-700`
- **Active**: `bg-purple-600`
---
## Implementation Complexity
### Phase 1: Core Gallery (Estimated: 8-12 hours)
- [ ] Database schema updates (Media model)
- [ ] File upload endpoint with storage
- [ ] Media listing API with filtering
- [ ] Basic grid UI with thumbnails
- [ ] Type filtering UI
### Phase 2: Media Viewing (Estimated: 6-8 hours)
- [ ] Thumbnail generation service
- [ ] Lightbox component
- [ ] Audio player component
- [ ] Photo navigation (prev/next)
- [ ] EXIF extraction service
### Phase 3: Storage Management (Estimated: 4-6 hours)
- [ ] Storage stats calculation
- [ ] Delete single media
- [ ] Bulk delete functionality
- [ ] Storage visualization UI
- [ ] Delete confirmation dialogs
### Phase 4: Advanced Features (Estimated: 8-10 hours)
- [ ] Date range filtering
- [ ] Infinite scroll pagination
- [ ] Search by date
- [ ] Object storage support (S3)
- [ ] Media encryption at rest
### Total Estimated Time: 26-36 hours
---
## Priority Recommendation
### High Priority (Phase 1 + Phase 2)
- **Target**: MVP Gallery functionality
- **Rationale**: Core value proposition - users need to see their media
- **Features**:
- Grid view of media
- Basic type filtering
- Photo lightbox
- Audio playback
- Thumbnail generation
### Medium Priority (Phase 3)
- **Target**: Storage management
- **Rationale**: Important for self-hosted users with limited storage
- **Features**:
- Storage usage display
- Delete functionality
- Bulk operations
### Low Priority (Phase 4)
- **Target**: Advanced features
- **Rationale**: Nice-to-have enhancements
- **Features**:
- Object storage
- Date range filters
- Advanced search
---
## Migration Strategy
### Backward Compatibility
- Existing `Event.mediaPath` field can be migrated to new `Media` model
- Create migration script to:
1. Create Media records from existing mediaPath values
2. Generate thumbnails for photos
3. Update Event records to reference Media
4. Deprecate (not remove) mediaPath field
### Data Migration Script
```typescript
// Pseudocode for migration
async function migrateMedia() {
const events = await prisma.event.findMany({
where: { mediaPath: { not: null } }
});
for (const event of events) {
// Create Media record
const media = await prisma.media.create({
data: {
userId: event.userId,
eventId: event.id,
type: event.type === 'voice' ? 'voice' : 'photo',
filePath: event.mediaPath,
// ... extract metadata
}
});
// Update event to link to media
await prisma.event.update({
where: { id: event.id },
data: { mediaId: media.id, mediaPath: null }
});
}
}
```
---
## Testing Considerations
1. **Unit Tests**
- Thumbnail generation
- EXIF parsing
- Storage size calculation
- Date filtering logic
2. **Integration Tests**
- Upload flow
- Media listing with filters
- Delete operations
- Event-media linking
3. **UI Tests**
- Grid responsive layout
- Lightbox navigation
- Audio player controls
- Filter interactions
---
## Performance Considerations
1. **Thumbnail Caching**
- Generate on upload, store in cache
- Serve static thumbnails directly
2. **Pagination**
- Limit initial load to 20-50 items
- Infinite scroll for more
3. **Lazy Loading**
- Use intersection observer for images
- Load thumbnails first, full-res on demand
4. **Database Indexing**
- Index on userId + type for filtering
- Index on userId + createdAt for sorting
- Index on eventId for event-media lookups
---
## Security Considerations
1. **File Validation**
- Verify MIME types server-side
- Limit file sizes (max 50MB for photos, 20MB for audio)
- Sanitize filenames
2. **Access Control**
- Users can only access their own media
- API key authentication required for all media endpoints
3. **Storage Security**
- Store outside web root
- Optional encryption for sensitive data
- Secure file deletion (overwrite before unlink)
---
## References
- [Sharp](https://sharp.pixelplumbing.com/) - Image processing library
- [EXIF Parser](https://github.com/mifi/exiftool-vendored) - EXIF data extraction
- [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API) - Audio playback
- [React Player](https://github.com/cookpete/react-player) - Audio/video player React component

331
todo/mobile.md Normal file
View File

@@ -0,0 +1,331 @@
# DearDiary Mobile & PWA Support Research
Current state: DearDiary already has a basic `manifest.json` (standalone display, icons) but no service worker or offline support.
---
## 1. PWA Foundation (Service Worker + Manifest)
### Feature: Full PWA Implementation with Offline Support
**Description**: Enable DearDiary to be installable on mobile devices and work offline with full caching strategy.
**Technical Requirements**:
- Add `vite-plugin-pwa` to vite.config.ts
- Configure workbox strategies (cache-first for assets, network-first for API)
- Service worker with precaching for app shell
- Enhanced manifest with `categories`, `orientation`, `launch_handler`
**Implementation Steps**:
1. Install `vite-plugin-pwa` and `workbox-window`
2. Update vite.config.ts with PWA plugin configuration
3. Extend manifest.json with categories, screenshots, maskable icons
4. Create custom service worker for API caching strategies
**Complexity**: Medium (3-5 hours)
**Priority**: HIGH - Foundation for all mobile features
**PWA Plugin Config Example**:
```typescript
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['icon-192.png', 'icon-512.png'],
manifest: {
categories: ['productivity', 'lifestyle'],
orientation: 'portrait',
},
workbox: {
globPatterns: ['**/*.{js,css,html,png,svg}'],
runtimeCaching: [{
urlPattern: /^https:\/\/api\//,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: { maxEntries: 50, maxAgeSeconds: 86400 }
}
}]
}
})
]
})
```
---
## 2. Offline-First Architecture
### Feature: IndexedDB Local Storage with Background Sync
**Description**: Store events locally when offline, sync to server when connection restored.
**Technical Requirements**:
- IndexedDB via `idb` library or `dexie.js`
- Sync queue for pending events
- Background Sync API (`sync` event) for automatic retry
- Conflict resolution for server/client changes
**Implementation Steps**:
1. Create IndexedDB schema: `events`, `journals`, `syncQueue`
2. Build `OfflineStorage` service with add/get/sync methods
3. Modify QuickAddWidget to use offline storage first
4. Implement BackgroundSync handler in service worker
5. Add sync status indicator in UI
**Complexity**: High (6-8 hours)
**Priority**: HIGH - Core offline functionality
**Dexie Schema Example**:
```typescript
import Dexie from 'dexie';
class DearDiaryDB extends Dexie {
events!: Table<Event>;
journals!: Table<Journal>;
syncQueue!: Table<SyncItem>;
constructor() {
super('DearDiaryDB');
this.version(1).stores({
events: '++id, date, type, createdAt, synced',
journals: 'date, syncedAt',
syncQueue: '++id, type, data, createdAt'
});
}
}
```
**Sync Flow**:
1. User creates event → save to IndexedDB → add to syncQueue → show "pending sync" badge
2. Service worker registers 'sync' event → fetch all queued items → POST to API
3. On success → delete from queue → mark event as synced
4. On failure → retry on next sync or when online
---
## 3. Install Prompt & App-Like Experience
### Feature: Custom Install Prompt with Native Feel
**Description**: Detect when PWA is installable, show custom prompt, improve app icon/splash.
**Technical Requirements**:
- `beforeinstallprompt` event listener
- Custom install button in UI (not browser prompt)
- Splash screen configuration via `theme_color` + `background_color`
- Maskable adaptive icons for Android
**Implementation Steps**:
1. Add install prompt handler in App.tsx
2. Create custom "Add to Home Screen" UI component
3. Generate adaptive icon with SVG maskable version
4. Add `<meta name="apple-mobile-web-app-capable">` for iOS
5. Add iOS-specific icons (apple-touch-icon)
**Complexity**: Low (2-3 hours)
**Priority**: MEDIUM
**iOS Support**:
```html
<link rel="apple-touch-icon" href="/icon-192.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
```
---
## 4. Mobile-Responsive UI Improvements
### Feature: Mobile-Optimized Layout & Touch Targets
**Description**: Improve mobile usability with larger touch targets, bottom navigation, responsive grid.
**Technical Requirements**:
- Minimum 44px touch targets
- Bottom tab bar for mobile (Dashboard, Today, History, Settings)
- Responsive padding and font sizes
- Swipe gestures for navigation
**Implementation Steps**:
1. Create responsive layout component with mobile nav
2. Adjust QuickAddWidget for bottom-sheet on mobile
3. Add touch-action CSS for swipe handling
4. Media queries for mobile-specific styling
5. Test on actual device or responsive mode
**Complexity**: Medium (4-5 hours)
**Priority**: HIGH - User-facing
**Mobile Navigation Example**:
```tsx
// BottomNav.tsx
<nav className="fixed bottom-0 left-0 right-0 bg-slate-900 border-t border-slate-800 flex justify-around py-3">
<NavLink to="/" icon={Home} />
<NavLink to="/today" icon={PlusCircle} />
<NavLink to="/history" icon={Clock} />
<NavLink to="/settings" icon={Cog} />
</nav>
```
---
## 5. Push Notifications
### Feature: Event Reminder Notifications
**Description**: Send push notifications to remind users to log events throughout the day.
**Technical Requirements**:
- Web Push API (VAPID keys)
- Service worker push event handler
- Notification permission request flow
- Configurable reminder schedule in settings
**Implementation Steps**:
1. Generate VAPID key pair
2. Add Web Push subscription endpoint to backend
3. Create notification service in frontend
4. Add "Enable Reminders" toggle in Settings
5. Schedule notifications via service worker
**Complexity**: High (5-7 hours)
**Priority**: MEDIUM
**Note**: Web Push requires VAPID keys and HTTPS. For iOS Safari, use APNs integration (requires native wrapper or PWABuilder).
---
## 6. Touch-Friendly Interactions
### Feature: Gesture-Based Navigation & Input
**Description**: Add swipe gestures, long-press actions, haptic feedback for mobile.
**Technical Requirements**:
- Touch event handlers for swipe detection
- Haptic feedback API (`navigator.vibrate`)
- Pull-to-refresh on event lists
- Swipe-to-delete on events
**Implementation Steps**:
1. Add gesture detection hook (useSwipe)
2. Implement pull-to-refresh on Today page
3. Add haptic feedback on button presses
4. Implement swipe actions on event list items
**Complexity**: Medium (3-4 hours)
**Priority**: MEDIUM
**Implementation Pattern**:
```typescript
function useSwipe(onSwipeLeft: () => void, onSwipeRight: () => void) {
const touchStart = useRef({ x: 0, y: 0 });
const onTouchStart = (e: TouchEvent) => {
touchStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
};
const onTouchEnd = (e: TouchEvent) => {
const deltaX = e.changedTouches[0].clientX - touchStart.current.x;
if (Math.abs(deltaX) > 50) {
deltaX > 0 ? onSwipeRight() : onSwipeLeft();
}
};
return { onTouchStart, onTouchEnd };
}
```
---
## 7. Offline Event Capture (Sync Later)
### Feature: Queue Events When Offline, Sync When Online
**Description**: Full implementation of offline-first event creation.
**Technical Requirements**:
- IndexedDB event storage
- Visual queue indicator (X events pending)
- Force sync button
- Automatic sync on reconnection
**Implementation Steps**:
1. Build offline event queue system (see section 2)
2. Add sync status banner to Today page
3. Add manual "Sync Now" button
4. Show toast on successful sync
5. Handle partial sync failures gracefully
**Complexity**: High (already covered in section 2)
**Priority**: HIGH
---
## 8. Background Sync for API Calls
### Feature: Automatic Background Synchronization
**Description**: Use Background Sync API to automatically sync pending operations when online.
**Technical Requirements**:
- Service worker `sync` event handler
- Tag pending requests with sync ID
- Retry with exponential backoff
- Notify user of sync completion
**Implementation Steps**:
1. Register background sync in service worker
2. Trigger sync when events are added offline
3. Implement retry logic with backoff
4. Send desktop notification on sync complete
**Complexity**: Medium (3-4 hours)
**Priority**: MEDIUM
---
## Implementation Roadmap
### Phase 1: Foundation (HIGH)
- [ ] PWA manifest & service worker setup
- [ ] Basic offline caching for app shell
- [ ] IndexedDB local storage
### Phase 2: Offline Sync (HIGH)
- [ ] Offline event capture
- [ ] Background sync
- [ ] Sync status UI
### Phase 3: Mobile UX (MEDIUM)
- [ ] Responsive layout with bottom nav
- [ ] Touch-friendly interactions
- [ ] Install prompt
### Phase 4: Advanced (LOW)
- [ ] Push notifications
- [ ] Advanced gestures
- [ ] iOS PWA optimization
---
## Summary
| Feature | Complexity | Priority |
|---------|------------|----------|
| PWA Setup + Service Worker | Medium | HIGH |
| IndexedDB + Offline Storage | High | HIGH |
| Mobile UI + Bottom Nav | Medium | HIGH |
| Install Prompt | Low | MEDIUM |
| Background Sync | Medium | MEDIUM |
| Push Notifications | High | MEDIUM |
| Touch Gestures | Medium | MEDIUM |
**Recommended First Steps**:
1. Add `vite-plugin-pwa` and configure basic caching
2. Implement IndexedDB with Dexie for event storage
3. Build offline-first QuickAddWidget
4. Add sync queue UI indicator

316
todo/search.md Normal file
View File

@@ -0,0 +1,316 @@
# Full-Text Search Feature Research
## Overview
This document outlines research and implementation ideas for adding full-text search to DearDiary, an AI-powered daily journaling app.
---
## 1. Feature Description
### Core Functionality
- Search across diary content (journal titles and bodies)
- Search across raw event content
- Filter by date range
- Sort by relevance (BM25) or date
- Real-time instant search as user types
### User Stories
1. **Quick Recall**: User types "meeting with Sarah" → sees matching diary entries and events
2. **Date-based Search**: User searches "vacation" → filters to summer 2024 entries
3. **Deep Search**: User searches for specific phrase → finds exact match in event content
---
## 2. Technical Approach
### Option A: SQLite FTS5 (Recommended for v1)
**Pros:**
- Zero external dependencies
- Built into SQLite (already in use)
- BM25 ranking built-in
- Real-time indexing (update on insert)
- Lowest implementation complexity
- No additional infrastructure
**Cons:**
- No typo tolerance (unless using trigram/token helpers)
- Limited to SQLite (migration cost if switching DB)
- Single-node only (fine for self-hosted)
**Implementation:**
```sql
-- FTS5 virtual table for journals
CREATE VIRTUAL TABLE journal_fts USING fts5(
title,
content,
content_rowid='rowid',
tokenize='porter unicode61'
);
-- FTS5 virtual table for events
CREATE VIRTUAL TABLE event_fts USING fts5(
content,
type,
content_rowid='rowid',
tokenize='porter unicode61'
);
-- Triggers to keep FTS in sync
CREATE TRIGGER journal_ai AFTER INSERT ON Journal BEGIN
INSERT INTO journal_fts(rowid, title, content)
VALUES (NEW.rowid, NEW.title, NEW.content);
END;
```
**Performance:** FTS5 handles 100k+ rows easily on SQLite. For typical personal journaling (10 years = ~3650 entries, ~10k events), performance will be sub-100ms.
### Option B: External Search (Typesense/Meilisearch)
**Pros:**
- Typo tolerance (fuzzy search)
- Better ranking algorithms
- Scalable to millions of records
- REST API, language-agnostic
**Cons:**
- Additional infrastructure (Docker service)
- Sync complexity (real-time indexing)
- More complex setup for self-hosted users
- Resource overhead (CPU/RAM for search service)
**Recommendation:** Defer to v2. External search only becomes necessary when:
- User wants fuzzy/typo-tolerant search
- Dataset exceeds 500k+ records
- Multi-language support needed
---
## 3. Indexing Strategy
### Fields to Index
| Table | Field | Indexed | Reason |
|-------|-------|---------|--------|
| Journal | title | Yes | Primary search target |
| Journal | content | Yes | Full diary text |
| Journal | date | Yes | Filtering |
| Event | content | Yes | Raw event text |
| Event | type | Yes | Filter by event type |
| Event | date | Yes | Date filtering |
### What NOT to Index
- `Event.metadata` - JSON blob, search within JSON handled separately if needed
- `Event.mediaPath` - File paths, not searchable content
- `User` fields - Not needed for user-facing search
### Sync Strategy
1. **On Insert/Update**: Write to main table, then update FTS via trigger
2. **On Delete**: FTS trigger removes from index
3. **Reindex**: Manual endpoint for recovery/debugging
---
## 4. Database Schema Changes
### Prisma Schema Addition
```prisma
// Optional: Search history for "recent searches" feature
model SearchHistory {
id String @id @default(uuid())
userId String
query String
createdAt DateTime @default(now())
@@index([userId, createdAt])
}
```
Note: FTS5 tables are virtual and managed via raw SQL, not Prisma models. We'll use `prisma.$executeRaw` for FTS operations.
### Migration Steps
1. Create FTS5 virtual tables (raw SQL)
2. Create triggers for auto-sync
3. Backfill existing data
4. Add SearchHistory model (optional)
---
## 5. API Changes
### New Endpoints
```
GET /api/v1/search?q=<query>&type=diary|event|all&from=2024-01-01&to=2024-12-31&sort=relevance|date&page=1&limit=20
```
**Response:**
```typescript
interface SearchResult {
type: 'diary' | 'event';
id: string;
date: string;
title?: string; // For diaries
content: string; // Truncated/preview
highlight?: string; // Matched text with <mark> tags
score: number; // BM25 relevance
}
interface SearchResponse {
data: {
results: SearchResult[];
total: number;
page: number;
limit: number;
} | null;
error: null;
}
```
### Optional Endpoints
```
GET /api/v1/search/history // Recent searches
DELETE /api/v1/search/history // Clear history
POST /api/v1/search/reindex // Force reindex (admin)
```
---
## 6. UI/UX Considerations
### Search Modal
- **Trigger**: Cmd/Ctrl+K keyboard shortcut (standard pattern)
- **Position**: Centered modal with overlay
- **Features**:
- Instant search as you type (debounced 150ms)
- Filter tabs: All | Diaries | Events
- Date range picker (quick presets: Today, This Week, This Month, This Year)
- Results show date, type, preview with highlighted matches
### Sidebar (Alternative)
- Persistent search box in navigation
- Results in scrollable list below
- Less intrusive, always visible
### Result Cards
```
┌─────────────────────────────────────────┐
│ 📅 2024-03-15 [Diary] │
│ Meeting with Sarah about project... │
│ ─────────────────────────────────────── │
│ ...discussed timeline and <mark>budget</mark>... │
└─────────────────────────────────────────┘
```
### UX Details
- **Empty state**: Show recent diaries/events when no query
- **No results**: Friendly message + suggestions
- **Loading**: Subtle spinner (search should be <100ms)
- **Keyboard**: Arrow keys to navigate results, Enter to open
### Mobile Considerations
- Tap search icon in header → full-screen search
- Larger touch targets for filters
---
## 7. Performance Considerations
### Query Performance
- FTS5 BM25 queries: ~50-100ms for 10k records
- Add LIMIT to prevent unbounded results
- Use connection pooling if many concurrent searches
### Write Performance
- Triggers add ~5-10ms per insert/update
- Batch backfill for existing data (1000 rows/batch)
### Caching Strategy
- Cache recent searches (Redis optional, or in-memory)
- Cache FTS index in memory (SQLite mmap)
### Scaling Thresholds
- < 10k entries: No optimization needed
- 10k-100k: Consider FTS5 optimization (tokenizer, prefix search)
- > 100k: Consider external search
---
## 8. Implementation Complexity
### Complexity Assessment: **MEDIUM**
| Component | Complexity | Notes |
|-----------|------------|-------|
| FTS5 setup | Low | Raw SQL, one-time |
| Triggers | Low | Auto-sync, minimal code |
| API endpoint | Low | Standard CRUD pattern |
| Frontend modal | Medium | Keyboard shortcuts, state |
| Filters/Date | Medium | Multiple filter combinations |
| Backfill | Low | One-time script |
### Phased Implementation
**Phase 1 (MVP - 2-3 days)**
- FTS5 tables + triggers
- Basic search API
- Simple modal UI with text input
**Phase 2 (Enhancements - 1-2 days)**
- Date filtering
- Type filtering (diary/event)
- Result highlighting
**Phase 3 (Polish - 1 day)**
- Search history
- Keyboard navigation
- Mobile responsive
---
## 9. Priority Recommendation
### Recommended Priority: **MEDIUM-HIGH**
**Rationale:**
- Search is a core journaling feature (user wants to find past entries)
- Competitor apps (Day One, Journey) have robust search
- Implementation complexity is manageable (medium)
- Zero external dependencies (SQLite FTS5)
### Factors Supporting High Priority
1. **User Value**: High - helps users find meaningful memories
2. **Implementation Cost**: Medium - achievable in 1 week
3. **Dependency Risk**: Low - no external services needed
4. **Future-proofing**: FTS5 is mature, well-supported
### Factors Against Very High Priority
- Current core features (capture, generate) are stable
- Small dataset users may not notice missing search
- Can be added post-MVP without breaking changes
---
## 10. Open Questions / Further Research
1. **Typo tolerance**: Is exact match sufficient, or do users expect fuzzy search?
2. **Search ranking**: Should recent results be boosted higher?
3. **Multi-language**: Support languages other than English (tokenizer considerations)
4. **Export/Import**: Should search index be rebuilt on data import?
5. **Shared access**: Multi-user search (future consideration)
---
## 11. Summary
| Aspect | Recommendation |
|--------|----------------|
| **Search Engine** | SQLite FTS5 (built-in) |
| **UI Pattern** | Cmd/Ctrl+K modal |
| **Features** | Instant search, date filter, type filter, relevance sort |
| **Complexity** | Medium (3-5 days) |
| **Priority** | Medium-High |
| **Schema Changes** | FTS5 via raw SQL + optional SearchHistory model |
| **API Changes** | New `/search` endpoint with query params |

539
todo/stats.md Normal file
View File

@@ -0,0 +1,539 @@
# Statistics Feature Specification
## Overview
Add a comprehensive statistics dashboard to track journaling habits and provide insights. All analytics are computed locally from user data - no external services required.
---
## 1. Streak Tracking
### Feature Description
Track consecutive days of journaling activity to motivate users to maintain their habit.
### Metrics to Track
- **Current Streak**: Consecutive days with at least one event
- **Longest Streak**: All-time record of consecutive days
- **Streak Start Date**: When current streak began
- **Days Until Milestone**: Days until 7/14/30/60/90 day milestones
- **Streak Risk**: Days with no events that would break streak
### Visualization Approaches
- Flame icon with day count (🔥 12 days)
- Progress bar to next milestone
- Calendar mini-view showing streak days
- Warning indicator when streak at risk
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ 🔥 12 Day Streak 🏆 45 days │
│ ████████░░░░░░░░ 4 days to 16 │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Current streak calculation
const events = await prisma.event.findMany({
where: { userId },
select: { date: true },
orderBy: { date: 'desc' }
});
// Group by date, find consecutive days
```
### Implementation Complexity
- **Backend**: Medium - requires date range queries with grouping
- **Frontend**: Low - simple counter display
- **Priority**: HIGH - strong motivation driver
---
## 2. Word Count Statistics
### Feature Description
Track total words written in events and journal entries over time.
### Metrics to Track
- **Total Words**: All-time word count
- **Daily Average**: Average words per day (active days)
- **Today/This Week/This Month**: Rolling word counts
- **Longest Entry**: Day with most words
- **Word Count Distribution**: Histogram by day
- **Writing Sessions**: Days with 100+/500+/1000+ words
### Visualization Approaches
- Line chart showing word count over time (30/90/365 days)
- Comparison bar chart: this week vs last week
- Mini sparkline showing recent trend
- Percentile badges (e.g., "Top 10% writers")
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ 📝 12,450 words total │
│ Avg: 156/day • This week: 1,234 │
│ ┌────────────────────────────────┐ │
│ │ ▂▃▅▇▅▃▂▄▅▇█▄▃▂▅▇ │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Word count aggregation
await prisma.$queryRaw`
SELECT date, SUM(LENGTH(content) - LENGTH(REPLACE(content, ' ', '')) + 1) as word_count
FROM Event
WHERE userId = ${userId}
GROUP BY date
`;
```
### Implementation Complexity
- **Backend**: Low - simple aggregation queries
- **Frontend**: Low - chart library needed
- **Priority**: MEDIUM
---
## 3. Entry Frequency Heatmap
### Feature Description
GitHub-style contribution heatmap showing activity intensity by day.
### Metrics to Track
- Days with events (boolean per day)
- Event count per day (intensity levels)
- Active weeks/months count
- Most productive day of week
- Most productive month
### Visualization Approaches
- GitHub-style grid (52 weeks × 7 days)
- Color scale: empty → light → dark (slate-800 to purple-600)
- Tooltip on hover showing date + count
- Day-of-week labels on left
- Month labels on top
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ Activity Heatmap │
│ Jan Feb Mar Apr May Jun │
│ Mon ░░░████▓▓░░░░░░░ │
│ Tue ░▓▓████▓▓░░░░░░░ │
│ Wed ░░░████▓▓░░░░░░░ │
│ Thu ░░░░░░▓▓░░░░░░░░ │
│ Fri ░░░░░░░░░░░░░░░░░ │
│ │
│ Less ░▒▓█ More │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Heatmap data
await prisma.event.groupBy({
by: ['date'],
where: { userId },
_count: { id }
});
```
### Implementation Complexity
- **Backend**: Low - groupBy query
- **Frontend**: Medium - requires heatmap component (use react-heatmap-grid or custom SVG)
- **Priority**: HIGH - visual engagement
---
## 4. Event Type Distribution
### Feature Description
Breakdown of events by type (text, photo, voice, health, etc.).
### Metrics to Track
- Count per event type
- Percentage distribution
- Type trends over time (increasing/decreasing)
- Media attachments count
### Visualization Approaches
- Donut/pie chart with legend
- Horizontal bar chart (easier to read)
- Stacked area chart over time
- Type badges with counts
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ Event Types │
│ ┌──────────┐ │
│ │ text │ ████████ 45% │
│ │ photo │ █████░░░ 28% │
│ │ voice │ ███░░░░░ 15% │
│ │ health │ █░░░░░░░ 8% │
│ │ event │ ██░░░░░░ 4% │
│ └──────────┘ │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Type distribution
await prisma.event.groupBy({
by: ['type'],
where: { userId },
_count: { id }
});
```
### Implementation Complexity
- **Backend**: Low - simple groupBy
- **Frontend**: Low - chart library
- **Priority**: MEDIUM - nice-to-have insight
---
## 5. Time-of-Day Patterns
### Feature Description
Analyze when users typically log events (morning person vs night owl).
### Metrics to Track
- Events by hour of day (0-23)
- Events by time block (Morning 5-11, Afternoon 12-17, Evening 18-22, Night 23-4)
- Most active hour
- Average time of first event
- Average time of last event
### Visualization Approaches
- 24-hour radial/bar chart
- Time block pie chart
- Timeline showing first-last event range
- "Your prime time" indicator
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ Writing Time Patterns │
│ ▔▔▔▔▔▔▔ │
│ ▔▔ ▔▔▔ │
│ ▔▔ ● ▔▔ │
│ ▔ ▔▔ ▔ │
│ Morning Afternoon Evening │
│ (6-12) (12-6) (6-12) │
│ │
│ ☀️ Peak: 9:00 AM │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Hour extraction from createdAt
await prisma.$queryRaw`
SELECT strftime('%H', createdAt) as hour, COUNT(*) as count
FROM Event
WHERE userId = ${userId}
GROUP BY hour
`;
```
### Implementation Complexity
- **Backend**: Low - SQL date functions
- **Frontend**: Medium - radial chart
- **Priority**: LOW - interesting but not core
---
## 6. Monthly/Yearly Summaries
### Feature Description
Aggregate statistics for specific time periods with comparison to previous periods.
### Metrics to Track
- Monthly: events, journals, words, active days
- Yearly: same metrics
- Month-over-month change (%)
- Year-over-year change (%)
- Best month ever
- Days with 100% journal generation
### Visualization Approaches
- Monthly bar chart comparison
- Year-in-review cards
- Progress ring showing yearly goals
- "This time last year" comparison
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ 2024 Year in Review │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 342 │ │ 45 │ │ 15,600 │ │
│ │Events │ │ Journals│ │ Words │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ Best month: November (52 events) │
│ +12% vs 2023 │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Monthly aggregation
await prisma.event.groupBy({
by: ['date'],
where: {
userId,
date: { gte: '2024-01-01', lte: '2024-12-31' }
},
_count: { id }
});
```
### Implementation Complexity
- **Backend**: Low - grouped queries with date filters
- **Frontend**: Low-Medium - cards and charts
- **Priority**: MEDIUM
---
## 7. Progress & Motivation Features
### Feature Description
Gamification elements to encourage consistent journaling.
### Metrics & Features
- **Achievement Badges**:
- "First Entry" (1 event)
- "Week Warrior" (7 day streak)
- "Month Master" (30 day streak)
- "Century" (100 events)
- "Word Smith" (10,000 words)
- "Photojournalist" (50 photos)
- "Consistent" (journal every day for a month)
- **Goals**: Set daily/weekly targets
- **Streak Protection**: 1 "freeze" per month to preserve streak
- **Weekly Report**: Auto-generated summary every Sunday
- **Milestone Celebrations**: Confetti at 7/14/30/60/90 days
### Visualization Approaches
- Badge grid (earned + locked)
- Progress rings for goals
- Streak flame animation
- Weekly summary card
### Dashboard Layout
```
┌─────────────────────────────────────┐
│ Achievements │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │🔥7 │ │🌟30│ │📝1K│ │📸10│ │
│ └────┘ └────┘ └────┘ └────┘ │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │🔒 │ │🔒 │ │🔒 │ (locked) │
│ └────┘ └────┘ └────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Daily Goal: 5 events │
│ ████████░░░░░░░░░ 3/5 │
└─────────────────────────────────────┘
```
### Database Queries
```typescript
// Achievement checks - multiple queries
const eventCount = await prisma.event.count({ where: { userId } });
const streak = await calculateStreak(userId);
```
### Implementation Complexity
- **Backend**: Medium - multiple queries, achievement logic
- **Frontend**: Medium - badge components, progress rings
- **Priority**: MEDIUM - strong engagement driver
---
## 8. Privacy-Preserving Analytics
### Feature Description
Ensure all statistics computation keeps data on-device.
### Approach
- **All aggregations in SQLite**: Use Prisma raw queries on server
- **No external analytics services**: No GA, Mixpanel, etc.
- **Opt-in sharing**: Optional anonymous stats (streak length, event count) for community features
- **Local-first**: Dashboard computes from local API responses
- **Data export**: Users can export all their data
### Implementation
```typescript
// All stats computed server-side via API
// Frontend receives aggregated numbers, not raw data
GET /api/v1/stats/streak
GET /api/v1/stats/words
GET /api/v1/stats/heatmap
```
### Privacy Considerations
- No tracking cookies
- No cross-user analytics
- API key = user identity (no additional tracking)
- Full data deletion on user request
---
## API Changes
### New Endpoints
```typescript
// Statistics aggregation
GET /api/v1/stats/streak { currentStreak, longestStreak, streakStart }
GET /api/v1/stats/words { total, dailyAvg, thisWeek, thisMonth, trend }
GET /api/v1/stats/heatmap [{ date, count }]
GET /api/v1/stats/types [{ type, count, percentage }]
GET /api/v1/stats/time-of-day [{ hour, count }]
GET /api/v1/stats/summary { monthly: [], yearly: [] }
GET /api/v1/stats/achievements { earned: [], available: [], progress: {} }
GET /api/v1/stats/month/:year { events, journals, words, activeDays }
```
### Response Types
```typescript
interface StreakStats {
currentStreak: number;
longestStreak: number;
streakStartDate: string;
daysToMilestone: number;
}
interface WordStats {
total: number;
dailyAverage: number;
thisWeek: number;
thisMonth: number;
trend: 'up' | 'down' | 'stable';
longestEntry: { date: string; count: number };
}
interface HeatmapData {
date: string;
count: number;
level: 0 | 1 | 2 | 3 | 4;
}
interface Achievement {
id: string;
name: string;
description: string;
icon: string;
earned: boolean;
earnedAt?: string;
progress?: number;
target: number;
}
```
---
## Database Schema Additions
### Optional: Cached Stats Table
For expensive queries, cache results:
```prisma
model UserStats {
userId String @id
currentStreak Int @default(0)
longestStreak Int @default(0)
totalWords Int @default(0)
totalEvents Int @default(0)
lastUpdated DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
**Update Strategy**: Recalculate on event create/delete or daily cron.
---
## Frontend Implementation
### New Page: /stats
```tsx
// /frontend/src/pages/Stats.tsx
import { useState, useEffect } from 'react';
import { api } from '../lib/api';
export default function Stats() {
const [streak, setStreak] = useState<StreakStats | null>(null);
const [words, setWords] = useState<WordStats | null>(null);
const [heatmap, setHeatmap] = useState<HeatmapData[]>([]);
// ...
}
```
### Chart Library Recommendation
- **Recharts**: Good for line/bar charts (used with React commonly)
- **react-heatmap-grid**: For activity heatmap
- **react-circular-progressbar**: For goal progress rings
- Build custom SVG for simple visualizations
---
## Implementation Roadmap
### Phase 1: Core Stats (Week 1)
1. API endpoints for streak, word count, heatmap
2. Basic stats page with counters
3. Heatmap visualization
### Phase 2: Visualizations (Week 2)
1. Word count trend chart
2. Event type distribution chart
3. Time-of-day analysis
### Phase 3: Gamification (Week 3)
1. Achievement system
2. Daily goals
3. Streak UI improvements
### Phase 4: Summaries (Week 4)
1. Monthly/yearly summaries
2. "This time last year" comparison
3. Weekly report
---
## Priority Recommendations
| Feature | Priority | Impact | Effort |
|---------|----------|--------|--------|
| Streak Tracking | HIGH | High | Low |
| Heatmap | HIGH | High | Medium |
| Word Count | MEDIUM | Medium | Low |
| Event Types | MEDIUM | Low | Low |
| Time Patterns | LOW | Low | Medium |
| Monthly/Yearly | MEDIUM | Medium | Low |
| Achievements | MEDIUM | High | Medium |
| Goals | MEDIUM | Medium | Low |
---
## Summary
Statistics feature transforms DearDiary from a passive journal into an active self-improvement tool. Key priorities:
1. **Streak tracking** - strongest motivation driver
2. **Heatmap** - visual engagement, GitHub-style
3. **Achievements** - gamification layer
4. **Word count** - progress visualization
All data stays local - no privacy concerns. Implementation can be phased with clear value at each step.

317
todo/tags.md Normal file
View File

@@ -0,0 +1,317 @@
# Tags Feature Specification
## Overview
Add a tagging system to DearDiary allowing users to categorize events with customizable tags, enabling powerful filtering and organization of their journaling data.
---
## 1. Feature Description
### Core Functionality
- **Tag Creation**: Users can create custom tags with name, color, and optional icon
- **Multi-tag Support**: Events can have multiple tags assigned
- **Suggested Tags**: Pre-configured tags for common categories (work, health, personal, travel, family, finance, hobbies, social)
- **Tag Filtering**: Filter events/days by one or multiple tags on history page
- **Tag Search**: Search events by tag name
- **AI Auto-tagging**: AI automatically suggests tags when generating journals
- **Tag Management**: CRUD operations for tags via settings page
- **Tag Cloud**: Visual representation of tag usage frequency on dashboard
### User Flow
1. User creates event → Tag selector appears (autocomplete + suggestions)
2. User assigns tags → Tags stored with event
3. User visits history → Filter sidebar shows tag filters
4. User clicks filter → Only matching days/events shown
5. User generates diary → AI suggests tags based on content
---
## 2. Database Schema Changes
```prisma
model Tag {
id String @id @default(uuid())
userId String
name String
color String @default("#8b5cf6") // Default purple
icon String? // emoji or icon name
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
eventTags EventTag[]
@@unique([userId, name])
@@index([userId])
}
model EventTag {
id String @id @default(uuid())
eventId String
tagId String
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@unique([eventId, tagId])
@@index([eventId])
@@index([tagId])
}
```
**Event Model Update** (add relation):
```prisma
model Event {
// ... existing fields
tags EventTag[]
}
```
### API Response Updates
- `GET /days` → Add `tags: string[]` to each day (aggregated from events)
- `GET /days/:date` → Add `tags: string[]` to each event
- `GET /events` → New endpoint with tag filtering support
- `POST /events` → Accept `tagIds: string[]`
- `PUT /events/:id` → Accept `tagIds: string[]` for updating tags
---
## 3. UI/UX for Tagging
### Event Input Component
- **Tag Input Field**: Combobox with autocomplete
- **Suggested Tags**: Show 4-6 most-used tags as clickable chips
- **Create New Tag**: Type new name + choose color in dropdown
- **Display**: Show selected tags as colored chips below input
```tsx
// UI Mock - Event Input
<div className="tag-input-container">
<input
placeholder="Add tag..."
list="tag-suggestions"
onChange={handleTagInput}
/>
<datalist id="tag-suggestions">
{userTags.map(tag => (
<option key={tag.id} value={tag.name} />
))}
</datalist>
<div className="selected-tags">
{selectedTags.map(tag => (
<span
key={tag.id}
className="tag-chip"
style={{ backgroundColor: tag.color }}
>
{tag.name} ×
</span>
))}
</div>
</div>
```
### Tag Selector Colors
Pre-defined palette:
- Purple: `#8b5cf6` (default)
- Blue: `#3b82f6`
- Green: `#22c55e`
- Yellow: `#eab308`
- Orange: `#f97316`
- Red: `#ef4444`
- Pink: `#ec4899`
- Cyan: `#06b6d4`
- Slate: `#64748b`
### Settings Page - Tag Management
- List all tags with event count
- Create tag: Name, color picker, icon (optional)
- Edit tag: Modify any field
- Delete tag: Confirm dialog, option to reassign events
- Bulk operations: Merge duplicate tags
### History Page - Filter Sidebar
- Collapsible sidebar on left
- Checkbox list of all tags with event counts
- "Any match" vs "All match" toggle
- Clear filters button
- Active filters shown as removable chips
### Tag Cloud (Dashboard)
- Word cloud layout using tag size based on frequency
- Click tag → Navigate to filtered history
- Top 20 most-used tags shown
- Animation on hover
---
## 4. Filter/Search Integration
### History Page Filters
```
GET /days?tags=work,health&tagMatch=any
```
| Parameter | Type | Description |
|-----------|------|-------------|
| tags | string | Comma-separated tag names |
| tagMatch | 'any' \| 'all' | Match any or all tags |
### Search Integration
- Global search input in header
- Search by tag name: `tag:work` or `#work`
- Search by multiple tags: `tag:work+health`
### API Endpoints
```
GET /tags // List user's tags with counts
POST /tags // Create tag
PUT /tags/:id // Update tag
DELETE /tags/:id // Delete tag
GET /days?tags=work // Filter days by tags
GET /events?tags=work // Filter events by tags
POST /events/:id/tags // Add tags to event
DELETE /events/:id/tags/:tagId // Remove tag from event
```
---
## 5. AI Auto-Tagging Prompt Ideas
### Option A: Tag on Generation
When generating diary, AI suggests relevant tags:
```system
You are a diary analyzer. After generating the diary, suggest 2-5
tags that best describe the day's events.
Available tags (use these if relevant): work, health, personal,
travel, family, finance, hobbies, social, exercise, meals, sleep,
mood, productivity, creativity, relationships
Respond with JSON:
{"tags": ["work", "health"]}
```
### Option B: Standalone Auto-Tag
Separate endpoint for tagging events:
```system
Analyze the following journal entries and suggest appropriate tags.
Choose from: work, health, personal, travel, family, finance,
hobbies, social, exercise, meals, sleep, mood, productivity,
creativity, relationships
If none match, return empty array. Be conservative - only tag
if clearly relevant.
Entries:
{events.map(e => `- ${e.content}`).join('\n')}
Respond with JSON:
{"tags": ["tag1", "tag2"]}
```
### Implementation
- Add `autoTag: boolean` to Settings
- On journal generation, call AI with events → get suggested tags
- Show suggested tags as "AI suggests: [tag]" with one-click accept
- Optionally auto-apply (configurable)
---
## 6. Implementation Complexity
### Backend (Medium)
- [ ] Add Tag and EventTag models to Prisma schema
- [ ] Create migration
- [ ] Add tag CRUD endpoints
- [ ] Update event creation/update to accept tagIds
- [ ] Add tag filtering to /days and /events endpoints
- [ ] Add tag aggregation queries
- [ ] AI auto-tagging service (new endpoint)
### Frontend (Medium-High)
- [ ] Tag selector component with autocomplete
- [ ] Tag chip display component
- [ ] Settings page tag management
- [ ] History page filter sidebar
- [ ] Tag cloud component for dashboard
- [ ] Search integration with tag filtering
### Estimated Effort
| Component | Complexity | Notes |
|-----------|------------|-------|
| DB Schema | Easy | Simple many-to-many |
| Tag CRUD API | Easy | Standard CRUD |
| Event tagging | Medium | Update create/update flows |
| Tag filtering | Medium | Query construction |
| AI auto-tag | Medium | New service + UI |
| Frontend components | Medium | Multiple new components |
| Filter UI | Medium | Sidebar + state |
**Total**: ~3-4 days for full implementation
---
## 7. Priority Recommendation
### Recommended Order
1. **Phase 1: Core Infrastructure** (Priority: HIGH)
- Database schema + migration
- Tag CRUD API
- Event tagging on create/update
2. **Phase 2: Basic UI** (Priority: HIGH)
- Tag selector in event input
- Tag display on events
- Settings page tag management
3. **Phase 3: Filtering** (Priority: MEDIUM)
- History page filter sidebar
- Tag filter API integration
- Tag search support
4. **Phase 4: Advanced Features** (Priority: LOW)
- AI auto-tagging
- Tag cloud visualization
- Bulk tag operations
### MVP Definition
- Users can create/edit/delete tags
- Tags can be assigned to events
- History page shows tag filter sidebar
- Filtering works correctly
### Out of Scope for V1
- AI auto-tagging (defer to future)
- Tag cloud (defer to future)
- Bulk operations
- Tag merging
---
## 8. Additional Considerations
### Tag Inheritance
- Should tags be inherited when generating journal?
- Decision: Tags stay with individual events only
### Tag Limits
- Max tags per user: 50 (configurable)
- Max tags per event: 10
- Tag name max length: 30 characters
### Data Migration
- No migration needed for new feature
- Future: Migrate from metadata-based tags if any exist
### Performance
- Index on `(userId, name)` for tag lookups
- Index on `(eventId, tagId)` for junction table
- Cache tag counts, invalidate on changes
### Privacy
- Tags are user-specific (not shared)
- No public tag feeds or global tag suggestions