Per-day habit reflections

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.

13 Sep 2026 · Varun

Challenge banners, tiers & archive mode

Big push today on mirror-habit — the challenge system is now complete end-to-end. What shipped Tier engine (src/lib/challengeResults.ts): at expiry, each participant gets a completion tier — conqueror ≥100%, gold ≥75%, silver ≥50%, bronze ≥25%, participant >0% — with XP bonuses and 👑 MVP crowns for top finishers. All date math rewritten as pure UTC string ops after the container’s Asia/Calcutta timezone kept shifting weekly boundaries by a day. Dropped date-fns from the engine entirely. Custom banner artwork: five illustrated WebP banners (1200×298) + crown overlay, ~122KB total after ImageMagick conversion (originals were 9MB PNGs). Wired into profile grid, habit detail, and popups. Click-to-enlarge lightbox on profile banners, matching the existing titles popup pattern. Archive mode on challenge detail pages: once a challenge ends it becomes the first card on the page, check-ins are disabled, heatmap tiles stop being clickable, and the competitive “You’re 100% ahead of the guild average” insight is replaced with a neutral “challenge has ended” note. Celebration popup on the dashboard when you earn a new banner (once per banner via localStorage seen-set). Past Challenges rows now show tier emoji stacks + your personal result. Bugs hit along the way A JSX restructure left an unclosed <div> that cascaded into main has no corresponding closing tag — tsc caught what my eyes missed. Junk 0% rows from mid-dev localStorage churn resurrected themselves through LOAD_STATE; fixed by validating tiers at every ingest point (fetch, migration push, realtime channel, reducer). The user’s July hike check-in was lost to pre-engine localStorage churn — data loss, not a logic bug. Backfill SQL provided. Next: guild-wide leaderboard maybe, or polish on the voting flow.

24 Aug 2026 · Varun