Skip to content

Add menu bar status item and popover#3

Closed
Newarr wants to merge 10 commits intofeature/headless-service-initfrom
feature/menu-bar-status-item
Closed

Add menu bar status item and popover#3
Newarr wants to merge 10 commits intofeature/headless-service-initfrom
feature/menu-bar-status-item

Conversation

@Newarr
Copy link
Copy Markdown
Owner

@Newarr Newarr commented Mar 20, 2026

Problem

OpenOats has no menu bar presence. When running as a background listener (upcoming LSUIElement mode), users need a way to see recording state, start/stop sessions, and access the main window without a dock icon.

Solution

Two new files implementing a menu bar status item via NSStatusItem + NSPopover (not SwiftUI MenuBarExtra, which cannot reliably handle dynamic icons or survive activation policy changes).

MenuBarController

Manages the NSStatusItem with two template image states:

  • Idle: waveform.circle
  • Recording: waveform.circle.fill

Polls coordinator.isRecording at 250ms to toggle the icon. Hosts the popover via NSHostingController.

MenuBarPopoverView

280pt-wide SwiftUI popover with 4 items:

  1. Status line: "Idle" / "Listening for meetings..." / "Recording - 3:42" (live timer from actual start time)
  2. Start/Stop Recording: .borderedProminent, red tint for stop. Drives coordinator directly (no ContentView dependency).
  3. Open OpenOats: Re-shows the main window
  4. Quit OpenOats: Terminates the app

Changed files

File Change
MenuBarController.swift New -- NSStatusItem + NSPopover management, icon state polling
MenuBarPopoverView.swift New -- SwiftUI popover with status, controls, and elapsed timer

Test plan

  • Menu bar icon appears on launch
  • Icon switches to filled variant when recording starts
  • Icon switches back to outline when recording stops
  • Popover shows correct status in all three states (idle, listening, recording)
  • Timer shows actual elapsed time, not reset-on-reopen
  • "Start Recording" works and begins transcription
  • "Stop Recording" stops transcription
  • "Open OpenOats" brings main window to front
  • "Quit OpenOats" terminates the app

PR 3 of 4 in the menu bar background mode series.
Depends on: #2 Extract service initialization
Next: #4 Enable background mode lifecycle

@Newarr Newarr mentioned this pull request Mar 20, 2026
11 tasks
@Newarr Newarr force-pushed the feature/headless-service-init branch from c522a3a to 0a8df8c Compare March 20, 2026 18:48
@Newarr Newarr force-pushed the feature/menu-bar-status-item branch from c11552e to 07b634e Compare March 20, 2026 18:48
NSStatusItem with two icon states: waveform.circle (idle) and
waveform.circle.fill (recording). Icon updates reactively via
withObservationTracking (zero wakeups when idle).

Popover shows live recording status with elapsed timer computed
from actual session start time, Start/Stop button with consent
gate, Show App, and Quit. Uses NSPopover with NSHostingController
for reliable sizing and focus behavior.
@Newarr Newarr force-pushed the feature/menu-bar-status-item branch from 07b634e to 7c582f4 Compare March 20, 2026 19:01
yazins-ai and others added 9 commits March 20, 2026 22:13
Add acoustic echo cancellation via Apple Voice Processing. Enables setVoiceProcessingEnabled on AVAudioEngine mic input to cancel speaker echo that causes duplicate transcription. On by default with toggle in Settings > Recording. Closes yazinsai#88
Redesign NotesView: 2-tab model with batch cleanup

Replaces the 3-state Raw/Refined/Notes detail view with a clean 2-tab segmented picker (Transcript / Notes). Adds batch transcript cleanup via TranscriptCleanupEngine for on-demand refinement of past sessions.

Based on the design direction from @Newarr in PRs yazinsai#82 and yazinsai#83 — thank you for the original work! Reimplemented to keep drain() timeout at 5s and strip unrelated scope changes.

Closes yazinsai#82, closes yazinsai#83.
Add structured Markdown meeting output (openoats/v1)

Produces `.md` files in ~/Documents/OpenOats/ alongside existing `.txt` output.
YAML frontmatter with date, duration, participants, ASR engine, and meeting app.
Body sections: Summary, Action Items, Decisions, Transcript (high-signal first).
Speaker-attributed transcript lines with relative timestamps.

Three processing stages: raw transcript (always written at finalization),
LLM-enriched sections inserted when notes are generated. Existing .txt
output is preserved unchanged.

Includes format specification, example transcript, and 42 unit tests.

Closes no issues — new capability.
Derive isRecording from coordinator state machine

Replace writable stored `_isRecording` property with a computed property
that reads directly from the coordinator's authoritative `MeetingState`.
Eliminates stale-value risk from the 100ms polling loop.

Also adds `MeetingMetadata.manual()` static factory to deduplicate inline
metadata construction across call sites.
Extract service initialization for headless operation

Add ensureServicesInitialized() on AppRuntime with an idempotency guard.
Coordinator-owned services (TranscriptionEngine, TranscriptLogger,
RefinementEngine, AudioRecorder) are created on first call from wherever
runs first. ContentView.task now only creates view-local services
(KnowledgeBase, SuggestionEngine) that don't need to outlive the window.
Add clearModelCache() to TranscriptionBackend protocol so corrupt model
files are removed on load failure. Each backend (Parakeet, Qwen3,
WhisperKit) implements targeted cache deletion. TranscriptionEngine now
clears cache and resets download state on failure, so "Download Now"
triggers a fresh download.
Add horizontal wordmark logo (icon + text) for marketing and documentation use. Contributed by @Alex-Wengg.
…yazinsai#95) (yazinsai#103)

Fix mic tap format failure and AEC conflict with system audio capture. Based on analysis from @brandonbloom in yazinsai#95.
Newarr pushed a commit that referenced this pull request Mar 22, 2026


- Make cancel() async and await task completion to prevent data races (#1)
- Move session-switch guard before backfillRefinedText to prevent stale writes (#3)
- Cancel cleanup engine in teardownMeetingDetection (#4)
- Add .pre-llm.md backup before overwriting Markdown with LLM sections (yazinsai#10)
- Add safety documentation for nonisolated(unsafe) vars (#2)
- Add unit tests for chunkRecords and parseResponse (#5)

https://claude.ai/code/session_01QTKjQDQpoVqFrPJM8bp218
@Newarr Newarr deleted the branch feature/headless-service-init March 30, 2026 11:41
@Newarr Newarr closed this Mar 30, 2026
@Newarr Newarr deleted the feature/menu-bar-status-item branch March 30, 2026 11:41
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.

3 participants