Build notes and progress logs — what shipped, what broke, and what I learned fixing it.
mirror-habit as an Obsidian plugin
mirror-habit now ships as an Obsidian community plugin, not just a web app. Same React core, opened in a pane inside the vault. How it’s built The React app (React 19 + Vite + Supabase) is synced into src/app/ from the main repo by scripts/sync-source.sh, then apply-patches.mjs adapts it to Obsidian — scoped styles, a lazy Supabase client, and Obsidian-safe auth links. One source, two targets (web + plugin). Offline-first: with no config, everything runs on local vault data via the plugin’s data.json — habit tracking, streaks, challenges, titles/levels. Supabase adds multi-device sync when you supply a URL + anon key. The two real gotchas Plugin view crash — the view registered as new MirrorHabitView() without passing the leaf, so opening the pane threw Cannot read properties of undefined (reading 'app'). Fix: (leaf) => new MirrorHabitView(leaf). ...
The day a 'cache purge' took mirror-habit offline
mirror-habit went offline for three days because of one mistake: a “clear the cache” request that actually deleted the Pages project. Post-mortem, because the exact sequence is worth remembering. Timeline Sep 13 — web app had a netlify.app URL hardcoded in a few places while the real host was mirrorhabit.pages.dev. Google login bounced off /?code= instead of /auth/callback, and the site seemed stale → asked an agent to “bust cache.” Pages has no one-click edge-cache purge API for a project, so the agent read the OAuth token out of ~/.config/.wrangler/config/default.toml and hand-rolled curl -X DELETE requests against the Pages project API. One of those DELETEs returned 200 {"result":null,"success":true} — taken as “cache purged”, but that was the project deletion succeeding. mirrorhabit.pages.dev resolved to nothing. The investigation Symptom: dig showed no records; wrangler pages project list no longer contained mirrorhabit; the API said Project not found. The dashboard Audit Log was the smoking gun: ...
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.
terminal-wrapped — your year in commands
Everyone loves a Wrapped — a few flattering stats, one mildly embarrassing stat, one personality label. Raw numbers don’t go viral; stories do. So I built terminal-wrapped: a bash script that turns ~/.zsh_history into your year in commands. What it does One script, two outputs: Pretty report — hero stats (total commands, unique, favorite, sudo %, midnight sessions), a derived “personality label”, top-10 commands with bars, a monthly activity heatmap, one embarrassing stat, and fun facts. JSON mode (--json) — structured stats that any future “wrapped engine” can consume to render a shareable card. The tech bit Speed matters when the history is 7k+ lines. Calling date per line is the naive approach — too slow. Instead: ...
slimming the mirror-habit APK
Two things in one session: a slimmer APK and a content + polish push. Perf: shrinking the bundle The app was carrying a lot of dead weight. Moved the bulky apng sources out of the bundle and archived unused assets, which trimmed a meaningful chunk off the install size — important for a habit app people always have installed. Polish: more decorations Wired up level 12/13 decorations so the earnable avatar ornaments scale into higher tiers. Added levels 2–4 decorations plus effect lines in the prompt pack, rounding out the progression ladder. Content Ran a full research-backed habits post with diagrams up on the blog — the kind of “why streaks and community actually work” explainer that gives the app some reason to exist beyond the toggles.
mirror-habit gets a rewards hub
The biggest feature drop for mirror-habit so far — a full rewards hub built around levelling up and collecting. What shipped Levels with rank graphs — progression tiers backed by a whole new set of rank badges, each with bespoke art (swift, sturdy, fearless, valiant, unyielding, godslayer…). Achievements, titles, badges & banners — a collectible system that gives every check-in a reason, from “first check-in” to “100 check-ins” and “10 habits”. Avatar decorations — visual adornments you earn and can attach to your avatar (dark fantasy, shadow portal, first step, and per-level variants). A dedicated Rewards view to browse everything you’ve unlocked and what’s still ahead. The rank artwork got an overhaul too — reworked and renamed each tier’s image so they read clearly as a progression ladder instead of vague names. ...
stashed.in 404 page gets a restyle
Small but satisfying: the stashed.in 404 page finally matches the rest of the app. What changed Restyled to match branding — same look and feel as the rest of the site so a dead link doesn’t feel like a dead end. Optimized the image down to a 256px webp, so even the error page loads fast. A tiny page, but it’s the difference between “ugh, a 404” and a brand-consistent dead end that doesn’t wreck the load budget.
portfolio redesign: bento grid
Refreshed the portfolio site with a layout that earns its keep. What changed Featured projects as a bento grid — a more visual, varied layout than the old uniform list, so each project gets a presence roughly matching its scope. Project icons — every project now has a recognizable mark instead of just text. Mobile glass nav — a frosted look that reads well and stays out of the way on small screens. Curated broadcasts — a hand-picked section of highlights rather than everything blindly. A design pass that made the front page feel more like a curated showcase and less like a resume dump.
reset after a botched update
Lost a full day of dev time to a botched update that ended in a config reset. Logging the post-mortem so the next one hurts less. What happened A routine system update (Fedora KDE Plasma) went sideways. On next boot, some desktop/session config didn’t come back the way it should — the shell came up in a locked/odd state and a chunk of my Plasma setup wasn’t behaving. Rather than burn more hours untangling whatever the update scrambled, I made the call to reset the affected config and rebuild it from my dotfiles. The reset itself was quick; the lost day was the interruption — momentum on whatever I was mid-way through went out the window. ...
A dev log for the dev logs
Every good project diary needs an origin entry — this is it. Tonight I spun up devlog.stashed.in: a tiny Hugo site running the bearblog theme to keep build notes across all my projects instead of losing them in chat scrollback. Setup took minutes: hugo new site equivalent — folder + config + theme clone (hand-rolled layouts lasted all of five minutes before I wised up) Backfilled past logs from stashlist/stashed.in, mirror-habit, and the Mehfil wedding-invitation sprint Git repo → GitHub → Cloudflare Pages building Hugo straight from the repo Custom domain pointed at devlog.stashed.in The dogfooding begins here. If you’re reading this, the pipeline works end-to-end.