Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d146087
feat(desktop): notch silhouette shape with independently animatable c…
katipally Jul 21, 2026
5708a9b
feat(desktop): notch geometry metrics, springs, and pure closed-size …
katipally Jul 21, 2026
a7f51f5
feat(desktop): presentation ladder - one value drives both size and c…
katipally Jul 21, 2026
35bf734
feat(desktop): per-display notch view model with dynamic chat height
katipally Jul 21, 2026
25eb3ae
feat(desktop): notch panel window with gated keyboard capture
katipally Jul 21, 2026
9af0deb
refactor(desktop): extract the omi dot-ring mark for shared chrome
katipally Jul 21, 2026
0f6326d
feat(desktop): main-window reveal helper keyed off the window-key signal
katipally Jul 21, 2026
a7c9b81
feat(desktop): chat tab renders the shared timeline with measured height
katipally Jul 21, 2026
0903fe2
feat(desktop): floating capsule composer - attach, send, stop, live w…
katipally Jul 21, 2026
fc8b21b
feat(desktop): agents tab with drill-in over AgentPillsManager
katipally Jul 21, 2026
bad3a5c
feat(desktop): proactive notification card ported to the notch body
katipally Jul 21, 2026
a3dc0cf
feat(desktop): root notch view - stable island layer, two animation t…
katipally Jul 21, 2026
bd608e0
feat(desktop): per-display screen manager with menu-aim auto-close
katipally Jul 21, 2026
ecab275
feat(desktop): cut the manager over to the notch stack
katipally Jul 21, 2026
f1cbbd2
feat(desktop): live voice-turn mirror and notch hints
katipally Jul 21, 2026
3a10e30
test(desktop): adapt agent lifecycle tripwires to the notch architecture
katipally Jul 21, 2026
fc4b5aa
test(desktop): cover notch sources in the floating-bar functional flow
katipally Jul 21, 2026
d2e14e4
chore(desktop): changelog fragment for the notch rework
katipally Jul 21, 2026
8bfaca0
chore: ratchet floating-bar line-count baselines down for the rework
katipally Jul 21, 2026
b2533c9
fix(desktop): clear the sent chat draft synchronously so it can't res…
katipally Jul 21, 2026
bce426f
feat(desktop): empty the notch composer on send and add a mic stop co…
katipally Jul 21, 2026
a87187d
feat(desktop): show the live voice partial in the notch listening pill
katipally Jul 21, 2026
c442ca1
fix(desktop): make the whole agent spawn card open the agent
katipally Jul 21, 2026
4f44a22
test(desktop): add send_main_draft bridge action for the composer sen…
katipally Jul 21, 2026
b20aa36
fix(desktop): respect persisted floating-bar state when the notch lau…
katipally Jul 22, 2026
f76ebe5
fix(desktop): port Ask Omi close off the dead floating window and dro…
katipally Jul 22, 2026
2d544eb
fix(desktop): capture the notch panel and add debug_bar_state text
katipally Jul 22, 2026
c2581f9
feat(desktop): voice-first notch with a morphing Omi orb
katipally Jul 22, 2026
02168db
test(desktop): cover the responding state and voice sizing
katipally Jul 22, 2026
d0c5b94
feat(desktop): tune voice orb width morph and match the logo ring
katipally Jul 22, 2026
6731de6
test(desktop): add a non-prod usage-limit bypass for PTT testing
katipally Jul 22, 2026
055d49c
feat(desktop): make the speaking waveform react to Omi's voice
katipally Jul 22, 2026
82805b5
fix(desktop): thinking width floor and cleaner responding placeholder
katipally Jul 22, 2026
f301cbd
feat(desktop): linger the reply, shimmer the text, refine the orb
katipally Jul 22, 2026
ddcd9cb
test(desktop): cover the response linger lifecycle
katipally Jul 22, 2026
c39fcb5
feat(desktop): add a PTT end cue symmetric to the start cue
katipally Jul 22, 2026
defd8a7
feat(desktop): scroll long voice replies and Esc to dismiss the linger
katipally Jul 22, 2026
bd78b82
feat(desktop): 30% voice cap, auto-follow the stream, tap-to-open hint
katipally Jul 22, 2026
0ed29cc
feat(desktop): pace the reply to speech and justify the voice text
katipally Jul 22, 2026
8abdeb0
fix(desktop): remove the response-done flicker and add breathing space
katipally Jul 22, 2026
e7f322d
fix(desktop): seamless stream-to-final reply and a faded live transcript
katipally Jul 22, 2026
30d8034
fix(desktop): flow the transcript left and speed up the reply reveal
katipally Jul 22, 2026
76bd81b
fix(desktop): center the live transcript
katipally Jul 22, 2026
479c64b
fix(desktop): kill the new-line flicker, 40% cap, drop the live trans…
katipally Jul 22, 2026
7466aca
chore(desktop): changelog fragment for the voice-first notch
katipally Jul 22, 2026
1d24ef2
style(desktop): for-in loop over the Esc monitors (swift-format)
katipally Jul 22, 2026
d30d79b
chore(ci): ratchet desktop line-count baselines for the voice notch
katipally Jul 22, 2026
08f8aba
test(desktop): cover the voice-notch sources in the floating-bar flow
katipally Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"files": {
"desktop/macos/Desktop/Sources/FloatingControlBar/AgentPill.swift": 2375,
"desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarView.swift": 2853,
"desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swift": 4842,
"desktop/macos/Desktop/Sources/FloatingControlBar/PushToTalkManager.swift": 2423,
"desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarView.swift": 2818,
"desktop/macos/Desktop/Sources/FloatingControlBar/FloatingControlBarWindow.swift": 4515,
"desktop/macos/Desktop/Sources/FloatingControlBar/PushToTalkManager.swift": 2413,
"desktop/macos/Desktop/Sources/FloatingControlBar/RealtimeHubSession.swift": 1621
},
"raise_justifications": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"files": {
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift": 1926,
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift": 1930,
"desktop/macos/Desktop/Sources/MainWindow/Pages/AppsPage.swift": 3643,
"desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift": 4543,
"desktop/macos/Desktop/Sources/MainWindow/Pages/MemoriesPage.swift": 3298,
"desktop/macos/Desktop/Sources/MainWindow/Pages/TasksPage.swift": 5518,
"desktop/macos/Desktop/Sources/MainWindow/SidebarView.swift": 1570
},
"raise_justifications": {
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift": "Keeps all chat tool groups compact and collapsed while streamed steps increment, with an explicit expansion policy, fixed collapsed height, and regression coverage.",
"desktop/macos/Desktop/Sources/MainWindow/Components/ChatBubble.swift": "The whole agent spawn card now opens the agent (card-level tap plus the corner affordance) so a delegated task is reachable from every surface that renders it; +4 lines.",
"desktop/macos/Desktop/Sources/MainWindow/Pages/AppsPage.swift": "Rebase reconciliation applies the pinned swift-format to the main-branch app-detail safety fixes; runtime behavior is unchanged.",
"desktop/macos/Desktop/Sources/MainWindow/Pages/DashboardPage.swift": "Home redesign: chat-as-home surface plus the 2nd-brain knows-list hub replace the wordmark/ribbon/WMN-card layout; a component split is tracked follow-up.",
"desktop/macos/Desktop/Sources/MainWindow/Pages/MemoriesPage.swift": "Bridge memory search now awaits the active initial or pagination projection before refreshing, preventing an immediate post-navigation marker search from using stale lifecycle capability state.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"files": {
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": 6214,
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": 6218,
"desktop/macos/Desktop/Sources/Providers/ChatToolExecutor.swift": 2861
},
"raise_justifications": {
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": "ChatTurnOwner reasoning-effort lane derivation",
"desktop/macos/Desktop/Sources/Providers/ChatProvider.swift": "sendMainDraft clears the sent draft up front and restores it only if the send never lands, so the notch composer can't retain or resurface the sent text; +4 lines.",
"desktop/macos/Desktop/Sources/Providers/ChatToolExecutor.swift": "Already-granted permissions no longer reopen System Settings: screen-recording and full-disk-access requests gate their Settings/drag-card opens behind the granted result."
},
"threshold": 1500
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"files": {
"desktop/macos/Desktop/Sources/AuthService.swift": 3294,
"desktop/macos/Desktop/Sources/CloudConnectorFormAutomation.swift": 1678,
"desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift": 4238,
"desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift": 4291,
"desktop/macos/Desktop/Sources/MemoryExportService.swift": 1578,
"desktop/macos/Desktop/Sources/OmiApp.swift": 1638
},
"raise_justifications": {
"desktop/macos/Desktop/Sources/AuthService.swift": "Apple first-auth name capture signals observers (authNameDidUpdate) and persists the name to Firebase so it survives reinstalls.",
"desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift": "Reach-error card gains a debug bridge action so the actionable failure surface is verifiable in-process.",
"desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift": "Non-prod voice-notch verification: debug_bar_state gains a text param and a reset_usage_limit test bypass, and capture_floating_bar_png now falls back to the NotchWindow so the in-process capture works in the notch build; +27 lines.",
"desktop/macos/Desktop/Sources/MemoryExportService.swift": "Pinned swift-format normalizes line layout without changing this source's runtime behavior.",
"desktop/macos/Desktop/Sources/OmiApp.swift": "shared openMainAppWindow helper for menu bar + Open Omi shortcut"
},
Expand Down
18 changes: 17 additions & 1 deletion desktop/macos/Desktop/Sources/AudioLevelMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ class AudioLevelMonitor: ObservableObject {
/// System audio level (0.0 - 1.0)
@Published private(set) var systemLevel: Float = 0.0

/// Voice-response (TTS) output level (0.0 - 1.0), fed from the streaming PCM
/// player as Omi speaks. Drives the notch orb's speaking waveform.
@Published private(set) var playbackLevel: Float = 0.0

// Throttle: only publish at ~5 Hz (every ~200ms)
private let updateInterval: Double = 1.0 / 5.0
private var lastMicUpdate: Double = 0.0
private var lastSysUpdate: Double = 0.0
private var lastPlaybackUpdate: Double = 0.0
private var pendingMicLevel: Float = 0.0
private var pendingSysLevel: Float = 0.0

Expand Down Expand Up @@ -46,10 +51,21 @@ class AudioLevelMonitor: ObservableObject {
}
}

