'use client'; import { useEffect, useMemo } from 'react'; import { EditShell } from '@/components/edit/EditShell'; import { SlideNavRail } from '@/components/edit/SlideNavRail'; import { EditDock } from '@/components/edit/EditDock/EditDock'; import { HeaderControls } from '@/components/stage/header-controls'; import { isMaicEditorEnabled } from '@/lib/config/feature-flags'; import { preloadEditor } from '@/lib/edit/preload-editor'; import { sceneEditorRegistry } from '@/lib/edit/scene-editor-registry'; import { getScenePagerState } from '@/lib/edit/scene-pager'; import { useStageStore } from '@/lib/store/stage'; import { useInWorkbenchPanel } from '@/lib/workbench/panel-context'; import { supportsNarrationTimeline } from './scene-timeline'; import type { Scene } from '@/lib/types/stage'; interface EditChromeRootProps { readonly scene: Scene; readonly isEditable: boolean; readonly onToggleEditMode?: () => void; } /** * Edit-mode root — wraps the Pro mode chrome assembly so `stage.tsx` * has a single component to mount in the edit branch instead of a * 13-line inline JSX with three children. * * Owned here: `EditShell` (Frame + CommandBar + canvas + overlays), * `SlideNavRail` (leftRail slot), the standalone-only `HeaderControls` * trailing that rides in CommandBar's right slot, * The legacy Edit-with-AI right rail has been retired: agentic edits live * exclusively in the Pro workspace conversation, and the classroom roster is * edited from a dialog opened off the edit dock's global bar. * * `scene` is required (non-null). The parent gates mounting on * `mode === 'edit' && currentScene` to satisfy this contract. */ export function EditChromeRoot({ scene, isEditable, onToggleEditMode }: EditChromeRootProps) { // Hosted inside the workbench panel? Then two pieces of chrome are // meaningless here: the Pro Switch (the panel is Pro-locked; there is // nowhere to toggle to) and the Edit-with-AI right rail (the workbench // conversation on the left is its successor — the agent it would talk to // is the one building this course). const inWorkbenchPanel = useInWorkbenchPanel(); // Deck paging (‹ n/m ›). Same state source and setter the SlideNavRail // thumbnails use — `currentSceneId` / `setCurrentSceneId` — so flipping pages // from the dock and clicking a rail thumbnail can never disagree about which // page is open. const scenes = useStageStore.use.scenes(); const currentSceneId = useStageStore.use.currentSceneId(); const setCurrentSceneId = useStageStore.use.setCurrentSceneId(); const pagerState = useMemo( () => getScenePagerState(scenes, currentSceneId), [scenes, currentSceneId], ); const pager = pagerState ? { ...pagerState, onPrev: () => { if (pagerState.prevSceneId) setCurrentSceneId(pagerState.prevSceneId); }, onNext: () => { if (pagerState.nextSceneId) setCurrentSceneId(pagerState.nextSceneId); }, } : undefined; // Mark the body while edit mode is mounted, so the editor-scoped CSS // rule in globals.css that pins `body.padding-right` to 0 only fires // in Pro mode — not on non-editor pages where Radix's // react-remove-scroll compensation is still wanted. Lifted from // SlideCanvas (which was mounted only for slide scenes) so the // attribute now covers read-only scene types in Pro mode too. useEffect(() => { document.body.dataset.maicEditor = 'true'; return () => { delete document.body.dataset.maicEditor; }; }, []); // Safety net: the editor chunk (fonts + slide surface registration) is // normally preloaded by the Pro Switch handler in stage.tsx BEFORE mode // flips, so by the time we mount the surface is already registered and // EditShell resolves it immediately (no NOOP flash). This call is a // promise-cached no-op in that path; it only does real work if edit mode // is ever entered without going through the handler. Render is NOT gated // on it — the preload-before-flip contract keeps the chrome smooth. useEffect(() => { void preloadEditor(); }, []); // Whether this scene type has a registered canvas editor surface (slide/quiz). // Authoring surface is separate from narration timeline availability. const authoringEnabled = !!sceneEditorRegistry.resolve(scene.type); // The narration timeline is decoupled from the canvas editor surface (like // agentEnabled below): it applies to registered surfaces (slide/quiz) AND // view-only canvases that still carry a spoken script (interactive/pbl). It is // also the dock's gate: the timeline is the dock's first tool, so where there // is no timeline there is no bench to hang other tools off either. const timelineEnabled = supportsNarrationTimeline(scene.type, authoringEnabled); const headerControls = inWorkbenchPanel ? undefined : ( ); return ( } bottomRail={ timelineEnabled ? ( ) : undefined } commandTrailing={headerControls} // The pager normally lives in the dock's global edit bar (handed to `EditDock` // above). Only a scene type that gets no dock at all keeps the floating // form — otherwise those scenes would lose paging entirely. pager={timelineEnabled ? undefined : pager} hideCommandBar={inWorkbenchPanel} /> ); }