From 23862021eef14e6085f472017d0bd9899455e70f Mon Sep 17 00:00:00 2001 From: EtienneLescot Date: Sat, 22 Aug 2026 13:20:15 +0200 Subject: [PATCH 1/3] refactor(editor): delete the orphaned v3 left rail and media pane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v4 refactor (03972b0e) moved the Chat/Media choice out of a left rail and into the top bar's Media/Edit/Rec switch, then pinned the sole LeftPanel mount to the literal active="chat" — but left the media half in the tree. LeftRail has had zero call sites since, and MediaPane, MediaList and SourceTranscriptModal have been unreachable behind a branch that cannot run. Unreachable code that still looks live attracts fixes: 493c7917 and 05eb06b4 both landed language-picker work on SourceTranscriptModal before anyone noticed no user could open it. That is the cost this removes. ChatStripPanel — the only branch that ever ran — is now exported and mounted directly, so LeftPanel goes with the rest. Also removed because nothing else referenced them: toAxcutTranscriptDsl (its doc comment names the deleted modal as its only consumer), 18 i18n keys across 13 locales, and the orphaned CSS rules in NewEditorShell.module.css. Per the repo's convention for retired surfaces, the three names are added to check-docs' LEGACY list and the removal is recorded in decisions.md. --- scripts/check-docs.mjs | 3 + .../LeftPanel.providerRefresh.test.tsx | 4 +- src/components/ai-edition/LeftPanel.tsx | 321 +---------- src/components/ai-edition/Modals.tsx | 500 +----------------- .../ai-edition/NewEditorShell.module.css | 159 ------ src/components/ai-edition/NewEditorShell.tsx | 4 +- src/components/ai-edition/v4/MediaStage.tsx | 8 +- src/i18n/locales/ar/common.json | 6 - src/i18n/locales/ar/editor.json | 18 - src/i18n/locales/en/common.json | 6 - src/i18n/locales/en/editor.json | 18 - src/i18n/locales/es/common.json | 6 - src/i18n/locales/es/editor.json | 18 - src/i18n/locales/fr/common.json | 6 - src/i18n/locales/fr/editor.json | 18 - src/i18n/locales/it/common.json | 6 - src/i18n/locales/it/editor.json | 18 - src/i18n/locales/ja-JP/common.json | 6 - src/i18n/locales/ja-JP/editor.json | 18 - src/i18n/locales/ko-KR/common.json | 6 - src/i18n/locales/ko-KR/editor.json | 18 - src/i18n/locales/pt-BR/common.json | 6 - src/i18n/locales/pt-BR/editor.json | 18 - src/i18n/locales/ru/common.json | 6 - src/i18n/locales/ru/editor.json | 18 - src/i18n/locales/tr/common.json | 6 - src/i18n/locales/tr/editor.json | 18 - src/i18n/locales/vi/common.json | 6 - src/i18n/locales/vi/editor.json | 18 - src/i18n/locales/zh-CN/common.json | 6 - src/i18n/locales/zh-CN/editor.json | 18 - src/i18n/locales/zh-TW/common.json | 6 - src/i18n/locales/zh-TW/editor.json | 18 - .../ai-edition/document/transcribe.test.ts | 73 +-- src/lib/ai-edition/document/transcribe.ts | 46 -- src/lib/ai-edition/timeline/format.test.ts | 6 +- src/lib/ai-edition/timeline/format.ts | 10 +- .../architecture/decisions.md | 1 + .../architecture/editor-shell.md | 4 +- .../architecture/llm-providers.md | 2 +- .../architecture/overview.md | 2 +- .../transcription-and-captions.md | 22 +- 42 files changed, 40 insertions(+), 1437 deletions(-) diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs index 65119765e..f1c671962 100644 --- a/scripts/check-docs.mjs +++ b/scripts/check-docs.mjs @@ -27,6 +27,9 @@ const LEGACY = [ "Titlebar", "TranscriptEditor", "BackgroundPane", + "LeftRail", + "MediaPane", + "SourceTranscriptModal", "ai-edition-roadmap", "ai-edition-collision-analysis", "openscreen-inventory", diff --git a/src/components/ai-edition/LeftPanel.providerRefresh.test.tsx b/src/components/ai-edition/LeftPanel.providerRefresh.test.tsx index 6163ed8d3..f14a8d73c 100644 --- a/src/components/ai-edition/LeftPanel.providerRefresh.test.tsx +++ b/src/components/ai-edition/LeftPanel.providerRefresh.test.tsx @@ -46,7 +46,7 @@ vi.mock("@/contexts/I18nContext", () => ({ })); import { EditorDialogsProvider, useEditorDialogActions } from "@/contexts/EditorDialogsContext"; -import { LeftPanel } from "./LeftPanel"; +import { ChatStripPanel } from "./LeftPanel"; let dialogActions: ReturnType | null = null; @@ -82,7 +82,7 @@ describe("ChatStripPanel, against the lifted provider dialog", () => { render( - + , ); // Mount: the dialog is closed, so the same effect that watches for a close seeds the diff --git a/src/components/ai-edition/LeftPanel.tsx b/src/components/ai-edition/LeftPanel.tsx index 06a349ef1..b737e60b5 100644 --- a/src/components/ai-edition/LeftPanel.tsx +++ b/src/components/ai-edition/LeftPanel.tsx @@ -1,29 +1,21 @@ -import { ArrowLeft, Check, Film, Loader2, MessageSquare, Plus, Search, X } from "lucide-react"; +import { ArrowLeft, Check, Loader2, X } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { toast } from "sonner"; import { useEditorDialogActions, useEditorDialogSection } from "@/contexts/EditorDialogsContext"; import { useScopedT } from "@/contexts/I18nContext"; -import type { AxcutAsset } from "@/lib/ai-edition/schema"; import { applyAgentDocumentIfCurrent, runAgentTurn, } from "@/lib/ai-edition/store/agentDocumentApply"; import { useProjectStore } from "@/lib/ai-edition/store/projectStore"; -import { - useAssetTranscriptions, - useTranscriptionStore, -} from "@/lib/ai-edition/store/transcriptionStore"; import { useChatPromptBus } from "@/lib/ai-edition/store/useChatPromptBus"; -import { splitRoundedTime } from "@/lib/ai-edition/timeline/format"; -import type { AssetTranscriptionView } from "@/lib/ai-edition/transcription/status"; import { nativeBridgeClient } from "@/native/client"; import type { AiEditionChatEvent, AiEditionLlmConfig, AiEditionToolCallSummary, } from "@/native/contracts"; -import { formatBytes } from "@/utils/formatBytes"; import { getReasoningEffortLabel, getReasoningEffortOptions, @@ -32,286 +24,10 @@ import { } from "../../../electron/ai-edition/provider-registry"; import { ChatWelcome } from "./ChatWelcome"; import { canSendChat } from "./chatAvailability"; -import { ChatHistoryModal, SourceTranscriptModal } from "./Modals"; +import { ChatHistoryModal } from "./Modals"; import styles from "./NewEditorShell.module.css"; -import { TranscriptionStatusDot } from "./TranscriptionStatus"; import { useChatBudget } from "./useChatBudget"; -export type LeftTab = "chat" | "media"; - -const THUMB_PALETTE = ["thumbRed", "thumbGreen", "thumbAmber", "thumbCyan"] as const; - -// `h:mm:ss.t`, hours always shown — a third shape, so it formats itself rather -// than calling into format.ts. It shares `splitRoundedTime` because the carry is -// the part that must not be re-derived: deriving the minute field from the raw -// value while the second field rounded is what rendered `0:00:60.0`. -function formatTimecode(sec: number | undefined): string { - if (!sec || !Number.isFinite(sec)) return "0:00:00.0"; - const { totalMinutes, seconds } = splitRoundedTime(sec); - const h = Math.floor(totalMinutes / 60); - const m = totalMinutes % 60; - // padStart(4), not (3): "5.0" is already 3 chars, so a single-digit second - // rendered as `0:00:5.0` instead of `0:00:05.0`. - return `${h}:${m.toString().padStart(2, "0")}:${seconds.toFixed(1).padStart(4, "0")}`; -} - -function basename(path: string): string { - return path.split(/[\\/]/).pop() ?? path; -} - -function MediaList({ - assets, - onOpenTranscript, - transcriptions, -}: { - assets: AxcutAsset[]; - onOpenTranscript?: (asset: AxcutAsset) => void; - /** Per-asset transcription state, keyed by asset id (see transcriptionStore). */ - transcriptions: Record; -}) { - const t = useScopedT("editor"); - if (assets.length === 0) { - return ( -

