Skip to content

Commit 6dbab8d

Browse files
committed
fix(captions): open caption settings in place of the transcript pane
Caption settings replace the transcript pane inside the same inspector card instead of floating in a popover beside it, so they share its position and sizing. The header's "Show captions" shortcut is removed.
1 parent 0d29fd7 commit 6dbab8d

19 files changed

Lines changed: 38 additions & 113 deletions

‎src/components/ai-edition/CaptionsPane.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ export function CaptionsPane({ onClose }: { onClose?: () => void } = {}) {
198198
className={styles.paneHead}
199199
style={{
200200
position: "relative",
201-
paddingRight: "var(--sp-4)",
202201
flexShrink: 0,
203202
}}
204203
>

‎src/components/ai-edition/NewEditorShell.module.css‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,20 +1335,6 @@ textarea.control {
13351335
box-shadow: 0 0 0 2px color-mix(in oklab, var(--brand), transparent 70%);
13361336
}
13371337

1338-
.captionsPopover {
1339-
width: 310px;
1340-
display: flex;
1341-
flex-direction: column;
1342-
border: 1px solid var(--border);
1343-
border-radius: 14px;
1344-
background: var(--surface-1);
1345-
box-shadow: var(--elev-pop);
1346-
backdrop-filter: blur(18px);
1347-
-webkit-backdrop-filter: blur(18px);
1348-
max-height: min(var(--radix-popover-content-available-height, 80vh), 560px);
1349-
overflow: hidden;
1350-
}
1351-
13521338
/* ─── toggle (switch) ──────────────────────────────────────────── */
13531339
.toggle {
13541340
width: 40px;

‎src/components/ai-edition/RightPanes.tsx‎

Lines changed: 13 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ import { removedRawSpans } from "@/lib/ai-edition/timeline/programme-time";
9696
import {
9797
type AssetTranscriptionView,
9898
type TranscriptGateReason,
99-
transcriptHasSpeech,
10099
} from "@/lib/ai-edition/transcription/status";
101100
import { getAssetPath } from "@/lib/assetPath";
102101
import { resolveWebcamLayoutPreset, supportsWebcamReactiveZoom } from "@/lib/compositeLayout";
@@ -767,51 +766,17 @@ function TranscriptLaneSwitch({
767766
}
768767

769768
/**
770-
* Caption settings, reached from the transcript tab (issue #560).
771-
*
772-
* The pane is reused VERBATIM rather than rebuilt into a popover body: it is ~600
773-
* lines of settings that already work, and "make it a popover" is a question about
774-
* where it is mounted, not about what it contains. Rebuilding it would have been the
775-
* one reliable way to arrive at a popover that is not at parity with the tab it
776-
* replaces.
777-
*
778-
* Safe inside a Popover specifically because nothing in it takes focus away — no file
779-
* input, no OS dialog. That is the trap `useWallpaperFileInput` documents above, and
780-
* it is worth re-checking if a picker is ever added to captions.
769+
* Caption settings, reached from the transcript tab (issue #560). Opening them swaps
770+
* the transcript pane for `CaptionsPane` in the same inspector card, so the settings
771+
* take the transcript's place and size instead of floating beside it.
781772
*/
782-
function CaptionSettingsButton({ onShowCaptions }: { onShowCaptions?: () => void }) {
773+
function CaptionSettingsButton({ onOpen }: { onOpen: () => void }) {
783774
const ts = useScopedT("settings");
784-
const [open, setOpen] = useState(false);
785775
return (
786-
<>
787-
{/* Captions are off by default: once there is speech to caption, offer them where
788-
the transcript is, rather than leaving them behind the settings popover. */}
789-
{onShowCaptions ? (
790-
<button type="button" className={styles.paneHeadBtn} onClick={onShowCaptions}>
791-
{ts("captions.turnOn")}
792-
</button>
793-
) : null}
794-
<Popover open={open} onOpenChange={setOpen}>
795-
<PopoverTrigger asChild>
796-
<button type="button" className={styles.paneHeadBtn} aria-expanded={open}>
797-
<CaptionsIcon size={14} />
798-
{ts("facets.captions")}
799-
</button>
800-
</PopoverTrigger>
801-
<PopoverContent
802-
align="end"
803-
side="bottom"
804-
sideOffset={8}
805-
collisionPadding={16}
806-
animated={false}
807-
className="w-auto border-0 bg-transparent p-0 shadow-none z-50"
808-
>
809-
<div className={styles.captionsPopover}>
810-
<CaptionsPane onClose={() => setOpen(false)} />
811-
</div>
812-
</PopoverContent>
813-
</Popover>
814-
</>
776+
<button type="button" className={styles.paneHeadBtn} onClick={onOpen}>
777+
<CaptionsIcon size={14} />
778+
{ts("facets.captions")}
779+
</button>
815780
);
816781
}
817782

@@ -936,6 +901,7 @@ export function TranscriptPane({
936901
// engine, nothing attempted) leaves the button worth pressing.
937902
const silentMedia = blocked?.reason === "no-audio";
938903
const transcriptionLabel = useTranscriptionLabel();
904+
const [captionsOpen, setCaptionsOpen] = useState(false);
939905
const paneBusyLabel = transcriptionBusyLabel(
940906
busyView ??
941907
(isTranscribing ? { assetId: "", status: "running", phase: "loading-model" } : undefined),
@@ -949,13 +915,15 @@ export function TranscriptPane({
949915
insertionsEnabled() ? "transcript.editingHintDev" : "transcript.editingHint",
950916
);
951917

918+
if (captionsOpen) return <CaptionsPane onClose={() => setCaptionsOpen(false)} />;
919+
952920
if (placements.length === 0 || !hasAnyTranscript) {
953921
return (
954922
<Pane
955923
title={ts("transcript.title")}
956924
icon={<FileText size={16} />}
957925
helpText={helpText}
958-
actions={<CaptionSettingsButton />}
926+
actions={<CaptionSettingsButton onOpen={() => setCaptionsOpen(true)} />}
959927
>
960928
{laneSwitch}
961929
<div
@@ -1001,22 +969,12 @@ export function TranscriptPane({
1001969
);
1002970
}
1003971

1004-
// Speech, not just segments: a silence-only transcript has nothing to caption.
1005-
const hasSpeech = sections.some((section) => transcriptHasSpeech(section.transcript ?? null));
1006972
return (
1007973
<Pane
1008974
title={ts("transcript.title")}
1009975
icon={<FileText size={16} />}
1010976
helpText={helpText}
1011-
actions={
1012-
<CaptionSettingsButton
1013-
onShowCaptions={
1014-
hasSpeech && !captionSettings.enabled
1015-
? () => void setCaptionSettings({ enabled: true })
1016-
: undefined
1017-
}
1018-
/>
1019-
}
977+
actions={<CaptionSettingsButton onOpen={() => setCaptionsOpen(true)} />}
1020978
>
1021979
{laneSwitch}
1022980
{/* The gestures are invisible until tried: nothing on a plain word stream says

‎src/components/ai-edition/TranscriptPane.captions.test.tsx‎

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// settings at all — and it really does open the pane, not a rebuilt stub of it.
1111

1212
import "@testing-library/jest-dom";
13-
import { cleanup, render, screen, within } from "@testing-library/react";
13+
import { cleanup, render, screen } from "@testing-library/react";
1414
import userEvent from "@testing-library/user-event";
1515
import { afterEach, describe, expect, it, vi } from "vitest";
1616
import { I18nProvider } from "@/contexts/I18nContext";
@@ -96,26 +96,23 @@ describe("caption settings on the transcript tab", () => {
9696
expect(screen.getByRole("button", { name: "Captions" })).toBeInTheDocument();
9797
});
9898

99-
it("opens the real caption settings rather than a stub", async () => {
99+
it("opens the real caption settings in place of the transcript", async () => {
100100
const user = userEvent.setup();
101101
mount([TRANSCRIPT]);
102102
await user.click(screen.getByRole("button", { name: "Captions" }));
103103
// A control that only the actual CaptionsPane renders — proof the pane was
104-
// mounted whole rather than reimplemented into the popover.
105-
const settings = await screen.findByRole("dialog");
106-
expect(within(settings).getByText("Show captions")).toBeInTheDocument();
104+
// mounted whole rather than reimplemented.
105+
expect(screen.getByText("Show captions")).toBeInTheDocument();
106+
// It takes the transcript's slot in the inspector, not a spot beside it.
107+
expect(screen.queryByRole("heading", { name: "Transcript" })).not.toBeInTheDocument();
107108
});
108109

109-
it("renders a close button and closes the popover when clicked", async () => {
110+
it("goes back to the transcript when closed", async () => {
110111
const user = userEvent.setup();
111112
mount([TRANSCRIPT]);
112113
await user.click(screen.getByRole("button", { name: "Captions" }));
113-
const settings = await screen.findByRole("dialog");
114-
expect(within(settings).getByText("Show captions")).toBeInTheDocument();
115-
116-
const closeBtn = within(settings).getByRole("button", { name: "Close" });
117-
expect(closeBtn).toBeInTheDocument();
118-
await user.click(closeBtn);
119-
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
114+
await user.click(screen.getByRole("button", { name: "Close" }));
115+
expect(screen.queryByText("Show captions")).not.toBeInTheDocument();
116+
expect(screen.getByRole("heading", { name: "Transcript" })).toBeInTheDocument();
120117
});
121118
});

‎src/i18n/locales/ar/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "بسيط",
254254
"light": "فاتح"
255255
},
256-
"customize": "تخصيص",
257-
"turnOn": "إظهار الترجمة"
256+
"customize": "تخصيص"
258257
},
259258
"panes": {
260259
"help": "مساعدة"

‎src/i18n/locales/cs/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "Minimální",
254254
"light": "Světlý"
255255
},
256-
"customize": "Upravit",
257-
"turnOn": "Zobrazit titulky"
256+
"customize": "Upravit"
258257
},
259258
"panes": {
260259
"help": "Nápověda"

‎src/i18n/locales/de/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "Minimal",
254254
"light": "Hell"
255255
},
256-
"customize": "Anpassen",
257-
"turnOn": "Untertitel anzeigen"
256+
"customize": "Anpassen"
258257
},
259258
"panes": {
260259
"help": "Hilfe"

‎src/i18n/locales/en/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "Minimal",
254254
"light": "Light"
255255
},
256-
"customize": "Customize",
257-
"turnOn": "Show captions"
256+
"customize": "Customize"
258257
},
259258
"panes": {
260259
"help": "Help"

‎src/i18n/locales/es/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "Minimalista",
254254
"light": "Claro"
255255
},
256-
"customize": "Personalizar",
257-
"turnOn": "Mostrar subtítulos"
256+
"customize": "Personalizar"
258257
},
259258
"panes": {
260259
"help": "Ayuda"

‎src/i18n/locales/fr/settings.json‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,7 @@
253253
"minimal": "Minimal",
254254
"light": "Clair"
255255
},
256-
"customize": "Personnaliser",
257-
"turnOn": "Afficher les sous-titres"
256+
"customize": "Personnaliser"
258257
},
259258
"panes": {
260259
"help": "Aide"

0 commit comments

Comments
 (0)