Track 3 submission: rework of notch floating bar into a chat-first dynamic island#10247
Track 3 submission: rework of notch floating bar into a chat-first dynamic island#10247katipally wants to merge 26 commits into
Conversation
…orner radii Top corners curve inward into the bezel, bottom corners flare out; both radii animate via AnimatablePair so the shape morphs between closed (6/14) and open (20/26).
…math Hardware measurement (auxiliary top areas + safe-area inset) extracted as injectable pure functions; open/close/tab springs live in one place.
…ontent open > listening > thinking > hint > notification > idle, as a pure derive function with the priority order pinned by tests.
Fixed window sized to the largest any presentation needs; measured chat height (4pt jitter filter upstream) clamps to half the screen and persists across opens. Hover dwell and grace use injected clocks so the tests never sleep.
Non-activating panel at the assistive-tech level (clears notch-companion and overlay apps, covers the menu bar), menu-tracking level drop so the notch cannot occlude its own menus, yields to system permission dialogs, and takes the keyboard only while expanded.
Settings gear and notification click-through activate the main window and post navigation only once its receivers are mounted (no fixed delays).
The notch chat is ChatMessagesView over ChatProvider.mainInstance (one transcript, INV-CHAT-1); the shared view gains an optional content height callback that drives the panel growth, and a live voice strip renders the in-flight turn until the journaled pair lands.
…aveform One glass slot below the body: the composer (paperclip picker + drop, 1-3 line field, Return sends / Shift+Return newlines), a pulsing Stop pill while responding, and a live waveform pill with the in-flight transcript while push-to-talk records. Liquid Glass on macOS 26 with a HUD-blur fallback at the 14.0 floor.
Lists running/recent agent sessions; a row opens that agent's conversation rendered by the same shared ChatMessagesView.
Same click-through/Execute/dismiss affordances; queueing and journal admission stay in the manager.
…imelines The black island is its own NotchShape layer whose frame always interpolates (identity can never break), content crossfades inside it. Discrete morphs ride the open/close/tab springs keyed on the presentation; streaming growth rides an isolated smooth timeline so the measure loop cannot oscillate. Closed chrome keeps the omi mark (with recording dot) and gear hugging the camera; open header tabs flank the camera void; voice answers auto-open the panel under the mouse.
One panel per display keyed by CGDirectDisplayID with debounced rebuilds; pointer monitors install only while a panel is open. The close zone is body-plus-tray generously inset, held while the pointer aims at it (velocity cone), with click-away, Esc, post-open grace, and a long outside dwell; guards for responding/voice/drag are honored.
setup() starts NotchScreenManager (the legacy window is no longer created; its code stays dormant in-tree for a follow-up removal). The query pipeline (openAIInputWithQuery, routeQuery, sendAIQuery, sendVoiceOnlyQuery, follow-ups, busy/journal handling), notification queue/present/dismiss, show/hide/snooze, toggles, agent timeline opens, recording indicator feed, and the automation surface all run on notchState and the screen manager. The Ask-Omi hotkey opens notch chat. Facade signatures unchanged for all external callers.
Hub voice turns journal only at turn end, so the bar state mirrors the finalized transcript and the streaming reply purely for display; the notch chat renders question-above-reply mid-turn and the journaled pair replaces the strip without duplication. A new hold clears the previous mirror. PTT blocked by the free-tier limit flashes a visible notch hint instead of silently no-oping.
Re-pins moved contracts (dot-ring mark extraction, drill-in clearing, canonical-session seam on AgentPillsManager) and drops asserts for strings absent on main.
…urface sendMainDraft cleared draftText only at journal-acceptance time. When a send creates a session mid-turn the active draft key drifts, so the sent text stayed persisted under the original key and reloaded into the composer once the context returned to it. Clear the draft up front (writing the empty value under the key it was typed against) and restore it only if the send never enters the timeline (usage limit, offline, busy).
…ntrol Clear the composer field in the same run loop as the tap so it repaints immediately (TextField(axis:.vertical) does not reliably reflect an async external clear during focus). Add a stop button to the listening pill that cancels the in-flight push-to-talk turn.
The partial transcript was suppressed on the bar by the noise policy, so the pill only ever read "Listening…". Surface the growing partial into the pill's ephemeral live text so the user sees their words as they speak; the settled final still governs the committed chat transcript.
The spawn card exposed only a 12pt corner link-out icon and no body tap, so it read as non-interactive. Make the entire card open the agent (the icon stays as an affordance); a spawn card has no competing tap action, so this is safe on every surface that renders it.
…d path ask_main_chat drives sendMessage directly and never exercised the composer's sendMainDraft path, so the draft-clear/restore behavior had no automation coverage. Add a non-prod send_main_draft action that drives the exact composer path so the notch/main text send can be verified end to end.
Git-on-my-level
left a comment
There was a problem hiding this comment.
Thanks for the thoughtful Track 3 submission — the chat-first notch direction is coherent and the new tests around geometry/presentation are useful. I can't approve or land this as-is yet because I found a couple of blocking integration regressions in the notch rewrite:
-
Disabled/snoozed floating-bar state is bypassed at startup.
NotchScreenManager.makePanelcallswindow.orderFrontRegardless()as soon as each panel is created, andFloatingControlBarManager.setup()starts the manager before checkingisEnabled/snooze state. That means the notch panels can appear on launch even when the user had hidden/disabled or deferred the floating bar; the old window path created the window without necessarily ordering it front. Please make initial panel creation respect the persisted enabled/snoozed/deferred state, and only order panels front through the existing show/temporary-notification paths. -
Some legacy
window-based paths are still wired even though the new setup leavesFloatingControlBarManager.windownil. For examplecloseAskOmiForAutomationnow always returnsfloating_bar_window_unavailable, anddeliverAgentArtifactCompletionToFloatingSurfacedrops completed agent artifacts behindguard let window else { return }. Those are part of the desktop automation/agent-surface contract and need to be ported tonotchScreenManager/notchStaterather than silently no-oping.
Given the size and UX direction here, this should also get human maintainer/product review after those blockers are fixed. I did not see an attack or supply-chain concern, and I appreciate the regression tests that were added; the remaining ask is to close these integration gaps and include validation evidence for the disabled/snoozed startup path plus the automation/agent-completion paths.
by AI on behalf of David — if you need David’s attention urgently, please @Git-on-my-level and escalate with need human response.
…nches NotchScreenManager.makePanel ordered every panel front the moment it was created, so the notch appeared on launch even when the user had disabled or snoozed the floating bar (the old window path created its window without ordering it front). Panels are now created hidden; visibility flows through applyPanelVisibility, which orders a panel on screen when the notch is passively visible OR that panel is explicitly open. An Ask Omi summon still surfaces the panel while the bar is disabled/snoozed and hides it again on close, matching the pre-notch "show temporarily, hide on close" behavior. Verification: - Hermetic: NotchScreenManagerVisibilityTests asserts real NSWindow.isVisible — panels start hidden, showAll reveals one per display, hideAll hides, and an explicit open surfaces while passively hidden then re-hides on close. - Live (named bundle, Quartz on-screen window list): enabled launch shows 2 notch windows; disabled launch shows 0; open_ask_omi while disabled surfaces one, close_ask_omi hides it again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p stale paths
FloatingControlBarManager.window is never assigned in the notch architecture,
so every window-guarded path silently no-oped. closeAskOmiForAutomation
(bridge action close_ask_omi) always returned floating_bar_window_unavailable;
it now drives notchScreenManager (closeAll + wait), mirroring open_ask_omi.
Removed the dead window field and the paths that only reached it:
- deliverAgentArtifactCompletionToFloatingSurface (completions already surface
via provider.projectJournalTurn into the shared notch timeline, INV-6)
- the orphaned openRecentNotificationConversation / openNotificationConversation
/ archiveVisibleConversationIfNeeded island (live path is openNotificationAsChat)
- the vestigial resizeForPTT wrapper (notch PTT sizing is owned by the
presentation ladder) and its now-dead caller
- write-only mostRecentNotificationKey and the unused recentNotificationReuseInterval
The legacy FloatingControlBarWindow class itself is left for a separate follow-up
to keep this diff reviewable.
Verification:
- Live (named bundle): close_ask_omi returns {"askOmiOpen":"false"} instead of an
error, in both enabled and disabled states.
- AgentPillLifecycleTests updated to pin the real completion delivery
(projectJournalTurn, no window path) and archiving on notchState.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
charge your keyboard |
|
@Git-on-my-level Thanks for the careful review. Both blockers were real and are now fixed, with validation.
Evidence (named bundle + Quartz on-screen window list): enabled launch = 2 notch windows, disabled launch = 0, open_ask_omi while disabled surfaces one, close_ask_omi hides it again. Hermetic NotchScreenManagerVisibilityTests assert real NSWindow.isVisible for created-hidden, showAll, hideAll, and summon-while-hidden.
Green across the board: full desktop Swift suite (352 suites), INV-6 continuity harness, swift-format, SwiftLint, line-count ratchet, e2e coverage. Failure-Class: FC-split-mutation-authority. No merge conflicts with main. |
|
@undivisible lol priorities, keyboard gets charged the moment this PR is done |
|
@undivisible Curious what you both think of this design direction. Worth me doubling down, more features, cleaner UI/UX and animations, pushing it toward a real Apple-native feel? Or keep it minimal for now? Want to build toward whatever fits your vision |
i personally love your design but we've been talking internally about removing chat from the notch to take a more verbal first direction when not using the app and keep text inputs within the app. |
This is my submission for the Omi Summer challenge (Track 3 — Extra). I already submitted for Track 1 (Agents) #10007 #10088 . While I wait to hear back, I went looking for something in the Omi desktop app I could make meaningfully better, and the macOS notch stood out. The notch already ships; this PR improves its UI/UX so it is clearer to understand and smoother to use. Nothing about what the notch can do was thrown away. I reworked how it looks, animates, and flows.
Where this stands (for maintainers)
This is a first pass I'm putting up so you can actually try the direction. What's here is finished and works; it just isn't a complete, top-to-bottom redesign of the notch. I focused on the core interaction (hover-to-chat, the grow animation, and voice) and kept every existing feature working.
If you like where it's going, I'm happy to double down: finish the full notch design, get every animation buttery smooth and clean, and make sure every feature maps over perfectly. Tell me what you'd change or how far you want me to take it, and I'll finish it out.
The problem I found
The notch is Omi's always-on floating bar, the quickest way to reach Omi without opening the full app. But a couple of things about it get in the way. The animation glitches: three separate drivers run at once and desync, an AppKit window-frame animation (
NSAnimationContext+animator().setFrame), a SwiftUI spring on the switcher morph (notchSwitcherProgress), and a scale plus opacity on the reveal (notchRevealProgress), so opening it reads as a fade or a jump instead of an actual grow. The flow is confusing too: the logo (an agent-pills row) opens the agent switcher, and the Ask-Omi button is wired to open the main app window (onAskAI→openMainAppWindow()) rather than letting you chat right in the notch. It never reads as one clean surface.So I cleaned it up. Same feature set, clearer design: hover the notch and your Omi chat grows straight out of it, with one motion and a flow that does the obvious thing.
Demo (under 2 minutes)
t3_demo.mp4
What changed, and why it feels right now
ChatProvider.mainInstance, not a copy), with a floating glass composer below it (attach, growing field, Return to send, Stop while responding). Ask Omi and the global hotkey now open the notch into chat instead of leaving to the main window. Height fits the conversation, caps at half the screen, and is remembered so the first open doesn't jump.Nothing was dropped
Every floating-bar feature still works after the redesign: PTT voice turns (hub and batch), voice playback settings, proactive notifications (queue, click-through, Execute, dismiss), the Ask-Omi hotkey, agent timeline opens, the recording indicator, show/hide/snooze preferences, the automation bridge (
omi-ctlstate now tells the truth about the notch), and usage limiting.Two things I deliberately retired because the new design makes them meaningless: the manual bottom-corner resize (height is fully dynamic now) and the draggable pill preference (every display just gets a notch). The old window and view code stays dormant in-tree so removing it can be its own reviewable follow-up rather than a 7k-line delete buried in this PR.
Product invariants
ChatProvider.mainInstance.messagesdirectly (no copy); the live voice strip is display-only mirror state, cleared once the journaled exchange lands; sends go throughsendMainDraftand the kernel-routed query pipeline; snapshot aliases unchanged.check-mobile-production-routing.pypasses (exit 0) on this branch.PTTBarPresenterstill projects reducer state; the query pipeline keeps owner-bound dispatch, a single terminal outcome, and journal-acceptance semantics. Only the presentation surface moved from the legacy window to the notch state. The live listening pill now mirrors the provider's partial transcript for display only; it never enters the committed turn, which is still governed by the settled final.sendMainDraftonly clears and restores the local composer draft around the existing send path; it does not touchruntimeOwnerId, session mint, orAuthSessionCoordinator. Thesend_main_draftbridge action is non-production only and drives the same owner-bound send.Verification
xcrun swift build -c debug --package-path Desktopgreen../scripts/agent-logic-harness.sh --swift-onlygreen (INV-6 filter suites included). Full isolated Swift suite run (scripts/swift-test-suites.sh, 351 suites) green.agent-continuity-gauntlet.sh --suite continuity: typed, PTT, and blind recall against the real backend on a named bundle) passed on this branch's continuity code (passed: true). The chat and PTT files under test are unchanged since that run (verified byte-identical); later rebases only advanced the upstream base over release and changelog commits.NotchViewModelTests(hover dwell via injected clock, grace, height clamp and persistence, sizing authority),NotchGeometryTests,NotchShapeTests,NotchAutoCloseZoneTests(zone math and menu-aim cone),NotchWindowLevelTests,NotchPresentationLadderTests. No wall-clock sleeps.omi-notch-v2): hover-open into chat; typed send streamed back with the same message visible in the main window (one timeline); expansion checked frame by frame; Esc and click-away close; a real hub PTT turn with the listening waveform, a mid-stream capture showing the question above the streaming reply, and the journaled pair landing without duplication; the blocked-PTT hint;open_ask_omiautomation opening the notch focused with a truthfulomi-ctl state.send_main_draftbridge action drivessendMainDraft, and after a full turn settles the composer draft reads empty (the sent text neither lingers nor resurfaces).make preflightandscripts/pr-preflightpass with this body; changelog fragment, e2e flow covers, and the line-count and test-quality ratchets are all satisfied.Failure-Class: none