rename TaskRunnerModal's track bindings off t, before keying it

No behaviour change. Separated from the conversion because a 40-site rename hiding
inside a string diff is unreviewable.

Read first rather than renamed blindly, and it earned it twice. `\bt\b` matches 93
times in this file and only some are tracks: `const t = ctx.currentTime` is an audio
timestamp in the beep helper, and `border-t` inside classNames matches too — a
blanket rename would have silently corrupted five CSS classes and the typecheck would
not have said a word about them.

So this renames the track bindings only, by property (`t.id`, `t.lang`, `t.codec`,
`t.channels`, `t.bitrate`, `t.title`) and by parameter, leaving the timestamp and the
class names alone.
This commit is contained in:
2026-08-23 16:02:48 +00:00
parent 084acecd22
commit 452029a6d3
@@ -291,14 +291,16 @@ type TaskInputFormProps = {
hideTrackPickers?: boolean; hideTrackPickers?: boolean;
}; };
const trackLabel = (t: { title: string; lang: string; id: number }) => const trackLabel = (track: { title: string; lang: string; id: number }) =>
t.title || (t.lang && t.lang !== 'und' && t.lang !== 'unknown' ? t.lang.toUpperCase() : '') || `Track ${t.id + 1}`; track.title ||
(track.lang && track.lang !== 'und' && track.lang !== 'unknown' ? track.lang.toUpperCase() : '') ||
`Track ${track.id + 1}`;
// Channel count → friendly layout name; audio meta → "stereo · aac 193k"-style detail for a track row. // Channel count → friendly layout name; audio meta → "stereo · aac 193k"-style detail for a track row.
const chLabel = (n: number) => const chLabel = (n: number) =>
n === 1 ? 'mono' : n === 2 ? 'stereo' : n === 6 ? '5.1' : n === 8 ? '7.1' : n ? `${n}ch` : ''; n === 1 ? 'mono' : n === 2 ? 'stereo' : n === 6 ? '5.1' : n === 8 ? '7.1' : n ? `${n}ch` : '';
const audioMeta = (t: AudioTrack) => const audioMeta = (track: AudioTrack) =>
[chLabel(t.channels), t.codec].filter(Boolean).join(' ') + (t.bitrate ? ` ${t.bitrate}k` : ''); [chLabel(track.channels), track.codec].filter(Boolean).join(' ') + (track.bitrate ? ` ${track.bitrate}k` : '');
// subtitle_edit input: per-subtitle keep flag + editable label, serialized to JSON in the form value. // subtitle_edit input: per-subtitle keep flag + editable label, serialized to JSON in the form value.
type SubtitleEditEntry = { id: number; keep: boolean; label: string }; type SubtitleEditEntry = { id: number; keep: boolean; label: string };
@@ -333,14 +335,16 @@ export const TaskInputForm = ({
if (def.type === 'subtitle_edit') { if (def.type === 'subtitle_edit') {
const tracks = subtitleTracks ?? []; const tracks = subtitleTracks ?? [];
const byId = new Map(parseSubtitleSpec(values[key]).map((e) => [e.id, e])); const byId = new Map(parseSubtitleSpec(values[key]).map((e) => [e.id, e]));
const entryFor = (t: SubtitleTrack): SubtitleEditEntry => const entryFor = (track: SubtitleTrack): SubtitleEditEntry =>
byId.get(t.id) ?? { id: t.id, keep: true, label: trackLabel(t) }; byId.get(track.id) ?? { id: track.id, keep: true, label: trackLabel(track) };
const update = (id: number, patch: Partial<SubtitleEditEntry>) => const update = (id: number, patch: Partial<SubtitleEditEntry>) =>
onChange( onChange(
key, key,
JSON.stringify(tracks.map((t) => (t.id === id ? { ...entryFor(t), ...patch } : entryFor(t)))), JSON.stringify(
tracks.map((track) => (track.id === id ? { ...entryFor(track), ...patch } : entryFor(track))),
),
); );
const keptCount = tracks.filter((t) => entryFor(t).keep).length; const keptCount = tracks.filter((track) => entryFor(track).keep).length;
return ( return (
<div key={key} className="flex flex-col gap-1.5"> <div key={key} className="flex flex-col gap-1.5">
<span className="text-sm font-medium text-duck-dark dark:text-foreground">{def.description ?? key}</span> <span className="text-sm font-medium text-duck-dark dark:text-foreground">{def.description ?? key}</span>
@@ -354,26 +358,26 @@ export const TaskInputForm = ({
) : ( ) : (
<> <>
<div className="flex flex-col gap-1 max-h-72 overflow-y-auto pr-1"> <div className="flex flex-col gap-1 max-h-72 overflow-y-auto pr-1">
{tracks.map((t) => { {tracks.map((track) => {
const e = entryFor(t); const e = entryFor(track);
return ( return (
<div key={t.id} className="flex items-center gap-2"> <div key={track.id} className="flex items-center gap-2">
<label className="flex items-center gap-2 cursor-pointer shrink-0"> <label className="flex items-center gap-2 cursor-pointer shrink-0">
<input <input
type="checkbox" type="checkbox"
checked={e.keep} checked={e.keep}
onChange={() => update(t.id, { keep: !e.keep })} onChange={() => update(track.id, { keep: !e.keep })}
className="accent-duck-teal cursor-pointer" className="accent-duck-teal cursor-pointer"
/> />
<span className="text-[10px] font-mono uppercase w-9 text-duck-dark/40 dark:text-foreground/40"> <span className="text-[10px] font-mono uppercase w-9 text-duck-dark/40 dark:text-foreground/40">
{t.lang || 'und'} {track.lang || 'und'}
</span> </span>
</label> </label>
<input <input
type="text" type="text"
value={e.label} value={e.label}
disabled={!e.keep} disabled={!e.keep}
onChange={(ev) => update(t.id, { label: ev.target.value })} onChange={(ev) => update(track.id, { label: ev.target.value })}
className="flex-1 min-w-0 px-2 py-1 text-sm rounded-md border border-duck-dark/15 dark:border-foreground/15 bg-background focus:outline-none focus:ring-1 focus:ring-duck-teal disabled:opacity-40" className="flex-1 min-w-0 px-2 py-1 text-sm rounded-md border border-duck-dark/15 dark:border-foreground/15 bg-background focus:outline-none focus:ring-1 focus:ring-duck-teal disabled:opacity-40"
/> />
</div> </div>
@@ -419,23 +423,23 @@ export const TaskInputForm = ({
<span className="text-xs text-duck-dark/50 dark:text-foreground/50">None</span> <span className="text-xs text-duck-dark/50 dark:text-foreground/50">None</span>
) : ( ) : (
<div className="flex flex-wrap gap-x-3 gap-y-1.5"> <div className="flex flex-wrap gap-x-3 gap-y-1.5">
{tracks.map((t) => ( {tracks.map((track) => (
<label <label
key={t.id} key={track.id}
className="flex items-center gap-1.5 text-sm cursor-pointer text-duck-dark dark:text-foreground" className="flex items-center gap-1.5 text-sm cursor-pointer text-duck-dark dark:text-foreground"
> >
<input <input
type="checkbox" type="checkbox"
checked={selected.has(t.id)} checked={selected.has(track.id)}
onChange={() => toggle(t.id)} onChange={() => toggle(track.id)}
className="accent-duck-teal cursor-pointer" className="accent-duck-teal cursor-pointer"
/> />
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
{trackLabel(t)} {trackLabel(track)}
{isAudio && ( {isAudio && (
<span className="text-duck-dark/40 dark:text-foreground/40"> <span className="text-duck-dark/40 dark:text-foreground/40">
{' '} {' '}
· {(t as AudioTrack).channels}ch · {(track as AudioTrack).channels}ch
</span> </span>
)} )}
</span> </span>
@@ -691,9 +695,9 @@ const pickerKinds = (defs: Record<string, TaskInputDef> | null): PickerKinds =>
}); });
const defaultGroupSel = (g: FolderTrackGroup): GroupSel => ({ const defaultGroupSel = (g: FolderTrackGroup): GroupSel => ({
audio: g.audioTracks.map((t) => t.id).join(','), // audio: keep all audio: g.audioTracks.map((track) => track.id).join(','), // audio: keep all
subs: 'none', // subtitles: keep none by default — you usually want only a few of many subs: 'none', // subtitles: keep none by default — you usually want only a few of many
subEdit: g.subtitleTracks.map((t) => ({ id: t.id, keep: false, label: trackLabel(t) })), subEdit: g.subtitleTracks.map((track) => ({ id: track.id, keep: false, label: trackLabel(track) })),
}); });
// Does a group's selection change anything vs keep-all + original labels? // Does a group's selection change anything vs keep-all + original labels?
@@ -703,9 +707,9 @@ const groupChanges = (g: FolderTrackGroup, s: GroupSel, has: PickerKinds): boole
if (has.subs && (s.subs === 'none' ? g.subtitleTracks.length > 0 : parseCsv(s.subs).size < g.subtitleTracks.length)) if (has.subs && (s.subs === 'none' ? g.subtitleTracks.length > 0 : parseCsv(s.subs).size < g.subtitleTracks.length))
return true; return true;
if (has.subEdit) { if (has.subEdit) {
for (const t of g.subtitleTracks) { for (const track of g.subtitleTracks) {
const e = s.subEdit.find((x) => x.id === t.id); const e = s.subEdit.find((x) => x.id === track.id);
if (!e || !e.keep || e.label !== trackLabel(t)) return true; if (!e || !e.keep || e.label !== trackLabel(track)) return true;
} }
} }
return false; return false;
@@ -726,7 +730,8 @@ const buildGroupConfig = (groups: FolderTrackGroup[], sel: GroupSel[], has: Pick
...(has.subEdit ...(has.subEdit
? { ? {
subEdit: g.subtitleTracks.map( subEdit: g.subtitleTracks.map(
(t) => s.subEdit.find((x) => x.id === t.id) ?? { id: t.id, keep: true, label: trackLabel(t) }, (track) =>
s.subEdit.find((x) => x.id === track.id) ?? { id: track.id, keep: true, label: trackLabel(track) },
), ),
} }
: {}), : {}),
@@ -780,11 +785,13 @@ const PerGroupTrackConfig = ({ groups, has, sel, onChange, probing }: PerGroupTr
n.has(id) ? n.delete(id) : n.add(id); n.has(id) ? n.delete(id) : n.add(id);
onChange(gi, { subs: n.size ? [...n].sort((a, b) => a - b).join(',') : 'none' }); onChange(gi, { subs: n.size ? [...n].sort((a, b) => a - b).join(',') : 'none' });
}; };
const entryFor = (t: SubtitleTrack): SubtitleEditEntry => const entryFor = (track: SubtitleTrack): SubtitleEditEntry =>
s.subEdit.find((x) => x.id === t.id) ?? { id: t.id, keep: true, label: trackLabel(t) }; s.subEdit.find((x) => x.id === track.id) ?? { id: track.id, keep: true, label: trackLabel(track) };
const updateEdit = (id: number, patch: Partial<SubtitleEditEntry>) => const updateEdit = (id: number, patch: Partial<SubtitleEditEntry>) =>
onChange(gi, { onChange(gi, {
subEdit: g.subtitleTracks.map((t) => (t.id === id ? { ...entryFor(t), ...patch } : entryFor(t))), subEdit: g.subtitleTracks.map((track) =>
track.id === id ? { ...entryFor(track), ...patch } : entryFor(track),
),
}); });
return ( return (
<div <div
@@ -805,20 +812,20 @@ const PerGroupTrackConfig = ({ groups, has, sel, onChange, probing }: PerGroupTr
{g.audioTracks.length === 0 ? ( {g.audioTracks.length === 0 ? (
<span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span> <span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span>
) : ( ) : (
g.audioTracks.map((t) => ( g.audioTracks.map((track) => (
<label <label
key={t.id} key={track.id}
className="flex items-center gap-2 text-sm cursor-pointer text-duck-dark dark:text-foreground" className="flex items-center gap-2 text-sm cursor-pointer text-duck-dark dark:text-foreground"
> >
<input <input
type="checkbox" type="checkbox"
checked={aSel.has(t.id)} checked={aSel.has(track.id)}
onChange={() => toggleA(t.id)} onChange={() => toggleA(track.id)}
className="accent-duck-teal cursor-pointer shrink-0" className="accent-duck-teal cursor-pointer shrink-0"
/> />
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
{trackLabel(t)} {trackLabel(track)}
<span className="text-duck-dark/40 dark:text-foreground/40"> · {audioMeta(t)}</span> <span className="text-duck-dark/40 dark:text-foreground/40"> · {audioMeta(track)}</span>
</span> </span>
</label> </label>
)) ))
@@ -832,20 +839,20 @@ const PerGroupTrackConfig = ({ groups, has, sel, onChange, probing }: PerGroupTr
{g.subtitleTracks.length === 0 ? ( {g.subtitleTracks.length === 0 ? (
<span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span> <span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span>
) : ( ) : (
g.subtitleTracks.map((t) => ( g.subtitleTracks.map((track) => (
<label <label
key={t.id} key={track.id}
className="flex items-center gap-2 text-sm cursor-pointer text-duck-dark dark:text-foreground" className="flex items-center gap-2 text-sm cursor-pointer text-duck-dark dark:text-foreground"
> >
<input <input
type="checkbox" type="checkbox"
checked={sSel.has(t.id)} checked={sSel.has(track.id)}
onChange={() => toggleS(t.id)} onChange={() => toggleS(track.id)}
className="accent-duck-teal cursor-pointer shrink-0" className="accent-duck-teal cursor-pointer shrink-0"
/> />
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
{trackLabel(t)} {trackLabel(track)}
<span className="text-duck-dark/40 dark:text-foreground/40"> · {t.codec}</span> <span className="text-duck-dark/40 dark:text-foreground/40"> · {track.codec}</span>
</span> </span>
</label> </label>
)) ))
@@ -859,26 +866,26 @@ const PerGroupTrackConfig = ({ groups, has, sel, onChange, probing }: PerGroupTr
{g.subtitleTracks.length === 0 ? ( {g.subtitleTracks.length === 0 ? (
<span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span> <span className="text-xs text-duck-dark/40 dark:text-foreground/40">none</span>
) : ( ) : (
g.subtitleTracks.map((t) => { g.subtitleTracks.map((track) => {
const e = entryFor(t); const e = entryFor(track);
return ( return (
<div key={t.id} className="flex items-center gap-2"> <div key={track.id} className="flex items-center gap-2">
<label className="flex items-center gap-2 cursor-pointer shrink-0"> <label className="flex items-center gap-2 cursor-pointer shrink-0">
<input <input
type="checkbox" type="checkbox"
checked={e.keep} checked={e.keep}
onChange={() => updateEdit(t.id, { keep: !e.keep })} onChange={() => updateEdit(track.id, { keep: !e.keep })}
className="accent-duck-teal cursor-pointer" className="accent-duck-teal cursor-pointer"
/> />
<span className="text-[10px] font-mono uppercase w-9 text-duck-dark/40 dark:text-foreground/40"> <span className="text-[10px] font-mono uppercase w-9 text-duck-dark/40 dark:text-foreground/40">
{t.lang || 'und'} {track.lang || 'und'}
</span> </span>
</label> </label>
<input <input
type="text" type="text"
value={e.label} value={e.label}
disabled={!e.keep} disabled={!e.keep}
onChange={(ev) => updateEdit(t.id, { label: ev.target.value })} onChange={(ev) => updateEdit(track.id, { label: ev.target.value })}
className="flex-1 min-w-0 px-2 py-1 text-sm rounded-md border border-duck-dark/15 dark:border-foreground/15 bg-background focus:outline-none focus:ring-1 focus:ring-duck-teal disabled:opacity-40" className="flex-1 min-w-0 px-2 py-1 text-sm rounded-md border border-duck-dark/15 dark:border-foreground/15 bg-background focus:outline-none focus:ring-1 focus:ring-duck-teal disabled:opacity-40"
/> />
</div> </div>
@@ -1015,10 +1022,12 @@ const ScriptRunner = ({
setFormValues((prev) => { setFormValues((prev) => {
const next = { ...prev }; const next = { ...prev };
for (const [key, def] of Object.entries(defs)) { for (const [key, def] of Object.entries(defs)) {
if (def.type === 'audio_tracks') next[key] = aud.map((t) => t.id).join(',') || 'none'; if (def.type === 'audio_tracks') next[key] = aud.map((track) => track.id).join(',') || 'none';
if (def.type === 'subtitle_tracks') next[key] = 'none'; if (def.type === 'subtitle_tracks') next[key] = 'none';
if (def.type === 'subtitle_edit') if (def.type === 'subtitle_edit')
next[key] = JSON.stringify(sub.map((t) => ({ id: t.id, keep: false, label: trackLabel(t) }))); next[key] = JSON.stringify(
sub.map((track) => ({ id: track.id, keep: false, label: trackLabel(track) })),
);
} }
return next; return next;
}); });