@@ -96,7 +96,6 @@ import { removedRawSpans } from "@/lib/ai-edition/timeline/programme-time";
9696import {
9797 type AssetTranscriptionView ,
9898 type TranscriptGateReason ,
99- transcriptHasSpeech ,
10099} from "@/lib/ai-edition/transcription/status" ;
101100import { getAssetPath } from "@/lib/assetPath" ;
102101import { 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
0 commit comments