/// Reset both levels to zero
/// Update the voice-response (TTS) playback level - called from the streaming
/// PCM player as chunks play. Throttled to ~5 Hz like the others.
func updatePlaybackLevel(_ level: Float) {
let now = CACurrentMediaTime()
if now - lastPlaybackUpdate >= updateInterval {
lastPlaybackUpdate = now
playbackLevel = level
}
}

/// Reset all levels to zero
func reset() {
microphoneLevel = 0.0
systemLevel = 0.0
playbackLevel = 0.0
pendingMicLevel = 0.0
pendingSysLevel = 0.0
}
Expand Down
59 changes: 56 additions & 3 deletions desktop/macos/Desktop/Sources/DesktopAutomationBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1601,7 +1601,7 @@ final class DesktopAutomationActionRegistry {
register(
name: "debug_bar_state",
summary: "Force floating-bar state: idle|listening|thinking|answering (visual verification)",
params: ["state"]
params: ["state", "text"]
) { params in
let s = (params["state"] ?? "thinking").lowercased()
guard let debugState = VoiceTurnDebugPresentationState(rawValue: s) else {
Expand All @@ -1613,9 +1613,33 @@ final class DesktopAutomationActionRegistry {
guard VoiceTurnCoordinator.shared.applyDebugPresentationState(debugState) else {
return ["error": "a non-debug voice turn is active"]
}
// Optional sample transcript/reply so the notch's live text + dynamic
// height grow can be exercised without a mic. Listening fills the user
// transcript; answering fills the streamed reply mirror.
if let text = params["text"], !text.isEmpty {
switch s {
case "listening": bar.liveVoiceUserText = text
case "answering": bar.liveVoiceAssistantText = text
default: break
}
}
return ["state": s, "usesNotchIsland": bar.usesNotchIsland ? "true" : "false"]
}

register(
name: "reset_usage_limit",
summary: "Bypass the free-tier usage limit on this named bundle so PTT/chat can be tested",
params: []
) { _ in
guard AppBuild.isNonProduction else {
return ["error": "reset_usage_limit is disabled on production bundles"]
}
return await MainActor.run { () -> [String: String] in
FloatingBarUsageLimiter.shared.debugBypassLimit = true
return ["bypassed": "true"]
}
}

register(
name: "debug_reach_error",
summary: "Show the actionable 'Couldn't reach Omi' card on the bar (Retry/Skip) for visual verification",
Expand Down Expand Up @@ -1821,6 +1845,32 @@ final class DesktopAutomationActionRegistry {
]
}

register(
name: "send_main_draft",
summary: "Send the main composer via the draft path (sendMainDraft), exactly like the notch/main text composer",
params: ["text"],
category: "chat",
surfaces: ["main_chat", "ask_omi"],
safety: "local",
sideEffects: ["local_storage", "network"],
examples: ["./scripts/omi-ctl action send_main_draft text='what did I do today?'"]
) { params in
guard AppBuild.isNonProduction else {
return ["error": "send_main_draft is disabled on production bundles"]
}
let text = (params["text"] ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return ["error": "missing 'text'"] }
guard let provider = ChatProvider.mainInstance else {
return ["error": "main ChatProvider not yet initialized"]
}
provider.draftText = text
let tracer = QueryTracer(query: text, inputMode: .text)
await QueryTracerContext.$current.withValue(tracer) {
_ = await provider.sendMainDraft(text)
}
return ["sent": text, "draft_after": provider.draftText]
}

register(
name: "chat_drafts_snapshot",
summary: "Read current main and floating composer drafts (non-prod persistence harness)",
Expand Down Expand Up @@ -2153,9 +2203,12 @@ final class DesktopAutomationActionRegistry {
return ["error": "missing 'path'"]
}
return await MainActor.run { () -> [String: String] in
// The notch build has no legacy FloatingControlBarWindow; capture the
// visible notch panel instead so this bridge works in both builds.
guard
let window = NSApp.windows.compactMap({ $0 as? FloatingControlBarWindow }).first,
window.isVisible,
let window = NSApp.windows.first(where: {
($0 is FloatingControlBarWindow || $0 is NotchWindow) && $0.isVisible
}),
let contentView = window.contentView
else {
return ["error": "no_floating_bar_window"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@ final class FloatingBarUsageLimiter: ObservableObject {
UserDefaults.standard.removeObject(forKey: Self.cachedPlanKey)
}

/// Non-production test override: when true the local usage preflight never
/// blocks, so PTT / chat can be exercised on a named bundle without hitting
/// the free-tier limit. Set via the `reset_usage_limit` bridge action.
var debugBypassLimit = false

var isLimitReached: Bool {
// Test bypass (non-production named bundles only).
if debugBypassLimit {
return false
}
// BYOK users pay their own LLM bill and are never limited. Honor local
// BYOK state so a heartbeat-lagged server quota (allowed=false right
// after activation) can't block chat for a fully-configured BYOK user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,20 +265,12 @@ class FloatingControlBarState: NSObject, ObservableObject {

func apply(_ projection: VoiceTurnUIProjection) {
guard let barState else { return }
let wasExpandedForVoice = barState.isVoiceListening
barState.applyVoiceProjection(projection)
let shouldExpandForVoice = barState.isVoiceListening

// Clear idle hover before the PTT resize so its animated surface cannot
// compete with the reducer-owned voice presentation.
// Clear idle hover so its animated surface cannot compete with the
// reducer-owned voice presentation. Notch PTT sizing is owned by the
// presentation ladder off applyVoiceProjection above.
barState.dismissNotchHoverForVoicePresentation()

if shouldExpandForVoice != wasExpandedForVoice,
!barState.showingAIConversation,
UserDefaults.standard.bool(forKey: .hasCompletedOnboarding)
{
FloatingControlBarManager.shared.resizeForPTT(expanded: shouldExpandForVoice)
}
}
}

Expand Down Expand Up @@ -381,10 +373,50 @@ class FloatingControlBarState: NSObject, ObservableObject {
/// True only when the notch-mode setting is enabled and the current display
/// exposes a real camera housing safe area. External displays keep old pill UI.
@Published var usesNotchIsland: Bool = false

/// Live voice-turn mirror for the notch chat. Hub voice turns journal the
/// exchange only at turn end, so mid-turn the provider timeline has nothing
/// to render; these carry the in-flight transcript and streaming assistant
/// text purely for display (never a second transcript store — the journaled
/// pair replaces them when it lands).
@Published var liveVoiceUserText: String = ""
@Published var liveVoiceAssistantText: String = ""

/// One-shot notch hint outside the voice projection (e.g. PTT blocked by
/// the usage limit). The notch's hint presentation falls back to this when
/// the projection-derived hint is empty.
@Published var transientHintText: String = ""
private var transientHintClearTask: Task<Void, Never>?

func flashHint(_ text: String, for seconds: TimeInterval = 3) {
transientHintText = text
transientHintClearTask?.cancel()
transientHintClearTask = Task { [weak self] in
try? await Task.sleep(for: .seconds(seconds))
guard !Task.isCancelled else { return }
self?.transientHintText = ""
}
}
@Published var notchRevealProgress: CGFloat = 1

private func applyVoiceProjection(_ projection: VoiceTurnUIProjection) {
// A new hold is a new turn: drop the previous turn's streamed reply so it
// can't flash under the fresh question.
if projection.isListening, !voiceProjection.isListening {
liveVoiceAssistantText = ""
liveVoiceUserText = ""
}
voiceProjection = projection
// Mirror the in-flight transcript for the notch chat's live strip; clear
// the whole mirror once the voice presentation fully ends (by then the
// journaled exchange has landed on the shared timeline).
if !projection.transcript.isEmpty {
liveVoiceUserText = projection.transcript
}
if !isVoicePresentationActive {
liveVoiceUserText = ""
liveVoiceAssistantText = ""
}
}

/// Whether the current query originated from voice (PTT). Used to decide
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1854,41 +1854,6 @@ private struct NotchResponseGlowView: View {
}
}

private struct NotchOmiMark: View {
var dotColors: [Color] = []

private static let dotCount = 8
private static let dotDiameterRatio: CGFloat = 0.18
private static let ringRadiusRatio: CGFloat = 0.33

var body: some View {
GeometryReader { geometry in
let size = min(geometry.size.width, geometry.size.height)
let center = CGPoint(
x: geometry.size.width / 2,
y: geometry.size.height / 2
)
let dotDiameter = size * Self.dotDiameterRatio
let ringRadius = size * Self.ringRadiusRatio

ZStack {
ForEach(0..<Self.dotCount, id: \.self) { index in
let angle = Double(index) / Double(Self.dotCount) * Double.pi * 2 - Double.pi
Circle()
.fill(dotColors.indices.contains(index) ? dotColors[index] : Color.white.opacity(0.96))
.frame(width: dotDiameter, height: dotDiameter)
.position(
x: center.x + CGFloat(cos(angle)) * ringRadius,
y: center.y + CGFloat(sin(angle)) * ringRadius
)
}
}
}
.drawingGroup(opaque: false, colorMode: .linear)
.accessibilityHidden(true)
}
}

/// The Omi mark rendered as a spinning "thinking" indicator. The ring's dots
/// carry a brightness trail (bright head → faint tail) so the continuous
/// rotation reads as a sweeping comet rather than a static ring of dots.
Expand Down
Loading