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:
539
todo/stats.md
Normal file
539
todo/stats.md
Normal 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.
|
||||
Reference in New Issue
Block a user