Skip to content

Commit 36db835

Browse files
Reduce recording start editor renders
Keep expensive recording-start hooks out of inactive render branches and read audio existence through a narrow query hook instead of the full playback context.
1 parent aac4b5f commit 36db835

4 files changed

Lines changed: 165 additions & 75 deletions

File tree

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
export { AudioPlayerProvider as Provider, useAudioPlayer } from "./provider";
1+
export {
2+
AudioPlayerProvider as Provider,
3+
useAudioExists,
4+
useAudioPlayer,
5+
} from "./provider";
26
export { Timeline } from "./timeline";
37
export { TimelineMeta, TimelineShell } from "./timeline-shell";

apps/desktop/src/audio-player/provider.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ export function useAudioTime(): TimeSnapshot {
9696
return useSyncExternalStore(timeStore.subscribe, timeStore.getSnapshot);
9797
}
9898

99+
export function useAudioExists(sessionId: string): boolean {
100+
const audioExists = useQuery({
101+
queryKey: ["audio", sessionId, "exist"],
102+
queryFn: () => fsSyncCommands.audioExist(sessionId),
103+
select: (result) => {
104+
if (result.status === "error") {
105+
throw new Error(result.error);
106+
}
107+
return result.data;
108+
},
109+
});
110+
111+
return audioExists.data ?? false;
112+
}
113+
99114
export function AudioPlayerProvider({
100115
sessionId,
101116
url,
@@ -113,17 +128,7 @@ export function AudioPlayerProvider({
113128
const [playbackRate, setPlaybackRateState] = useState(1);
114129
const timeStoreRef = useRef(new TimeStore());
115130
const stopRequestedRef = useRef(false);
116-
117-
const audioExists = useQuery({
118-
queryKey: ["audio", sessionId, "exist"],
119-
queryFn: () => fsSyncCommands.audioExist(sessionId),
120-
select: (result) => {
121-
if (result.status === "error") {
122-
throw new Error(result.error);
123-
}
124-
return result.data;
125-
},
126-
});
131+
const audioExistsValue = useAudioExists(sessionId);
127132

128133
const registerContainer = useCallback((el: HTMLDivElement | null) => {
129134
setContainer((prev) => (prev === el ? prev : el));
@@ -328,8 +333,6 @@ export function AudioPlayerProvider({
328333
},
329334
});
330335

331-
const audioExistsValue = audioExists.data ?? false;
332-
333336
const value = useMemo<AudioPlayerContextValue>(
334337
() => ({
335338
registerContainer,

apps/desktop/src/session/components/listen-action.tsx

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,46 @@ import { useStartListening } from "~/stt/useStartListening";
1717
export function ListenActionButton({ sessionId }: { sessionId: string }) {
1818
const { shouldRender, isDisabled, warningMessage } =
1919
useListenButtonState(sessionId);
20-
const { loading, stop } = useListener((state) => ({
21-
loading: state.live.loading && state.live.sessionId === sessionId,
22-
stop: state.stop,
23-
}));
20+
const loading = useListener(
21+
(state) => state.live.loading && state.live.sessionId === sessionId,
22+
);
23+
24+
if (loading) {
25+
return <StopListeningButton />;
26+
}
27+
28+
if (!shouldRender) {
29+
return null;
30+
}
31+
32+
return (
33+
<StartListeningButton
34+
sessionId={sessionId}
35+
isDisabled={isDisabled}
36+
warningMessage={warningMessage}
37+
/>
38+
);
39+
}
40+
41+
function StopListeningButton() {
42+
const stop = useListener((state) => state.stop);
43+
44+
return (
45+
<FloatingButton onClick={stop}>
46+
<Spinner />
47+
</FloatingButton>
48+
);
49+
}
50+
51+
function StartListeningButton({
52+
sessionId,
53+
isDisabled,
54+
warningMessage,
55+
}: {
56+
sessionId: string;
57+
isDisabled: boolean;
58+
warningMessage: string;
59+
}) {
2460
const startListening = useStartListening(sessionId);
2561
const openNew = useTabs((state) => state.openNew);
2662
const noteHasContent = useCurrentNoteHasContent(sessionId, { type: "raw" });
@@ -30,18 +66,6 @@ export function ListenActionButton({ sessionId }: { sessionId: string }) {
3066
openNew({ type: "settings", state: { tab: "transcription" } });
3167
}, [startListening, openNew]);
3268

33-
if (loading) {
34-
return (
35-
<FloatingButton onClick={stop}>
36-
<Spinner />
37-
</FloatingButton>
38-
);
39-
}
40-
41-
if (!shouldRender) {
42-
return null;
43-
}
44-
4569
return (
4670
<div>
4771
<OptionsMenu

apps/desktop/src/session/index.tsx

Lines changed: 104 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,45 @@ export function TabContentNote({
4242
tab: Extract<Tab, { type: "sessions" }>;
4343
}) {
4444
const sessionMode = useListener((state) => state.getSessionMode(tab.id));
45+
const audioExists = AudioPlayer.useAudioExists(tab.id);
46+
47+
const audioUrlQuery = useQuery({
48+
enabled: sessionMode !== "active" && sessionMode !== "finalizing",
49+
queryKey: ["audio", tab.id, "url"],
50+
queryFn: () => fsSyncCommands.audioPath(tab.id),
51+
select: (result) => {
52+
if (result.status === "error") {
53+
return null;
54+
}
55+
return convertFileSrc(result.data);
56+
},
57+
});
58+
const audioUrl = audioUrlQuery.data;
59+
60+
return (
61+
<CaretPositionProvider>
62+
{tab.state.autoStart && !standaloneWindow ? (
63+
<AutoStartListening tab={tab} />
64+
) : null}
65+
<SearchProvider>
66+
<AudioPlayer.Provider sessionId={tab.id} url={audioUrl ?? ""}>
67+
<TabContentNoteInner
68+
tab={tab}
69+
standaloneWindow={standaloneWindow}
70+
audioUrlReady={Boolean(audioUrl)}
71+
audioExists={audioExists}
72+
/>
73+
</AudioPlayer.Provider>
74+
</SearchProvider>
75+
</CaretPositionProvider>
76+
);
77+
}
78+
79+
function AutoStartListening({
80+
tab,
81+
}: {
82+
tab: Extract<Tab, { type: "sessions" }>;
83+
}) {
4584
const canStartLiveSession = useListener((state) =>
4685
state.canStartLiveSession(tab.id),
4786
);
@@ -51,15 +90,6 @@ export function TabContentNote({
5190
const hasAttemptedAutoStart = useRef(false);
5291

5392
useEffect(() => {
54-
if (!tab.state.autoStart) {
55-
hasAttemptedAutoStart.current = false;
56-
return;
57-
}
58-
59-
if (standaloneWindow) {
60-
return;
61-
}
62-
6393
if (hasAttemptedAutoStart.current) {
6494
return;
6595
}
@@ -76,67 +106,96 @@ export function TabContentNote({
76106
startListening();
77107
updateSessionTabState(tab, { ...tab.state, autoStart: null });
78108
}, [
79-
tab.id,
109+
tab,
80110
tab.state,
81-
tab.state.autoStart,
82-
standaloneWindow,
83111
canStartLiveSession,
84112
conn,
85113
startListening,
86114
updateSessionTabState,
87115
]);
88116

89-
const audioUrlQuery = useQuery({
90-
enabled: sessionMode !== "active" && sessionMode !== "finalizing",
91-
queryKey: ["audio", tab.id, "url"],
92-
queryFn: () => fsSyncCommands.audioPath(tab.id),
93-
select: (result) => {
94-
if (result.status === "error") {
95-
return null;
96-
}
97-
return convertFileSrc(result.data);
98-
},
99-
});
100-
const audioUrl = audioUrlQuery.data;
101-
102-
return (
103-
<CaretPositionProvider>
104-
<SearchProvider>
105-
<AudioPlayer.Provider sessionId={tab.id} url={audioUrl ?? ""}>
106-
<TabContentNoteInner
107-
tab={tab}
108-
standaloneWindow={standaloneWindow}
109-
audioUrlReady={Boolean(audioUrl)}
110-
/>
111-
</AudioPlayer.Provider>
112-
</SearchProvider>
113-
</CaretPositionProvider>
114-
);
117+
return null;
115118
}
116119

117120
function TabContentNoteInner({
118121
tab,
119122
standaloneWindow,
120123
audioUrlReady,
124+
audioExists,
121125
}: {
122126
tab: Extract<Tab, { type: "sessions" }>;
123127
standaloneWindow: boolean;
124128
audioUrlReady: boolean;
129+
audioExists: boolean;
125130
}) {
126131
const noteInputRef = React.useRef<NoteInputHandle>(null);
127132

128-
const { audioExists } = AudioPlayer.useAudioPlayer();
129-
const editorTabs = useEditorTabs({ sessionId: tab.id, audioExists });
130-
const currentView = useCurrentNoteTab(tab, { audioExists });
131-
const contentHydrated = useHydrateSessionContent(tab.id);
133+
const sessionId = tab.id;
134+
usePendingUpload(sessionId);
135+
136+
const hasTranscript = useHasTranscript(sessionId);
137+
const sessionMode = useListener((state) => state.getSessionMode(sessionId));
138+
const batchError = useListener((state) => state.batch[sessionId]?.error);
139+
const hasLiveSegments = useListener(
140+
(state) =>
141+
state.live.sessionId === sessionId && state.liveSegments.length > 0,
142+
);
143+
const canShowTranscript = getCanShowTranscript({
144+
audioExists,
145+
batchError: Boolean(batchError),
146+
hasLiveSegments,
147+
hasTranscript,
148+
sessionMode,
149+
});
150+
const canShowInsights = useCanShowInsights(sessionId);
151+
const enhancedNoteIds =
152+
main.UI.useSliceRowIds(
153+
main.INDEXES.enhancedNotesBySession,
154+
sessionId,
155+
main.STORE_ID,
156+
) ?? [];
157+
const firstEnhancedNoteId = enhancedNoteIds[0];
158+
useEnsureDefaultSummaryFromState({
159+
batchError: Boolean(batchError),
160+
enhancedNoteCount: enhancedNoteIds.length,
161+
hasTranscript,
162+
sessionId,
163+
sessionMode,
164+
});
165+
const contentHydrated = useHydrateSessionContent(sessionId);
132166
const updateSessionTabState = useTabs((state) => state.updateSessionTabState);
133167

134-
const sessionId = tab.id;
135168
const { skipReason } = useAutoEnhance(tab);
136-
const sessionMode = useListener((state) => state.getSessionMode(sessionId));
137169
const isTranscribing = shouldShowTranscriptTabSpinner(sessionMode);
170+
const isLiveSessionActive = sessionMode === "active";
171+
const editorTabs = React.useMemo(
172+
() =>
173+
createEditorTabs({
174+
enhancedNoteIds,
175+
canShowInsights,
176+
canShowTranscript,
177+
}),
178+
[enhancedNoteIds, canShowInsights, canShowTranscript],
179+
);
180+
const currentView = React.useMemo(() => {
181+
if (tab.state.view?.type === "insights" && canShowInsights) {
182+
return tab.state.view;
183+
}
184+
185+
return computeCurrentNoteTab(
186+
tab.state.view ?? null,
187+
isLiveSessionActive,
188+
firstEnhancedNoteId,
189+
canShowTranscript,
190+
);
191+
}, [
192+
tab.state.view,
193+
isLiveSessionActive,
194+
firstEnhancedNoteId,
195+
canShowTranscript,
196+
canShowInsights,
197+
]);
138198
useAutoFocusTitle({ sessionId, noteInputRef });
139-
usePendingUpload(sessionId);
140199

141200
const showTopAudioPlayer = shouldShowSessionTopAudioPlayer({
142201
audioExists,

0 commit comments

Comments
 (0)