From 7e94e5ee2f51c7d5e5104e3c49c355aa223f7719 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 16 Mar 2026 16:54:49 +0900 Subject: [PATCH 1/3] fix: consolidate listening controls into the header button - Remove the in-note active listening indicator and keep only the in-note resume action - Keep the top-right meeting button visible while recording and show live bars plus stop/finalizing states there - Move the resume listening action to the left of the metadata chip in the note header --- .../session/components/outer-header/index.tsx | 2 +- .../components/outer-header/listen.tsx | 81 +----------- .../src/shared/main/header-listen-button.tsx | 116 ++++++++++++------ 3 files changed, 81 insertions(+), 118 deletions(-) diff --git a/apps/desktop/src/session/components/outer-header/index.tsx b/apps/desktop/src/session/components/outer-header/index.tsx index 2aa7e62884..a9bbeb892e 100644 --- a/apps/desktop/src/session/components/outer-header/index.tsx +++ b/apps/desktop/src/session/components/outer-header/index.tsx @@ -20,8 +20,8 @@ export function OuterHeader({
- +
diff --git a/apps/desktop/src/session/components/outer-header/listen.tsx b/apps/desktop/src/session/components/outer-header/listen.tsx index 4a733d5e2b..6f48869ff8 100644 --- a/apps/desktop/src/session/components/outer-header/listen.tsx +++ b/apps/desktop/src/session/components/outer-header/listen.tsx @@ -1,8 +1,5 @@ -import { useHover } from "@uidotdev/usehooks"; -import { MicOff } from "lucide-react"; import { useCallback } from "react"; -import { DancingSticks } from "@hypr/ui/components/ui/dancing-sticks"; import { Tooltip, TooltipContent, @@ -16,18 +13,13 @@ import { useListenButtonState, } from "~/session/components/shared"; import { useTabs } from "~/store/zustand/tabs"; -import { useListener } from "~/stt/contexts"; import { useStartListening } from "~/stt/useStartListening"; export function ListenButton({ sessionId }: { sessionId: string }) { const { shouldRender } = useListenButtonState(sessionId); const hasTranscript = useHasTranscript(sessionId); - if (!shouldRender) { - return ; - } - - if (hasTranscript) { + if (shouldRender && hasTranscript) { return ; } @@ -82,74 +74,3 @@ function StartButton({ sessionId }: { sessionId: string }) { ); } - -function InMeetingIndicator({ sessionId }: { sessionId: string }) { - const [ref, hovered] = useHover(); - - const { mode, stop, amplitude, muted } = useListener((state) => ({ - mode: state.getSessionMode(sessionId), - stop: state.stop, - amplitude: state.live.amplitude, - muted: state.live.muted, - })); - - const active = mode === "active" || mode === "finalizing"; - const finalizing = mode === "finalizing"; - - if (!active) { - return null; - } - - return ( - - ); -} diff --git a/apps/desktop/src/shared/main/header-listen-button.tsx b/apps/desktop/src/shared/main/header-listen-button.tsx index 0d8c2b2af1..16b7e81b7e 100644 --- a/apps/desktop/src/shared/main/header-listen-button.tsx +++ b/apps/desktop/src/shared/main/header-listen-button.tsx @@ -1,4 +1,4 @@ -import { ChevronDown } from "lucide-react"; +import { ChevronDown, MicOff } from "lucide-react"; import { type MouseEvent, useCallback, @@ -8,6 +8,7 @@ import { } from "react"; import { Button } from "@hypr/ui/components/ui/button"; +import { DancingSticks } from "@hypr/ui/components/ui/dancing-sticks"; import { Popover, PopoverAnchor, @@ -53,7 +54,8 @@ function useHeaderListenVisible() { const isRecording = liveStatus === "active" || liveStatus === "finalizing"; - if (isRecording || loading) return false; + if (isRecording) return true; + if (loading) return false; if (currentTab?.type === "empty") return true; if (currentTab?.type === "sessions" && hasTranscript) return true; @@ -96,7 +98,16 @@ function HeaderListenButtonInner() { const handleClick = useNewNoteAndListen(); const handleUpload = useNewNoteAndUpload(); const openNew = useTabs((state) => state.openNew); + const { status, stop, amplitude, muted } = useListener((state) => ({ + status: state.live.status, + stop: state.stop, + amplitude: state.live.amplitude, + muted: state.live.muted, + })); const [open, setOpen] = useState(false); + const isActive = status === "active"; + const isFinalizing = status === "finalizing"; + const isRecording = isActive || isFinalizing; useEffect(() => { const node = containerRef.current; @@ -149,25 +160,54 @@ function HeaderListenButtonInner() { }); }, [handleUpload]); + const handleButtonClick = isActive ? stop : handleClick; + const button = ( ); @@ -212,36 +252,38 @@ function HeaderListenButtonInner() { ) : ( button )} - {chevron} + {!isRecording && {chevron}} - -
- - -
-
+ {!isRecording && ( + +
+ + +
+
+ )} ); } From ca19ee393aa4ce40da3fdd602359fd7c9241d913 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 16 Mar 2026 17:27:44 +0900 Subject: [PATCH 2/3] fix: consolidate listening controls into the header button - Remove the in-note active listening indicator and keep only the in-note resume action - Keep the top-right meeting button visible while recording and show larger live bars plus hover-to-stop behavior there - Hold the header listening pill at a fixed width across idle, live, and finalizing states - Move the resume listening action to the left of the metadata chip in the note header --- .../src/shared/main/header-listen-button.tsx | 56 ++++++++++++------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/shared/main/header-listen-button.tsx b/apps/desktop/src/shared/main/header-listen-button.tsx index 16b7e81b7e..3dd6a1d74b 100644 --- a/apps/desktop/src/shared/main/header-listen-button.tsx +++ b/apps/desktop/src/shared/main/header-listen-button.tsx @@ -34,6 +34,8 @@ import { useTabs } from "~/store/zustand/tabs"; import { useListener } from "~/stt/contexts"; import { useSTTConnection } from "~/stt/useSTTConnection"; +const LISTEN_BUTTON_WIDTH = "w-44"; + export function HeaderListenButton() { const visible = useHeaderListenVisible(); @@ -170,38 +172,54 @@ function HeaderListenButtonInner() { onContextMenu={isRecording ? undefined : handleOpenMenu} disabled={isFinalizing || (!isRecording && isDisabled)} className={cn([ - "inline-flex items-center justify-center rounded-full text-sm font-medium text-white select-none", - "gap-2", - isRecording ? "h-8 px-4" : "h-8 pr-8 pl-4", + "group relative inline-flex h-8 items-center justify-center rounded-full text-sm font-medium text-white select-none", + LISTEN_BUTTON_WIDTH, + isRecording ? "px-4" : "pr-8 pl-4", "border-2 border-stone-600 bg-stone-800", "transition-all duration-200 ease-out", "hover:bg-stone-700", isFinalizing && "cursor-wait", "disabled:opacity-50", ])} + aria-label={ + isFinalizing + ? "Finalizing" + : isActive + ? "Stop listening" + : "New meeting" + } > {isRecording ? ( - <> +
{isFinalizing ? ( - +
+ + Finalizing +
) : ( <> - {muted && } - + + + {muted && } + + + + + Stop listening + )} - - {isFinalizing ? "Finalizing" : "Stop listening"} - - +
) : ( <> From e25892b930e7ed8b32de7d31d7743781ad7074b7 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Mon, 16 Mar 2026 19:17:12 +0900 Subject: [PATCH 3/3] feat: improve listen button visual design and layout Replace Tailwind class shorthand with explicit width value and redesign button appearance with distinct recording state styling. Add red color scheme for active recording state with improved hover effects. Increase button height and adjust padding for better visual balance. Update warning tooltip to only show when not recording to avoid UI conflicts during active sessions. --- .../src/shared/main/header-listen-button.tsx | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/desktop/src/shared/main/header-listen-button.tsx b/apps/desktop/src/shared/main/header-listen-button.tsx index 3dd6a1d74b..726a01ba2b 100644 --- a/apps/desktop/src/shared/main/header-listen-button.tsx +++ b/apps/desktop/src/shared/main/header-listen-button.tsx @@ -34,7 +34,7 @@ import { useTabs } from "~/store/zustand/tabs"; import { useListener } from "~/stt/contexts"; import { useSTTConnection } from "~/stt/useSTTConnection"; -const LISTEN_BUTTON_WIDTH = "w-44"; +const LISTEN_BUTTON_WIDTH = "w-[160px]"; export function HeaderListenButton() { const visible = useHeaderListenVisible(); @@ -172,12 +172,18 @@ function HeaderListenButtonInner() { onContextMenu={isRecording ? undefined : handleOpenMenu} disabled={isFinalizing || (!isRecording && isDisabled)} className={cn([ - "group relative inline-flex h-8 items-center justify-center rounded-full text-sm font-medium text-white select-none", + "group relative inline-flex h-9 items-center justify-center rounded-full text-sm font-medium select-none", LISTEN_BUTTON_WIDTH, - isRecording ? "px-4" : "pr-8 pl-4", - "border-2 border-stone-600 bg-stone-800", + "px-3", + "border-2", + isRecording + ? "border-red-400 bg-red-50 text-red-600" + : "border-stone-600 bg-stone-800 text-white", "transition-all duration-200 ease-out", - "hover:bg-stone-700", + !isFinalizing && + (isRecording + ? "hover:bg-red-50 hover:text-red-700" + : "hover:bg-stone-700"), isFinalizing && "cursor-wait", "disabled:opacity-50", ])} @@ -200,31 +206,36 @@ function HeaderListenButtonInner() { <> - {muted && } + {muted && } - Stop listening + + + Stop listening + )} ) : ( - <> - - New meeting - + + + + New meeting + + )} ); @@ -232,7 +243,7 @@ function HeaderListenButtonInner() { const chevron = (