Skip to content

Feat/windows track1 parity#8067

Open
karthikyeluripati wants to merge 72 commits into
BasedHardware:mainfrom
karthikyeluripati:feat/windows-track1-parity
Open

Feat/windows track1 parity#8067
karthikyeluripati wants to merge 72 commits into
BasedHardware:mainfrom
karthikyeluripati:feat/windows-track1-parity

Conversation

@karthikyeluripati

@karthikyeluripati karthikyeluripati commented Jun 20, 2026

Copy link
Copy Markdown

Summary

This PR brings the Windows Electron desktop app to Track 1 parity with the macOS app. All features were built against the existing Omi backend with no API changes required.

Features added

  • Chat sessions sidebar — two-pane chat layout with named sessions, date grouping, create/delete/rename, persistent history via useSessionChat
  • Live Notes panel — AI-generated + manual notes during active recording sessions
  • Live Transcript panel — speaker bubbles with real-time diarization, tap-to-name speaker assignment (NameSpeakerSheet)
  • Focus page — macOS-style status card, vision-based focus analysis via Gemini Vision, proactive focus engine
  • People library — contacts view with 34 filter tags and multi-agent pills
  • Tasks — rich filter bar, detail modal, daily plan sheet (DailyTaskSheet)
  • Devices tab — Web Bluetooth BLE scan + connect, battery % read via GATT, device widget in sidebar
  • Memory export — destinations: Obsidian, Notion, ChatGPT, Claude, Agents/MCP
  • ChatLab — prompt engineering + evaluation tool
  • Overlay TTS + proactive notifications
  • Chat follow-up suggestion chips + send-while-streaming
  • Rewind PDF export + font scaling (Ctrl+=/-)
  • Win11 Mica/Acrylic backdrop, custom titlebar, Inter font, thin scrollbars

Bug fixes

  • Emoji garbling in overlay chatdone: base64 payload was decoded with atob() (Latin-1), then used to replace the streaming text. Fixed by ignoring done: text entirely; streaming data: chunks decoded by the browser's native UTF-8 layer are used directly.
  • Citation cards restoreddone: payload is now parsed with TextDecoder (correct UTF-8) for citation metadata only, never for text replacement. Both the overlay (useChat) and main Chat tab (useSessionChat) are fixed.
  • BYOK Anthropic path removed — overlay was conditionally routing to Anthropic's API directly if an Anthropic key was stored in preferences. Always uses Omi /v2/messages now.

Tests

  • Extracted parseDonePayload and parseSseLine into lib/chatSse.ts (pure, no React/Firebase)
  • 35 unit tests covering: UTF-8 emoji roundtrip (the regression), all citation field fallbacks (memories/citations/sources, memory_id, conversation_id, structured.title/emoji), malformed input, preview truncation, __CRLF__ replacement, done:/think:/data: line routing
  • All 543 existing tests continue to pass

Chores

  • Removed accidentally committed build artifacts (electron.vite.config.*.mjs, tsconfig.node.tsbuildinfo)
  • Extended .gitignore to cover both patterns

Review in cubic

karthikyeluripati and others added 30 commits June 18, 2026 07:37
The synchronous DELETE+INSERT transaction in replaceLocalGraph was running
on the Electron main thread, blocking all IPC for 1–3 s on large graphs.

Fix:
- kgWorker.ts: new worker_thread with its own WAL better-sqlite3
  connection; prepares all statements once at startup, runs the full
  replace transaction off the main thread, returns `{type:'done',ms}`.
- kg.ts: lazy worker lifecycle with coalescing queue (only the latest
  pending graph is kept while a write is in flight); in-memory kgSnapshot
  cache so empty-query reads skip SQLite entirely; kg:status and
  kg:saveGraph IPC return immediately.
- db.ts: WAL + NORMAL sync unconditional (was bench-only); main thread
  reads are no longer blocked while the worker holds the write lock.
- electron.vite.config.ts: second rollupOptions.input entry emits
  out/main/kgWorker.js alongside out/main/index.js.
- electron-builder.yml: kgWorker.js added to asarUnpack so worker_threads
  can require() it in packaged builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dispatch(): wrap ensureWorker().postMessage() in try-catch so a Worker
  construction failure (e.g. missing kgWorker.js in packaged build) resets
  workerBusy and retries via flushPending() instead of silently deadlocking
  all future kg:saveGraph calls for the session.

- kg:queryNodes: resolve cap = limit ?? 80 once before the snapshot/DB branch
  so both paths return the same node count; previously the DB fallback used
  queryKgNodes(q, limit) which defaulted to 12, while the snapshot path
  defaulted to 80 — callers saw different context depending on whether the
  first worker write had completed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sidebar: add Memories (Brain icon) as primary nav item between
  Conversations and Tasks — matches macOS sidebar order
