@dfosco/storyboard 0.7.1 → 0.9.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.
Files changed (323) hide show
  1. package/dist/storyboard-ui.css +1 -1
  2. package/dist/storyboard-ui.js +14128 -10953
  3. package/dist/storyboard-ui.js.map +1 -1
  4. package/dist/tailwind.css +1 -1
  5. package/dist/tiny-canvas.css +1 -1
  6. package/package.json +138 -128
  7. package/scaffold/AGENTS.md +9 -7
  8. package/scaffold/README.md +18 -0
  9. package/scaffold/deploy.yml +21 -0
  10. package/scaffold/gitignore +8 -14
  11. package/scaffold/preview.yml +40 -0
  12. package/scaffold/{manifest.json → scaffold.config.json} +73 -3
  13. package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
  14. package/scaffold/skills/canvas/SKILL.md +1 -1
  15. package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
  16. package/scaffold/skills/ship/SKILL.md +2 -2
  17. package/scaffold/skills/worktree/SKILL.md +1 -1
  18. package/scaffold/src/_prototype.jsx +17 -0
  19. package/scaffold/src/_story.jsx +13 -0
  20. package/scaffold/src/library/README.md +34 -0
  21. package/scaffold/src/library/_app.jsx +41 -0
  22. package/scaffold/src/library/_app.module.css +2 -0
  23. package/scaffold/src/library/create.jsx +10 -0
  24. package/scaffold/src/library/index.jsx +14 -0
  25. package/scaffold/src/library/mount.jsx +51 -0
  26. package/scaffold/src/library/prototypes-entry.jsx +57 -0
  27. package/scaffold/src/library/routes.jsx +205 -0
  28. package/scaffold/src/library/viewfinder.jsx +13 -0
  29. package/scaffold/src/library/workspace.jsx +17 -0
  30. package/scripts/aggregate-artifacts.mjs +257 -0
  31. package/src/canvas/style.css +16 -16
  32. package/src/core/artifact/operations.js +54 -15
  33. package/src/core/artifact/schemas/canvas.schema.json +7 -1
  34. package/src/core/autosync/server.test.js +1 -1
  35. package/src/core/canvas/hot-pool.js +21 -33
  36. package/src/core/canvas/materializer.js +39 -13
  37. package/src/core/canvas/selectedWidgets.js +1 -1
  38. package/src/core/canvas/server.js +376 -100
  39. package/src/core/canvas/server.test.js +472 -2
  40. package/src/core/canvas/terminal-server.js +144 -57
  41. package/src/core/canvas/undoRedo.js +174 -0
  42. package/src/core/canvas/undoRedo.test.js +250 -0
  43. package/src/core/cli/artifact.js +2 -0
  44. package/src/core/cli/buildManifest.js +34 -0
  45. package/src/core/cli/canvasRedo.js +44 -0
  46. package/src/core/cli/canvasUndo.js +40 -0
  47. package/src/core/cli/create.js +151 -23
  48. package/src/core/cli/dev.js +23 -35
  49. package/src/core/cli/index.js +21 -0
  50. package/src/core/cli/schemas.js +8 -4
  51. package/src/core/cli/serverUrl.js +24 -0
  52. package/src/core/cli/setup.js +14 -25
  53. package/src/core/cli/terminal-welcome.js +12 -6
  54. package/src/core/cli/updateVersion.js +42 -2
  55. package/src/core/cli/workleaf.js +140 -0
  56. package/src/core/comments/api.js +1 -1
  57. package/src/core/comments/queries.js +3 -0
  58. package/src/core/comments/ui/CommentWindow.jsx +3 -2
  59. package/src/core/comments/ui/comment-layout.css +14 -14
  60. package/src/core/data/artifactIndex.js +235 -0
  61. package/src/core/data/artifactIndex.test.js +203 -0
  62. package/src/core/data/artifactManifest.js +290 -0
  63. package/src/core/data/artifactManifest.test.js +207 -0
  64. package/src/core/data/viewfinder.js +47 -7
  65. package/src/core/index.js +24 -1
  66. package/src/core/inspector/highlighter.js +1 -1
  67. package/src/core/inspector/mouseMode.js +2 -2
  68. package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
  69. package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
  70. package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
  71. package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
  72. package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
  73. package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
  74. package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
  75. package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
  76. package/src/core/lib/components/ui/dialog/index.js +4 -34
  77. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
  78. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
  79. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
  80. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
  81. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
  82. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
  83. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
  84. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
  85. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
  86. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
  87. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
  88. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
  89. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
  90. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
  91. package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
  92. package/src/core/lib/components/ui/label/label.jsx +1 -2
  93. package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
  94. package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
  95. package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
  96. package/src/core/lib/components/ui/panel/panel.jsx +11 -2
  97. package/src/core/lib/components/ui/separator/separator.jsx +6 -4
  98. package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
  99. package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
  100. package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
  101. package/src/core/modes/modes.css +4 -4
  102. package/src/core/mountStoryboardCore.js +287 -83
  103. package/src/core/mountStoryboardCore.test.js +198 -0
  104. package/src/core/rename-watcher/config.json +1 -1
  105. package/src/core/rename-watcher/watcher.js +330 -37
  106. package/src/core/rename-watcher/watcher.test.js +253 -0
  107. package/src/core/scaffold/fragments.js +287 -0
  108. package/src/core/scaffold/fragments.test.js +401 -0
  109. package/src/core/scaffold/index.js +291 -0
  110. package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
  111. package/src/core/scaffold/scaffold-integration.test.js +247 -0
  112. package/src/core/scaffold.js +4 -97
  113. package/src/core/stores/canvasInteractionStore.js +120 -0
  114. package/src/core/stores/canvasInteractionStore.test.js +126 -0
  115. package/src/core/stores/configSchema.js +64 -8
  116. package/src/core/stores/configSchema.test.js +3 -0
  117. package/src/core/stores/customerModeConfig.js +24 -3
  118. package/src/core/stores/customerModeConfig.test.js +46 -0
  119. package/src/core/stores/presentationStore.js +118 -0
  120. package/src/core/stores/presentationStore.test.js +114 -0
  121. package/src/core/stores/recentArtifacts.js +49 -2
  122. package/src/core/stores/recentArtifacts.test.js +59 -6
  123. package/src/core/stores/themeBootstrap.js +264 -0
  124. package/src/core/stores/themeBootstrap.test.js +211 -0
  125. package/src/core/stores/themeStore.test.js +154 -0
  126. package/src/core/stores/themeStore.ts +235 -220
  127. package/src/core/stores/uiConfig.js +10 -2
  128. package/src/core/styles/generateThemesCss.js +63 -0
  129. package/src/core/styles/tailwind.css +150 -94
  130. package/src/core/styles/workspace-chrome.css +13 -0
  131. package/src/core/tools/handlers/paletteTheme.js +11 -8
  132. package/src/core/ui/ActionMenuButton.jsx +2 -1
  133. package/src/core/ui/AutosyncMenuButton.css +7 -7
  134. package/src/core/ui/BranchSelect.module.css +5 -5
  135. package/src/core/ui/CanvasCreateMenu.jsx +1 -19
  136. package/src/core/ui/ChromeSlot.jsx +92 -0
  137. package/src/core/ui/ChromeSlot.test.jsx +140 -0
  138. package/src/core/ui/CoreUIBar.css +5 -5
  139. package/src/core/ui/CoreUIBar.jsx +170 -142
  140. package/src/core/ui/InspectorPanel.css +5 -5
  141. package/src/core/ui/InspectorPanel.jsx +9 -9
  142. package/src/core/ui/PwaInstallBanner.css +5 -5
  143. package/src/core/ui/PwaInstallBanner.jsx +5 -5
  144. package/src/core/ui/ThemeMenuButton.jsx +45 -30
  145. package/src/core/ui/core-ui-colors.css +28 -66
  146. package/src/core/ui/sidepanel.css +12 -12
  147. package/src/core/vite/server-plugin.js +13 -0
  148. package/src/core/workleaf/cleanup.js +102 -0
  149. package/src/core/workleaf/create.js +199 -0
  150. package/src/core/workleaf/delete.js +101 -0
  151. package/src/core/workleaf/list.js +57 -0
  152. package/src/core/workleaf/merge.js +238 -0
  153. package/src/core/workleaf/paths.js +146 -0
  154. package/src/core/workleaf/paths.test.js +230 -0
  155. package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +130 -34
  156. package/src/core/workshop/features/createFlow/server.js +5 -1
  157. package/src/core/workshop/features/createPrototype/server.js +5 -1
  158. package/src/core/workshop/features/templateIndex.js +28 -1
  159. package/src/core/worktree/port.js +48 -5
  160. package/src/core/worktree/port.test.js +42 -0
  161. package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
  162. package/src/internals/ArtifactForm/ArtifactForm.module.css +51 -69
  163. package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
  164. package/src/internals/AuthModal/AuthModal.module.css +34 -34
  165. package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
  166. package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
  167. package/src/internals/BranchBar/BranchBar.module.css +4 -4
  168. package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
  169. package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
  170. package/src/internals/CommandPalette/CommandPalette.jsx +41 -29
  171. package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
  172. package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
  173. package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
  174. package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
  175. package/src/internals/CommandPalette/command-palette.css +14 -14
  176. package/src/internals/CommandPalette/createHelpers.js +30 -0
  177. package/src/internals/CreatePage/CreatePage.jsx +57 -0
  178. package/src/internals/CreatePage/CreatePage.module.css +34 -0
  179. package/src/internals/Dialog.jsx +51 -0
  180. package/src/internals/FlowError.module.css +5 -5
  181. package/src/internals/PrototypeErrorBoundary.module.css +15 -15
  182. package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +534 -0
  183. package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
  184. package/src/internals/Tooltip.jsx +39 -0
  185. package/src/internals/Viewfinder.jsx +229 -64
  186. package/src/internals/Viewfinder.module.css +253 -182
  187. package/src/internals/canvas/CanvasControls.module.css +11 -11
  188. package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
  189. package/src/internals/canvas/CanvasPage.jsx +358 -217
  190. package/src/internals/canvas/CanvasPage.module.css +20 -19
  191. package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
  192. package/src/internals/canvas/CanvasToolbar.module.css +11 -11
  193. package/src/internals/canvas/ConnectorLayer.jsx +39 -2
  194. package/src/internals/canvas/ConnectorLayer.module.css +10 -10
  195. package/src/internals/canvas/PageSelector.jsx +15 -1
  196. package/src/internals/canvas/PageSelector.module.css +41 -27
  197. package/src/internals/canvas/canvasApi.js +40 -5
  198. package/src/internals/canvas/canvasTheme.js +22 -111
  199. package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
  200. package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
  201. package/src/internals/canvas/isolation/prototypeRoutes.jsx +140 -0
  202. package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
  203. package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
  204. package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
  205. package/src/internals/canvas/useUndoRedo.js +75 -60
  206. package/src/internals/canvas/useUndoRedo.test.js +46 -178
  207. package/src/internals/canvas/widgets/CodePenEmbed.jsx +16 -36
  208. package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
  209. package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
  210. package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
  211. package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
  212. package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
  213. package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
  214. package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
  215. package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
  216. package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
  217. package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
  218. package/src/internals/canvas/widgets/LinkPreview.module.css +79 -80
  219. package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
  220. package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
  221. package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
  222. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +92 -17
  223. package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
  224. package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +118 -4
  225. package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
  226. package/src/internals/canvas/widgets/StorySetWidget.jsx +53 -25
  227. package/src/internals/canvas/widgets/StoryWidget.jsx +71 -56
  228. package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
  229. package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
  230. package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
  231. package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
  232. package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
  233. package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
  234. package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
  235. package/src/internals/canvas/widgets/WidgetChrome.module.css +76 -61
  236. package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
  237. package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
  238. package/src/internals/canvas/widgets/embedTheme.js +12 -104
  239. package/src/internals/canvas/widgets/expandUtils.js +19 -19
  240. package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
  241. package/src/internals/canvas/widgets/widgetConfig.js +5 -5
  242. package/src/internals/context.jsx +22 -1
  243. package/src/internals/context.test.jsx +47 -0
  244. package/src/internals/hooks/useArtifactIndex.js +31 -0
  245. package/src/internals/hooks/useArtifactIndex.test.js +45 -0
  246. package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
  247. package/src/internals/index.js +6 -0
  248. package/src/internals/loadConsumerWrapper.js +60 -0
  249. package/src/internals/story/ComponentSetPage.jsx +75 -34
  250. package/src/internals/story/ComponentSetPage.module.css +14 -14
  251. package/src/internals/story/StoryPage.jsx +16 -25
  252. package/src/internals/story/StoryPage.module.css +2 -2
  253. package/src/internals/vite/data-plugin.js +317 -149
  254. package/src/internals/vite/data-plugin.test.js +11 -11
  255. package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
  256. package/terminal.config.json +1 -1
  257. package/src/core/lib/components/ui/card/card-action.jsx +0 -11
  258. package/src/core/lib/components/ui/card/card-content.jsx +0 -11
  259. package/src/core/lib/components/ui/card/card-description.jsx +0 -11
  260. package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
  261. package/src/core/lib/components/ui/card/card-header.jsx +0 -19
  262. package/src/core/lib/components/ui/card/card-title.jsx +0 -11
  263. package/src/core/lib/components/ui/card/card.jsx +0 -17
  264. package/src/core/lib/components/ui/card/index.js +0 -25
  265. package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
  266. package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
  267. package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
  268. package/src/core/lib/components/ui/collapsible/index.js +0 -13
  269. package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
  270. package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
  271. package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
  272. package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
  273. package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
  274. package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
  275. package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
  276. package/src/core/lib/components/ui/popover/index.js +0 -28
  277. package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
  278. package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
  279. package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
  280. package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
  281. package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
  282. package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
  283. package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
  284. package/src/core/lib/components/ui/popover/popover.jsx +0 -4
  285. package/src/core/lib/components/ui/select/index.js +0 -37
  286. package/src/core/lib/components/ui/select/select-content.jsx +0 -30
  287. package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
  288. package/src/core/lib/components/ui/select/select-group.jsx +0 -15
  289. package/src/core/lib/components/ui/select/select-item.jsx +0 -26
  290. package/src/core/lib/components/ui/select/select-label.jsx +0 -11
  291. package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
  292. package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
  293. package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
  294. package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
  295. package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
  296. package/src/core/lib/components/ui/select/select.jsx +0 -4
  297. package/src/core/lib/components/ui/sheet/index.js +0 -34
  298. package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
  299. package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
  300. package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
  301. package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
  302. package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
  303. package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
  304. package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
  305. package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
  306. package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
  307. package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
  308. package/src/core/lib/components/ui/toggle/index.js +0 -8
  309. package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
  310. package/src/core/lib/components/ui/toggle-group/index.js +0 -10
  311. package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
  312. package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
  313. package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
  314. package/src/internals/canvas/WebGLContextPool.jsx +0 -291
  315. package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
  316. package/src/internals/canvas/componentIsolate.jsx +0 -144
  317. package/src/internals/canvas/componentSetIsolate.jsx +0 -300
  318. package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
  319. package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
  320. package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
  321. package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
  322. package/src/primer/ThemeSync.jsx +0 -73
  323. package/src/primer/index.js +0 -1
