@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -1,66 +1,709 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// every session, and buried at the bottom of the collapsed Manual nobody
|
|
4
|
-
// found it.
|
|
1
|
+
// Workspace MCP readiness and setup: the top bar's Agent chip opens it in a
|
|
2
|
+
// `Popover` (AgentChip.tsx), and Help repeats it behind a disclosure.
|
|
5
3
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
4
|
+
// TWO PRESENTATIONS OF ONE STATE. The chip owns the health probe
|
|
5
|
+
// (`useMcpHealth`) and hands it to `McpPopover`, so the chip's word and the
|
|
6
|
+
// details under it never disagree. Help's `McpSection` probes for itself,
|
|
7
|
+
// because Help is a modal dialog and a popover inside it would be a floating
|
|
8
|
+
// layer over a floating layer. The left column used to carry a 64px "band"
|
|
9
|
+
// with the same details; the chip replaced it, so the column's rows are the
|
|
10
|
+
// author's again.
|
|
11
|
+
//
|
|
12
|
+
// The live server owns the status contract:
|
|
13
|
+
// `GET /api/health` says whether `/mcp` is mounted and whether direct document
|
|
14
|
+
// writes are enabled. It does not report an active client, so this UI never
|
|
15
|
+
// claims one is connected.
|
|
16
|
+
//
|
|
17
|
+
// MCP is workspace-scoped. The current Project title and storage name are a
|
|
18
|
+
// target hint for the agent, not an access boundary. Hosted, an AI app connects
|
|
19
|
+
// to the MCP URL on MAIN and signs in with the member's Dailies account (worker
|
|
20
|
+
// oauth.ts). The connect rows show that URL, read from the discovery document
|
|
21
|
+
// this host serves, and link to the dashboard's Connect your AI page for each
|
|
22
|
+
// app's steps, the connected apps and the tokens for clients without OAuth.
|
|
23
|
+
// The editor origin cannot reach `/api/mcp`, so no token is made or shown here.
|
|
24
|
+
//
|
|
25
|
+
// Hosted, the write half is the owner's switch here: it writes `mcp.write` into
|
|
26
|
+
// the workspace's motion.config.json through the ordinary file route, against
|
|
27
|
+
// the hash it read, keeping every other key. A hosted workspace has no terminal
|
|
28
|
+
// to run `connect --write` in, and this is the same deliberate second act.
|
|
29
|
+
import { useEffect, useRef, useState } from "react";
|
|
30
|
+
import type { RefObject } from "react";
|
|
31
|
+
import { IconCheck, IconChevron, IconLink, IconLinkOff, Popover, Toggle } from "@dailies-studio/ui";
|
|
32
|
+
import { isCloudWorkspace } from "../../state/posterCapture.js";
|
|
33
|
+
import { isLoopbackHostname } from "../../stage/jsSelfProfiler.js";
|
|
34
|
+
|
|
35
|
+
const CONNECT_COMMAND = "npx dailies-studio connect";
|
|
36
|
+
const WRITE_COMMAND = "npx dailies-studio connect --write";
|
|
37
|
+
const STATUS_COMMAND = "dailies status";
|
|
38
|
+
/** The CLI's `CONFIG_FILE`, which the hosted MCP surface reads its write half
|
|
39
|
+
* from (worker mcpConfig.ts). */
|
|
40
|
+
export const MCP_CONFIG_FILE = "motion.config.json";
|
|
41
|
+
const WRITES_LABEL = "Let agents edit this workspace";
|
|
42
|
+
/** A client lists its tools when it connects, and this server can tell no
|
|
43
|
+
* connected client that the list changed (it keeps no stream). */
|
|
44
|
+
export const RECONNECT_NOTE =
|
|
45
|
+
"A client connected before a change keeps its old tools until it reconnects: in Claude Code, run /mcp and reconnect dailies.";
|
|
46
|
+
/** The `.mcp.json` server key the CLI writes (connectVerbs.ts MCP_SERVER_KEY)
|
|
47
|
+
* and the name the dashboard's command registers. */
|
|
48
|
+
const MCP_SERVER_KEY = "dailies";
|
|
49
|
+
/** The dashboard's Connect your AI page (dashboard Sidebar.tsx `View`). Relative
|
|
50
|
+
* on purpose: the editor host knows no MAIN origin, and it redirects every
|
|
51
|
+
* `/projects` path, query kept, to MAIN (worker index.ts `hostRoute`), the
|
|
52
|
+
* same way the account menu reaches the dashboard. */
|
|
53
|
+
export const AGENT_ACCESS_HREF = "/projects/?view=settings-agents";
|
|
54
|
+
/** `WORKER_HEADERS.mcpToken` / `CLOUD_HEADERS.mcpToken`. Chrome imports
|
|
55
|
+
* neither package, so the name is repeated here. */
|
|
56
|
+
const MCP_TOKEN_HEADER = "x-studio-mcp-token";
|
|
57
|
+
/** The env var a non-loopback studio requires (server.ts `mcpMountable`). The
|
|
58
|
+
* rows name it and never carry a value. */
|
|
59
|
+
const MCP_TOKEN_ENV = "STUDIO_MCP_TOKEN";
|
|
60
|
+
|
|
61
|
+
export type McpHealth =
|
|
62
|
+
| { kind: "checking" }
|
|
63
|
+
| { kind: "unavailable" }
|
|
64
|
+
/** `allowed`: whether this caller may use MCP (`mcp.connect`); `manage`:
|
|
65
|
+
* whether it may flip the write half (`mcp.manage`, Owner or Admin). Both
|
|
66
|
+
* from worker `storeHealth`; a local server has no roles, so both true. */
|
|
67
|
+
| { kind: "ready"; mounted: boolean; write: boolean; allowed: boolean; manage: boolean };
|
|
68
|
+
|
|
69
|
+
/** A hosted role without `mcp.connect` (Billing, or a paused seat): `/mcp` is
|
|
70
|
+
* there, but it refuses this caller, so nothing that leads to it is offered. */
|
|
71
|
+
export function adminsOnly(health: McpHealth): boolean {
|
|
72
|
+
return health.kind === "ready" && health.mounted && !health.allowed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The same sentence as the dashboard's refusal (ConnectAI.tsx). */
|
|
76
|
+
export const ADMINS_ONLY_NOTE =
|
|
77
|
+
"Your role in this workspace cannot connect an AI. An Owner or an Admin can change your role.";
|
|
78
|
+
|
|
79
|
+
/** The details layer's floor, so a chip in a short window still opens
|
|
80
|
+
* something readable, and the gap it keeps from the viewport's bottom edge
|
|
81
|
+
* plus the Popover's own 4px offset. */
|
|
82
|
+
const POPOVER_MIN_H = 160;
|
|
83
|
+
const POPOVER_EDGE = 12;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The one health probe: read on mount and whenever the Project changes, and
|
|
87
|
+
* again whenever the author comes back to the tab, since the switch, a file
|
|
88
|
+
* edit or an agent may have changed motion.config.json meanwhile. A panel still
|
|
89
|
+
* saying "Direct document writes enabled" over a file the workspace can no
|
|
90
|
+
* longer read would be a claim nothing backs.
|
|
91
|
+
*/
|
|
92
|
+
export function useMcpHealth(projectName: string | null): { health: McpHealth; retry: () => void } {
|
|
93
|
+
const [attempt, setAttempt] = useState(0);
|
|
94
|
+
const [health, setHealth] = useState<McpHealth>({ kind: "checking" });
|
|
17
95
|
|
|
18
96
|
useEffect(() => {
|
|
19
97
|
let alive = true;
|
|
20
|
-
|
|
98
|
+
// A ready answer stays on screen while it is read again (after the write
|
|
99
|
+
// switch saved, say), so the switch and what it reported do not blink out.
|
|
100
|
+
setHealth((current) => (current.kind === "ready" ? current : { kind: "checking" }));
|
|
101
|
+
void readHealth().then((next) => {
|
|
102
|
+
if (alive) setHealth(next);
|
|
103
|
+
});
|
|
104
|
+
return () => {
|
|
105
|
+
alive = false;
|
|
106
|
+
};
|
|
107
|
+
}, [attempt, projectName]);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const again = (): void => setAttempt((value) => value + 1);
|
|
111
|
+
window.addEventListener("focus", again);
|
|
112
|
+
return () => window.removeEventListener("focus", again);
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
return { health, retry: () => setAttempt((value) => value + 1) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The chip's word: health only. "MCP ready" means writes are on, "Read only"
|
|
119
|
+
* that the server answers but edits nothing, "MCP off" that `/mcp` is not
|
|
120
|
+
* mounted, "No access" that this caller's role may not use it. Never
|
|
121
|
+
* "connected": health cannot see a client. */
|
|
122
|
+
export function mcpWord(health: McpHealth): string {
|
|
123
|
+
if (health.kind === "checking") return "Checking MCP";
|
|
124
|
+
if (health.kind === "unavailable") return "Status unknown";
|
|
125
|
+
if (!health.mounted) return "MCP off";
|
|
126
|
+
if (adminsOnly(health)) return "No access";
|
|
127
|
+
return health.write ? "MCP ready" : "Read only";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface ProjectProps {
|
|
131
|
+
projectName?: string | null;
|
|
132
|
+
projectTitle?: string | null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Help's presentation: the details behind a disclosure, with a probe of its
|
|
136
|
+
* own. `Dialog` returns null while closed, so it reads health only once the
|
|
137
|
+
* author opens Help. */
|
|
138
|
+
export function McpSection({ projectName = null, projectTitle = null }: ProjectProps) {
|
|
139
|
+
const { health, retry } = useMcpHealth(projectName);
|
|
140
|
+
return (
|
|
141
|
+
<details className="mcp-section" data-testid="mcp-section">
|
|
142
|
+
<summary>
|
|
143
|
+
{/* The house chevron, not a `"›"` glyph in a `::before`. Two
|
|
144
|
+
disclosure shapes in one surface is one more than a reader can be
|
|
145
|
+
asked to learn, and the outline already draws this icon. */}
|
|
146
|
+
<IconChevron className="chevron" />
|
|
147
|
+
<span className="mcp-heading">
|
|
148
|
+
<IconLink />
|
|
149
|
+
<span>MCP connection</span>
|
|
150
|
+
</span>
|
|
151
|
+
<McpStatus health={health} />
|
|
152
|
+
</summary>
|
|
153
|
+
<McpBody health={health} onRetry={retry} projectName={projectName} projectTitle={projectTitle} />
|
|
154
|
+
</details>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The chip's presentation: the same body in a `Popover` under `anchor`. The
|
|
160
|
+
* caller owns `open` and the probe; this owns focus.
|
|
161
|
+
*
|
|
162
|
+
* FOCUS GOES INTO THE DETAILS, and comes back to the anchor. The popover is
|
|
163
|
+
* portaled to the end of <body>, so without this a Tab from the chip walks the
|
|
164
|
+
* rest of the shell and reaches the switch, Retry and Copy last of all. A
|
|
165
|
+
* timeout rather than the effect itself: `Popover` holds the layer at
|
|
166
|
+
* `visibility: hidden` until its layout effect has measured it, and `focus()`
|
|
167
|
+
* on a hidden element is a no-op. That re-render is flushed in the same task,
|
|
168
|
+
* so one macrotask later the layer is shown.
|
|
169
|
+
*/
|
|
170
|
+
export function McpPopover({
|
|
171
|
+
anchor,
|
|
172
|
+
open,
|
|
173
|
+
onClose,
|
|
174
|
+
health,
|
|
175
|
+
onRetry,
|
|
176
|
+
projectName = null,
|
|
177
|
+
projectTitle = null,
|
|
178
|
+
handoff,
|
|
179
|
+
}: ProjectProps & {
|
|
180
|
+
anchor: RefObject<HTMLElement | null>;
|
|
181
|
+
open: boolean;
|
|
182
|
+
onClose: () => void;
|
|
183
|
+
health: McpHealth;
|
|
184
|
+
onRetry: () => void;
|
|
185
|
+
/** The prompt to copy for an agent, built at the click (agentHandoff.ts). */
|
|
186
|
+
handoff?: () => string | null;
|
|
187
|
+
}) {
|
|
188
|
+
const details = useRef<HTMLDivElement>(null);
|
|
189
|
+
const [cap, setCap] = useState(POPOVER_MIN_H);
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
if (!open) return;
|
|
193
|
+
// The layer opens BELOW the chip, and `Popover` only flips up when the
|
|
194
|
+
// whole layer fits there. So it is capped to the room under the anchor,
|
|
195
|
+
// measured at the open; a long body scrolls inside it.
|
|
196
|
+
const bottom = anchor.current?.getBoundingClientRect().bottom ?? 0;
|
|
197
|
+
setCap(Math.max(POPOVER_MIN_H, window.innerHeight - bottom - POPOVER_EDGE));
|
|
198
|
+
const id = window.setTimeout(() => details.current?.focus(), 0);
|
|
199
|
+
return () => window.clearTimeout(id);
|
|
200
|
+
}, [open, anchor]);
|
|
201
|
+
|
|
202
|
+
/** Every way out of the details. Focus that was inside them goes back to
|
|
203
|
+
* the anchor, because the node holding it is about to unmount and would
|
|
204
|
+
* leave it on <body>. Focus elsewhere (an outside press) stays where the
|
|
205
|
+
* author put it. */
|
|
206
|
+
const close = (): void => {
|
|
207
|
+
const inside = details.current?.contains(document.activeElement) === true;
|
|
208
|
+
onClose();
|
|
209
|
+
if (inside) anchor.current?.focus();
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<Popover open={open} anchor={anchor} onClose={close} align="right" className="mcp-popover">
|
|
214
|
+
<div
|
|
215
|
+
ref={details}
|
|
216
|
+
tabIndex={-1}
|
|
217
|
+
className="mcp-popover-body"
|
|
218
|
+
role="dialog"
|
|
219
|
+
aria-label="MCP connection"
|
|
220
|
+
data-testid="mcp-details"
|
|
221
|
+
style={{ maxHeight: `${cap}px` }}
|
|
222
|
+
>
|
|
223
|
+
<McpBody
|
|
224
|
+
health={health}
|
|
225
|
+
onRetry={onRetry}
|
|
226
|
+
projectName={projectName}
|
|
227
|
+
projectTitle={projectTitle}
|
|
228
|
+
handoff={handoff}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
</Popover>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** A copy that says "Copied" on the row it came from. The value stays
|
|
236
|
+
* selectable when clipboard permission is refused. */
|
|
237
|
+
export function useCopy(resetKey: unknown = null): { copied: string | null; copy: (target: string, value: string) => void } {
|
|
238
|
+
const [copied, setCopied] = useState<string | null>(null);
|
|
239
|
+
// A "Copied" names what was copied, so it clears when that changes.
|
|
240
|
+
useEffect(() => setCopied(null), [resetKey]);
|
|
241
|
+
const mounted = useRef(true);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
mounted.current = true;
|
|
244
|
+
return () => {
|
|
245
|
+
mounted.current = false;
|
|
246
|
+
};
|
|
247
|
+
}, []);
|
|
248
|
+
const copy = (target: string, value: string): void => {
|
|
249
|
+
const clipboard = navigator.clipboard;
|
|
250
|
+
if (clipboard === undefined) return;
|
|
251
|
+
void clipboard.writeText(value).then(
|
|
252
|
+
() => {
|
|
253
|
+
if (mounted.current) setCopied(target);
|
|
254
|
+
},
|
|
255
|
+
() => {},
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
return { copied, copy };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function McpBody({
|
|
262
|
+
health,
|
|
263
|
+
onRetry,
|
|
264
|
+
projectName = null,
|
|
265
|
+
projectTitle = null,
|
|
266
|
+
handoff,
|
|
267
|
+
}: ProjectProps & { health: McpHealth; onRetry: () => void; handoff?: (() => string | null) | undefined }) {
|
|
268
|
+
const hosted = isCloudWorkspace();
|
|
269
|
+
const { copied, copy } = useCopy(projectName);
|
|
270
|
+
const title = projectTitle?.trim() || projectName?.trim() || null;
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div className="mcp-body">
|
|
274
|
+
{/* A Member has no agent to hand a storage name or a prompt to. */}
|
|
275
|
+
{title === null || adminsOnly(health) ? null : (
|
|
276
|
+
<div className="mcp-project" data-testid="mcp-project-target">
|
|
277
|
+
<span className="mcp-kicker">Project target</span>
|
|
278
|
+
<strong>{title}</strong>
|
|
279
|
+
{projectName === null ? null : (
|
|
280
|
+
<div className="mcp-project-name">
|
|
281
|
+
<span>Storage name</span>
|
|
282
|
+
<code>{projectName}</code>
|
|
283
|
+
<CopyButton copied={copied === "project"} onClick={() => copy("project", projectName)} />
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
<p>An agent sees the whole workspace. Give it this storage name to work on this Project.</p>
|
|
287
|
+
{handoff === undefined ? null : (
|
|
288
|
+
<div className="mcp-handoff" data-testid="mcp-handoff">
|
|
289
|
+
<span>A prompt naming this Shot, frame and selection</span>
|
|
290
|
+
<CopyButton
|
|
291
|
+
copied={copied === "handoff"}
|
|
292
|
+
onClick={() => {
|
|
293
|
+
const prompt = handoff();
|
|
294
|
+
if (prompt !== null) copy("handoff", prompt);
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
|
|
302
|
+
<Readiness health={health} hosted={hosted} onRetry={onRetry} />
|
|
303
|
+
|
|
304
|
+
{hosted && health.kind === "ready" && health.mounted && health.allowed && health.manage ? (
|
|
305
|
+
<HostedWrites write={health.write} onSaved={onRetry} />
|
|
306
|
+
) : null}
|
|
307
|
+
|
|
308
|
+
<InTabTools />
|
|
309
|
+
|
|
310
|
+
<ConnectRows health={health} />
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The OTHER surface: the tools this page registers for an agent running in the
|
|
317
|
+
* browser (WebMCP, `state/webmcp.ts`).
|
|
318
|
+
*
|
|
319
|
+
* Read once at render and not subscribed: `document.modelContext` either exists
|
|
320
|
+
* when the page loads or does not, and the registration happens before React
|
|
321
|
+
* commits. A secure context is required, so a dev server reached over the LAN by
|
|
322
|
+
* plain http reads as unsupported even in a browser that has the API.
|
|
323
|
+
*/
|
|
324
|
+
function InTabTools() {
|
|
325
|
+
const supported = typeof document.modelContext !== "undefined";
|
|
326
|
+
return (
|
|
327
|
+
<p className="mcp-note" data-testid="mcp-webmcp">
|
|
328
|
+
In-tab tools (WebMCP):{" "}
|
|
329
|
+
{supported
|
|
330
|
+
? "on. A browser agent can read and edit this Project in this tab."
|
|
331
|
+
: "not in this browser. Nothing else changes."}
|
|
332
|
+
</p>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The status pill at the end of Help's summary, in its one-word forms. */
|
|
337
|
+
function McpStatus({ health }: { health: McpHealth }) {
|
|
338
|
+
if (health.kind === "checking") return <span className="mcp-status">Checking</span>;
|
|
339
|
+
if (health.kind === "unavailable") {
|
|
340
|
+
return (
|
|
341
|
+
<span className="mcp-status is-unknown" role="status">
|
|
342
|
+
<IconLinkOff /> Unknown
|
|
343
|
+
</span>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (!health.mounted || adminsOnly(health)) {
|
|
347
|
+
return (
|
|
348
|
+
<span className="mcp-status is-off" role="status">
|
|
349
|
+
<IconLinkOff /> {health.mounted ? "No access" : "Off"}
|
|
350
|
+
</span>
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
return (
|
|
354
|
+
<span className={`mcp-status${health.write ? " is-write" : " is-read"}`} role="status">
|
|
355
|
+
{health.write ? <IconCheck /> : <IconLink />}
|
|
356
|
+
{health.write ? "Write tools enabled" : "Read only"}
|
|
357
|
+
</span>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function Readiness({ health, hosted, onRetry }: { health: McpHealth; hosted: boolean; onRetry: () => void }) {
|
|
362
|
+
if (health.kind === "checking") {
|
|
363
|
+
return <p className="mcp-note">{hosted ? "Checking this workspace." : "Checking the live Studio server."}</p>;
|
|
364
|
+
}
|
|
365
|
+
if (health.kind === "unavailable") {
|
|
366
|
+
return (
|
|
367
|
+
<div className="mcp-readiness is-unknown">
|
|
368
|
+
<div>
|
|
369
|
+
<strong>{hosted ? "Couldn't check this workspace" : "Couldn't check the live server"}</strong>
|
|
370
|
+
{hosted ? (
|
|
371
|
+
<span>The workspace did not answer. Retry, or reload the editor.</span>
|
|
372
|
+
) : (
|
|
373
|
+
<span>Run <code>{STATUS_COMMAND}</code> in the workspace to inspect it from the terminal.</span>
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
<button type="button" onClick={onRetry}>Retry</button>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (!health.mounted) {
|
|
381
|
+
return (
|
|
382
|
+
<div className="mcp-readiness is-off">
|
|
383
|
+
<IconLinkOff />
|
|
384
|
+
<div>
|
|
385
|
+
<strong>{hosted ? "MCP is not available here" : "MCP is off on this running server"}</strong>
|
|
386
|
+
<span>
|
|
387
|
+
{hosted
|
|
388
|
+
? "This deployment does not serve MCP, so no AI can connect to this workspace."
|
|
389
|
+
: "Configure it below, then restart the Studio server."}
|
|
390
|
+
</span>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (adminsOnly(health)) {
|
|
396
|
+
return (
|
|
397
|
+
<div className="mcp-readiness is-off" data-testid="mcp-admins-only">
|
|
398
|
+
<IconLinkOff />
|
|
399
|
+
<div>
|
|
400
|
+
<strong>MCP is not open to your role</strong>
|
|
401
|
+
<span>{ADMINS_ONLY_NOTE}</span>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return (
|
|
407
|
+
<div className={`mcp-readiness${health.write ? " is-write" : " is-read"}`}>
|
|
408
|
+
{health.write ? <IconCheck /> : <IconLink />}
|
|
409
|
+
<div>
|
|
410
|
+
<strong>MCP server ready</strong>
|
|
411
|
+
<span>
|
|
412
|
+
{health.write
|
|
413
|
+
? "Direct document writes enabled."
|
|
414
|
+
: hosted
|
|
415
|
+
? health.manage
|
|
416
|
+
? `Dailies documents: read-only. Turn on "${WRITES_LABEL}" below to let an agent edit.`
|
|
417
|
+
: "Dailies documents: read-only. An Owner or an Admin can turn on AI edits."
|
|
418
|
+
: "Dailies documents: read-only."}
|
|
419
|
+
</span>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* The hosted write switch. It shows what the workspace answers now (health),
|
|
427
|
+
* and a flip writes `mcp.write` into motion.config.json (`setMcpWrites`). The
|
|
428
|
+
* health is read again either way, so a refused or conflicting save leaves the
|
|
429
|
+
* switch showing what is actually saved.
|
|
430
|
+
*/
|
|
431
|
+
function HostedWrites({ write, onSaved }: { write: boolean; onSaved: () => void }) {
|
|
432
|
+
const [busy, setBusy] = useState(false);
|
|
433
|
+
const [problem, setProblem] = useState<string | null>(null);
|
|
434
|
+
const alive = useRef(true);
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
alive.current = true;
|
|
437
|
+
return () => {
|
|
438
|
+
alive.current = false;
|
|
439
|
+
};
|
|
440
|
+
}, []);
|
|
441
|
+
const flip = (next: boolean): void => {
|
|
442
|
+
setBusy(true);
|
|
443
|
+
setProblem(null);
|
|
444
|
+
void setMcpWrites(next).then((refused) => {
|
|
445
|
+
if (!alive.current) return;
|
|
446
|
+
setBusy(false);
|
|
447
|
+
setProblem(refused);
|
|
448
|
+
onSaved();
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
return (
|
|
452
|
+
<div className="mcp-writes" data-testid="mcp-hosted-writes">
|
|
453
|
+
<div>
|
|
454
|
+
<strong>{WRITES_LABEL}</strong>
|
|
455
|
+
<span>
|
|
456
|
+
{write
|
|
457
|
+
? "An agent can change Projects, storyboards, code and assets here."
|
|
458
|
+
: "An agent can read this workspace but change nothing."}
|
|
459
|
+
</span>
|
|
460
|
+
</div>
|
|
461
|
+
<Toggle checked={write} disabled={busy} ariaLabel={WRITES_LABEL} onChange={flip} />
|
|
462
|
+
<p className="mcp-note" data-testid="mcp-hosted-reconnect">{RECONNECT_NOTE}</p>
|
|
463
|
+
{problem === null ? null : (
|
|
464
|
+
<p className="mcp-writes-problem" role="alert">
|
|
465
|
+
{problem}
|
|
466
|
+
</p>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Write `mcp.write` into the workspace's motion.config.json, keeping every
|
|
474
|
+
* other key, through `/api/files`: PUT against the hash just read, or POST
|
|
475
|
+
* (create) when there is no file, which then holds only this key. It never
|
|
476
|
+
* rewrites a file it cannot parse and never merges: a file that moved in
|
|
477
|
+
* between answers 409, which is reported. Null when saved, else why not.
|
|
478
|
+
*/
|
|
479
|
+
export async function setMcpWrites(write: boolean): Promise<string | null> {
|
|
480
|
+
const url = `/api/files/${MCP_CONFIG_FILE}`;
|
|
481
|
+
let config: Record<string, unknown> = {};
|
|
482
|
+
let baseHash: string | undefined;
|
|
483
|
+
try {
|
|
484
|
+
const read = await fetch(url, { cache: "no-store", headers: { accept: "application/json" } });
|
|
485
|
+
if (read.ok) {
|
|
486
|
+
const file = (await read.json()) as { content?: unknown; hash?: unknown };
|
|
487
|
+
if (typeof file.content !== "string" || typeof file.hash !== "string") return `Couldn't read ${MCP_CONFIG_FILE}.`;
|
|
488
|
+
let parsed: unknown = null;
|
|
21
489
|
try {
|
|
22
|
-
|
|
23
|
-
if (!res.ok) return;
|
|
24
|
-
const body = (await res.json()) as { command?: unknown };
|
|
25
|
-
if (alive && typeof body.command === "string") setCommand(body.command);
|
|
490
|
+
parsed = JSON.parse(file.content);
|
|
26
491
|
} catch {
|
|
27
|
-
|
|
28
|
-
|
|
492
|
+
/* refused below */
|
|
493
|
+
}
|
|
494
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
495
|
+
return `${MCP_CONFIG_FILE} is not a JSON object, so it was left as it is. Fix it in the file tree, then try again.`;
|
|
29
496
|
}
|
|
30
|
-
|
|
497
|
+
config = parsed as Record<string, unknown>;
|
|
498
|
+
baseHash = file.hash;
|
|
499
|
+
} else if (read.status !== 404) {
|
|
500
|
+
return `Couldn't read ${MCP_CONFIG_FILE} (${read.status}).`;
|
|
501
|
+
}
|
|
502
|
+
const mcp = config["mcp"];
|
|
503
|
+
const next = {
|
|
504
|
+
...config,
|
|
505
|
+
mcp: { ...(typeof mcp === "object" && mcp !== null && !Array.isArray(mcp) ? mcp : {}), write },
|
|
506
|
+
};
|
|
507
|
+
const content = `${JSON.stringify(next, null, 2)}\n`;
|
|
508
|
+
const saved = await fetch(url, {
|
|
509
|
+
method: baseHash === undefined ? "POST" : "PUT",
|
|
510
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
511
|
+
body: JSON.stringify(baseHash === undefined ? { content } : { content, baseHash }),
|
|
512
|
+
});
|
|
513
|
+
if (saved.ok) return null;
|
|
514
|
+
if (saved.status === 409) {
|
|
515
|
+
return `${MCP_CONFIG_FILE} changed while this was being saved. The switch shows the saved setting now; flip it again if that is not what you want.`;
|
|
516
|
+
}
|
|
517
|
+
const body = (await saved.json().catch(() => null)) as { error?: unknown } | null;
|
|
518
|
+
return `Couldn't save ${MCP_CONFIG_FILE}${typeof body?.error === "string" ? `: ${body.error}` : ` (${saved.status})`}.`;
|
|
519
|
+
} catch {
|
|
520
|
+
return `Couldn't reach the workspace to save ${MCP_CONFIG_FILE}.`;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* What to paste into a client, for the local origin this page was served
|
|
526
|
+
* from: the studio server or the chrome's dev server (which proxies `/mcp`,
|
|
527
|
+
* vite.config.ts). Hosted has no rows: an app adds the MCP URL and signs in
|
|
528
|
+
* (`HostedConnect`).
|
|
529
|
+
*
|
|
530
|
+
* - `claude`: Claude Code's `claude mcp add`. Off loopback the studio mounts
|
|
531
|
+
* `/mcp` only behind `STUDIO_MCP_TOKEN`, so the header names the variable,
|
|
532
|
+
* never its value, in DOUBLE quotes: the author's shell fills it in at the
|
|
533
|
+
* add. Claude Code stores the header as given and never expands a bare
|
|
534
|
+
* `$VAR`, so single quotes would register a header that always fails.
|
|
535
|
+
* - `json`: the `.mcp.json` entry `connect --http` writes (connectVerbs.ts),
|
|
536
|
+
* with the header as Claude Code's `${VAR}` expansion off loopback.
|
|
537
|
+
*/
|
|
538
|
+
export function connectCommands(where: { origin: string; hostname: string }): { claude: string; json: string } {
|
|
539
|
+
const url = `${where.origin}/mcp`;
|
|
540
|
+
const add = `claude mcp add --transport http ${MCP_SERVER_KEY} ${url}`;
|
|
541
|
+
const loopback = isLoopbackHostname(where.hostname);
|
|
542
|
+
const entry = {
|
|
543
|
+
type: "http",
|
|
544
|
+
url,
|
|
545
|
+
...(loopback ? {} : { headers: { [MCP_TOKEN_HEADER]: `\${${MCP_TOKEN_ENV}}` } }),
|
|
546
|
+
};
|
|
547
|
+
return {
|
|
548
|
+
claude: loopback ? add : `${add} --header "${MCP_TOKEN_HEADER}: $${MCP_TOKEN_ENV}"`,
|
|
549
|
+
json: JSON.stringify({ mcpServers: { [MCP_SERVER_KEY]: entry } }),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* The connect rows. Local: `connect` first while `/mcp` is not known to be
|
|
555
|
+
* mounted (it writes the config and the entry), then the Claude Code command
|
|
556
|
+
* and the `.mcp.json` entry for this origin. Hosted: the MCP URL and a link to
|
|
557
|
+
* the dashboard's Connect your AI page, in a new tab so the editor stays open,
|
|
558
|
+
* unless `/mcp` is not mounted there or refuses this role (Readiness says
|
|
559
|
+
* why), so no step is offered that cannot work.
|
|
560
|
+
*/
|
|
561
|
+
export function ConnectRows({ health }: { health: McpHealth }) {
|
|
562
|
+
const hosted = isCloudWorkspace();
|
|
563
|
+
const { copied, copy } = useCopy();
|
|
564
|
+
|
|
565
|
+
if (hosted) {
|
|
566
|
+
if ((health.kind === "ready" && !health.mounted) || adminsOnly(health)) return null;
|
|
567
|
+
return <HostedConnect />;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const rows = connectCommands(window.location);
|
|
571
|
+
const mounted = health.kind === "ready" && health.mounted;
|
|
572
|
+
return (
|
|
573
|
+
<div className="mcp-setup" data-testid="mcp-local-setup">
|
|
574
|
+
{mounted ? null : (
|
|
575
|
+
<>
|
|
576
|
+
<p className="mcp-note">
|
|
577
|
+
Run the helper from this workspace root first. It updates <code>motion.config.json</code> and{" "}
|
|
578
|
+
<code>.mcp.json</code>.
|
|
579
|
+
</p>
|
|
580
|
+
<CommandRow
|
|
581
|
+
label="Read-only setup"
|
|
582
|
+
value={CONNECT_COMMAND}
|
|
583
|
+
copied={copied === "connect"}
|
|
584
|
+
onCopy={() => copy("connect", CONNECT_COMMAND)}
|
|
585
|
+
/>
|
|
586
|
+
</>
|
|
587
|
+
)}
|
|
588
|
+
<CommandRow
|
|
589
|
+
label="Claude Code"
|
|
590
|
+
value={rows.claude}
|
|
591
|
+
copied={copied === "claude"}
|
|
592
|
+
onCopy={() => copy("claude", rows.claude)}
|
|
593
|
+
/>
|
|
594
|
+
<CommandRow
|
|
595
|
+
label=".mcp.json"
|
|
596
|
+
value={rows.json}
|
|
597
|
+
copied={copied === "json"}
|
|
598
|
+
onCopy={() => copy("json", rows.json)}
|
|
599
|
+
/>
|
|
600
|
+
<p className="mcp-footnote">
|
|
601
|
+
Read-only is the default. To allow direct Project document edits, run <code>{WRITE_COMMAND}</code>, then restart
|
|
602
|
+
the Studio server. Verify the live state with <code>{STATUS_COMMAND}</code>.
|
|
603
|
+
</p>
|
|
604
|
+
</div>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** The discovery document every host serves (worker oauth.ts). Its
|
|
609
|
+
* `resource` is the MCP URL on MAIN, which the editor host cannot name. */
|
|
610
|
+
const RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource/mcp";
|
|
611
|
+
|
|
612
|
+
/** The MCP URL, or null until read or when the read fails. */
|
|
613
|
+
function useMcpUrl(): string | null {
|
|
614
|
+
const [url, setUrl] = useState<string | null>(null);
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
let alive = true;
|
|
617
|
+
void readJson(RESOURCE_METADATA_PATH).then((body) => {
|
|
618
|
+
const resource = (body as { resource?: unknown } | null)?.resource;
|
|
619
|
+
if (alive && typeof resource === "string" && /^https?:\/\/[^/]+\/mcp$/.test(resource)) setUrl(resource);
|
|
620
|
+
});
|
|
31
621
|
return () => {
|
|
32
622
|
alive = false;
|
|
33
623
|
};
|
|
34
624
|
}, []);
|
|
625
|
+
return url;
|
|
626
|
+
}
|
|
35
627
|
|
|
628
|
+
/** Hosted: the URL to add to an AI app, and the page with every app's steps.
|
|
629
|
+
* Without the URL (the read failed), the link alone still gets there. */
|
|
630
|
+
function HostedConnect() {
|
|
631
|
+
const url = useMcpUrl();
|
|
632
|
+
const { copied, copy } = useCopy();
|
|
36
633
|
return (
|
|
37
|
-
<div className="mcp-
|
|
38
|
-
|
|
39
|
-
{command === null ? (
|
|
40
|
-
<p className="help-note">
|
|
41
|
-
Run <code>npx dailies-studio connect</code> in this project, then start Claude Code in the
|
|
42
|
-
same folder — it picks up <code>.mcp.json</code> and can edit this scene.
|
|
43
|
-
</p>
|
|
44
|
-
) : (
|
|
634
|
+
<div className="mcp-setup" data-testid="mcp-hosted-setup">
|
|
635
|
+
{url === null ? null : (
|
|
45
636
|
<>
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
</p>
|
|
49
|
-
<div className="help-command">
|
|
50
|
-
<code>{command}</code>
|
|
51
|
-
<button
|
|
52
|
-
type="button"
|
|
53
|
-
onClick={() => {
|
|
54
|
-
void navigator.clipboard?.writeText(command);
|
|
55
|
-
setCopied(true);
|
|
56
|
-
window.setTimeout(() => setCopied(false), 1500);
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{copied ? "Copied" : "Copy"}
|
|
60
|
-
</button>
|
|
61
|
-
</div>
|
|
637
|
+
<CommandRow label="Server URL" value={url} copied={copied === "url"} onCopy={() => copy("url", url)} />
|
|
638
|
+
<p className="mcp-note">Add this URL to your AI. It signs in with your Dailies account.</p>
|
|
62
639
|
</>
|
|
63
640
|
)}
|
|
641
|
+
<a className="mcp-connect-link" href={AGENT_ACCESS_HREF} target="_blank" rel="noopener">
|
|
642
|
+
Connect your AI: steps for each app
|
|
643
|
+
</a>
|
|
64
644
|
</div>
|
|
65
645
|
);
|
|
66
646
|
}
|
|
647
|
+
|
|
648
|
+
export function CommandRow({
|
|
649
|
+
label,
|
|
650
|
+
value,
|
|
651
|
+
copied,
|
|
652
|
+
onCopy,
|
|
653
|
+
}: {
|
|
654
|
+
label: string;
|
|
655
|
+
value: string;
|
|
656
|
+
copied: boolean;
|
|
657
|
+
onCopy: () => void;
|
|
658
|
+
}) {
|
|
659
|
+
return (
|
|
660
|
+
<div className="mcp-command">
|
|
661
|
+
<span>{label}</span>
|
|
662
|
+
<div>
|
|
663
|
+
<code>{value}</code>
|
|
664
|
+
<CopyButton copied={copied} onClick={onCopy} />
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export function CopyButton({ copied, onClick }: { copied: boolean; onClick: () => void }) {
|
|
671
|
+
return (
|
|
672
|
+
<button type="button" onClick={onClick} aria-live="polite">
|
|
673
|
+
{copied ? "Copied" : "Copy"}
|
|
674
|
+
</button>
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export async function readHealth(): Promise<McpHealth> {
|
|
679
|
+
const body = await readJson("/api/health");
|
|
680
|
+
if (typeof body !== "object" || body === null) return { kind: "unavailable" };
|
|
681
|
+
const record = body as { ok?: unknown; name?: unknown; mcp?: unknown };
|
|
682
|
+
// Chrome cannot import the CLI, so this repeats the two-name test in
|
|
683
|
+
// `probeStudioHealth` (packages/cli/src/devServerLock.ts). That is the source
|
|
684
|
+
// this must match.
|
|
685
|
+
if (record.ok !== true || (record.name !== "dailies" && record.name !== "motion-studio")) {
|
|
686
|
+
return { kind: "unavailable" };
|
|
687
|
+
}
|
|
688
|
+
if (typeof record.mcp !== "object" || record.mcp === null) return { kind: "unavailable" };
|
|
689
|
+
const mcp = record.mcp as { mounted?: unknown; write?: unknown; allowed?: unknown; manage?: unknown };
|
|
690
|
+
if (typeof mcp.mounted !== "boolean" || typeof mcp.write !== "boolean") return { kind: "unavailable" };
|
|
691
|
+
// A local server has no roles and sends neither.
|
|
692
|
+
return {
|
|
693
|
+
kind: "ready",
|
|
694
|
+
mounted: mcp.mounted,
|
|
695
|
+
write: mcp.write,
|
|
696
|
+
allowed: mcp.allowed !== false,
|
|
697
|
+
manage: mcp.manage !== false,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function readJson(path: string): Promise<unknown | null> {
|
|
702
|
+
try {
|
|
703
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
704
|
+
if (!response.ok) return null;
|
|
705
|
+
return await response.json();
|
|
706
|
+
} catch {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
}
|