93 lines
4.5 KiB
Markdown
93 lines
4.5 KiB
Markdown
# 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
|