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 byscripts/sync-source.sh, thenapply-patches.mjsadapts 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 threwCannot read properties of undefined (reading 'app'). Fix:(leaf) => new MirrorHabitView(leaf). -
OAuth inside Obsidian’s sandbox — the plug-in origin can’t complete a Google redirect flow, so Google sign-in opens the web app in a browser tab (
mirrorhabit.pages.dev/login) and the plugin watches for the session to land. This is also why the app’s default URL had to be corrected topages.dev(it was stillnetlify.appin several spots after the migration).
Shipping notes
- Built via esbuild (JS) + a small CSS build step, outputs
main.js+styles.csscopied into the vault’s.obsidian/plugins/. - Manifest declares
minAppVersion 1.4.0; license MIT; README lays out the required commerce/backend disclosures for community publishing (offline default, external Supabase only when configured, no telemetry).
The “same React core, two surfaces” pattern is holding up — the plugin is mostly plumbing, with the hard thinking already done in the web app.