{"repo": "arpitpatel25/unmute-dictation", "files": {"ROADMAP.md": {"86d11f1a3aa21f516327ea5752fd6a20176d4755": {"t": 1785674374.0153735, "n": 10243, "h": "45732a0d854937eb", "c": "# unmute — Open-Source Roadmap\n\nThis document is the single source of truth for turning the copied Bolo codebase into\n**unmute**: a lean, local, open-source voice dictation app for macOS. Everything below\nwas decided in the planning conversation. Work top-to-bottom; later phases assume earlier\nones are done.\n\n> **Product in one line:** Press a key, speak, and clean *raw* text appears at your cursor.\n> Formatting and transforms happen only when you ask. Bring your own Groq API key. Fully\n> local — no servers, no accounts.\n\n---\n\n## Guiding principles\n\n- **Local-first, zero backend.** No Supabase, no Cloudflare, no auth, no telemetry, no quota.\n The Electron app calls Groq directly with the user's own key.\n- **Raw by default.** We never auto-format. Default dictation = the transcript, lightly\n cleaned in code. The LLM only runs when the user explicitly instructs (Control key).\n- **Groq-only, no choices.** One provider, one set of models, hardcoded. No model picker.\n- **Invisible until needed.** The HUD acks silently (glow), shows text only on failure.\n- **Showcase craft.** Latency, native key capture, real-time audio, and a polished release\n are the things that make this read as a strong builder project.\n\n---\n\n## Phase 0 — Repo setup ✅ DONE\n\n- [x] Create new repo `unmute-dictation` as a sibling of `BoloAI`, fresh git history.\n- [x] Exclude secrets/heavy artifacts (`.env`, `node_modules`, `dist`/`release`/`build`,\n Python venv, `.webm`).\n- [x] Baseline commit on `main`. `BoloAI` left untouched.\n\n---\n\n## Phase 1 — Backend teardown (rip out Supabase + Cloudflare)\n\nGoal: delete everything that exists only to hide keys / meter usage / authenticate.\n\n- [ ] Delete `supabase/` entirely (migrations, `functions/admin`, edge functions).\n- [ ] Delete `cloudflare/` entirely (config, pipeline, quick-chat, supabase-proxy, shared).\n- [ ] Remove `@supabase/supabase-js` dependency and `renderer/shared/supabaseClient.ts`.\n- [ ] Remove auth flow: `renderer/app/Auth.tsx`, sign in/up, JWT (`auth:token` IPC),\n Account-tab sign-out, OAuth, the `unmute://auth/callback` deep-link handling.\n- [ ] Remove quota/daily-caps logic, `featureFlags.ts`, dev-whitelist gating, telemetry.\n- [ ] Remove `deploy:*` scripts from `package.json`.\n- [ ] Port the **needed** config values out of `cloudflare/shared/config.ts` into a local\n constants file (chunking params, junk-detection, the Groq STT + LLM model IDs we keep).\n- [ ] Remove the backend-selection setting (Supabase/Cloudflare toggle).\n\n---\n\n## Phase 2 — Go local with BYO Groq key\n\n- [ ] Rewrite `electron/api.ts` to call Groq directly:\n - STT → `https://api.groq.com/openai/v1/audio/transcriptions`\n - LLM → `https://api.groq.com/openai/v1/chat/completions`\n - Add `Authorization: Bearer `; drop all JWT logic.\n- [ ] **Key storage via OS Keychain.** Use Electron `safeStorage.encryptString` on save,\n `decryptString` on read. Never write the key in plaintext. Never log it.\n- [ ] **Settings UI:** a single \"Groq API key\" field.\n - [ ] Masked display after save (e.g. `gsk_••••1234`).\n - [ ] Optional \"Test key\" button that pings Groq once to validate.\n - [ ] Friendly empty-state: link to where to get a key + \"paste it here to start\".\n- [ ] Block the pipeline when no key is set, with a clear prompt to add one.\n- [ ] **Groq-only:** strip Cerebras / Cartesia / Sarvam provider configs, the\n `ACTIVE_MODELS` knob, and any model-selection UI. Hardcode the chosen Groq STT\n (Whisper) + Groq LLM model.\n\n---\n\n## Phase 3 — Cut Quick Chat\n\n- [ ] Delete `renderer/quickchat/`, `electron/quickChatManager.ts`, the Quick Chat window\n in `windowManager.ts`, and `renderer/app/QuickChatPage.tsx`.\n- [ ] Remove the Option+Space hotkey + `quick-chat-toggle` handling.\n- [ ] Remove all `quick-chat:*` IPC channels and the `groq-compound` / SSE / markdown /\n web-search code paths.\n- [ ] Result: two windows only — main (settings/history) + the HUD pill.\n\n---\n\n## Phase 4 — Raw-by-default + deterministic stitching (the behavior shift)\n\n- [ ] **Remove the LLM from the default dictation path.** Default output = transcript with\n lightweight **deterministic** cleanup in code: trim, collapse whitespace, strip a\n trailing phantom \"thank you\", de-dupe chunk-boundary overlap.\n- [ ] **Formatting/transform on-demand only** — the LLM runs only when the user gives a\n Control instruction (transform / context / instruction-only flows keep working).\n- [ ] **Deterministic chunk stitching** (kills the `[Chunk 1/8]:` bug):\n - [ ] Cut chunks cleanly at silence (no overlap) so reassembly is a plain join.\n - [ ] Order chunks by zero-padded index and `join(' ')` in code.\n - [ ] Remove the LLM-merge prompt + all chunk labelling/markers.\n - [ ] (If overlap is ever needed: deterministic longest-suffix/prefix de-dupe, no LLM.)\n- [ ] Keep the on-demand transform prompts (`prompts.ts`) for the instruction flows; drop the\n auto-cleanup-via-LLM dictation prompt.\n\n---\n\n## Phase 5 — UX improvements\n\n- [ ] **HUD on success:** remove the transcript preview text. Show a brief green glow /\n minimal success ack, then auto-hide. (Lean toward glow-only; checkmark optional.)\n- [ ] **HUD on failure:** keep showing the error + note that the text is still on the\n clipboard (this is the one case the user can't see the result anywhere else).\n- [ ] **Silence guard (no wasted API calls):** track whether any audio crossed the RMS\n speech threshold during a recording. If silence-only, **skip STT entirely** and show\n \"Didn't catch that.\" Reuse the existing VAD machinery.\n- [ ] **Live \"not hearing you\" cue:** when N seconds of continuous silence is detected\n mid-recording, dim/flatten the waveform or show a subtle hint. Visual only.\n- [ ] (Optional, default OFF) auto-stop on prolonged silence — only if testing shows demand.\n- [ ] **Audio recovery:** keep the 5-session on-disk retention (already implemented). Add a\n **retry / re-transcribe** affordance (History row and/or HUD error state) that loads the\n saved audio via `loadAudioFile` and re-runs the pipeline. Turns the safety net into a\n visible \"never lose what you said\" feature.\n\n---\n\n## Phase 6 — Rename Bolo → unmute (everywhere)\n\nMechanical but thorough. \"Bolo\" appears in several forms.\n\n- [ ] Code identifiers / strings: `Bolo`, `Bolo AI`, `BOLO` wordmark → `unmute`.\n- [ ] `package.json` `name: \"bolo\"` → `unmute`.\n- [ ] SQLite file `bolo.db` → `unmute.db` (handle existing-file path).\n- [ ] CSS design tokens `--bolo-*` → `--unmute-*` (referenced widely; do carefully).\n- [ ] IPC channel names, env var prefixes, log tags (`[audio]` etc. are fine, but `bolo`-named ones).\n- [ ] App/product name in `electron-builder.yml`, window titles, tray.\n- [ ] The `unmute://` URL scheme already exists — keep it.\n- [ ] Rename / rewrite docs: `BOLO.md` → `README`-style doc; update `DESIGN_SYSTEM.md`.\n\n---\n\n## Phase 7 — Open-source readiness\n\nNon-negotiables first.\n\n- [ ] **LICENSE** — pick MIT or Apache-2.0 (permissive) and add the file. *Required.*\n- [ ] **Secret / personal-data scrub** — verify none remain in the working tree *or* the new\n git history:\n - [ ] hardcoded Supabase JWKS public key (gone with Cloudflare teardown).\n - [ ] `DEV_WHITELIST` personal email.\n - [ ] Supabase URLs / anon key, `.env.example` contents.\n - [ ] confirm `.env` never committed.\n- [ ] **README** — what it is, a GIF/screenshots, \"get a Groq key → paste in Settings\",\n install + build + run steps, permissions explanation (mic, accessibility, input monitoring).\n- [ ] **Reproducible build** — ensure `npm install && npm run dev` works on a clean Mac;\n document the native-binary build (Swift `globe-listener`, whisper) or commit a working\n `compile:native` path.\n- [ ] **Remove proprietary assets** — `PRD_Bolo.docx`, `TechSpec_Bolo.docx`, old logos;\n replace with unmute branding.\n- [ ] **Contribution hygiene** (lighter): `CONTRIBUTING.md`, `.gitignore` correctness,\n issue template. Optional at launch.\n- [ ] Create the GitHub remote and push (user-triggered) once scrubbed.\n\n---\n\n## Open decisions (resolve before building the affected phase)\n\n- [ ] **Local STT (whisper.cpp / faster-whisper):** keep as an offline option, or remove for\n a Groq-only lean core? \"Groq-only, no choice\" leans toward removal; \"local-first\n showcase\" leans toward keeping. Affects Phases 2, 4, and resources/scripts cleanup.\n- [ ] **Sarvam / Hinglish path:** likely removed with Groq-only STT — confirm. (Resolves\n KNOWN_ISSUES #4.)\n- [ ] **Local LLM (`localLLM.ts`) + `prompts.ts` \"copied verbatim\" rationale:** keep for the\n on-demand transform, or simplify now that there's no local/cloud parity to maintain?\n- [ ] License choice: MIT vs Apache-2.0.\n- [ ] Widget success ack: glow-only vs glow + checkmark.\n\n---\n\n## Deferred — the \"fuzzy\" work (separate conversation, after the lean core exists)\n\n- **UI redesign.** The current UI is weak; this is the biggest open piece and the highest-ROI\n item for \"showcase.\" Design it against the simplified, real codebase — not now.\n- **More feature enhancements** (TBD — user has many in mind).\n- **Distribution:** code signing + notarization, release pipeline, auto-update.\n- **Showcase polish:** latency engineering as the headline (measure p50/p95, before/after),\n streaming/partial STT, a transcript-accuracy benchmark/eval harness.\n- **Remaining KNOWN_ISSUES** not auto-resolved by the above.\n\n---\n\n## Bug-fix ledger (from KNOWN_ISSUES.md — how each resolves)\n\n| # | Issue | Resolution |\n|---|---|---|\n| 1 | `[Chunk x/y]` markers leak into output | **Fixed** by deterministic code stitching (Phase 4) |\n| 2 | Phantom \"thank you\" appended | Deterministic trailing-phrase strip in cleanup (Phase 4) |\n| 3 / 7 | Spelled-out word + scaffolding both appear | Only relevant in the on-demand format path now (Phase 4) |\n| 4 | Sarvam outputs digit \"1\" for \"one\" | Likely **moot** if Sarvam removed (Open decision) |\n| 5 | LLM formatting passes raw text ~half the time | **Moot** — no auto-format by default (Phase 4) |\n| 6 | Spoken symbols not converted | On-demand format/transform concern; revisit later |\n"}}}}