auricui-skill 0.0.0-stage → 0.1.2

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.
Files changed (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/bin/install.mjs +66 -0
  4. package/package.json +29 -4
  5. package/skill/SKILL.md +64 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/assets/examples/ApprovalCard.tsx +8 -0
  8. package/skill/assets/examples/BasicApp.tsx +38 -0
  9. package/skill/assets/examples/BriefingCard.tsx +8 -0
  10. package/skill/assets/examples/ChatControls.tsx +49 -0
  11. package/skill/assets/examples/ChatPanel.tsx +73 -0
  12. package/skill/assets/examples/Checkbox.tsx +13 -0
  13. package/skill/assets/examples/Combobox.tsx +14 -0
  14. package/skill/assets/examples/Drawer.tsx +10 -0
  15. package/skill/assets/examples/MemoryCard.tsx +9 -0
  16. package/skill/assets/examples/MessageList.tsx +8 -0
  17. package/skill/assets/examples/Theme.tsx +14 -0
  18. package/skill/assets/examples/ToolCall.tsx +8 -0
  19. package/skill/assets/examples/Tree.tsx +15 -0
  20. package/skill/assets/examples/examples.css +11 -0
  21. package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
  22. package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
  23. package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
  24. package/skill/assets/upstream/LICENSE +21 -0
  25. package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
  26. package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
  27. package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
  28. package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
  29. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
  30. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
  31. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
  32. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
  33. package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
  34. package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
  35. package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
  36. package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
  37. package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
  38. package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
  39. package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
  40. package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
  41. package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
  42. package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
  43. package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
  44. package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
  45. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
  46. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
  47. package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
  48. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
  49. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
  50. package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
  51. package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
  52. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
  53. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
  54. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
  55. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
  56. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
  57. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
  58. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
  59. package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
  60. package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
  61. package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
  62. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
  63. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
  64. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
  65. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
  66. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
  67. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
  68. package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
  69. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
  70. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
  71. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
  72. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
  73. package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
  74. package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
  75. package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
  76. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
  77. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
  78. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
  79. package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
  80. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
  81. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
  82. package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
  83. package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
  84. package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
  85. package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
  86. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
  87. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
  88. package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
  89. package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
  90. package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
  91. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
  92. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
  93. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
  94. package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
  95. package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
  96. package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
  97. package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
  98. package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
  99. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
  100. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
  101. package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
  102. package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
  103. package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
  104. package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
  105. package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
  106. package/skill/assets/upstream/apps/desktop/package.json +51 -0
  107. package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
  108. package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
  109. package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
  110. package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
  111. package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
  112. package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
  113. package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
  114. package/skill/assets/upstream/packages/openfilm/package.json +88 -0
  115. package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
  116. package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
  117. package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
  118. package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
  119. package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
  120. package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
  121. package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
  122. package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
  123. package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
  124. package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
  125. package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
  126. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
  127. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
  128. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
  129. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
  130. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
  131. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
  132. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
  133. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
  134. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
  135. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
  136. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
  137. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
  138. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
  139. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
  140. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
  141. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
  142. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
  143. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
  144. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
  145. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
  146. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
  147. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
  148. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
  149. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
  150. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
  151. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
  152. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
  153. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
  154. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
  155. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
  156. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
  157. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
  158. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
  159. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
  160. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
  161. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
  162. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
  163. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
  164. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
  165. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
  166. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
  167. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
  168. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
  169. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
  170. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
  171. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
  172. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
  173. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
  174. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
  175. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
  176. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
  177. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
  178. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
  179. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
  180. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
  181. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
  182. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
  183. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
  184. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
  185. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
  186. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
  187. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
  188. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
  189. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
  190. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
  191. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
  192. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
  193. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
  194. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
  195. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
  196. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
  197. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
  198. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
  199. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
  200. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
  201. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
  202. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
  203. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
  204. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
  205. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
  206. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
  207. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
  208. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
  209. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
  210. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
  211. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
  212. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
  213. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
  214. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
  215. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
  216. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
  217. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
  218. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
  219. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
  220. package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
  221. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
  222. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
  223. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
  224. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
  225. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
  226. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
  227. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
  228. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
  229. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
  230. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
  231. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
  232. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
  233. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
  234. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
  235. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
  236. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
  237. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
  238. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
  239. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
  240. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
  241. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
  242. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
  243. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
  244. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
  245. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
  246. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
  247. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
  248. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
  249. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
  250. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
  251. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
  252. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
  253. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
  254. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
  255. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
  256. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
  257. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
  258. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
  259. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
  260. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
  261. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
  262. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
  263. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
  264. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
  265. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
  266. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
  267. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
  268. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
  269. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
  270. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
  271. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
  272. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
  273. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
  274. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
  275. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
  276. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
  277. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
  278. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
  279. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
  280. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
  281. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
  282. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
  283. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
  284. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
  285. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
  286. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
  287. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
  288. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
  289. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
  290. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
  291. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
  292. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
  293. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
  294. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
  295. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
  296. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
  297. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
  298. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
  299. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
  300. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
  301. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
  302. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
  303. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
  304. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
  305. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
  306. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
  307. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
  308. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
  309. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
  310. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
  311. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
  312. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
  313. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
  314. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
  315. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
  316. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
  317. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
  318. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
  319. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
  320. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
  321. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
  322. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
  323. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
  324. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
  325. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
  326. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
  327. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
  328. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
  329. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
  330. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
  331. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
  332. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
  333. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
  334. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
  335. package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
  336. package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
  337. package/skill/assets/upstream/packages/shared/package.json +45 -0
  338. package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
  339. package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
  340. package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
  341. package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
  342. package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
  343. package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
  344. package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
  345. package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
  346. package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
  347. package/skill/references/agent-components.md +33 -0
  348. package/skill/references/api-index.json +2693 -0
  349. package/skill/references/chat-components.md +65 -0
  350. package/skill/references/components.md +23 -0
  351. package/skill/references/core-components.md +44 -0
  352. package/skill/references/dependencies.md +22 -0
  353. package/skill/references/esm-package.md +48 -0
  354. package/skill/references/integration.md +43 -0
  355. package/skill/references/library-manifest.json +12 -0
  356. package/skill/references/source-manifest.json +325 -0
  357. package/skill/references/studio-components.md +24 -0
  358. package/skill/references/theme-motion.md +49 -0
  359. package/skill/references/troubleshooting.md +27 -0
  360. package/skill/references/verification.md +13 -0
  361. package/skill/scripts/check_dependencies.py +109 -0
  362. package/skill/scripts/inspect_api.py +60 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * A family's weights and italics, as the inspector's weight menu offers them ("Light", "Bold Italic"), and
3
+ * a CSS `font-family` stack's first family, as the font field shows and changes it.
4
+ *
5
+ * What a family has comes from its faces: an installed font's OS/2 weight classes and italic bits (GET /api/fonts),
6
+ * a project font's `@font-face` weights and styles (GET /api/projects/:id/fonts). A variable font has every weight
7
+ * of its range; the menu offers the named ones in it.
8
+ */
9
+
10
+ /** What a family has. `italics`: the weights it has an italic of; `variable`: its weight range. */
11
+ export interface FontStyles {
12
+ weights: readonly number[];
13
+ italics?: readonly number[];
14
+ variable?: readonly [number, number];
15
+ }
16
+
17
+ /** One choice of the weight menu. */
18
+ export interface FontStyle {
19
+ weight: number;
20
+ italic: boolean;
21
+ }
22
+
23
+ /** The names CSS gives weights, as the menu says them (i18n `inspector.weights.w<key>`). */
24
+ export const NAMED_WEIGHTS = [100, 200, 300, 400, 500, 600, 700, 800, 900] as const;
25
+
26
+ /** The named weight `w` is called by: the nearest (Avenir's 275 is Light), a tie the lighter. */
27
+ export function weightKey(w: number): (typeof NAMED_WEIGHTS)[number] {
28
+ let best: (typeof NAMED_WEIGHTS)[number] = 400;
29
+ for (const named of NAMED_WEIGHTS) if (Math.abs(named - w) < Math.abs(best - w)) best = named;
30
+ return best;
31
+ }
32
+
33
+ /** `normal` / `bold` / `700` as a number; null when it is none (`bolder`, a variable). */
34
+ export function weightNumber(v: string | number | undefined | null): number | null {
35
+ if (typeof v === 'number') return Number.isFinite(v) ? v : null;
36
+ const raw = String(v ?? '').trim().toLowerCase();
37
+ if (raw === 'normal') return 400;
38
+ if (raw === 'bold') return 700;
39
+ const n = Number(raw);
40
+ return raw !== '' && Number.isFinite(n) && n >= 1 && n <= 1000 ? n : null;
41
+ }
42
+
43
+ /** Whether a `font-style` is a slanted one. */
44
+ export const isItalic = (v: string | undefined | null) => /^(italic|oblique)\b/i.test(String(v ?? '').trim());
45
+
46
+ /**
47
+ * The weight of `available` the browser draws `want` with, as CSS font matching picks it: 400–500 look up to 500
48
+ * first, then down, then up; lighter look down first; heavier look up first. A variable range has `want` itself.
49
+ */
50
+ export function nearestWeight(available: readonly number[], want: number, variable?: readonly [number, number]): number {
51
+ if (variable && want >= variable[0] && want <= variable[1]) return want;
52
+ const list = [...new Set(variable ? [...available, variable[0], variable[1]] : available)].sort((a, b) => a - b);
53
+ if (!list.length) return want;
54
+ if (list.includes(want)) return want;
55
+ const below = list.filter((w) => w < want);
56
+ const above = list.filter((w) => w > want);
57
+ const down = () => below[below.length - 1];
58
+ const up = () => above[0];
59
+ if (want >= 400 && want <= 500) {
60
+ const toFive = above.find((w) => w <= 500);
61
+ return toFive ?? down() ?? up()!;
62
+ }
63
+ return want < 400 ? down() ?? up()! : up() ?? down()!;
64
+ }
65
+
66
+ /** The menu's choices for a family, lightest first, each upright then its italic. */
67
+ export function styleOptions(styles: FontStyles): FontStyle[] {
68
+ const named = (list: readonly number[]) => {
69
+ const range = styles.variable;
70
+ /* a variable font: the named weights in its range (and any it says it has) */
71
+ const all = range && list.length ? [...list, ...NAMED_WEIGHTS.filter((w) => w >= range[0] && w <= range[1])] : list;
72
+ return [...new Set(all)].sort((a, b) => a - b);
73
+ };
74
+ const italics = new Set(named(styles.italics ?? []));
75
+ const out: FontStyle[] = [];
76
+ for (const weight of named(styles.weights)) {
77
+ out.push({ weight, italic: false });
78
+ if (italics.has(weight)) { out.push({ weight, italic: true }); italics.delete(weight); }
79
+ }
80
+ for (const weight of italics) out.push({ weight, italic: true });
81
+ return out.sort((a, b) => a.weight - b.weight || Number(a.italic) - Number(b.italic));
82
+ }
83
+
84
+ /** The choice that stands for a weight and slant in the menu's value (`700`, `700 italic`). */
85
+ export const styleValue = (s: FontStyle) => (s.italic ? `${s.weight} italic` : String(s.weight));
86
+ export function parseStyleValue(v: string): FontStyle | null {
87
+ const m = /^(\d+)( italic)?$/.exec(v.trim());
88
+ return m ? { weight: Number(m[1]), italic: Boolean(m[2]) } : null;
89
+ }
90
+
91
+ /**
92
+ * What a family draws `want` as: its nearest weight, and italic only when it has one (else upright: an italic it
93
+ * does not have would be slanted by the browser, which no one picks).
94
+ */
95
+ export function snapStyle(styles: FontStyles, want: FontStyle): FontStyle {
96
+ const italics = styles.italics ?? [];
97
+ const italic = want.italic && italics.length > 0;
98
+ const list = italic ? italics : styles.weights.length ? styles.weights : italics;
99
+ return { weight: nearestWeight(list, want.weight, styles.variable), italic };
100
+ }
101
+
102
+ /* ── a font-family stack ── */
103
+
104
+ /**
105
+ * The font field shows the first family only; the fallbacks after it stay as written — changing the
106
+ * main font must not replace `Inter, "PingFang SC", sans-serif` with one name.
107
+ */
108
+ export function splitFontFamily(stack: string): [string, string] {
109
+ const comma = stack.search(/,(?=(?:[^"']|"[^"]*"|'[^']*')*$)/);
110
+ const head = (comma < 0 ? stack : stack.slice(0, comma)).trim().replace(/^["']|["']$/g, '');
111
+ return [head, comma < 0 ? '' : stack.slice(comma + 1).trim()];
112
+ }
113
+
114
+ /** A family as CSS writes it: quoted unless it is plain words (a generic name, `Geist`). */
115
+ export function cssFamily(name: string): string {
116
+ const n = name.trim();
117
+ if (/^["']/.test(n)) return n;
118
+ return /^-?[A-Za-z_][\w-]*$/.test(n) ? n : `"${n.replace(/["\\]/g, '\\$&')}"`;
119
+ }
120
+
121
+ /** `primary` before the fallbacks `rest`, the fallbacks kept, `primary` not again among them. */
122
+ export function joinFontFamily(primary: string, rest: string): string {
123
+ const name = cssFamily(primary);
124
+ const plain = primary.trim().replace(/^["']|["']$/g, '').toLowerCase();
125
+ /* the fallbacks as written (a `var()` stays one), but for the new font itself */
126
+ const others = rest.split(/,(?=(?:[^"']|"[^"]*"|'[^']*')*$)/).map((s) => s.trim())
127
+ .filter((s) => s && s.replace(/^["']|["']$/g, '').trim().toLowerCase() !== plain);
128
+ return [name, ...others].join(', ');
129
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Freeze frame: the frame of a video clip under the playhead, made a still (studio/server/files.mjs freezeFrame) and
3
+ * put in at the playhead on the clip's track: an insert cuts the video there and what follows on that track moves on
4
+ * by the still's length (with linkage, what rides on the main track along); an overwrite has the still
5
+ * cover that much of the video after the playhead. The still looks as the video does (its box, class and style).
6
+ */
7
+ import type { Clip, Op } from '../api.ts';
8
+ import { planRoom, type EditMode, type EditModel, type Switches } from './timeline-edit.ts';
9
+ import { snapToFrame } from './timecode.ts';
10
+ import type { TimelineBlock } from './timeline-layout';
11
+
12
+ /** How long a freeze frame lasts: 3 s. */
13
+ export const FREEZE_MS = 3000;
14
+
15
+ /** The moment of the clip's file the playhead is on (ms), or null when the playhead is not on the clip. */
16
+ export function freezeSourceMs(block: Pick<TimelineBlock, 'startMs' | 'endMs' | 'inMs' | 'speed'>, atMs: number): number | null {
17
+ if (!(atMs >= block.startMs && atMs < block.endMs)) return null;
18
+ return Math.round((block.inMs ?? 0) + (atMs - block.startMs) * (block.speed ?? 1));
19
+ }
20
+
21
+ export interface FreezeSpec extends Partial<Switches> {
22
+ /** The video clip, as film.html has it. */
23
+ clip: Clip;
24
+ /** Its track (film.html index). */
25
+ track: number;
26
+ /** The playhead. */
27
+ atMs: number;
28
+ /** The still made of its frame (a project path). */
29
+ still: string;
30
+ mode: EditMode;
31
+ durMs?: number;
32
+ }
33
+
34
+ /** The edit: the still in at the playhead, and room made for it. Null when the track is locked. */
35
+ export function planFreeze(model: EditModel, spec: FreezeSpec): Op[] | null {
36
+ if (model.find((t) => t.index === spec.track)?.locked) return null;
37
+ const startMs = Math.max(0, snapToFrame(spec.atMs));
38
+ const durMs = spec.durMs ?? FREEZE_MS;
39
+ const { box, class: cls, style } = spec.clip;
40
+ const still: Partial<Clip> & { src: string } = {
41
+ src: spec.still,
42
+ time: [0, durMs / 1000],
43
+ ...(box ? { box } : {}),
44
+ ...(cls ? { class: cls } : {}),
45
+ ...(style ? { style } : {}),
46
+ };
47
+ const room = planRoom(model, [{ track: spec.track, startMs, endMs: startMs + durMs }], { mode: spec.mode, magnet: spec.magnet, linkage: spec.linkage });
48
+ return [{ op: 'insert', clip: still, at: Math.round(startMs) / 1000, track: spec.track }, ...room.ops];
49
+ }
@@ -0,0 +1,438 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * The app Studio is embedded in, when there is one. An app that shows Studio in its own window (rather than a browser
5
+ * tab) sets `window.openfilmHost` before Studio's modules run (a preload, or the app's own page that builds Studio's
6
+ * editor in, served with `startStudio({ editorDir })`); Studio then leaves out what only makes sense in a browser,
7
+ * such as the button that introduces the desktop app.
8
+ */
9
+ /** A tab of the app's window (`tabs` below): a project's, its title as Studio shows it. */
10
+ export interface HostTab { id: string; projectId: string; title: string; active: boolean; working: boolean; asking: boolean; exporting: number | null }
11
+
12
+ /** Settings → Developer, as the app keeps it (`developer` below). */
13
+ export interface HostDeveloper { logs: boolean; changes: boolean }
14
+
15
+ /** The project identity and current folder shared with an embedded chat. */
16
+ export interface HostProject { id: string; name: string; path: string }
17
+
18
+ export interface StudioHost {
19
+ /** the version of this interface the app speaks */
20
+ version: 1;
21
+ /** The native Settings menu asks the current page to open General, including the launcher. */
22
+ settings?: { onOpen: (listener: () => void) => () => void };
23
+ /** the app's name, for the person */
24
+ name?: string;
25
+ /** A successful rename, here or in another page: the chat keeps its identity but uses the new name and folder. */
26
+ projectChanged?: (project: HostProject) => void;
27
+ /**
28
+ * The window has no title bar of its own: Studio's top bar is where the window is dragged, and `inset` pixels at its
29
+ * left (macOS) and `insetRight` at its right (Windows) are left free for the window's controls. `onChange` tells of a
30
+ * new left inset (a full-screen window has no controls to make room for).
31
+ */
32
+ titlebar?: { inset: number; insetRight?: number; onChange?: (listener: (inset: number) => void) => void };
33
+ /**
34
+ * The app has a chat: whatever the person points at becomes a reference in the message being written. Studio calls
35
+ * this from "Reference in chat" on its menus (clips, layers, tracks, media), from ⌘L (what is selected; the range
36
+ * marked on the timeline; else the frame at the playhead) and from a box drawn on the picture with ⌘ held.
37
+ * Studio's things can also be dragged into the app's composer: they carry STUDIO_REF_TYPE (see there).
38
+ */
39
+ refer?: (ref: StudioRef) => void;
40
+ /**
41
+ * What is selected in the project shown and where the playhead is, each time either changes (the time a few times
42
+ * a second while playing): what a chat's "@current selection" and "@current time" point at.
43
+ */
44
+ onSelection?: (selection: StudioSelection) => void;
45
+ /**
46
+ * What the pointer is over in Studio, each time that changes (null: nothing it points at): a clip's block on the
47
+ * timeline, a layer on the picture. A chat lights its pills that point at the same thing.
48
+ */
49
+ onHover?: (ref: StudioRef | null) => void;
50
+ /**
51
+ * A panel of the app's own on Studio's right, under the top bar (its chat), drawn by the app in Studio's page: Studio's
52
+ * top bar runs over it; below the bar Studio leaves `width` px free for it while it is open, and its Layout menu
53
+ * shows and hides it as it does its own panes. The panel's element carries `data-studio-host-panel`, so Studio's
54
+ * shortcuts leave the keys pressed in it alone. `onChange` tells Studio of a new width, or of the panel opened or
55
+ * closed from the app.
56
+ *
57
+ * `assets`, when the app has it, is where the project's media lives instead of Studio's own left pane (the viewer is
58
+ * then first on the left): a button in the panel opens them over it, and Studio draws them there, above the bottom
59
+ * `bottom` px the panel keeps (its composer). `setAssetsOpen` opens or closes them from Studio; `setWidth` sizes the
60
+ * panel from the seam beside them (the panel's own seam is under them then); `done` ends a drag.
61
+ */
62
+ panel?: {
63
+ label: string;
64
+ open: boolean;
65
+ width: number;
66
+ assets?: { open: boolean; bottom: number };
67
+ setOpen: (open: boolean) => void;
68
+ setAssetsOpen?: (open: boolean) => void;
69
+ setWidth?: (width: number, done?: boolean) => void;
70
+ onChange: (listener: (state: { open: boolean; width: number; assets?: { open: boolean; bottom: number } }) => void) => void;
71
+ };
72
+ /**
73
+ * The app's chat names a skill in the message being written: the skills (opened from the chat, `show-examples`)
74
+ * call this for one tried there, `newConversation` true (Try now: a new conversation with it named).
75
+ */
76
+ useSkill?: (skill: { name: string; description: string; newConversation?: boolean }) => void;
77
+ /** The person's own skills, where the app keeps them: one added, the app's own way (from a link, a folder, or made
78
+ with the agent). The Community's skills offer it. */
79
+ skills?: { add: () => void };
80
+ /** Let the app ask Studio to show something: Studio calls this once with its listener. */
81
+ onCommand?: (listener: (command: StudioCommand) => void) => void;
82
+ /**
83
+ * The agents the app's chat can use, for Settings → Agents: connected or not, shown in the chat's menu or not, and
84
+ * the person's own key for the one that takes one. `onChange` tells of every change (a sign-in finished, a key set up)
85
+ * and returns a way to stop listening. One of the person's own agents, found or not, can be pointed at a program
86
+ * of the person's choosing (`locate`: the system's file dialog, with Studio's words; `{ ok: false }` and no error
87
+ * when cancelled) and set back to being found (`useAutomatic`); `diagnose` says what was searched (an older app
88
+ * has none of these). One not found that has an official installer (`HostAgent.installer`) can be installed:
89
+ * `install` runs it in a terminal window, once the person agreed to that command (an older app has none).
90
+ */
91
+ agents?: {
92
+ list: () => Promise<HostAgent[]>;
93
+ connect: (id: HostAgentId) => Promise<{ ok: boolean; error?: string }>;
94
+ disconnect: (id: HostAgentId) => Promise<unknown>;
95
+ /** Codex: what is left of its plan, as it says for the account signed in (null: it can't say; other agents, null) */
96
+ usage?: (id: HostAgentId, fresh?: boolean) => Promise<HostAgentUsage | null>;
97
+ setShown: (id: HostAgentId, shown: boolean) => Promise<unknown>;
98
+ locate?: (id: HostAgentId, words?: { title?: string; button?: string }) => Promise<{ ok: boolean; error?: string; code?: 'not-a-program' | 'did-not-start' | 'app' | 'not-cursor-agent' | string }>;
99
+ install?: (id: HostAgentId) => Promise<{ ok: boolean; error?: string }>;
100
+ useAutomatic?: (id: HostAgentId) => Promise<unknown>;
101
+ diagnose?: (id: HostAgentId) => Promise<HostAgentDiagnosis | null>;
102
+ /** The app's part of Settings → Developer's diagnostic report: the app, and each agent's program, version and state, as text. */
103
+ report?: () => Promise<string>;
104
+ /**
105
+ * One of the person's own models set up (no `id`) or changed: checked before it is kept. A key left empty keeps its
106
+ * own, or takes the one of another model on the same address.
107
+ */
108
+ saveOwnModel: (config: HostOwnModelConfig) => Promise<{ ok: true; id: string } | { ok: false; error: string; code?: string }>;
109
+ removeOwnModel: (id: string) => Promise<unknown>;
110
+ /**
111
+ * The MCP servers the person added to an agent themselves (its maker's not among them), each let into the app or
112
+ * not: hidden there until let in. `filtered` false: the agent cannot be told, and has them all.
113
+ */
114
+ mcp?: (id: HostAgentId) => Promise<{ filtered: boolean; servers: Array<{ name: string; detail: string; allowed: boolean }> }>;
115
+ allowMcp?: (id: HostAgentId, name: string, allowed: boolean) => Promise<unknown>;
116
+ /** The same for the skills the person installed in an agent themselves (OpenFilm's own skill for terminal agents is never among them). */
117
+ skills?: (id: HostAgentId) => Promise<{ filtered: boolean; skills: Array<{ name: string; description: string; allowed: boolean }> }>;
118
+ allowSkill?: (id: HostAgentId, name: string, allowed: boolean) => Promise<unknown>;
119
+ onChange: (listener: (agents: HostAgent[]) => void) => () => void;
120
+ };
121
+ /**
122
+ * Questions an agent's `openfilm get` asks the person before it spends (a video clip), and what it tells them (a
123
+ * provider with no balance left: "Manage" opens its page), for an app with a chat to show where the person talks to
124
+ * the agent, above its composer, instead of Studio's own card: the agent's command waits for a question's answer. Studio calls `show` with every question waiting in the project shown (an
125
+ * empty list when none is left), worded in the language shown; `answer` gives the person's answer.
126
+ */
127
+ asks?: { show: (asks: HostAsk[], answer: (id: string, allow: boolean) => void) => void };
128
+ /**
129
+ * The app's own updates, shown in Studio's top bar beside the projects button: a new version downloaded and ready,
130
+ * and Restart to install it. `onChange` gives the state now and on every change (null: nothing to show), and returns
131
+ * a way to stop listening. `restart` is refused while an agent works or Studio exports; the update then stays ready,
132
+ * with the reason (`waiting`).
133
+ */
134
+ update?: {
135
+ onChange: (listener: (update: HostUpdate | null) => void) => () => void;
136
+ restart: () => Promise<{ ok: boolean; error?: string }>;
137
+ /** Settings → About: the whole state now and on every change, and a check made now (it resolves when it ends) */
138
+ onState?: (listener: (state: HostUpdateState) => void) => () => void;
139
+ check?: () => Promise<HostUpdateState | null>;
140
+ };
141
+ /**
142
+ * The app's chat, for Settings → Developer: `logs`, each turn's full log (everything the agent sent and was
143
+ * answered, as it happened); `changes`, what each turn changed in the project's files, under its reply. `onChange`
144
+ * tells of a change from anywhere and returns a way to stop.
145
+ */
146
+ developer?: {
147
+ get: () => Promise<HostDeveloper>;
148
+ set: (next: Partial<HostDeveloper>) => Promise<unknown>;
149
+ onChange: (listener: (prefs: HostDeveloper) => void) => () => void;
150
+ };
151
+ /**
152
+ * The app shows each project in a tab of its own window and draws no tab strip of its own: Studio draws the tabs in
153
+ * its top bar (where the projects button and the title are otherwise), with a `+` that opens the projects window to
154
+ * pick the project a new tab shows (`open`; one open already is shown). With no tab open the page is at `/`, and
155
+ * Studio shows the projects there. `list` gives the tabs and which is this page's (`self`); `onChange` every change.
156
+ * A page `report`s what it shows (its title, an export's progress, its project gone: its tab closes), and `retitle`s
157
+ * another tab whose project it renamed. Studio's
158
+ * "open this project" (heard by every page) goes to `openRequest`, which the app settles once; `onPick` is the app
159
+ * asking for the projects window (a new tab from its menu).
160
+ */
161
+ tabs?: {
162
+ list: () => Promise<{ self: string | null; tabs: HostTab[] }>;
163
+ onChange: (listener: (tabs: HostTab[]) => void) => () => void;
164
+ show: (id: string) => void;
165
+ close: (id?: string) => void;
166
+ /** a project's tab: shown, opened when it is not */
167
+ open: (projectId: string) => void;
168
+ move: (id: string, index: number) => void;
169
+ report: (state: { title?: string; exporting?: number | null; gone?: boolean }) => void;
170
+ /** projects this page deleted: their tabs close together, none of them to be opened again */
171
+ gone?: (projectIds: string[]) => Promise<unknown>;
172
+ /** another tab's title, its project renamed from this page's tabs (its page may be gone, and cannot say it) */
173
+ retitle?: (id: string, title: string) => Promise<unknown>;
174
+ openRequest: (to: string) => void;
175
+ onPick: (listener: () => void) => () => void;
176
+ /** another page shown in front of this one (a tab it opened, ready): what it showed meanwhile can go */
177
+ onCovered?: (listener: () => void) => () => void;
178
+ };
179
+
180
+ /**
181
+ * The app picks folders, and knows where a dropped one is: the Projects list gets "Open…", which asks the app for a
182
+ * folder to open as a project (`pick`: null when the person cancelled), and opens a folder dropped on it (`pathOf`:
183
+ * its full path, '' when the app cannot tell). The export's "Save to" asks with its own words and the folder it
184
+ * starts in (`title`, `button`, `at`).
185
+ */
186
+ folders?: {
187
+ pick: (options?: { title?: string; button?: string; at?: string }) => Promise<string | null>;
188
+ pathOf: (file: File) => string;
189
+ };
190
+ }
191
+
192
+ /** A question about spending, worded for the person (see `asks`). */
193
+ export interface HostAsk { id: string; title: string; body: string | null; allowLabel: string; denyLabel: string }
194
+
195
+ /**
196
+ * Where the app's update is: `off` (this build never updates) · idle · checking · current · available · downloading
197
+ * (`progress` 0–100) · staging (macOS unpacks it) · ready · installing · error. `availableVersion`: the newer version
198
+ * found; `checkedAt`: when a check last answered (ms); `error`: offline · unavailable · failed (a check or download),
199
+ * agent-busy · exporting · install-failed (a Restart not done).
200
+ */
201
+ export interface HostUpdateState {
202
+ status: 'off' | 'idle' | 'checking' | 'current' | 'available' | 'downloading' | 'staging' | 'ready' | 'installing' | 'error';
203
+ version: string;
204
+ channel: 'stable' | 'beta';
205
+ availableVersion: string | null;
206
+ progress: number;
207
+ checkedAt: number | null;
208
+ error: string | null;
209
+ }
210
+
211
+ /** An update of the app, ready to install or installing; `waiting`: why the last Restart did not go ahead. */
212
+ export type HostUpdate = { status: 'ready' | 'installing'; version: string; waiting?: 'agent-busy' | 'exporting' | 'install-failed' | null };
213
+
214
+ export type HostAgentId = 'codex' | 'claude' | 'copilot' | 'cursor' | 'opencode' | 'codebuddy' | 'qwen' | 'kimi' | 'minimax' | 'deepseek' | 'byok';
215
+ /**
216
+ * What was searched for an agent (`agents.diagnose`): the report to copy, the program's name, where it was found, and
217
+ * when it was not, why: chosen-missing (the program chosen is gone) · wsl (only inside WSL, which the app cannot use) ·
218
+ * unusable (files with its name, none that starts) · none.
219
+ */
220
+ export interface HostAgentDiagnosis { found: string | null; program?: string; hint: 'chosen-missing' | 'wsl' | 'unusable' | 'none' | null; text: string }
221
+
222
+ /** The API a person's own key is for: OpenAI's Responses or Chat Completions, or Anthropic's Messages. */
223
+ export type HostByokFormat = 'responses' | 'chat' | 'anthropic';
224
+
225
+ /**
226
+ * One agent as the app has it. `status`: ready · off (disconnected in the app; its own sign-in is kept) · sign-in (its
227
+ * own sign-in is needed) · waiting (signing in, in Terminal) · missing (not installed: `install` says where from) ·
228
+ * key (no key set up) · unavailable (not offered yet) · checking. `account`: whose sign-in it uses, as the program says
229
+ * (no key or token).
230
+ */
231
+ export interface HostAgent {
232
+ id: HostAgentId;
233
+ name: string;
234
+ shown: boolean;
235
+ status: 'ready' | 'off' | 'sign-in' | 'waiting' | 'missing' | 'key' | 'unavailable' | 'checking';
236
+ install?: string | null;
237
+ /** What installing it takes, when `install` is not a download: 'code-tab', Claude Code comes with the Claude app (its Code tab). */
238
+ setupRequired?: string | null;
239
+ /** Not installed: the maker's desktop app when only that is here (`Cursor`), and the official command that installs it. */
240
+ app?: string | null;
241
+ installer?: string | null;
242
+ /** Its installer is running in a terminal window: it is looked for again until it is there. */
243
+ installing?: boolean;
244
+ /** The program used (the person's own agents), and the one the person chose for it, if they did. */
245
+ path?: string | null;
246
+ chosen?: string | null;
247
+ error?: string;
248
+ /** Found, but it cannot read its own settings (Codex's config file), as it says: no session starts until fixed. */
249
+ problem?: string;
250
+ account?: HostAgentAccount;
251
+ /** The own key's models, each set up on its own; the first is the one a conversation starts on. */
252
+ byok?: { models: HostOwnModel[] };
253
+ }
254
+
255
+ /**
256
+ * One of the person's own models: its `id` (the app's), the `name` the chat's menu shows, its service (API `format`
257
+ * and `baseUrl`), the `model` asked for there, its efforts, its `context` length in tokens (none: 128K), and the last
258
+ * four characters of its key.
259
+ */
260
+ export interface HostOwnModel extends HostByokEffort { id: string; name: string; format: HostByokFormat; baseUrl: string; model: string; context?: number; last4: string }
261
+
262
+ /** What setting up one of the person's own models sends: `id` to change one, no `key` to keep its key, no `context` for 128K. */
263
+ export interface HostOwnModelConfig { id?: string; name?: string; format: HostByokFormat; baseUrl: string; model: string; key?: string; levels: HostByokEffortLevel[]; effort: HostByokEffortLevel; context?: number }
264
+
265
+ /**
266
+ * The reasoning efforts a model of the person's own key offers, in this order (`auto`: none is sent, the service decides),
267
+ * and the one a conversation starts on. A model given none offers `auto` alone.
268
+ */
269
+ export const BYOK_EFFORTS = ['auto', 'off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;
270
+ export type HostByokEffortLevel = (typeof BYOK_EFFORTS)[number];
271
+ export interface HostByokEffort { levels: HostByokEffortLevel[]; effort: HostByokEffortLevel }
272
+
273
+ /**
274
+ * What is left of an agent's plan: each window's share used (0–100), how long it is (minutes) and when it resets (ms);
275
+ * `kind`: five_hour · seven_day · window (another length).
276
+ * `account`: whose it is, when the program says it here (Codex's email and plan).
277
+ */
278
+ export interface HostAgentUsage {
279
+ plan: string | null;
280
+ windows: Array<{ kind: 'five_hour' | 'seven_day' | 'window'; minutes: number | null; usedPercent: number; resetsAt: number | null }>;
281
+ account?: { email?: string; plan?: string };
282
+ }
283
+
284
+ /** Whose sign-in an agent uses: how (a claude.ai or ChatGPT account, an API key…), and what the program tells of it. */
285
+ export interface HostAgentAccount { method: 'claude.ai' | 'console' | 'api-key' | 'chatgpt' | 'other'; email?: string; org?: string; plan?: string }
286
+
287
+ /** A box on the picture, in the film's stage pixels (the stage is `stage.w` × `stage.h`, film.html's stage). */
288
+ export interface StageBox { x: number; y: number; w: number; h: number }
289
+
290
+ /**
291
+ * A picture for a reference, for its pill and for the agent. Either `src`, an address the host can load (a clip's own
292
+ * frame or a still on Studio's origin, a data: URL), or `viewer`: what the viewer shows right now, which the host
293
+ * takes itself as it hears of the reference (a screenshot of the viewer). `crop`, when only part of it is meant (a
294
+ * region, a layer), is that part in stage pixels of a picture that shows the whole stage (`stage`).
295
+ *
296
+ * The moment at the playhead, a region and a layer are `viewer` pictures: drawing the whole film on the server for
297
+ * them was slow, and while it drew Studio answered nothing else (an edit waited half a minute). A range and a subtitle
298
+ * line have none: their times and clips say enough.
299
+ */
300
+ export interface StudioRefImage { src?: string; viewer?: boolean; crop?: StageBox; stage?: { w: number; h: number } }
301
+
302
+ /**
303
+ * Something the person points at, as the host hears of it. Times are seconds of the film; `id`s are film.html clip
304
+ * ids; `loc` is a clip's place (`film.html#<track>.<clip>`); `track` is film.html's track index (0 = the top one).
305
+ *
306
+ * · clip a clip on the timeline: its file, its span on the film, its trim (`in`, source seconds) and `speed`
307
+ * · layer an element inside a page clip: its words and tag, where it is on the picture at `time` (`box`), and
308
+ * where it is written (`source`, `file:line` of the page, when Studio knows it)
309
+ * · region a box drawn on the picture at `time`: the clips showing there, the words inside it
310
+ * · time a moment of the film
311
+ * · range a stretch of the film (marked on the timeline's ruler, or with I and O): the clips playing in it
312
+ * · track a whole track
313
+ * · file a file of the project (the media pane): its path, kind, and length / size when known
314
+ * · example an example film (ExamplesGallery), or a part of it (`start`–`end`, a `segment` it names): where it comes
315
+ * from (`url`), and its folder on this computer to read how it is made (`folder`: fetched by the app
316
+ * when the message is sent, if not yet)
317
+ * · subtitle a line of the film's subtitles (the subtitle panel), or words picked inside one: their span on the film,
318
+ * the words, the language they are in when a translation is shown (`lang`), the line's number in the panel
319
+ * (`index`, from 1), and each word's own span when the transcript has word times (`words`)
320
+ *
321
+ * `image` is the picture its pill shows and the agent is given (see StudioRefImage): a clip's own frame, the viewer as
322
+ * it is for a moment, a region and a layer, none for a range or a subtitle line.
323
+ */
324
+ export type StudioRef = (
325
+ | { kind: 'clip'; id: string | null; loc: string | null; label: string; clipKind: string; src: string | null; start: number; end: number; track?: number; in?: number; speed?: number }
326
+ | { kind: 'layer'; label: string; loc: string | null; clipId: string | null; text: string | null; tag: string | null; time?: number; box?: StageBox; source?: string | null }
327
+ | { kind: 'region'; time: number; box: StageBox; clipIds: string[]; texts: string[] }
328
+ | { kind: 'time'; time: number }
329
+ | { kind: 'range'; start: number; end: number; clipIds: string[] }
330
+ | { kind: 'track'; track: number; label: string; clipIds: string[] }
331
+ | { kind: 'file'; path: string; fileKind: string; duration?: number; w?: number; h?: number }
332
+ | { kind: 'subtitle'; start: number; end: number; text: string; lang?: string | null; index?: number; words?: Array<{ text: string; start: number; end: number }> }
333
+ | { kind: 'example'; id: string; title: string; url: string; folder?: string; start?: number; end?: number; segment?: string; description?: string }
334
+ ) & { image?: StudioRefImage };
335
+
336
+ /**
337
+ * The drag type Studio's things carry when dragged out (a clip, a layer, the marked range, a file of the media pane,
338
+ * a subtitle line or the words picked in it):
339
+ * a JSON array of StudioRef. The app's composer takes them as references.
340
+ */
341
+ export const STUDIO_REF_TYPE = 'application/x-openfilm-ref';
342
+
343
+ export interface StudioSelection {
344
+ projectId: string;
345
+ /** the playhead, in seconds */
346
+ time: number;
347
+ /** the clips selected, then the layer picked inside a page clip, then the subtitle line picked in the subtitle panel */
348
+ refs: StudioRef[];
349
+ /** the range marked on the timeline's ruler, when there is one */
350
+ range?: { start: number; end: number } | null;
351
+ /** the film's stage, for boxes in stage pixels */
352
+ stage?: { w: number; h: number };
353
+ }
354
+
355
+ /**
356
+ * What an app can ask Studio to show: a clip selected (by its film.html id, or its place), a moment, a range marked,
357
+ * a box on the picture shown at its moment, a track, a file of the media pane, a subtitle line (its span marked, the
358
+ * subtitle panel open on it).
359
+ *
360
+ * And what a chat points at, without moving the playhead or changing the selection: `highlight`, the thing a pill the
361
+ * person hovers stands for, lit until the next one (null: none); `draft-refs`, the things the message being written
362
+ * references, in its order (the numbers the agent reads them by), marked with those numbers until the next list
363
+ * (empty: sent or cleared).
364
+ */
365
+ export type StudioCommand =
366
+ | { type: 'select-clip'; id?: string | null; loc?: string | null; seek?: boolean }
367
+ | { type: 'seek'; time: number }
368
+ | { type: 'show-range'; start: number; end: number }
369
+ | { type: 'show-box'; time: number; box: StageBox }
370
+ | { type: 'show-track'; track: number }
371
+ | { type: 'show-file'; path: string }
372
+ | { type: 'show-subtitle'; start: number; end: number }
373
+ | { type: 'highlight'; ref: StudioRef | null }
374
+ | { type: 'draft-refs'; refs: StudioRef[] }
375
+ /* the app's chat sends the person to Settings (an agent to set up) */
376
+ | { type: 'settings'; section?: 'agents' | 'providers' }
377
+ /* the app's chat opens the examples over the film, to point at one or a part of one, or name a skill; `open`: on
378
+ that skill's or example's page (a pill of one clicked) */
379
+ | { type: 'show-examples'; open?: CommunityItem };
380
+
381
+ /** A skill or an example in the Community, to open on its page. */
382
+ export type CommunityItem = { kind: 'skill'; name: string } | { kind: 'example'; id: string };
383
+
384
+ export const studioHost: StudioHost | null = (globalThis as { openfilmHost?: StudioHost }).openfilmHost ?? null;
385
+
386
+ /** Make room for the host window's controls, and let Studio's top bars drag the window (see `titlebar`). */
387
+ export function applyStudioHost(root: HTMLElement = document.documentElement) {
388
+ const titlebar = studioHost?.titlebar;
389
+ if (!titlebar) return;
390
+ const set = (inset: unknown) => {
391
+ if (typeof inset === 'number' && inset >= 0) root.style.setProperty('--titlebar-logo-x', `${Math.max(6, Math.min(inset, 200))}px`);
392
+ };
393
+ root.dataset.hostTitlebar = '';
394
+ if (typeof titlebar.insetRight === 'number' && titlebar.insetRight > 0) root.style.setProperty('--titlebar-right-x', `${Math.min(titlebar.insetRight, 240)}px`);
395
+ set(titlebar.inset);
396
+ titlebar.onChange?.(set);
397
+ }
398
+
399
+ /** The app's own panel (see `panel`): whether it is open and how wide, and its assets drawer, kept current. Null without one. */
400
+ export type HostPanel = {
401
+ label: string;
402
+ open: boolean;
403
+ width: number;
404
+ setOpen: (open: boolean) => void;
405
+ /** null: the app keeps no assets of its own (Studio's left pane holds them) */
406
+ assets: { open: boolean; bottom: number; setOpen: (open: boolean) => void } | null;
407
+ setWidth: (width: number, done?: boolean) => void;
408
+ };
409
+
410
+ export function useHostPanel(): HostPanel | null {
411
+ const panel = studioHost?.panel;
412
+ const read = (next: { open: boolean; width: number; assets?: { open: boolean; bottom: number } }) => ({
413
+ open: Boolean(next.open),
414
+ width: Math.max(0, Number(next.width) || 0),
415
+ assetsOpen: Boolean(next.assets?.open),
416
+ bottom: Math.max(0, Number(next.assets?.bottom) || 0),
417
+ });
418
+ const [state, setState] = React.useState(() => (panel ? read(panel) : null));
419
+ React.useEffect(() => { panel?.onChange((next) => setState(read(next))); }, [panel]);
420
+ if (!panel || !state) return null;
421
+ const setAssetsOpen = panel.setAssetsOpen;
422
+ return {
423
+ label: panel.label,
424
+ open: state.open,
425
+ width: state.width,
426
+ setOpen: (open) => panel.setOpen(open),
427
+ setWidth: (width, done) => panel.setWidth?.(width, done),
428
+ assets: setAssetsOpen ? { open: state.open && state.assetsOpen, bottom: state.bottom, setOpen: (open) => setAssetsOpen(open) } : null,
429
+ };
430
+ }
431
+
432
+ /** The app's update to show in the top bar (see `update`), kept current. Null without one. */
433
+ export function useHostUpdate(): { update: HostUpdate; restart: () => Promise<{ ok: boolean; error?: string }> } | null {
434
+ const host = studioHost?.update;
435
+ const [update, setUpdate] = React.useState<HostUpdate | null>(null);
436
+ React.useEffect(() => host?.onChange(setUpdate), [host]);
437
+ return host && update ? { update, restart: host.restart } : null;
438
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Dragging a number in the inspector: how many steps a horizontal drag of so many pixels is worth.
3
+ *
4
+ * The step is the field's own; Shift is ten times coarser and Alt ten times finer — the same rule as
5
+ * the arrow keys (Shift is ten steps).
6
+ */
7
+
8
+ const PX_PER_STEP = 6;
9
+
10
+ export function snapToStep(value: number, step: number): number {
11
+ if (!(step > 0) || !Number.isFinite(value)) return value;
12
+ const ticks = Math.round(value / step);
13
+ const decimals = step >= 1 ? 2 : Math.min(4, (String(step).split('.')[1] ?? '00').length);
14
+ return Number((ticks * step).toFixed(decimals));
15
+ }
16
+
17
+ export function scrubFromDrag(
18
+ start: number,
19
+ dx: number,
20
+ step: number,
21
+ mods?: { shift?: boolean; alt?: boolean },
22
+ ): number {
23
+ const stride = mods?.shift ? step * 10 : mods?.alt ? step / 10 : step;
24
+ const unit = stride > 0 ? stride : step;
25
+ return snapToStep(start + (dx / PX_PER_STEP) * unit, unit);
26
+ }