Skip to content

Commit 10e94eb

Browse files
Hide empty transcript tabs
Treat transcript rows without words as empty and show related meetings without an empty transcript panel.
1 parent 4f9dc33 commit 10e94eb

5 files changed

Lines changed: 242 additions & 43 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ export function AudioPlayerProvider({
328328
},
329329
});
330330

331-
const audioExistsValue = Boolean(url) || (audioExists.data ?? false);
331+
const audioExistsValue = audioExists.data ?? false;
332332

333333
const value = useMemo<AudioPlayerContextValue>(
334334
() => ({

apps/desktop/src/session/components/bottom-accessory/index.test.tsx

Lines changed: 184 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { act, renderHook } from "@testing-library/react";
1+
import {
2+
act,
3+
cleanup,
4+
fireEvent,
5+
render,
6+
renderHook,
7+
screen,
8+
} from "@testing-library/react";
29
import { isValidElement } from "react";
310
import { beforeEach, describe, expect, it, vi } from "vitest";
411

@@ -25,6 +32,7 @@ const hoisted = vi.hoisted(() => ({
2532
summary: string | null;
2633
isGenerating: boolean;
2734
}>,
35+
batch: {} as Record<string, { error: string | null }>,
2836
generateMissingPastNotes: vi.fn(),
2937
regeneratePastNote: vi.fn(),
3038
}));
@@ -69,10 +77,12 @@ vi.mock("~/stt/contexts", () => ({
6977
requestedLiveTranscription: boolean | null;
7078
liveTranscriptionActive: boolean | null;
7179
};
80+
batch: Record<string, { error: string | null }>;
7281
}) => unknown,
7382
) =>
7483
selector({
7584
live: hoisted.live,
85+
batch: hoisted.batch,
7686
}),
7787
}));
7888

@@ -88,12 +98,14 @@ import { useSessionBottomAccessory } from "./index";
8898

