- 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
540 lines
16 KiB
Markdown
540 lines
16 KiB
Markdown
# 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.
|