- {t("leftPanel.emptyHint")} -

- ); - } - return ( -
    - {assets.map((asset, i) => { - const label = asset.label || basename(asset.originalPath); - const tc = formatTimecode(asset.durationSec); - const size = formatBytes(asset.sizeBytes); - const palette = THUMB_PALETTE[i % THUMB_PALETTE.length]; - const transcription = transcriptions[asset.id] ?? { - assetId: asset.id, - status: "idle" as const, - }; - - return ( -
  • { - e.dataTransfer.setData("application/x-axcut-asset", asset.id); - e.dataTransfer.effectAllowed = "copy"; - }} - > - -
  • - ); - })} -
- ); -} - -export function MediaPane() { - const t = useScopedT("editor"); - const projectId = useProjectStore((s) => s.projectId); - const document = useProjectStore((s) => s.document); - const addAsset = useProjectStore((s) => s.addAsset); - // Transcripts land on their own (transcriptionStore's background pass); the - // pane reports where each one is at and offers a per-asset re-run. - const transcriptions = useAssetTranscriptions(); - const requestTranscription = useTranscriptionStore((s) => s.request); - const [query, setQuery] = useState(""); - const [busy, setBusy] = useState(false); - const [srcTranscriptAsset, setSrcTranscriptAsset] = useState(null); - const selectedTranscription = srcTranscriptAsset - ? transcriptions[srcTranscriptAsset.id] - : undefined; - - const handleImport = async () => { - if (!projectId) { - toast.error(t("mediaStage.openProjectFirst")); - return; - } - const picker = await window.electronAPI?.openVideoFilePicker(); - if (!picker?.success || !picker.path) return; - setBusy(true); - try { - const label = picker.name || basename(picker.path); - await addAsset(picker.path, label); - toast.success(t("mediaStage.added", { label })); - } catch (err) { - toast.error(t("mediaStage.couldNotAddAsset"), { - description: err instanceof Error ? err.message : String(err), - }); - } finally { - setBusy(false); - } - }; - - const filtered = (document?.assets ?? []).filter((a) => { - if (!query) return true; - const text = `${a.label} ${a.originalPath}`.toLowerCase(); - return text.includes(query.toLowerCase()); - }); - - return ( - - ); -} - -export function LeftPanel({ active }: { active: LeftTab }) { - return active === "chat" ? : ; -} - interface ChatDisplayMessage { id?: string; role: string; @@ -726,7 +442,7 @@ function ThinkingBlock({ ); } -function ChatStripPanel() { +export function ChatStripPanel() { const t = useScopedT("editor"); const tc = useScopedT("common"); // The Auto-enhance confirmation is timeline-owned copy, fired from here — @@ -1967,34 +1683,3 @@ function ChatStripPanel() { ); } - -const RAIL_BUTTONS: Array<{ id: LeftTab; labelKey: string; icon: React.ElementType }> = [ - { id: "chat", labelKey: "leftRail.chat", icon: MessageSquare }, - { id: "media", labelKey: "leftRail.media", icon: Film }, -]; - -export function LeftRail({ - active, - onChange, -}: { - active: LeftTab; - onChange: (id: LeftTab) => void; -}) { - const t = useScopedT("editor"); - return ( - - ); -} diff --git a/src/components/ai-edition/Modals.tsx b/src/components/ai-edition/Modals.tsx index 3be5687fd..aeab7b1fa 100644 --- a/src/components/ai-edition/Modals.tsx +++ b/src/components/ai-edition/Modals.tsx @@ -1,41 +1,15 @@ -import { - AlertTriangle, - Crop, - FolderOpen, - FolderPlus, - Loader2, - Maximize2, - Pencil, - Plus, - RefreshCw, - RotateCcw, - Trash2, - Triangle, - X, -} from "lucide-react"; +import { AlertTriangle, Crop, FolderOpen, FolderPlus, Pencil, Plus, Trash2, X } from "lucide-react"; import { type ReactNode, type PointerEvent as ReactPointerEvent, useEffect, - useMemo, useRef, useState, } from "react"; -import { toFileUrl } from "@/components/video-editor/projectPersistence"; import type { CropRegion } from "@/components/video-editor/types"; -import { useI18n, useScopedT } from "@/contexts/I18nContext"; -import { toAxcutTranscriptDsl } from "@/lib/ai-edition/document/transcribe"; -import { - type AxcutClip, - type AxcutTranscript, - type TranscriptLanguageCode, - transcriptLanguageSchema, -} from "@/lib/ai-edition/schema"; -import { formatSec, formatSeconds } from "@/lib/ai-edition/timeline/format"; -import { - languageLabel, - sortedLanguageOptions, -} from "@/lib/ai-edition/transcription/languageLabels"; +import { useScopedT } from "@/contexts/I18nContext"; +import type { AxcutClip } from "@/lib/ai-edition/schema"; +import { formatSeconds } from "@/lib/ai-edition/timeline/format"; import styles from "./NewEditorShell.module.css"; import type { VideoSource } from "./VirtualPreview"; @@ -1516,472 +1490,6 @@ export function InsertSourceModal({ ); } -/** - * `AxcutTranscript.language` is `z.string().min(1)`, not validated against - * the known code list, so a stored transcript can hold a value no - * `