8999
describe("useSessionBottomAccessory", () => {
90100
beforeEach(() => {
101+
cleanup();
91102
hoisted.hotkeys.clear();
92103
hoisted.live.status = "inactive";
93104
hoisted.live.sessionId = null;
94105
hoisted.live.requestedLiveTranscription = true;
95106
hoisted.live.liveTranscriptionActive = true;
96107
hoisted.pastNotes = [];
108+
hoisted.batch = {};
97109
hoisted.generateMissingPastNotes.mockClear();
98110
hoisted.regeneratePastNote.mockClear();
99111
useShellMock.mockReturnValue({
@@ -113,7 +125,7 @@ describe("useSessionBottomAccessory", () => {
113125
useSessionBottomAccessory({
114126
sessionId: "session-1",
115127
sessionMode: "inactive",
116-
audioUrl: "file:///session.wav",
128+
audioExists: true,
117129
hasTranscript: true,
118130
}),
119131
);
@@ -166,7 +178,7 @@ describe("useSessionBottomAccessory", () => {
166178
useSessionBottomAccessory({
167179
sessionId: "session-1",
168180
sessionMode: "inactive",
169-
audioUrl: "file:///session.wav",
181+
audioExists: true,
170182
hasTranscript: true,
171183
}),
172184
);
@@ -199,7 +211,7 @@ describe("useSessionBottomAccessory", () => {
199211
useSessionBottomAccessory({
200212
sessionId: "session-1",
201213
sessionMode: "inactive",
202-
audioUrl: "file:///session.wav",
214+
audioExists: true,
203215
hasTranscript: true,
204216
}),
205217
);
@@ -222,7 +234,7 @@ describe("useSessionBottomAccessory", () => {
222234
useSessionBottomAccessory({
223235
sessionId: "session-1",
224236
sessionMode: "inactive",
225-
audioUrl: "file:///session.wav",
237+
audioExists: true,
226238
hasTranscript: true,
227239
}),
228240
);
@@ -249,7 +261,7 @@ describe("useSessionBottomAccessory", () => {
249261
useSessionBottomAccessory({
250262
sessionId: "session-1",
251263
sessionMode: "inactive",
252-
audioUrl: "file:///session.wav",
264+
audioExists: true,
253265
hasTranscript: true,
254266
}),
255267
);
@@ -273,6 +285,165 @@ describe("useSessionBottomAccessory", () => {
273285
});
274286
});
275287

288+
it("uses related meetings as the only tab when there is no transcript content", () => {
289+
hoisted.pastNotes = [
290+
{
291+
sessionId: "past-session",
292+
title: "Weekly sync",
293+
dateLabel: "May 28, 2026",
294+
summary: null,
295+
isGenerating: false,
296+
},
297+
];
298+
299+
const { result } = renderHook(() =>
300+
useSessionBottomAccessory({
301+
sessionId: "session-1",
302+
sessionMode: "inactive",
303+
audioExists: false,
304+
hasTranscript: false,
305+
}),
306+
);
307+
308+
expect(result.current.bottomAccessoryState).toEqual({
309+
mode: "transcript_only",
310+
expanded: false,
311+
});
312+
313+
render(result.current.bottomBorderHandle);
314+
315+
expect(screen.queryByRole("button", { name: /Transcript/ })).toBeNull();
316+
317+
fireEvent.click(
318+
screen.getByRole("button", { name: "Expand Related meetings" }),
319+
);
320+
321+
expect(hoisted.generateMissingPastNotes).toHaveBeenCalledTimes(1);
322+
expect(result.current.bottomAccessoryState).toEqual({
323+
mode: "transcript_only",
324+
expanded: true,
325+
});
326+
});
327+
328+
it("keeps the transcript panel available after batch transcription fails without words", () => {
329+
hoisted.batch = {
330+
"session-1": {
331+
error: "batch start failed: connection refused",
332+
},
333+
};
334+
335+
const { result } = renderHook(() =>
336+
useSessionBottomAccessory({
337+
sessionId: "session-1",
338+
sessionMode: "inactive",
339+
audioExists: false,
340+
hasTranscript: false,
341+
}),
342+
);
343+
344+
expect(result.current.bottomAccessoryState).toEqual({
345+
mode: "transcript_only",
346+
expanded: false,
347+
});
348+
expect(result.current.bottomBorderHandle).not.toBeNull();
349+
});
350+
351+
it("keeps the transcript tab visible for batch errors next to related meetings", () => {
352+
hoisted.batch = {
353+
"session-1": {
354+
error: "batch start failed: connection refused",
355+
},
356+
};
357+
hoisted.pastNotes = [
358+
{
359+
sessionId: "past-session",
360+
title: "Weekly sync",
361+
dateLabel: "May 28, 2026",
362+
summary: null,
363+
isGenerating: false,
364+
},
365+
];
366+
367+
const { result } = renderHook(() =>
368+
useSessionBottomAccessory({
369+
sessionId: "session-1",
370+
sessionMode: "inactive",
371+
audioExists: false,
372+
hasTranscript: false,
373+
}),
374+
);
375+
376+
render(result.current.bottomBorderHandle);
377+
378+
expect(
379+
screen.getByRole("button", { name: "Expand Transcript" }),
380+
).not.toBeNull();
381+
expect(
382+
screen.getByRole("button", { name: "Expand Related meetings" }),
383+
).not.toBeNull();
384+
});
385+
386+
it("keeps playback disabled until the audio URL is ready", () => {
387+
const { result, rerender } = renderHook(
388+
({ audioUrlReady }: { audioUrlReady: boolean }) =>
389+
useSessionBottomAccessory({
390+
sessionId: "session-1",
391+
sessionMode: "inactive",
392+
audioExists: true,
393+
audioUrlReady,
394+
hasTranscript: false,
395+
}),
396+
{
397+
initialProps: {
398+
audioUrlReady: false,
399+
},
400+
},
401+
);
402+
403+
expect(result.current.bottomAccessoryState).toEqual({
404+
mode: "transcript_only",
405+
expanded: false,
406+
});
407+
expect(result.current.bottomBorderHandle).not.toBeNull();
408+
409+
rerender({ audioUrlReady: true });
410+
411+
expect(result.current.bottomAccessoryState).toEqual({
412+
mode: "playback",
413+
expanded: false,
414+
});
415+
});
416+
417+
it("keeps the post-session handle visible while audio lookup is loading", () => {
418+
const { result, rerender } = renderHook(
419+
({ isAudioLoading }: { isAudioLoading: boolean }) =>
420+
useSessionBottomAccessory({
421+
sessionId: "session-1",
422+
sessionMode: "inactive",
423+
audioExists: false,
424+
audioUrlReady: false,
425+
isAudioLoading,
426+
hasTranscript: false,
427+
}),
428+
{
429+
initialProps: {
430+
isAudioLoading: true,
431+
},
432+
},
433+
);
434+
435+
expect(result.current.bottomAccessoryState).toEqual({
436+
mode: "transcript_only",
437+
expanded: false,
438+
});
439+
expect(result.current.bottomBorderHandle).not.toBeNull();
440+
441+
rerender({ isAudioLoading: false });
442+
443+
expect(result.current.bottomAccessoryState).toBeNull();
444+
expect(result.current.bottomBorderHandle).toBeNull();
445+
});
446+
276447
it("hides the bottom accessory while recording for batch transcription", () => {
277448
hoisted.live.requestedLiveTranscription = false;
278449
hoisted.live.liveTranscriptionActive = false;
@@ -281,7 +452,7 @@ describe("useSessionBottomAccessory", () => {
281452
useSessionBottomAccessory({
282453
sessionId: "session-1",
283454
sessionMode: "active",
284-
audioUrl: null,
455+
audioExists: false,
285456
hasTranscript: false,
286457
}),
287458
);
@@ -296,7 +467,7 @@ describe("useSessionBottomAccessory", () => {
296467
useSessionBottomAccessory({
297468
sessionId: "session-1",
298469
sessionMode: "finalizing",
299-
audioUrl: null,
470+
audioExists: false,
300471
hasTranscript: false,
301472
}),
302473
);
@@ -314,7 +485,7 @@ describe("useSessionBottomAccessory", () => {
314485
useSessionBottomAccessory({
315486
sessionId: "session-1",
316487
sessionMode: "inactive",
317-
audioUrl: "file:///session.wav",
488+
audioExists: true,
318489
hasTranscript: true,
319490
}),
320491
);
@@ -332,7 +503,7 @@ describe("useSessionBottomAccessory", () => {
332503
useSessionBottomAccessory({
333504
sessionId: "session-1",
334505
sessionMode: "running_batch",
335-
audioUrl: "file:///session.wav",
506+
audioExists: true,
336507
hasTranscript: true,
337508
}),
338509
);
@@ -350,7 +521,7 @@ describe("useSessionBottomAccessory", () => {
350521
useSessionBottomAccessory({
351522
sessionId: "session-1",
352523
sessionMode: "running_batch",
353-
audioUrl: "file:///session.wav",
524+
audioExists: true,
354525
hasTranscript: true,
355526
}),
356527
);
@@ -369,7 +540,7 @@ describe("useSessionBottomAccessory", () => {
369540
useSessionBottomAccessory({
370541
sessionId: "session-1",
371542
sessionMode,
372-
audioUrl: "file:///session.wav",
543+
audioExists: true,
373544
hasTranscript: true,
374545
}),
375546
{
@@ -416,7 +587,7 @@ describe("useSessionBottomAccessory", () => {
416587
useSessionBottomAccessory({
417588
sessionId: "session-1",
418589
sessionMode: "active",
419-
audioUrl: null,
590+
audioExists: false,
420591
hasTranscript: false,
421592
}),
422593
);

0 commit comments

Comments
 (0)