- Rewind: surface the already-built RewindSearchBar with a Search
  button; add Close search button to return to the timeline view
  (showSearch was permanently false with no toggle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Electron Tray created at launch with the existing app icon
- Right-click menu: Open Omi, Screen Capture toggle (checkbox, reads/writes
  rewind settings and broadcasts rewind:settings to renderer so the sidebar
  toggle stays in sync), Quit Omi
- Left-click on tray icon shows/focuses the main window
- Close-to-tray: window X button hides the window instead of destroying it;
  app stays alive in the tray (mirrors macOS menu-bar-app behavior)
- isQuitting flag prevents hide-to-tray from blocking actual app.quit() calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rsations widget

- Sidebar: rename nav label "Home" → "Dashboard" to match macOS first-screen naming
- Add QuickConversationsWidget: fetches /v1/conversations, shows 3 most recent with
  emoji + title + relative time (Today/Xm ago/Yesterday/date), links to Conversations,
  spans both grid columns as a full-width "Recent Activity" row
- Home: add convsReady to the 3-widget reveal gate (all 3 appear together to avoid
  layout jank); safety timer covers the 6s fallback for all three
- Widget grid now: [Tasks][Goals] / [Recent Conversations — full width]
- Chat flow unchanged; existing route /home preserved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add RecordingStatusBar component that mirrors the macOS sidebar recording
panel: a pulsing rose dot + Listening/Connecting label + MM:SS elapsed timer
+ last-6-words transcript snippet, placed just above the mic/screen toggles.

- Subscribes to liveConversation singleton (status: idle|connecting|live|error,
  transcript segments) so it reacts to always-on continuous recording
- Also shows when recorder.recording is true (manual one-off recording)
- Collapsed sidebar: shows only the dot with a tooltip label + elapsed
- Hidden when idle — no visual noise when not recording
- No new IPC or backend changes required

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Settings (gear icon) to navItems after Apps — Windows sidebar now
  has 7 items matching macOS exactly (Dashboard · Conversations · Memories
  · Tasks · Rewind · Apps · Settings)
- Convert account-avatar row from NavLink → Link so it doesn't
  double-highlight alongside the new Settings nav item
- Update TRACK1_SWIFT_PARITY.md: App Shell now Partial→improved, Settings
  nav placement noted as done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix Memories.tsx: replace macOS-only ⌘/Ctrl shortcut hint with
  Windows-correct 'Ctrl+Enter to save'
- Update TRACK1_SWIFT_PARITY.md: revised summary totals (6 ✅ / 13 🟡 / 3 ❌),
  mark all P0 gaps as resolved, document remaining P1/P2 gaps honestly
- Add TRACK1_SUBMISSION_CHECKLIST.md: build commands, installer path, full
  manual test checklist, 10-step demo script, known limitations table,
  PR summary bullets, submission readiness verdict

npm run typecheck ✅  |  npm run build:win ✅

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Enable window resize: resizable=true with minWidth/maxWidth locked to
  OVERLAY_WIDTH (336px) so only height is user-resizable; minHeight=80
- Improve drag handle: h-7 / 3px / w-10 / 60% opacity (more visible)
- Add OmiPill: green status dot + 'Omi' label above the input row,
  matching macOS AgentPillsRowView style (static default agent only;
  no agent VM backend on Windows)
- Add ResizeGrip: 3-dot SVG at bottom-right, mirrors macOS
  FloatingControlBarView ResizeHandleView (pointer-events-none, visual only)
- Update TRACK1_SWIFT_PARITY.md: Floating Overlay Partial→improved

npm run typecheck ✅  |  npm run build:win ✅

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…abs, starred conversations

Source-trace all 14 remaining macOS parity gaps. Implement the top 4 IMPLEMENT_NOW items:

1. Chat citation cards — parse `done:` SSE event (base64 JSON), extract `memories`
   array, attach to last assistant `ChatMsg`, render tappable source cards below
   the reply bubble (links to /conversations/{id}). Data was already in the stream
   but discarded. `useChat.ts` + `ChatMessages.tsx`.

2. Shortcuts settings tab — `ShortcutsTab.tsx` exposes the overlay accelerator
   (already wired to main via `setAccelerator`) so users can change the Ask Omi
   shortcut without re-running onboarding.

3. Integrations settings tab — `IntegrationsTab.tsx` (Sticky Notes + Google OAuth)
   was fully built but not registered. Added `shortcuts` and `integrations` to
   `SETTINGS_TABS` and `TAB_COMPONENTS`.

4. Conversations starred filter — `ConversationRow` now carries `starred` from the
   API response; star toggle button appears on hover for cloud rows (calls
   `PATCH /v1/conversations/{id}/starred`); new Starred filter chip added.

Also adds `TRACK1_REMAINING_SOURCE_TRACE.md` with full source evidence for all 14 gaps.

typecheck ✅  build:win ✅

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…export

Add OcrPanel (collapsible, selectable, empty state) to RewindPlayer toggled
by new Text button; add Maximize2 fullscreen button floating top-right of
player; add Export button downloading omi-rewind-{date}.json via blob URL
(no new IPC). Update parity docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…act view

Add date filter dropdown (All time/Today/This week/This month) using
client-side sortAt filtering; load /v1/folders for folder tab strip;
add compact view toggle with macOS-style emoji badge row; switch cloud
row timestamps to macOS format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cOS parity

Add SupportTab with app identity card (logo + version + Electron/Node runtime),
external links (omi.me, help.omi.me, GitHub issues, Privacy, ToS), local data note.
Inject __APP_VERSION__ via electron-vite renderer define. Reorder Settings tabs:
Integrations before Shortcuts, Support at end matching macOS About position.
Update TRACK1 docs: mark Support/About DONE, document remaining infeasible gaps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, category filters, conversation row actions, folder CRUD

- Insights.tsx: new page showing SQLite-persisted insight history with category
  tabs (All/Productivity/Communication/Learning/Health/Other), search, expandable
  reasoning cards; wired into Sidebar (Lightbulb icon) and MainViews
- Rewind.tsx: date picker filters to any past day via rewindFrames(from,to);
  Markdown export alongside JSON (one ## section per frame with app/window/OCR)
- Memories.tsx: category filter tab strip derived from live memory.category values;
  interactive={true} on BrainGraph so nodes are clickable
- Conversations.tsx: hover action toolbar on each row (edit title inline, copy
  preview to clipboard, delete single); folder strip now always visible with
  + New folder (POST /v1/folders) and × delete (DELETE /v1/folders/{id})

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elete single, folder CRUD

Hover toolbar on each row (expanded + compact): Pencil (inline title edit with
PATCH /v1/conversations/{id}), Clipboard (copy title+preview), Trash (5s undo
delete). updateLocalConversationTitle used for local rows.

Folder strip always visible: + New folder creates via POST /v1/folders; × on
folder pill deletes via DELETE /v1/folders/{id} with optimistic removal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y tabs, doc updates

Rewind: date picker (type=date, max=today) fetches rewindFrames for any past
date; Markdown export downloads omi-rewind-{date}.md with ## timestamp sections
per frame; JSON export filename now uses selected date not today.

Memories: category filter tabs strip above memory grid; tabs computed from
live memory.category values; persists across manage mode; BrainGraph now
interactive (nodes clickable).

Docs: TRACK1_REMAINING_SOURCE_TRACE.md updated to reflect all implemented items.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aker names

- Conversations: move conversation to folder (PATCH /v1/conversations/{id}/folder),
  copy shareable link (visibility=shared + h.omi.me URL), multi-select merge
  (POST /v1/conversations/merge), folder picker dropdown in both compact and expanded rows
- Memories: inline edit via pencil button (PATCH /v3/memories/{id}?value=),
  cancel/save/Ctrl+Enter, optimistic update via useMemories.editMemory
- ConversationDetail: speaker display names resolved from people[] returned by
  GET /v1/conversations/{id} — shows person name instead of SPEAKER_00 when known

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…assignment

- BYOK: add byokKeys to preferences; inject X-BYOK-* headers on every omiApi
  request; new API Keys settings tab with 4 masked inputs, SHA-256 fingerprint
  activation (POST /v1/users/me/byok-active) and deactivation
- Chat audio: paperclip button in chat bar opens file picker; uploads audio to
  POST /v2/voice-messages as multipart; streams SSE response (message: + data:)
  into the chat thread with live transcript update
- Speaker assignment: click speaker chip in ConversationDetail to open person
  picker; fetches GET /v1/users/people; calls PATCH assign-speaker endpoint;
  supports creating new people inline; updates display names immediately
- Support tab: add Check for Updates row linking to GitHub releases page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tail, audio bar button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ignment done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cker

- Focus: manual Pomodoro timer + Rewind-powered app time breakdown (focus vs
  distraction by category heuristic); session history in localStorage
- Notifications: new Settings tab consolidating insight notification settings
  (interval, style, denylist) + recording-saved Web Notification toggle
- Devices: honest Settings tab listing supported Omi device families with clear
  "BLE not yet available on Windows" message and mobile-app workaround
- Updates: GitHub API release checker in Support tab — shows available version or
  "up to date"; falls back to Releases link; no electron-updater dep required
- Recording-saved notification fires via Web Notification API after conversation
  is saved (respects notifyOnRecordingSaved preference)
- Add Focus to sidebar nav (Target icon, between Tasks and Rewind)
- typecheck: PASS; build:win: PASS (✓ built in 11.43s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/Updates done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Focus: LLM-based focus/distracted/neutral classification via existing Gemini
proxy (geminiClient.ts + insightActivity.ts) with heuristic fallback. Periodic
analysis loop, observation history, sustained-distraction Web Notification.
Configurable via Notifications tab (interval, distraction alert toggle).

BLE: Web Bluetooth requestDevice() in DevicesTab with runtime feature detection.
Main process registers setPermissionCheckHandler + setDevicePermissionHandler for
bluetooth and handles select-bluetooth-device with a native Electron dialog.
Scan button visible only when navigator.bluetooth is available.

Notifications: added Focus analysis section (enable, interval, distraction alert).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… screenshots

Adds three-tier focus classification to match macOS FocusAssistant.swift:
1. Vision tier — selects 1-2 most recent Rewind frames with stored JPEGs,
   fetches them via existing validated rewind:frameImage IPC, sends as
   inlineData parts to Gemini Vision with 8 s total timeout. Returns
   visual_evidence field (max 20 words describing what Gemini sees).
   In-memory cache keyed by frame ts+path avoids redundant Gemini calls.
2. Text/OCR tier — summarizeActivity() → Gemini text prompt (existing path).
3. Heuristic tier — keyword match on exe/app name, no network.

New preference focusVisionEnabled (default off) controls tier 1. Toggle
exposed in Notifications → Focus analysis → "Screenshot vision analysis".

Focus.tsx shows method badge (Vision / Text-OCR / Heuristic), visualEvidence
description, and a fallback note when vision is enabled but fell through.
Typecheck and build:win both pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates TRACK1_REMAINING_SOURCE_TRACE.md and TRACK1_SWIFT_PARITY.md to
reflect the three-tier focus engine (Vision → Text-OCR → Heuristic),
the vision toggle in NotificationsTab, and the method/visualEvidence
display in Focus.tsx. Removes the ✗ "no ML pipeline" entry; replaces
with ✓ row pointing to focusEngine.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DevicesTab — move from discovery-only to full connect flow:
- requestDevice with optionalServices: [battery_service, device_information]
  so standard GATT services are accessible post-connection
- gatt.connect() with phase state machine: scanning → connecting → reading
  → connected; gattserverdisconnected event fires → disconnected phase
- Battery Service (0x180F / 0x2A19): read level, show percentage; show
  "Battery unavailable" if service absent (battery === -1 sentinel)
- Device Information Service (0x180A): read manufacturer_name_string +
  model_number_string via TextDecoder; silently skip missing characteristics
- Disconnect button calls gatt.disconnect(); cleans up event listener
- Persist last device name/id/seenAt to localStorage (omi.ble.lastDevice.v1)
- Local Web Bluetooth type stubs (dom lib doesn't include these)
- Minimal type stubs: BleDevice/BleServer/BleService/BleChar/BleApi

SupportTab — UX improvements since electron-updater install is blocked:
- Show "Installed: X.X.X" in all update states for quick comparison
- When update available: show both installed and latest version + note
  "Native auto-install unavailable until release feed is configured"
- "Recheck" button always visible alongside Download link when available

Blocker documented: npm junction C:\Program Files\nodejs\node_modules\npm
still points to nvm\v22.9.0 even with Node v24.16.0 active; npm install
fails with "Class extends value undefined" (minipass-flush/Node compat).
electron-updater not installed; GitHub API checker remains the mechanism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate UX

TRACK1_REMAINING_SOURCE_TRACE.md:
- Section 6: Devices now DONE — Web Bluetooth connect + Battery Service
  (0x180F/0x2A19) + Device Information Service (0x180A) read; disconnect;
  last-device localStorage. Source-traced to iOS OmiBleManager (CBUUID 2A19)
  and Android OmiBleManager (UUID 00002a19-...).
- Section 15: Update auto-install blocker documented exactly — npm junction
  at C:\Program Files\nodejs\node_modules\npm still routes to nvm v22.9.0.
- Summary table updated: BLE connect row ✓; auto-update row updated.

TRACK1_SWIFT_PARITY.md:
- Settings section: Remaining gaps narrowed; documents what was added
  (Notifications tab, Devices connect+battery, Support version compare).

TRACK1_SUBMISSION_CHECKLIST.md:
- Known Limitations: BLE row updated to reflect connect+battery support.
- Known Limitations: explicit native auto-update blocker row added.
- Manual test steps: added Devices scan/disconnect and Support check/recheck.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nce, copy actions

7 small parity wins, all source-backed:

Rewind keyboard shortcuts (macOS RewindPage parity):
- ArrowLeft / ArrowRight: step one frame back / forward (uses mutable ref
  updated each render to avoid stale closure on the single registered listener)
- Space: toggle play/pause (today timeline only)
- Ctrl+F: toggle search bar (macOS Cmd+F equivalent on Windows)
- Escape: close search bar when open

RewindPlayer fullscreen + OCR copy:
- Fullscreen overlay extracted to FullscreenOverlay component; adds keydown
  listener that closes on Escape (mounted only while expanded to avoid leak)
- OcrPanel: Copy button with ✓ Copied feedback; uses navigator.clipboard

Settings tab persistence:
- Active tab saved to localStorage 'omi.settings.lastTab' on every tab select
- Restored on Settings mount (validated against known tab ids)

Last-route persistence (App.tsx):
- Current pathname saved to localStorage 'omi.lastRoute' on every route change
  (skips /settings and /home which are implicit start states)
- Restored on AppShellInner mount after consuming pending route from onboarding

Window bounds persistence (main/index.ts):
- Saves getBounds() to userData/main-window-bounds.json on resize and move
  (skips when maximized/minimized/fullscreen)
- Reads saved bounds on createWindow() to restore position and size

Sidebar Ctrl+1–9 keyboard navigation:
- useNavigate + keydown handler maps Ctrl+digit to navItems[digit-1].to
- Guards: ctrlKey only, no alt/meta/shift, no active input, digit in range

Chat message copy on hover (ChatMessages.tsx):
- CopyMsgButton appears on `group-hover` below each completed assistant bubble
- Not shown while streaming (isStreaming guard); opacity-0 → opacity-100 on hover
- ✓ Copied feedback with 1.5 s timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
karthikyeluripati and others added 26 commits June 20, 2026 00:58
…rity

- SettingsGear component (⚙ button, left side of header) opens Settings
  in the main window via overlay:openMainRoute — mirrors macOS gear icon
- ResizeGrip opacity raised from 20% → 50% so the three-dot handle is
  visible, matching the macOS FloatingControlBarView ResizeHandleView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…S parity

- Profile menu popover: clicking account row shows a floating menu with
  Refer a Friend (affiliate.omi.me), Discord, Settings — mirrors macOS
  profileMenuPopover with identical options
- MoreHorizontal ellipsis icon in account row matches macOS ellipsis
- Update available widget: checks GitHub releases API on startup;
  shows purple pulsing card with version number when newer build exists
  (same glow animation as macOS sidebar updateAvailableWidget)
- updateGlow @Keyframes in globals.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Filter dismissed insights out by default (dismissed !== 0 hidden)
- Show dismissed toggle in PageHeader actions — matches macOS menu option
- Dismissed count shown in subtitle when filter is active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create audioAnalyser.ts singleton; wire AnalyserNode into the
omiListenClient mic pipeline between the source node and the
ScriptProcessor. Set/clear the global analyser on session start/stop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace CSS-animated bars with requestAnimationFrame loop reading
real mic amplitude from the global AnalyserNode. Add lock icons on
nav items when currentTierLevel (omi.tier.level in localStorage) is
below the item's required tier — mirrors macOS onboarding tier flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New LiveTranscriptPanel component mirrors macOS LiveTranscriptPanel:
floats bottom-right during recording, shows speaker-labeled bubbles
(You = accent color, others = neutral), elapsed timer, collapse/dismiss
controls, and auto-scrolls to bottom on new segments. Mounted in App
alongside other background hosts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…+ date grouping

macOS parity: ChatSessionsSidebar (220px, matches Swift app width) groups past chat
sessions by Today / Yesterday / This Week / This Month. New Chat button, starred
filter, inline search, per-row rename/star/delete. Starred state in localStorage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reload + persistence

Replaces single-lifetime useChat for the Chat page. Reloads history by ID when
sessionId prop changes; streams /v2/messages SSE; persists thread as kind='chat'
local conversation with messages array. Returned sessionTitle tracks the stored title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eSessionChat

Rewrites Chat.tsx to use ChatSessionsSidebar (left rail) + useSessionChat (main area).
Active session persisted to localStorage. New session button creates a fresh ID.
Suggested prompts on empty state match macOS first-launch experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ve recording

Floating bottom-left panel (mirrors macOS LiveNotesView). AI mode calls /v2/chat/completions
every 50 spoken words to generate a concise note. Manual input field + delete per note.
Collapse toggle, dismiss, AI-on/off badge. Resets on each new session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Speaker labels in the live transcript are now clickable — clicking opens an inline
input to rename "Speaker 1" → "Alice". The name applies to all existing and future
bubbles for that speaker (session-scoped). LiveNotesPanel mounted in AppShellInner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches macOS ChatLabView. Two prompt editors (floating bar + main system prompt),
persistent test question list, Run All → calls /v2/messages per question + AI auto-grades
responses 0-5, human star rating, summary avg scores. Generate Improved Prompt uses
eval results to call backend for a suggested next version. Version history in localStorage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, Claude, Agents/MCP

Matches macOS MemoryExportDestinationSheet. Export button in Memories header opens a
two-pane modal: destination list (Obsidian, Notion, ChatGPT, Claude, Agents/MCP) on the
left, per-destination panel on the right. Obsidian: copy Markdown + open obsidian:// URI.
Notion/ChatGPT/Claude: copy context prompts. Agents: MCP server URL + setup prompt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After each AI response, 3 contextual follow-up chips appear (generated via /v2/messages).
Clicking a chip fires it as the next message. Chips clear on new input or session switch.
Send button no longer disabled while streaming — follow-up sends are allowed mid-response,
matching macOS pendingFollowUpText behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…typography

Accent: #5b02e0 → #8B5CF6 (OmiColors.purplePrimary exact match)
Nav selection bg: #141414 → #252525 (OmiColors.backgroundTertiary)
Chat user bubble: glass dark → solid #43389F (OmiColors.userBubble)
Chat assistant bubble: glass-subtle → bg-[#252525]/95 (macOS backgroundTertiary @ 95%)
Bubble corner radius: 16px → 20px continuous (matches SwiftUI .cornerRadius(20, .continuous))
Bubble padding: px-4 py-3 → px-[14px] py-[10px] (matches .padding(.horizontal,14).padding(.vertical,10))
Message gap: 8px → 18px (matches LazyVStack(spacing: 18))
Typing indicator: static '…' → animated 3-dot bounce (mirrors TypingIndicator component)
Copy button: icon+text label → icon-only (matches macOS doc.on.doc icon-only)
Message truncation: none → 500-char limit with "Show more" expand
Sidebar width: 240px → 260px (matches SidebarView.expandedWidth = 260)
Nav icon size: 16px → 18px (matches .scaledFont(size: 18))
Nav label font: 14px → 13px (matches .scaledFont(size: 13, weight: .medium))
Nav horizontal padding: 10px → 16px (matches macOS .padding(.horizontal, 16))
Nav transition: 150ms → 200ms easeInOut (matches .animation(.easeInOut(duration: 0.2)))
Audio bar width: 2px → 3px (matches macOS bar thickness)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tyle status card

- Sidebar collapse animation: ease-out → ease-in-out (matches macOS .easeInOut(duration:0.2))
- Device widget: shows battery % with macOS-exact color coding (<20% red, 20-40% orange, 40%+ green);
  DevicesTab now saves batteryLevel to LAST_DEVICE_KEY so sidebar reads it on next open
- Focus Analysis Card: replaced surface-card glass with macOS-exact colored overlay
  (bg-[status]/8%, border [status]/20%, rounded-[16px]); status display upgraded to macOS
  56×56 circle icon + 20px semibold title + app name + 12px pulse dot; ObsRow gets
  rounded-lg + bg-[#252525]/40 default / bg-[#252525] hover matching macOS backgroundTertiary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DevicesTab now persists batteryLevel in LAST_DEVICE_KEY so the sidebar
widget can display it color-coded (<20% red, 20-40% orange, 40%+ green),
matching macOS batteryColor() and batteryIconName() sidebar logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches macOS FocusStatusCard exactly:
- Status card bg: Color.[status].opacity(0.08), border: opacity(0.2), cornerRadius(16)
- Hero row: 56×56 filled circle (opacity 0.2) + Eye/EyeOff/Clock icon at 24px +
  20pt semibold status title + 13pt app name + 12×12 pulse dot
- ObsRow session rows: rounded-lg, bg-[#252525]/40 default, bg-[#252525] on hover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation transcript drawer + NameSpeakerSheet, overlay TTS + proactive notifications

- Tasks: 19-tag filter panel (Status/Date/Source/Priority/Category groups, AND across groups OR within), replaces basic open/done tabs
- Tasks: TaskDetailModal (550px) with status/priority/category chips, core fields card, tags, source conversation link, delete confirmation
- Tasks: DailyTaskSheet (450px) matching macOS DailyTaskCreationSheet — description + priority chips + create button
- Tasks: task rows grouped by due bucket (Overdue/Today/Tomorrow/Upcoming/No date)
- ConversationDetail: 450px right-side slide-in TranscriptDrawer button in header
- ConversationDetail: NameSpeakerSheet modal (400px) replacing inline picker — You/person chips + Add Person + allSegments toggle
- Overlay: Web Speech API TTS reads each AI reply aloud (window.speechSynthesis)
- Overlay: ProactiveCard notification (bell + title + body + Execute/Dismiss) via overlay:notification IPC channel
- tailwind.config: add animate-slide-in-right keyframe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r speaker fix

- People.tsx: new /people page matching macOS People library — list with avatar initials + color, conversation count, create (inline form, duplicate check), delete with confirm, search filter
- Sidebar: added People nav item (Users icon, tier 3, between Tasks and Focus)
- MainViews: wired PeoplePanel to /people route
- Tasks filter: expanded to 34 tags across 6 groups — Status(4: Open/In Progress/Done/Canceled), Date(4: Due Today/Last 7 Days/This Month/Overdue), Source(5: From Conversation/Manual/iOS/Voice/Friend), Priority(3), Category(10), Origin(6: iOS Mic/Omi Recording/Friend Device/Computer/Web/No Device)
- OverlayApp: replaced single OmiPill with multi-pill row (Omi + Memory + Screen) matching macOS FloatingBarAgentPillsView
- ConversationDetail: fixed NameSpeakerSheet "You" assignment — uses is_user assign_type with graceful fallback + populates personNames['user']='You' for immediate display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The overlay BrowserWindow loaded stale module code via HMR, causing the
done: base64 payload to be decoded with plain atob() (Latin-1) instead of
TextDecoder (UTF-8). The garbled cleanTextFromDone then replaced the
correctly-streamed assistantText at stream-end.

Fix: remove all done: post-processing from both useChat and useSessionChat.
Streaming data: chunks are decoded by the browser's native SSE UTF-8 layer
and are always correct — no replacement needed. Also remove the Anthropic
BYOK fast-path from useChat so the overlay always uses Omi /v2/messages
with full server-side RAG.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove [chat:perf] timing logs (fired in all builds), overlay console-message
diagnostic routing, and add *.tsbuildinfo to .gitignore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…bling

Parse the done: payload from Omi SSE for citation metadata only using
TextDecoder (correct UTF-8). Never use done: text to replace streaming
text — streaming data: chunks decoded by the browser native UTF-8 layer
are always correct. The old text replacement was the source of garbled emoji.
Citations now appear in both overlay (useChat) and main Chat tab (useSessionChat).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove electron.vite.config.*.mjs (vite temp file with timestamp in
name) and tsconfig.node.tsbuildinfo (TS incremental cache) from git
tracking. Both were accidentally committed; *.tsbuildinfo was already
gitignored but git rm --cached was missed for the node variant.
Add electron.vite.config.*.mjs pattern to prevent recurrence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… parsing and SSE line parsing

Extract parseDonePayload and parseSseLine from inline hook closures into
a pure lib/chatSse.ts module (no React, no Firebase) so they can be unit
tested. Both useChat and useSessionChat now import from chatSse, removing
the code duplication.

Tests cover: UTF-8 emoji roundtrip (the regression), all fallback id/title/
preview/emoji fields, key aliasing (memories/citations/sources), malformed
input resilience, __CRLF__ replacement, done:/think:/data: line routing,
and preview truncation at 120 chars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

28 issues found across 81 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="desktop/windows/src/renderer/src/components/ui/GoalCelebration.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/ui/GoalCelebration.tsx:48">
P2: Timeouts are not tracked/cleared, so overlapping celebrations can be interrupted by stale callbacks. This also leaves pending state updates after unmount.</violation>
</file>

<file name="desktop/windows/src/renderer/src/pages/Help.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/pages/Help.tsx:18">
P2: Wrong Crisp query keys (`email`, `nickname`) prevent user identity prefill in the help chat. Use `user_email` and `user_nickname` to match Crisp embed parameters.</violation>
</file>

<file name="desktop/windows/src/main/ipc/kg.ts">

<violation number="1" location="desktop/windows/src/main/ipc/kg.ts:58">
P2: Failed worker dispatch drops the current graph instead of retrying or surfacing an error. This can silently lose a save request on worker startup/postMessage failures.</violation>

<violation number="2" location="desktop/windows/src/main/ipc/kg.ts:108">
P2: `kg:saveGraph` now acknowledges success before the graph is written, so callers can show a successful rebuild even when persistence is still pending or failed.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/layout/Sidebar.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/layout/Sidebar.tsx:239">
P2: Version ordering is incorrect because semver strings are compared lexicographically. This can show/hide the update card for the wrong release.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx:10">
P2: Optional-chained `getAppVersion` is followed by an unconditional `.then`, which can throw when the bridge method is unavailable.</violation>

<violation number="2" location="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx:17">
P2: The update check reports success even when `checkForUpdates` is unavailable, producing a false “up to date” status.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/preferences.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/preferences.ts:44">
P1: Raw BYOK API keys are being persisted in localStorage. Move secret storage to OS-backed secure storage (e.g., keychain/credential vault) and keep preferences limited to non-secret metadata.</violation>
</file>

<file name="desktop/windows/src/main/ipc/db.ts">

<violation number="1" location="desktop/windows/src/main/ipc/db.ts:76">
P1: `synchronous = NORMAL` is now unconditional, which weakens durability for user conversation data in `omi.db`. On OS/power-loss crashes, the latest committed write can be lost.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/chatSse.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/chatSse.ts:34">
P2: Unsafe string casts can throw on malformed citation fields and cause the whole done payload to be discarded. Guard field types before calling `.trim()` so one bad item does not erase all parsed citations.</violation>
</file>

<file name="backend/routers/chat.py">

<violation number="1" location="backend/routers/chat.py:348">
P2: Fallback done-path skips DB persistence for AI replies. Users can see a streamed reply that disappears from stored chat history.</violation>
</file>

<file name="desktop/windows/scripts/copy-koffi-native.mjs">

<violation number="1" location="desktop/windows/scripts/copy-koffi-native.mjs:21">
P1: Binary selection is tied to build host, not packaging target. This can copy the wrong koffi.node when building `--win --x64` from a different host arch/platform.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/omiListenClient.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/omiListenClient.ts:80">
P2: Global audio analyser is set for all sources; system capture can replace mic analyser and drive mic UI from wrong input.</violation>

<violation number="2" location="desktop/windows/src/renderer/src/lib/omiListenClient.ts:120">
P2: Analyser cleanup is not ownership-safe; one session can null out another live session's analyser.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/recording/RecordingStatusBar.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/recording/RecordingStatusBar.tsx:64">
P2: Status text can show stale transcript during manual recording. Only use live snippet while `liveStatus` is active; otherwise fall back to the status label.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/chat/ChatMessages.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/chat/ChatMessages.tsx:88">
P2: Truncated preview slices raw code units and can cut an emoji surrogate pair. Use the same boundary snap used by streaming reveal before slicing.</violation>
</file>

<file name="desktop/windows/src/renderer/src/App.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/App.tsx:42">
P2: Last-route restore is effectively broken on normal startup because `/` is persisted first and replaces the prior saved route.</violation>
</file>

<file name="desktop/windows/src/preload/index.ts">

<violation number="1" location="desktop/windows/src/preload/index.ts:218">
P2: `onNotification` listens on an IPC channel that is never emitted by main. Proactive overlay notifications will never fire.</violation>
</file>

<file name="desktop/windows/src/main/index.ts">

<violation number="1" location="desktop/windows/src/main/index.ts:248">
P2: Bounds persistence performs sync file writes on every resize/move event. This can introduce visible jank while moving or resizing the window.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/overlay/OverlayApp.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/overlay/OverlayApp.tsx:174">
P2: TTS dedup keys on message text instead of message identity. Repeated assistant replies with the same content will not be spoken on later turns.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx:58">
P2: `commitNewPerson` lacks an in-flight guard, so repeated Enter presses can fire duplicate create requests.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic
Partial review: This PR has more than 50 files, so cubic reviewed the highest-priority files first. During the trial, paid plans get a higher file limit.
You can try an ultrareview to bypass the file limit, comment @cubic-dev-ai ultrareview. Learn more.

Re-trigger cubic

onboardingCompletedAt?: number
// BYOK (Bring Your Own Keys) — stored locally; SHA-256 fingerprints are sent to
// the backend on activation, but actual keys only leave this device as request headers.
byokKeys?: {

@cubic-dev-ai cubic-dev-ai Bot Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Raw BYOK API keys are being persisted in localStorage. Move secret storage to OS-backed secure storage (e.g., keychain/credential vault) and keep preferences limited to non-secret metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/windows/src/renderer/src/lib/preferences.ts, line 44:

<comment>Raw BYOK API keys are being persisted in localStorage. Move secret storage to OS-backed secure storage (e.g., keychain/credential vault) and keep preferences limited to non-secret metadata.</comment>

<file context>
@@ -39,6 +39,50 @@ export type Preferences = {
   onboardingCompletedAt?: number
+  // BYOK (Bring Your Own Keys) — stored locally; SHA-256 fingerprints are sent to
+  // the backend on activation, but actual keys only leave this device as request headers.
+  byokKeys?: {
+    openai?: string
+    anthropic?: string
</file context>
Fix with cubic

// (may lose the last committed transaction on OS power-loss; acceptable for
// this derived cache). Previously bench-only; now unconditional.
db.pragma('journal_mode = WAL')
db.pragma('synchronous = NORMAL')

@cubic-dev-ai cubic-dev-ai Bot Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: synchronous = NORMAL is now unconditional, which weakens durability for user conversation data in omi.db. On OS/power-loss crashes, the latest committed write can be lost.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/windows/src/main/ipc/db.ts, line 76:

<comment>`synchronous = NORMAL` is now unconditional, which weakens durability for user conversation data in `omi.db`. On OS/power-loss crashes, the latest committed write can be lost.</comment>

<file context>
@@ -68,12 +68,12 @@ function get(): Database.Database {
+  // (may lose the last committed transaction on OS power-loss; acceptable for
+  // this derived cache). Previously bench-only; now unconditional.
+  db.pragma('journal_mode = WAL')
+  db.pragma('synchronous = NORMAL')
   // Migrate away the incompatible local_kg_* schema from the parked KG experiment.
   dropIfMissingColumn(db, 'local_kg_nodes', 'summary')
</file context>
Fix with cubic

const root = join(__dirname, '..')
const pnpmStore = join(root, 'node_modules', '.pnpm')

const platform = process.platform // e.g. win32

@cubic-dev-ai cubic-dev-ai Bot Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Binary selection is tied to build host, not packaging target. This can copy the wrong koffi.node when building --win --x64 from a different host arch/platform.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/windows/scripts/copy-koffi-native.mjs, line 21:

<comment>Binary selection is tied to build host, not packaging target. This can copy the wrong koffi.node when building `--win --x64` from a different host arch/platform.</comment>

<file context>
@@ -0,0 +1,61 @@
+const root = join(__dirname, '..')
+const pnpmStore = join(root, 'node_modules', '.pnpm')
+
+const platform = process.platform // e.g. win32
+const arch = process.arch // e.g. x64
+const triplet = `${platform}_${arch}` // e.g. win32_x64
</file context>
Fix with cubic

Comment thread desktop/windows/src/renderer/src/components/chat/ChatSessionsSidebar.tsx Outdated
Comment thread desktop/windows/src/main/index.ts Outdated
Comment thread desktop/windows/src/renderer/src/components/home/QuickConversationsWidget.tsx Outdated
Comment thread desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx Outdated
Comment thread desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx Outdated
P1 - ChatSessionsSidebar: add optional chain before .slice() on possibly-undefined trim() result
P1 - index.ts: replace blanket permission-allow-all with explicit allowlist (bluetooth, media, display-capture, notifications)
P2 - ChatSessionsSidebar: wrap title update in try/finally so editingId always clears on IPC failure
P2 - ChatSessionsSidebar: replace outer <button> with <div role=button> to fix invalid button-in-button nesting
P2 - QuickConversationsWidget: guard /home pathname effect with userId check before fetch
P3 - NameSpeakerSheet: fix speaker label regex — SPEAKER_00 was producing empty index; now uses capture group
P3 - NameSpeakerSheet: scope duplicate-name error to addingNew state only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="desktop/windows/src/renderer/src/components/ui/GoalCelebration.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/ui/GoalCelebration.tsx:48">
P2: Timeouts are not tracked/cleared, so overlapping celebrations can be interrupted by stale callbacks. This also leaves pending state updates after unmount.</violation>
</file>

<file name="desktop/windows/src/renderer/src/pages/Help.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/pages/Help.tsx:18">
P2: Wrong Crisp query keys (`email`, `nickname`) prevent user identity prefill in the help chat. Use `user_email` and `user_nickname` to match Crisp embed parameters.</violation>
</file>

<file name="desktop/windows/src/main/ipc/kg.ts">

<violation number="1" location="desktop/windows/src/main/ipc/kg.ts:58">
P2: Failed worker dispatch drops the current graph instead of retrying or surfacing an error. This can silently lose a save request on worker startup/postMessage failures.</violation>

<violation number="2" location="desktop/windows/src/main/ipc/kg.ts:108">
P2: `kg:saveGraph` now acknowledges success before the graph is written, so callers can show a successful rebuild even when persistence is still pending or failed.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/layout/Sidebar.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/layout/Sidebar.tsx:239">
P2: Version ordering is incorrect because semver strings are compared lexicographically. This can show/hide the update card for the wrong release.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx:10">
P2: Optional-chained `getAppVersion` is followed by an unconditional `.then`, which can throw when the bridge method is unavailable.</violation>

<violation number="2" location="desktop/windows/src/renderer/src/components/settings/tabs/AboutTab.tsx:17">
P2: The update check reports success even when `checkForUpdates` is unavailable, producing a false “up to date” status.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/preferences.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/preferences.ts:44">
P1: Raw BYOK API keys are being persisted in localStorage. Move secret storage to OS-backed secure storage (e.g., keychain/credential vault) and keep preferences limited to non-secret metadata.</violation>
</file>

<file name="desktop/windows/src/main/ipc/db.ts">

<violation number="1" location="desktop/windows/src/main/ipc/db.ts:76">
P1: `synchronous = NORMAL` is now unconditional, which weakens durability for user conversation data in `omi.db`. On OS/power-loss crashes, the latest committed write can be lost.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/chatSse.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/chatSse.ts:34">
P2: Unsafe string casts can throw on malformed citation fields and cause the whole done payload to be discarded. Guard field types before calling `.trim()` so one bad item does not erase all parsed citations.</violation>
</file>

<file name="backend/routers/chat.py">

<violation number="1" location="backend/routers/chat.py:348">
P2: Fallback done-path skips DB persistence for AI replies. Users can see a streamed reply that disappears from stored chat history.</violation>
</file>

<file name="desktop/windows/scripts/copy-koffi-native.mjs">

<violation number="1" location="desktop/windows/scripts/copy-koffi-native.mjs:21">
P1: Binary selection is tied to build host, not packaging target. This can copy the wrong koffi.node when building `--win --x64` from a different host arch/platform.</violation>
</file>

<file name="desktop/windows/src/renderer/src/lib/omiListenClient.ts">

<violation number="1" location="desktop/windows/src/renderer/src/lib/omiListenClient.ts:80">
P2: Global audio analyser is set for all sources; system capture can replace mic analyser and drive mic UI from wrong input.</violation>

<violation number="2" location="desktop/windows/src/renderer/src/lib/omiListenClient.ts:120">
P2: Analyser cleanup is not ownership-safe; one session can null out another live session's analyser.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/recording/RecordingStatusBar.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/recording/RecordingStatusBar.tsx:64">
P2: Status text can show stale transcript during manual recording. Only use live snippet while `liveStatus` is active; otherwise fall back to the status label.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/chat/ChatMessages.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/chat/ChatMessages.tsx:88">
P2: Truncated preview slices raw code units and can cut an emoji surrogate pair. Use the same boundary snap used by streaming reveal before slicing.</violation>
</file>

<file name="desktop/windows/src/renderer/src/App.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/App.tsx:42">
P2: Last-route restore is effectively broken on normal startup because `/` is persisted first and replaces the prior saved route.</violation>
</file>

<file name="desktop/windows/src/preload/index.ts">

<violation number="1" location="desktop/windows/src/preload/index.ts:218">
P2: `onNotification` listens on an IPC channel that is never emitted by main. Proactive overlay notifications will never fire.</violation>
</file>

<file name="desktop/windows/src/main/index.ts">

<violation number="1" location="desktop/windows/src/main/index.ts:248">
P2: Bounds persistence performs sync file writes on every resize/move event. This can introduce visible jank while moving or resizing the window.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/overlay/OverlayApp.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/overlay/OverlayApp.tsx:174">
P2: TTS dedup keys on message text instead of message identity. Repeated assistant replies with the same content will not be spoken on later turns.</violation>
</file>

<file name="desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx">

<violation number="1" location="desktop/windows/src/renderer/src/components/conversations/NameSpeakerSheet.tsx:58">
P2: `commitNewPerson` lacks an in-flight guard, so repeated Enter presses can fire duplicate create requests.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread desktop/windows/src/main/index.ts Outdated
- saveEdit: only close editor on success or empty input; keep open on IPC
  error so the user can retry without losing their unsaved title text
- index.ts: add clipboard-sanitized-write to permission allowlist to
  prevent copy-to-clipboard actions from silently breaking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant