This commit is contained in:
92
docs/addiction-features/01-memory-streak.md
Normal file
92
docs/addiction-features/01-memory-streak.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# 01 β Memory Streak Counter
|
||||
|
||||
**Author:** CC (Block B design pass)
|
||||
**Date:** 2026-05-01
|
||||
**Status:** AWAITING_RATIFICATION
|
||||
**Estimate:** ~180 LOC + ~3-4h wall-clock
|
||||
**Touches:** apps/web (3 files), packages/server (1 route), packages/core (1 store)
|
||||
|
||||
---
|
||||
|
||||
## User story
|
||||
|
||||
As a user, when I save a memory or have a meaningful chat session, I want to see a visible streak counter (π₯ 5 days in a row) somewhere I'll glance at often, so I'm reinforced to come back tomorrow.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
1. Streak chip renders in the desktop StatusBar (bottom-right cluster), wrapped in a HintTooltip explaining the rules.
|
||||
2. Streak count = consecutive days with at least one memory frame committed (any source: chat, harvest, manual).
|
||||
3. Streak resets if 24h pass with zero new frames AND zero qualifying activity (configurable). Default: pure 24h gap = reset.
|
||||
4. Optional "weekend skip" toggle in Settings β Behavior β "Streaks count weekdays only" (default OFF; international users decide).
|
||||
5. Visible cold-start path: streak=0 day 1 hides chip; streak=1 shows "π₯ 1 day"; streak=N shows "π₯ N days" with subtle pulse animation when count increments live.
|
||||
6. Live increment fires when a new frame lands today and `streak.lastBumpAt` was a previous day β no full-day debounce, but client throttles repeat re-renders to once/min.
|
||||
|
||||
## UI sketch
|
||||
|
||||
```
|
||||
StatusBar bottom-right (existing cluster):
|
||||
[memory icon] 12 mem [chat icon] 5 sessions π₯ 5 days [time]
|
||||
|
||||
Hover tooltip:
|
||||
Memory streak: 5 days
|
||||
Save at least one memory each day to keep it going.
|
||||
(Settings β Behavior to skip weekends.)
|
||||
```
|
||||
|
||||
Day-1 user: chip suppressed entirely (no shame for a 0-streak); appears at streak=1 onwards.
|
||||
|
||||
Streak break: chip flashes amber for 4 hours after reset, copy reads "Streak broken β start fresh today" with action `Got it`.
|
||||
|
||||
## Data model
|
||||
|
||||
New table `streaks` in personal mind:
|
||||
```
|
||||
id INTEGER PRIMARY KEY,
|
||||
streak_kind TEXT NOT NULL CHECK (streak_kind IN ('memory','chat')),
|
||||
current_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_bump_at TEXT NOT NULL, -- ISO date YYYY-MM-DD (no time)
|
||||
longest_count INTEGER NOT NULL DEFAULT 0,
|
||||
weekend_skip INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT NOT NULL
|
||||
```
|
||||
|
||||
One row per `streak_kind` per personal mind. v1 ships only `memory` kind; `chat` reserved.
|
||||
|
||||
Server route: `GET /api/streaks` β `{ memory: {current, longest, lastBumpAt, weekendSkip} }`. `POST /api/streaks/bump` (called by frame-store on every frame insert) β server-side computes current_count by checking lastBumpAt vs today.
|
||||
|
||||
## Implementation notes
|
||||
|
||||
- Bump trigger: hook into `FrameStore.createIFrame()` callsite OR via a SQLite trigger on `INSERT ON memory_frames`. Trigger is cleaner β no orchestrator changes needed.
|
||||
- Reset detection: pure read-time computation. When client fetches `/api/streaks`, server compares lastBumpAt to today; if gap > 1 day (or > 1 weekday with weekendSkip), reset current to 0 before returning.
|
||||
- StatusBar wiring: existing `agentStatus`-style hook β new `useStreaks()` hook polling `/api/streaks` every 2 min + on `waggle:frame-saved` event.
|
||||
|
||||
## Estimate
|
||||
|
||||
- Server route + SQLite migration: ~50 LOC
|
||||
- `useStreaks()` hook + StatusBar render: ~60 LOC
|
||||
- Settings toggle: ~30 LOC
|
||||
- Tests (server bump logic, reset boundary, weekend-skip math): ~40 LOC
|
||||
- **Total ~180 LOC, ~3-4h with verification.**
|
||||
|
||||
## Risks + open questions
|
||||
|
||||
1. **Timezone** β bump uses server's local TZ (`new Date().toISOString().slice(0,10)`). Travelers crossing dates lose/gain a day. Acceptable v1; document.
|
||||
2. **What counts as a frame?** β currently any frame insert. PM may want to exclude `temporary` importance from bumps. Default: count all non-`deprecated` frames. Open question.
|
||||
3. **Streak breakage notification** β silent reset, or a one-time toast "Streak broken β yesterday you missed it"? PM call.
|
||||
4. **Cosmetic** β emoji π₯ may clash with Hive DS aesthetic. Alternative: `bg-amber-500` flame icon from lucide-react. PM call.
|
||||
5. **Migration risk** β adds new SQLite table. Use migration framework. Idempotent CREATE TABLE IF NOT EXISTS.
|
||||
|
||||
## Out of scope (v1)
|
||||
|
||||
- Per-workspace streaks (just personal-mind global v1).
|
||||
- Calendar heatmap (GitHub-style activity grid).
|
||||
- Streak leaderboards across team. (TEAMS tier only later.)
|
||||
- Streak freeze / "streak protector" purchases. (Anti-pattern in Waggle DS β no buyable shortcuts.)
|
||||
|
||||
## PM decisions needed
|
||||
|
||||
- [ ] GO / MODIFY / SKIP
|
||||
- [ ] Frame inclusion rule (all / non-temporary / >threshold importance)
|
||||
- [ ] Weekend-skip default (OFF / ON / detect locale)
|
||||
- [ ] Reset notification (silent / toast / amber flash)
|
||||
- [ ] Emoji vs Lucide icon
|
||||
Reference in New Issue
Block a user