Electron desktop port of Mike: Supabase and S3 stripped, SQLite and local filesystem in

rafal-fryc has taken the upstream cloud SaaS stack and rebuilt it as a single-user Windows desktop app, replacing every cloud dependency with local equivalents. The interesting part is how little the route layer had to change.

infrastructuresecurity

The main commit (e2a80b08) is a +19,351/-15,651 line swap. Supabase Postgres becomes SQLite via better-sqlite3. Rather than rewriting the 14-file Express route layer, rafal-fryc built a Postgrest-shaped compatibility shim at backend/src/db/supabaseShim.ts (808 lines) so existing handlers compile and run unchanged. S3/R2 becomes a filesystem tree under <workspace>/files with a path-traversal guard. Supabase Auth becomes approximately 50 lines of Node crypto doing HS256 JWT - no jsonwebtoken dependency. The password is scrypt-hashed and stored in <workspace>/.mike/auth.json. An Electron shell handles workspace selection and the lock screen; NSIS packages the result.

A pre-ship code review (committed as CODE-REVIEW-0.2.0.md) caught five Critical issues before tagging. The most significant: DOWNLOAD_SIGNING_SECRET was falling back to the literal string "dev-secret" in every packaged build, because the env injection block that populates the backend child process never set it. Anyone who read the source could forge /download/:token URLs for any stored file. The second commit (79acd8a6) closes all five Critical findings: per-launch DOWNLOAD_SIGNING_SECRET minted in electron/main.ts, alg/typ header validation in verifyLocalJwt, sandbox:true on the renderer, a missing chat_messages.workflow column that was silently dropping every user message, and a raw Claude stream logger writing unbounded user data to disk.

The project carries DECISIONS.md logging trade-offs (Electron over Tauri, SQLite over PGlite, scrypt over bcrypt, no bundled LibreOffice in v0.1), a TODO.md of deferred work, and per-phase notes under .claude/phases/. Two of three pre-ship reviewers returned "do not ship as-is"; the second commit fixed everything blocking. That process discipline is visible in the output.

Known constraints: the 808-line compat shim is bespoke maintenance debt that will drift as upstream evolves Supabase queries. Cloudflare/OpenNext is fully removed - re-enabling dual-target deployment means going back to upstream. The installer is unsigned (SmartScreen warning). API keys land in plaintext in SQLite, acknowledged in DECISIONS.md. Single user, single workspace by design.

So what Worth a close look if you want a desktop SKU: this is high-quality reference architecture, not a hobby experiment. The `supabaseShim.ts` facade pattern is the piece most worth studying in isolation - it's what let rafal-fryc preserve all existing routes unchanged. The `DECISIONS.md` / `CODE-REVIEW-0.2.0.md` discipline is worth adopting regardless. Skip it if you need dual-target (desktop + cloud), macOS/Linux, or multi-user - none of those are supported, and some would require structural rework.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

3 commits from rafal-fryc/mikelocal, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
e2a80b08 Convert to local desktop app (Electron + SQLite + workspace folder) riffyraf 2026-05-01 ↗ GitHub
commit body
Replace every cloud dependency with a local equivalent and ship as a
downloadable Windows app:

- Electron shell with workspace picker, scrypt-hashed local password,
  lock screen on every launch
- Supabase Auth → local HS256 JWT (Node crypto, no library deps)
- Supabase Postgres → SQLite via better-sqlite3 with a Postgrest-shaped
  compat shim, so existing route handlers stay unchanged
- S3/R2 storage → local filesystem under <workspace>/files with
  path-traversal guard and short-lived token-bearing URLs
- API key management via existing Settings UI, persisted in user_profiles
- LibreOffice runtime detection with graceful degradation
- electron-builder NSIS installer config, Next.js standalone build
- Full README rewrite for end users plus DECISIONS.md, TODO.md, and
  per-phase notes under .claude/phases/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79acd8a6 feat(security): address review-critical findings + lock-screen polish riffyraf 2026-05-03 ↗ GitHub
commit body
- Critical (5): per-launch DOWNLOAD_SIGNING_SECRET, JWT alg/typ + exp
  type-guard in verifyLocalJwt, sandbox:true on the renderer, raw-stream
  log gated behind MIKE_DEBUG_RAW_STREAM, drop dead workflow column
  insert.
- Session 1 (security): CSP on packaged builds, DevTools gated to
  unpackaged + non-lock-screen, dotenv only loaded in standalone dev,
  pickWorkspace realpath + install-dir guard.
- Session 2 (backend): belt-and-braces user_id on documents DELETE +
  project_id on folder cleanup, global Express error handler,
  127.0.0.1 in signed URLs, 60s timeout + 200MB cap on LibreOffice
  conversion.
- Session 3 (frontend): drop dead incrementMessageCredits, cache JWT
  module-scope in supabase shim, suppress double-loading spinners after
  unlock.
- Lock screen: scope display:flex so .status[hidden] actually hides
  the "Starting Mike..." spinner.

DECISIONS.md updated for the lockout-state choice.
TODO.md updated with the deferred Important + Minor items.
.gitignore now excludes /vendor/ and backend/.dist-bundle/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
b89e5763 docs(readme): comprehensive project guide riffyraf 2026-05-03 ↗ GitHub
commit body
Replace minimal README with full project documentation:
architecture overview (process model + IPC surface + supabase shim),
security model (threat model + scrypt params + JWT verifier + sandbox/CSP +
filesystem guard + lockout-state caveat), data layout, build
prerequisites + EPERM/Developer Mode notes, expanded project layout,
tech stack table, full diff-from-upstream summary, known limitations,
and contributor conventions. Also fixes the stale "LibreOffice not
bundled" line - it has been bundled since the LO-installer fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-100.md from inside the repo you want the changes in.

⬇ Download capture-thread-100.md