Shipped per-day reflections for mirror-habit: a note, effort, and mood attached to each habit’s daily cell, plus effort trend charts.

What shipped

  • Reflection editor (CheckInNoteEditor) on every check-in surface — dashboards, habit detail, private habits, and the daily briefing.
  • A note/effort/mood is attached to the (habit_id, user_id, date) cell, not individual entries rows — so counted habits (many rows/day) get exactly one reflection per day.
  • Effort trend charts on habit detail pages, and reflections surfaced in the yearly heatmap.
  • supabase/migrations/20260913_entry_notes.sql: PK-enforced table, RLS (read-all / write-your-own), indexes, and realtime replication alongside entries.

DB design notes

  • effort/mood are CHECK-constrained to 1..5 so the bar charts never get nonsense.
  • PK (habit_id, user_id, date) is what keeps “one reflection per cell” true even when a counted habit logs several entries in a day.
  • Reflections are in the realtime publication like entries, so guild-mate notes stream across devices without a reload.

What got tricky

  • Store churn: every writer path (fetch, migration push, realtime channel) had to validate reflections the same way or stale 0% rows leak through the reducer — the same class of bug the challenge tiers had. Fix: validate at every ingest point.
  • Moving the reflection data through store.tsx meant keeping streak math and counts untouched — the store diff is mostly additive.

A good “small feature, careful data model” push.