@@ -33,9 +33,9 @@ const bannerStyle = {
33
33
  alignItems: 'center',
34
34
  gap: '0.5rem',
35
35
  padding: '0.75rem 1rem',
36
- background: 'var(--bgColor-default, #0d1117)',
37
- color: 'var(--fgColor-default, #e6edf3)',
38
- border: '1px solid var(--borderColor-default, #30363d)',
36
+ background: 'var(--color-background, #0d1117)',
37
+ color: 'var(--color-foreground, #e6edf3)',
38
+ border: '1px solid var(--color-border, #30363d)',
39
39
  borderRadius: '0.75rem',
40
40
  fontFamily: '"Mona Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif',
41
41
  fontSize: '0.8125rem',
@@ -53,14 +53,14 @@ const btnBase = {
53
53
 
54
54
  const installBtnStyle = {
55
55
  ...btnBase,
56
- background: 'var(--button-primary-bgColor-rest, #238636)',
56
+ background: 'var(--color-background-success-emphasis, #238636)',
57
57
  color: '#fff',
58
58
  }
59
59
 
60
60
  const dismissBtnStyle = {
61
61
  ...btnBase,
62
62
  background: 'transparent',
63
- color: 'var(--fgColor-muted, #8d96a0)',
63
+ color: 'var(--color-foreground-muted, #8d96a0)',
64
64
  padding: '0.375rem',
65
65
  }
66
66
 
@@ -2,7 +2,11 @@ import { useState, useEffect } from 'react'
2
2
  import { TriggerButton } from '../lib/components/ui/trigger-button/index.js'
3
3
  import * as DropdownMenu from '../lib/components/ui/dropdown-menu/index.js'
4
4
  import Icon from './Icon.jsx'
5
- import { themeState, setTheme, getTheme, THEMES, themeSyncState, getThemeSyncTargets, setThemeSyncTarget } from '../index.js'
5
+ import {
6
+ themeState, setTheme, getTheme,
7
+ surfaceSyncState, getSurfaceSync, setSurfaceSync,
8
+ } from '../index.js'
9
+ import { getConfig, subscribeToConfig } from '../stores/configStore.js'
6
10
 
7
11
  export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _localOnly, tabindex = -1 }) {
8
12
  void _data
@@ -11,7 +15,8 @@ export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _
11
15
  const [settingsOpen, setSettingsOpen] = useState(false)
12
16
  const [canvasActive, setCanvasActive] = useState(false)
13
17
  const [theme, setThemeState] = useState(getTheme)
14
- const [syncState, setSyncState] = useState(getThemeSyncTargets)
18
+ const [syncState, setSyncState] = useState(getSurfaceSync)
19
+ const [themesCfg, setThemesCfg] = useState(() => getConfig('theming') || {})
15
20
 
16
21
  useEffect(() => {
17
22
  const unsub = themeState.subscribe(s => setThemeState(s.theme))
@@ -19,7 +24,13 @@ export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _
19
24
  }, [])
20
25
 
21
26
  useEffect(() => {
22
- const unsub = themeSyncState.subscribe(s => setSyncState({ ...s }))
27
+ const unsub = surfaceSyncState.subscribe(s => setSyncState({ ...s }))
28
+ return unsub
29
+ }, [])
30
+
31
+ useEffect(() => {
32
+ // Re-derive registry on config HMR (e.g. user edits storyboard.config.json)
33
+ const unsub = subscribeToConfig(() => setThemesCfg(getConfig('theming') || {}))
23
34
  return unsub
24
35
  }, [])
25
36
 
@@ -61,9 +72,30 @@ export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _
61
72
 
62
73
  function handleSyncToggle(e, target) {
63
74
  e.preventDefault()
64
- setThemeSyncTarget(target, !syncState[target])
75
+ setSurfaceSync(target, !syncState[target])
65
76
  }
66
77
 
78
+ // Theme list — config-driven. "system" is a synthetic top entry not in
79
+ // the registry; surface it explicitly above the configured themes.
80
+ const themeEntries = Object.entries(themesCfg.themes || {})
81
+ const themeOptions = [
82
+ { value: 'system', label: 'System' },
83
+ ...themeEntries.map(([value, def]) => ({ value, label: def?.label || value })),
84
+ ]
85
+
86
+ // Surface list — config-driven. The "Apply theme to" sub-menu renders one
87
+ // checkbox per configured surface. The Canvas↔Prototype swap stays —
88
+ // when a canvas is mounted we show Canvas instead of Prototype so the
89
+ // user only sees the surface they're currently looking at.
90
+ const surfaces = themesCfg.surfaces || {}
91
+ const surfaceIds = Object.keys(surfaces)
92
+ const canvasShown = canvasActive && surfaceIds.includes('canvas')
93
+ const visibleSurfaces = surfaceIds.filter((id) => {
94
+ if (id === 'canvas') return canvasShown
95
+ if (id === 'prototype') return !canvasShown
96
+ return true
97
+ })
98
+
67
99
  return (
68
100
  <DropdownMenu.Root open={menuOpen} onOpenChange={handleMenuOpenChange}>
69
101
  <DropdownMenu.Trigger>
@@ -87,13 +119,13 @@ export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _
87
119
  {config.label && <DropdownMenu.Label>{config.label}</DropdownMenu.Label>}
88
120
 
89
121
  <DropdownMenu.RadioGroup value={theme}>
90
- {THEMES.map((option) => (
122
+ {themeOptions.map((option) => (
91
123
  <DropdownMenu.RadioItem
92
124
  key={option.value}
93
125
  value={option.value}
94
126
  onClick={() => handleSelect(option.value)}
95
127
  >
96
- {option.name}
128
+ {option.label}
97
129
  </DropdownMenu.RadioItem>
98
130
  ))}
99
131
  </DropdownMenu.RadioGroup>
@@ -104,36 +136,19 @@ export default function ThemeMenuButton({ config = {}, data: _data, localOnly: _
104
136
  <DropdownMenu.SubTrigger>Theme settings</DropdownMenu.SubTrigger>
105
137
  <DropdownMenu.SubContent className="min-w-[180px]">
106
138
  <DropdownMenu.Label>Apply theme to</DropdownMenu.Label>
107
- {canvasActive ? (
139
+ {visibleSurfaces.map((id) => (
108
140
  <DropdownMenu.CheckboxItem
109
- checked={syncState.canvas}
110
- onSelect={(e) => handleSyncToggle(e, 'canvas')}
141
+ key={id}
142
+ checked={!!syncState[id]}
143
+ onSelect={(e) => handleSyncToggle(e, id)}
111
144
  >
112
- Canvas
145
+ {surfaces[id]?.label || id}
113
146
  </DropdownMenu.CheckboxItem>
114
- ) : (
115
- <DropdownMenu.CheckboxItem
116
- checked={syncState.prototype}
117
- onSelect={(e) => handleSyncToggle(e, 'prototype')}
118
- >
119
- Prototype
120
- </DropdownMenu.CheckboxItem>
121
- )}
122
- <DropdownMenu.CheckboxItem
123
- checked={syncState.toolbar}
124
- onSelect={(e) => handleSyncToggle(e, 'toolbar')}
125
- >
126
- Tools
127
- </DropdownMenu.CheckboxItem>
128
- <DropdownMenu.CheckboxItem
129
- checked={syncState.codeBoxes}
130
- onSelect={(e) => handleSyncToggle(e, 'codeBoxes')}
131
- >
132
- Code boxes
133
- </DropdownMenu.CheckboxItem>
147
+ ))}
134
148
  </DropdownMenu.SubContent>
135
149
  </DropdownMenu.Sub>
136
150
  </DropdownMenu.Content>
137
151
  </DropdownMenu.Root>
138
152
  )
139
153
  }
154
+
@@ -16,6 +16,10 @@
16
16
  *
17
17
  * TriggerButton uses the `trigger` button variant (no Tailwind dark:
18
18
  * classes) so its scoped CSS has full control over background/color.
19
+ *
20
+ * Concrete color values are pinned (not aliased) so toolbar styling
21
+ * stays locked to its own scheme regardless of the global theme cascade
22
+ * defined in `core/styles/tailwind.css`.
19
23
  */
20
24
 
21
25
  /* Default: light tokens */
@@ -27,43 +31,23 @@
27
31
  [data-slot="dialog-overlay"] {
28
32
  color-scheme: light;
29
33
 
30
- /* shadcn / Tailwind semantic tokens — pinned to light values */
31
- --color-background: hsl(0 0% 100%);
32
- --color-foreground: hsl(222.2 84% 4.9%);
33
- --color-popover: hsl(0 0% 100%);
34
- --color-popover-foreground: hsl(222.2 84% 4.9%);
35
- --color-primary: hsl(222.2 47.4% 11.2%);
36
- --color-primary-foreground: hsl(210 40% 98%);
37
- --color-secondary: hsl(210 40% 96.1%);
38
- --color-secondary-foreground: hsl(222.2 47.4% 11.2%);
39
- --color-muted: hsl(210 40% 96.1%);
40
- --color-muted-foreground: hsl(215.4 16.3% 46.9%);
41
- --color-accent: hsl(210 40% 96.1%);
42
- --color-accent-foreground: hsl(222.2 47.4% 11.2%);
43
- --color-border: hsl(214.3 31.8% 91.4%);
44
- --color-input: hsl(214.3 31.8% 91.4%);
45
-
46
- --sb--color-background: hsl(0 0% 100%);
47
- --sb--color-foreground: hsl(222.2 84% 4.9%);
48
- --sb--color-popover: hsl(0 0% 100%);
49
- --sb--color-popover-foreground: hsl(222.2 84% 4.9%);
50
- --sb--color-primary: hsl(222.2 47.4% 11.2%);
51
- --sb--color-primary-foreground: hsl(210 40% 98%);
52
- --sb--color-secondary: hsl(210 40% 96.1%);
53
- --sb--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
54
- --sb--color-muted: hsl(210 40% 96.1%);
55
- --sb--color-muted-foreground: hsl(215.4 16.3% 46.9%);
56
- --sb--color-accent: hsl(210 40% 96.1%);
57
- --sb--color-accent-foreground: hsl(222.2 47.4% 11.2%);
58
- --sb--color-border: hsl(214.3 31.8% 91.4%);
59
- --sb--color-input: hsl(214.3 31.8% 91.4%);
60
-
61
- /* Primer token aliases used in side panel + inspector chrome */
62
- --bgColor-default: #ffffff;
63
- --fgColor-default: #1f2328;
64
- --fgColor-muted: #656d76;
65
- --borderColor-default: #d1d9e0;
66
- --bgColor-neutral-muted: rgba(175, 184, 193, 0.2);
34
+ --color-background: #ffffff;
35
+ --color-foreground: #1f2328;
36
+ --color-foreground-muted: #59636e;
37
+ --color-popover: #ffffff;
38
+ --color-popover-foreground: #1f2328;
39
+ --color-primary: #1f2328;
40
+ --color-primary-foreground: #ffffff;
41
+ --color-secondary: #f6f8fa;
42
+ --color-secondary-foreground: #1f2328;
43
+ --color-muted: #f6f8fa;
44
+ --color-muted-foreground: #59636e;
45
+ --color-accent: #ddf4ff;
46
+ --color-accent-foreground: #0969da;
47
+ --color-border: #d1d9e0;
48
+ --color-border-muted: #d1d9e0b3;
49
+ --color-background-neutral-muted: rgba(175, 184, 193, 0.2);
50
+ --color-input: #d1d9e0;
67
51
 
68
52
  /* Slate palette tokens — used by trigger buttons and canvas toolbar */
69
53
  --color-slate-100: oklch(96.8% .007 247.896);
@@ -72,10 +56,7 @@
72
56
  --color-slate-600: oklch(44.6% .043 257.281);
73
57
  }
74
58
 
75
- /* Dark tokens — applied when toolbar syncs with a dark theme.
76
- * Uses Primer-aligned colors (matching base.css --sb-* dark tokens)
77
- * instead of generic shadcn dark values.
78
- */
59
+ /* Dark tokens — applied when toolbar syncs with a dark theme. */
79
60
  :root[data-sb-toolbar-theme^="dark"] [data-core-ui-bar],
80
61
  :root[data-sb-toolbar-theme^="dark"] [data-sidepanel],
81
62
  :root[data-sb-toolbar-theme^="dark"] [data-slot="dropdown-menu-content"],
@@ -86,6 +67,7 @@
86
67
 
87
68
  --color-background: #161b22;
88
69
  --color-foreground: #e6edf3;
70
+ --color-foreground-muted: #8b949e;
89
71
  --color-popover: #161b22;
90
72
  --color-popover-foreground: #e6edf3;
91
73
  --color-primary: #e6edf3;
@@ -94,36 +76,16 @@
94
76
  --color-secondary-foreground: #e6edf3;
95
77
  --color-muted: #21262d;
96
78
  --color-muted-foreground: #8b949e;
97
- --color-accent: #21262d;
98
- --color-accent-foreground: #e6edf3;
79
+ --color-accent: #388bfd1a;
80
+ --color-accent-foreground: #4493f8;
99
81
  --color-border: #30363d;
82
+ --color-border-muted: #30363db3;
83
+ --color-background-neutral-muted: rgba(110, 118, 129, 0.2);
100
84
  --color-input: #30363d;
101
85
 
102
- --sb--color-background: #161b22;
103
- --sb--color-foreground: #e6edf3;
104
- --sb--color-popover: #161b22;
105
- --sb--color-popover-foreground: #e6edf3;
106
- --sb--color-primary: #e6edf3;
107
- --sb--color-primary-foreground: #161b22;
108
- --sb--color-secondary: #21262d;
109
- --sb--color-secondary-foreground: #e6edf3;
110
- --sb--color-muted: #21262d;
111
- --sb--color-muted-foreground: #8b949e;
112
- --sb--color-accent: #21262d;
113
- --sb--color-accent-foreground: #e6edf3;
114
- --sb--color-border: #30363d;
115
- --sb--color-input: #30363d;
116
-
117
- /* Primer token aliases used in side panel + inspector chrome */
118
- --bgColor-default: #161b22;
119
- --fgColor-default: #e6edf3;
120
- --fgColor-muted: #8b949e;
121
- --borderColor-default: #30363d;
122
- --bgColor-neutral-muted: rgba(110, 118, 129, 0.2);
123
-
124
86
  /* Slate palette tokens — dark equivalents */
125
87
  --color-slate-100: oklch(20.8% .042 265.755);
126
88
  --color-slate-300: oklch(29.3% .042 264.531);
127
89
  --color-slate-400: oklch(44.6% .043 257.281);
128
- --color-slate-600: oklch(70.4% .04 256.788);
90
+ --color-slate-600: oklch(44.6% .043 257.281);
129
91
  }
@@ -99,11 +99,11 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
99
99
  z-index: 9998;
100
100
  display: flex;
101
101
  flex-direction: column;
102
- background-color: var(--bgColor-default, var(--sb--color-background, #ffffff));
103
- border-left: 1px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
102
+ background-color: var(--color-background, #ffffff);
103
+ border-left: 1px solid var(--color-border, #d0d7de);
104
104
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15);
105
105
  font-family: "Mona Sans", -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
106
- color: var(--fgColor-default, var(--sb--color-foreground, #1f2328));
106
+ color: var(--color-foreground, #1f2328);
107
107
  animation: sb-sidepanel-slide-in 0.25s ease;
108
108
  }
109
109
 
@@ -115,7 +115,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
115
115
  width: 100% !important;
116
116
  height: var(--sb--sidepanel-height, 300px);
117
117
  border-left: none;
118
- border-top: 1px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
118
+ border-top: 1px solid var(--color-border, #d0d7de);
119
119
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
120
120
  animation: sb-sidepanel-slide-up 0.25s ease;
121
121
  }
@@ -199,7 +199,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
199
199
  font-weight: 600;
200
200
  text-transform: uppercase;
201
201
  letter-spacing: 0.05em;
202
- color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
202
+ color: var(--color-foreground-muted, #656d76);
203
203
  padding-left: 4px;
204
204
  }
205
205
 
@@ -213,7 +213,7 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
213
213
  appearance: none;
214
214
  border: none;
215
215
  background: transparent;
216
- color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
216
+ color: var(--color-foreground-muted, #656d76);
217
217
  cursor: pointer;
218
218
  padding: 6px;
219
219
  border-radius: 6px;
@@ -224,8 +224,8 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
224
224
  }
225
225
 
226
226
  .sb-sidepanel-action-btn:hover {
227
- background: var(--bgColor-neutral-muted, rgba(110, 118, 129, 0.1));
228
- color: var(--fgColor-default, var(--sb--color-foreground, #1f2328));
227
+ background: var(--color-background-neutral-muted, rgba(110, 118, 129, 0.1));
228
+ color: var(--color-foreground, #1f2328);
229
229
  }
230
230
 
231
231
  /* Body */
@@ -246,8 +246,8 @@ html.sb-sidepanel-open.sb-sidepanel-bottom .sb-mode-switch {
246
246
  .sb-sidepanel-spinner {
247
247
  width: 20px;
248
248
  height: 20px;
249
- border: 2px solid var(--borderColor-default, var(--sb--color-border, #d0d7de));
250
- border-top-color: var(--fgColor-muted, var(--sb--color-muted-foreground, #656d76));
249
+ border: 2px solid var(--color-border, #d0d7de);
250
+ border-top-color: var(--color-foreground-muted, #656d76);
251
251
  border-radius: 50%;
252
252
  animation: sb-spin 0.6s linear infinite;
253
253
  }
@@ -263,7 +263,7 @@ html.storyboard-mode-inspect .sb-sidepanel:not(.sb-sidepanel--bottom) {
263
263
  top: 12px;
264
264
  right: 12px;
265
265
  bottom: 12px;
266
- border-radius: var(--borderRadius-default);
266
+ border-radius: var(--radius);
267
267
  border-left: none;
268
268
  }
269
269
 
@@ -275,7 +275,7 @@ html.storyboard-mode-inspect .sb-sidepanel--bottom {
275
275
  right: 12px;
276
276
  bottom: 12px;
277
277
  width: auto !important;
278
- border-radius: var(--borderRadius-default);
278
+ border-radius: var(--radius);
279
279
  border-top: none;
280
280
  }
281
281
 
@@ -35,6 +35,8 @@ import { initDeliveryBridge } from '../messaging/delivery.js'
35
35
  import { getHubsMap } from '../messaging/hub-manager.js'
36
36
  import { startMaintenance, stopMaintenance } from '../messaging/hub-maintenance.js'
37
37
  import { createArtifactRoutes } from '../artifact/routes.js'
38
+ import { buildArtifactManifest, resolveManifestEnv } from '../data/artifactManifest.js'
39
+ import { buildDataDiscovery } from '../../internals/vite/data-plugin.js'
38
40
 
39
41
  const API_PREFIX = '/_storyboard/'
40
42
 
@@ -647,6 +649,17 @@ export default function storyboardServer() {
647
649
  } catch { sendJsonLogged(res, 200, { branch: null }) }
648
650
  })
649
651
 
652
+ // Per-branch artifact manifest parity route for local dev.
653
+ routeHandlers.set('artifacts.json', async (req, res) => {
654
+ try {
655
+ const discovery = buildDataDiscovery(root, { includeDraft: true })
656
+ const env = resolveManifestEnv({ env: { ...process.env, basePath: base || '/' }, cwd: root })
657
+ sendJsonLogged(res, 200, buildArtifactManifest({ discovery, env }))
658
+ } catch (err) {
659
+ sendJsonLogged(res, 500, { error: err.message || 'Failed to build artifacts manifest' })
660
+ }
661
+ })
662
+
650
663
  // Watch toolbar.config.json for changes — trigger full reload so
651
664
  // CoreUIBar.jsx picks up menu/mode config changes during dev
652
665
  const toolbarConfigPath = path.resolve(
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Shared cleanup primitives for `workleaf merge` and `workleaf delete`.
3
+ *
4
+ * The two commands diverge only in whether they squash-merge first;
5
+ * everything below (stop server, remove worktree, delete branch,
6
+ * release port) is identical.
7
+ */
8
+
9
+ import * as p from '@clack/prompts'
10
+ import { execFileSync } from 'node:child_process'
11
+ import { existsSync, rmdirSync } from 'node:fs'
12
+ import { dirname } from 'node:path'
13
+ import { releasePort } from '../worktree/port.js'
14
+ import { findByWorktree, unregister } from '../worktree/serverRegistry.js'
15
+ import { dim } from '../cli/intro.js'
16
+
17
+ /**
18
+ * Send SIGTERM to every dev server registered for this workleaf branch,
19
+ * then unregister them. Best-effort — never throws.
20
+ */
21
+ export function stopDevServer(branch) {
22
+ const servers = findByWorktree(branch)
23
+ for (const s of servers) {
24
+ try {
25
+ process.kill(s.pid, 'SIGTERM')
26
+ } catch (err) {
27
+ if (err.code !== 'ESRCH') {
28
+ p.log.warn(`Could not stop pid ${s.pid}: ${err.message}`)
29
+ }
30
+ }
31
+ unregister(s.id)
32
+ }
33
+ return servers.length
34
+ }
35
+
36
+ /**
37
+ * Remove the git worktree directory. Uses --force so uncommitted state
38
+ * in the workleaf doesn't block delete (callers should validate dirty
39
+ * state first). Also tries to remove the now-empty parent group dir
40
+ * (workleafs/<parent>/) for cosmetic cleanliness.
41
+ */
42
+ export function removeWorktree(dir, root) {
43
+ if (!existsSync(dir)) return
44
+ try {
45
+ execFileSync('git', ['worktree', 'remove', '--force', dir], { cwd: root, stdio: 'pipe' })
46
+ } catch (err) {
47
+ p.log.warn(`git worktree remove failed: ${err.message}`)
48
+ p.log.info(dim(` Try: rm -rf ${dir} && git worktree prune`))
49
+ }
50
+ // Try to remove the empty parent group dir (workleafs/<parent>/) — best
51
+ // effort, silently no-ops if the dir still has siblings.
52
+ try {
53
+ rmdirSync(dirname(dir))
54
+ } catch { /* still has siblings or already gone */ }
55
+ }
56
+
57
+ /**
58
+ * Delete the workleaf branch. Uses -D to allow deleting unmerged branches
59
+ * (delete subcommand) — merge subcommand callers may want to verify
60
+ * merge-state first.
61
+ */
62
+ export function deleteBranch(branch, root) {
63
+ try {
64
+ execFileSync('git', ['branch', '-D', branch], { cwd: root, stdio: 'pipe' })
65
+ } catch (err) {
66
+ p.log.warn(`git branch -D ${branch} failed: ${err.message}`)
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Full cleanup sequence shared by merge + delete:
72
+ * 1. Stop & unregister dev server(s)
73
+ * 2. Remove the git worktree
74
+ * 3. Delete the branch
75
+ * 4. Release the port assignment
76
+ */
77
+ export function cleanupWorkleaf({ branch, dir, root }) {
78
+ const stopped = stopDevServer(branch)
79
+ removeWorktree(dir, root)
80
+ deleteBranch(branch, root)
81
+ releasePort(branch)
82
+ return { stopped }
83
+ }
84
+
85
+ /** Read the current branch of a directory. */
86
+ export function currentBranch(cwd) {
87
+ try {
88
+ return execFileSync('git', ['branch', '--show-current'], { cwd, encoding: 'utf8' }).trim()
89
+ } catch {
90
+ return null
91
+ }
92
+ }
93
+
94
+ /** Check for uncommitted state in a worktree. */
95
+ export function hasDirtyWorkingTree(cwd) {
96
+ try {
97
+ const out = execFileSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8' }).trim()
98
+ return out.length > 0
99
+ } catch {
100
+ return false
101
+ }
102
+ }