@dfosco/storyboard-react 4.2.0-beta.17 → 4.2.0-beta.18

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 (79) hide show
  1. package/package.json +3 -3
  2. package/src/BranchBar/BranchBar.jsx +3 -1
  3. package/src/BranchBar/BranchBar.module.css +2 -2
  4. package/src/BranchBar/useBranches.js +20 -6
  5. package/src/BranchBar/useBranches.test.js +68 -0
  6. package/src/CommandPalette/CommandPalette.jsx +250 -61
  7. package/src/CommandPalette/command-palette.css +12 -0
  8. package/src/Icon.jsx +46 -11
  9. package/src/Viewfinder.jsx +53 -133
  10. package/src/Viewfinder.module.css +20 -91
  11. package/src/Workspace.jsx +7 -0
  12. package/src/canvas/CanvasPage.jsx +601 -62
  13. package/src/canvas/CanvasPage.module.css +15 -2
  14. package/src/canvas/CanvasPage.multiselect.test.jsx +7 -0
  15. package/src/canvas/ConnectorLayer.jsx +120 -152
  16. package/src/canvas/ConnectorLayer.module.css +69 -0
  17. package/src/canvas/canvasApi.js +68 -2
  18. package/src/canvas/connectorGeometry.js +132 -0
  19. package/src/canvas/hotPoolDevLogs.js +25 -0
  20. package/src/canvas/useMarqueeSelect.js +30 -4
  21. package/src/canvas/widgets/CodePenEmbed.jsx +1 -0
  22. package/src/canvas/widgets/ComponentSetWidget.jsx +199 -0
  23. package/src/canvas/widgets/ComponentSetWidget.module.css +89 -0
  24. package/src/canvas/widgets/ComponentWidget.jsx +1 -0
  25. package/src/canvas/widgets/CropOverlay.jsx +219 -0
  26. package/src/canvas/widgets/CropOverlay.module.css +118 -0
  27. package/src/canvas/widgets/ExpandedPane.jsx +472 -0
  28. package/src/canvas/widgets/ExpandedPane.module.css +179 -0
  29. package/src/canvas/widgets/ExpandedPane.test.jsx +240 -0
  30. package/src/canvas/widgets/ExpandedPaneTopBar.jsx +111 -0
  31. package/src/canvas/widgets/ExpandedPaneTopBar.module.css +59 -0
  32. package/src/canvas/widgets/ExpandedPaneTopBar.test.jsx +45 -0
  33. package/src/canvas/widgets/FigmaEmbed.jsx +49 -102
  34. package/src/canvas/widgets/ImageWidget.jsx +129 -8
  35. package/src/canvas/widgets/ImageWidget.module.css +30 -0
  36. package/src/canvas/widgets/LinkPreview.jsx +93 -44
  37. package/src/canvas/widgets/MarkdownBlock.jsx +141 -16
  38. package/src/canvas/widgets/MarkdownBlock.module.css +25 -0
  39. package/src/canvas/widgets/PromptWidget.jsx +414 -0
  40. package/src/canvas/widgets/PromptWidget.module.css +273 -0
  41. package/src/canvas/widgets/PrototypeEmbed.jsx +46 -170
  42. package/src/canvas/widgets/ResizeHandle.jsx +17 -6
  43. package/src/canvas/widgets/StoryWidget.jsx +65 -11
  44. package/src/canvas/widgets/TerminalReadWidget.jsx +11 -5
  45. package/src/canvas/widgets/TerminalReadWidget.module.css +3 -1
  46. package/src/canvas/widgets/TerminalWidget.jsx +301 -124
  47. package/src/canvas/widgets/TerminalWidget.module.css +121 -12
  48. package/src/canvas/widgets/TilesWidget.jsx +302 -0
  49. package/src/canvas/widgets/TilesWidget.module.css +133 -0
  50. package/src/canvas/widgets/WidgetChrome.jsx +67 -152
  51. package/src/canvas/widgets/WidgetChrome.module.css +20 -1
  52. package/src/canvas/widgets/expandUtils.js +385 -16
  53. package/src/canvas/widgets/expandUtils.test.js +155 -0
  54. package/src/canvas/widgets/index.js +6 -2
  55. package/src/canvas/widgets/tilePool.js +23 -0
  56. package/src/canvas/widgets/tiles/diagonal-bl.png +0 -0
  57. package/src/canvas/widgets/tiles/diagonal-br.png +0 -0
  58. package/src/canvas/widgets/tiles/diagonal-tl.png +0 -0
  59. package/src/canvas/widgets/tiles/leaf.png +0 -0
  60. package/src/canvas/widgets/tiles/quarter-tl.png +0 -0
  61. package/src/canvas/widgets/tiles/quarter-tr.png +0 -0
  62. package/src/canvas/widgets/tiles/solid-a.png +0 -0
  63. package/src/canvas/widgets/tiles/solid-b.png +0 -0
  64. package/src/canvas/widgets/widgetConfig.js +37 -4
  65. package/src/canvas/widgets/widgetIcons.jsx +190 -0
  66. package/src/canvas/widgets/widgetProps.js +1 -0
  67. package/src/context.jsx +47 -19
  68. package/src/hooks/usePrototypeReloadGuard.js +64 -0
  69. package/src/index.js +4 -2
  70. package/src/story/ComponentSetPage.jsx +186 -0
  71. package/src/story/ComponentSetPage.module.css +121 -0
  72. package/src/story/StoryPage.jsx +32 -2
  73. package/src/vite/data-plugin.js +79 -35
  74. package/src/canvas/widgets/ActionWidget.jsx +0 -200
  75. package/src/canvas/widgets/ActionWidget.module.css +0 -122
  76. package/src/canvas/widgets/SplitExpandModal.jsx +0 -234
  77. package/src/canvas/widgets/SplitExpandModal.module.css +0 -335
  78. package/src/canvas/widgets/SplitScreenTopBar.jsx +0 -30
  79. package/src/canvas/widgets/SplitScreenTopBar.module.css +0 -58
package/src/Icon.jsx CHANGED
@@ -40,16 +40,6 @@ const customIcons = {
40
40
  'M4 12H20',
41
41
  ],
42
42
  },
43
- // Diamond grid icon (from StoryWidget/ComponentWidget title bar)
44
- 'component': {
45
- viewBox: '0 0 24 24',
46
- strokePaths: [
47
- 'M5.21173 15.1113L2.52473 12.4243C2.29041 12.1899 2.29041 11.8101 2.52473 11.5757L5.21173 8.88873C5.44605 8.65442 5.82595 8.65442 6.06026 8.88873L8.74727 11.5757C8.98158 11.8101 8.98158 12.1899 8.74727 12.4243L6.06026 15.1113C5.82595 15.3456 5.44605 15.3456 5.21173 15.1113Z',
48
- 'M11.5757 21.475L8.88874 18.788C8.65443 18.5537 8.65443 18.1738 8.88874 17.9395L11.5757 15.2525C11.8101 15.0182 12.19 15.0182 12.4243 15.2525L15.1113 17.9395C15.3456 18.1738 15.3456 18.5537 15.1113 18.788L12.4243 21.475C12.19 21.7094 11.8101 21.7094 11.5757 21.475Z',
49
- 'M17.9395 15.1113L15.2525 12.4243C15.0182 12.1899 15.0182 11.8101 15.2525 11.5757L17.9395 8.88873C18.1738 8.65442 18.5537 8.65442 18.788 8.88873L21.475 11.5757C21.7094 11.8101 21.7094 12.1899 21.475 12.4243L18.788 15.1113C18.5537 15.3456 18.1738 15.3456 17.9395 15.1113Z',
50
- 'M11.5757 8.74727L8.88874 6.06026C8.65443 5.82595 8.65443 5.44605 8.88874 5.21173L11.5757 2.52473C11.8101 2.29041 12.19 2.29041 12.4243 2.52473L15.1113 5.21173C15.3456 5.44605 15.3456 5.82595 15.1113 6.06026L12.4243 8.74727C12.19 8.98158 11.8101 8.98158 11.5757 8.74727Z',
51
- ],
52
- },
53
43
  // Smiley face icon (from assets/icons/canvas.svg)
54
44
  'canvas': {
55
45
  viewBox: '0 0 28 23',
@@ -60,10 +50,20 @@ const customIcons = {
60
50
  'M17.6103 6.5C18.2731 6.5 18.8104 7.03731 18.8104 7.70012C18.8104 8.36293 18.2731 8.90025 17.6103 8.90025H17.5986C16.9358 8.90025 16.3984 8.36293 16.3984 7.70012C16.3984 7.03731 16.9358 6.5 17.5986 6.5H17.6103Z',
61
51
  ],
62
52
  },
53
+ 'sticky-note': {
54
+ viewBox: '0 0 14 14',
55
+ fillRule: 'evenodd',
56
+ path: 'M3.709.471C4.763.353 5.867.25 7 .25s2.237.104 3.29.22h.003a3.694 3.694 0 0 1 3.247 3.256v.004c.113 1.049.21 2.145.21 3.27q-.001.595-.032 1.176l-.008.067c-.556 3.437-3.804 5.226-6.192 5.498h-.006l-.048.006a.6.6 0 0 1-.126 0q-.168.003-.338.003c-1.133 0-2.236-.103-3.29-.221h-.003A3.695 3.695 0 0 1 .46 10.272v-.004C.346 9.221.25 8.126.25 7s.097-2.222.21-3.27v-.003A3.694 3.694 0 0 1 3.707.472zm8.784 7.047h-2.37c-.854 0-1.514.656-1.628 1.442c-.22 1.52-.706 2.546-1.413 3.54H7c-1.061 0-2.108-.097-3.15-.213a2.445 2.445 0 0 1-2.148-2.153C1.592 9.1 1.5 8.057 1.5 7s.091-2.1.202-3.134A2.444 2.444 0 0 1 3.85 1.714C4.89 1.597 5.938 1.5 7 1.5s2.107.098 3.151.213a2.444 2.444 0 0 1 2.147 2.152C12.408 4.9 12.5 5.944 12.5 7q0 .259-.007.518',
57
+ },
63
58
  'agents': {
64
59
  viewBox: '0 0 32 32',
65
60
  path: 'M27.2 16c0-6.19-5.01-11.2-11.2-11.2S4.8 9.81 4.8 16S9.81 27.2 16 27.2S27.2 22.19 27.2 16m-5.6 2.1a1.4 1.4 0 0 1 0 2.8h-4.2a1.4 1.4 0 0 1 0-2.8zm-11.2-6.8a1.397 1.397 0 0 1 1.84.361l.08.119l2.1 3.5l.087.171a1.4 1.4 0 0 1 0 1.1l-.088.171l-2.1 3.5a1.4 1.4 0 0 1-2.4-1.44l1.67-2.78l-1.67-2.78l-.067-.127a1.394 1.394 0 0 1 .547-1.79zM30 16c0 7.73-6.27 14-14 14S2 23.73 2 16S8.27 2 16 2s14 6.27 14 14',
66
61
  },
62
+ 'codex': {
63
+ viewBox: '0 0 24 24',
64
+ fillRule: 'evenodd',
65
+ path: 'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z',
66
+ },
67
67
  'claude': {
68
68
  viewBox: '0 0 24 24',
69
69
  path: 'm4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z',
@@ -121,6 +121,41 @@ const iconoirIcons = {
121
121
  strokeWidth: '1.5',
122
122
  content: '<path d="M3 21V3.6C3 3.26863 3.26863 3 3.6 3H21" stroke="currentColor"/><path d="M17 21H20.4C20.7314 21 21 20.7314 21 20.4V17" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 7V9" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 12V14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M7 21H9" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 21H14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 4C3.55228 4 4 3.55228 4 3C4 2.44772 3.55228 2 3 2C2.44772 2 2 2.44772 2 3C2 3.55228 2.44772 4 3 4Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 22C3.55228 22 4 21.5523 4 21C4 20.4477 3.55228 20 3 20C2.44772 20 2 20.4477 2 21C2 21.5523 2.44772 22 3 22Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M21 4C21.5523 4 22 3.55228 22 3C22 2.44772 21.5523 2 21 2C20.4477 2 20 2.44772 20 3C20 3.55228 20.4477 4 21 4Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
123
123
  },
124
+ 'white-flag': {
125
+ viewBox: '0 0 24 24',
126
+ strokeWidth: '1.5',
127
+ content: '<path d="M5 15L5.95039 4.54568C5.97849 4.23663 6.23761 4 6.54793 4H20.343C20.6958 4 20.9725 4.30295 20.9405 4.65432L20.0496 14.4543C20.0215 14.7634 19.7624 15 19.4521 15H5ZM5 15L4.4 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
128
+ },
129
+ 'light-bulb': {
130
+ viewBox: '0 0 24 24',
131
+ strokeWidth: '1.5',
132
+ content: '<path d="M9 18H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 21H14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.00082 15C9.00098 13 8.50098 12.5 7.50082 11.5C6.50067 10.5 6.02422 9.48689 6.00082 8C5.95284 4.95029 8.00067 3 12.0008 3C16.001 3 18.0488 4.95029 18.0008 8C17.9774 9.48689 17.5007 10.5 16.5008 11.5C15.501 12.5 15.001 13 15.0008 15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
133
+ },
134
+ 'light-bulb-off': {
135
+ viewBox: '0 0 24 24',
136
+ strokeWidth: '1.5',
137
+ content: '<path d="M9 18H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 21H14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M16.4999 11.5C17.4997 10.5 17.9765 9.48689 17.9999 8C18.0479 4.95029 16 3 11.9999 3C10.8324 3 9.83119 3.16613 8.99988 3.47724" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M8.99985 15C9 13 8.5 12.5 7.49985 11.5C6.4997 10.5 6.02324 9.48689 5.99985 8C5.99142 7.46458 6.0476 6.96304 6.1676 6.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 3L21 21" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
138
+ },
139
+ 'keyframes': {
140
+ viewBox: '0 0 24 24',
141
+ strokeWidth: '1.5',
142
+ content: '<path d="M13.8476 13.317L9.50515 18.2798C8.70833 19.1905 7.29167 19.1905 6.49485 18.2798L2.15238 13.317C1.49259 12.563 1.49259 11.437 2.15238 10.683L6.49485 5.72018C7.29167 4.80952 8.70833 4.80952 9.50515 5.72017L13.8476 10.683C14.5074 11.437 14.5074 12.563 13.8476 13.317Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M13 19L17.8844 13.3016C18.5263 12.5526 18.5263 11.4474 17.8844 10.6984L13 5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 19L21.8844 13.3016C22.5263 12.5526 22.5263 11.4474 21.8844 10.6984L17 5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
143
+ },
144
+ 'keyframes-couple-solid': {
145
+ viewBox: '0 0 24 24',
146
+ fill: true,
147
+ content: '<path fill-rule="evenodd" clip-rule="evenodd" d="M14.0658 5.18029L12.5606 6.87362L11.4395 5.87707L12.9447 4.18374C14.0386 2.95308 15.9614 2.95308 17.0554 4.18373L22.3794 10.1733C23.3056 11.2152 23.3057 12.7855 22.3795 13.8273L17.0554 19.8169C15.9614 21.0476 14.0386 21.0477 12.9447 19.8169L11.4395 18.1236L12.5606 17.1271L14.0658 18.8204C14.563 19.3798 15.437 19.3798 15.9342 18.8204L21.2583 12.8308C21.6793 12.3572 21.6793 11.6435 21.2584 11.17L15.9343 5.18029C15.437 4.6209 14.563 4.6209 14.0658 5.18029" fill="currentColor"/><path d="M6.94474 4.18374C8.03866 2.95307 9.96152 2.95308 11.0555 4.18374L16.3795 10.1733C17.3057 11.2152 17.3058 12.7855 16.3796 13.8273L11.0555 19.8169C9.96155 21.0476 8.03866 21.0477 6.94474 19.8169L1.62067 13.8273C0.694507 12.7855 0.694485 11.2152 1.62064 10.1734L6.94474 4.18374Z" fill="currentColor"/>',
148
+ },
149
+ 'keyframe': {
150
+ viewBox: '0 0 24 24',
151
+ strokeWidth: '1.5',
152
+ content: '<path d="M20.777 13.3453L13.4799 21.3721C12.6864 22.245 11.3136 22.245 10.5201 21.3721L3.22304 13.3453C2.52955 12.5825 2.52955 11.4175 3.22304 10.6547L10.5201 2.62787C11.3136 1.755 12.6864 1.755 13.4799 2.62787L20.777 10.6547C21.4705 11.4175 21.4705 12.5825 20.777 13.3453Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
153
+ },
154
+ 'wrench': {
155
+ viewBox: '0 0 24 24',
156
+ strokeWidth: '1.5',
157
+ content: '<path d="M10.0503 10.6066L2.97923 17.6777C2.19818 18.4587 2.19818 19.725 2.97923 20.5061V20.5061C3.76027 21.2871 5.0266 21.2871 5.80765 20.5061L12.8787 13.435" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M10.0502 10.6066C9.20638 8.45358 9.37134 5.6286 11.1109 3.88909C12.8504 2.14957 16.0606 1.76777 17.8284 2.82843L14.7877 5.8691L14.5051 8.98014L17.6161 8.69753L20.6568 5.65685C21.7175 7.42462 21.3357 10.6349 19.5961 12.3744C17.8566 14.1139 15.0316 14.2789 12.8786 13.435" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>',
158
+ },
124
159
  }
125
160
 
126
161
  /* ─── React Component ─── */
@@ -176,7 +211,7 @@ export default function Icon({
176
211
  if (custom.path) {
177
212
  svgContent = (
178
213
  <svg width={size} height={size} viewBox={custom.viewBox} fill="currentColor" {...ariaProps}>
179
- <path d={custom.path} />
214
+ <path d={custom.path} fillRule={custom.fillRule} clipRule={custom.fillRule} />
180
215
  </svg>
181
216
  )
182
217
  } else if (custom.strokePaths) {
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Viewfinder — SaaS-style homescreen for Storyboard.
2
+ * Workspace — SaaS-style homescreen for Storyboard.
3
3
  *
4
- * Replaces the old list-based Viewfinder with a sidebar + grid layout.
5
- * Wired to real data from buildPrototypeIndex and listStories.
4
+ * Sidebar + grid layout wired to real data from buildPrototypeIndex and listStories.
5
+ * Formerly known as Viewfinder renamed to match the /workspace route.
6
6
  */
7
7
  import { useState, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react'
8
- import { buildPrototypeIndex, listStories, getStoryData, getLocal, setLocal } from '@dfosco/storyboard-core'
8
+ import { buildPrototypeIndex, listStories, getStoryData, getLocal, setLocal, BranchSelect } from '@dfosco/storyboard-core'
9
9
  import { MarkGithubIcon, GitBranchIcon, ChevronDownIcon, ChevronRightIcon, FileDirectoryFillIcon, PlusIcon, StarIcon, StarFillIcon, ThreeBarsIcon, XIcon, StackIcon, TrashIcon, ShieldLockIcon, KebabHorizontalIcon, PencilIcon } from '@primer/octicons-react'
10
10
  import { Menu } from '@base-ui/react/menu'
11
11
  import { Dialog } from '@base-ui/react/dialog'
12
12
  import Icon from './Icon.jsx'
13
+ import { useBranches } from './BranchBar/useBranches.js'
13
14
  import css from './Viewfinder.module.css'
14
15
 
15
16
  /* ─── Theme sync: read toolbar theme from DOM and apply to Primer/BaseUI ─── */
@@ -94,10 +95,10 @@ function useGitHubUser(basePath) {
94
95
 
95
96
  /* ─── localStorage helpers ─── */
96
97
 
97
- const STARRED_KEY = 'sb-viewfinder-starred'
98
- const RECENT_KEY = 'sb-viewfinder-recent'
98
+ const STARRED_KEY = 'sb-workspace-starred'
99
+ const RECENT_KEY = 'sb-workspace-recent'
99
100
  const MAX_RECENT = 30
100
- const GROUP_BY_FOLDERS_KEY = 'sb-viewfinder-group-folders'
101
+ const GROUP_BY_FOLDERS_KEY = 'sb-workspace-group-folders'
101
102
 
102
103
  function readJSON(key, fallback) {
103
104
  try { return JSON.parse(localStorage.getItem(key)) || fallback }
@@ -175,7 +176,7 @@ function getTypeLabel(type) {
175
176
  function getTypeIcon(type, size = 14) {
176
177
  if (type === 'prototype') return <Icon name="prototype" size={size} />
177
178
  if (type === 'canvas') return <Icon name="canvas" size={size} />
178
- if (type === 'component') return <Icon name="component" size={size} />
179
+ if (type === 'component') return <Icon name="iconoir/keyframe" size={size} />
179
180
  return null
180
181
  }
181
182
 
@@ -875,7 +876,7 @@ function CreateMenu({ onClose, basePath }) {
875
876
  const items = [
876
877
  { icon: <Icon name="canvas" size={18} />, title: 'Canvas', desc: 'Interactive board for prototypes, components, and documents' },
877
878
  { icon: <Icon name="prototype" size={18} />, title: 'Prototype', desc: 'Interactive page flow' },
878
- { icon: <Icon name="component" size={18} />, title: 'Component', desc: 'Reusable component' },
879
+ { icon: <Icon name="iconoir/-couple-solid" size={18} />, title: 'Component', desc: 'Reusable component' },
879
880
  ]
880
881
 
881
882
  const moreItems = [
@@ -945,141 +946,60 @@ const NAV_ITEMS = [
945
946
 
946
947
  const TAB_FILTERS = ['All', 'Recent', 'Starred']
947
948
 
948
- /* ─── Branch Dropdown ─── */
949
+ /* ─── Branch Navigation ─── */
949
950
 
950
- function useBranches(basePath) {
951
- const [branches, setBranches] = useState(() => {
952
- if (typeof window !== 'undefined' && Array.isArray(window.__SB_BRANCHES__)) {
953
- return window.__SB_BRANCHES__
954
- }
955
- return null
956
- })
957
-
958
- const [gitUser, setGitUser] = useState(null)
959
-
960
- useEffect(() => {
961
- const apiBase = (basePath || '/').replace(/\/$/, '')
962
-
963
- // Fetch git user info for "my branches" filtering
964
- fetch(`${apiBase}/_storyboard/git-user`).then(r => r.ok ? r.json() : null)
965
- .then(data => { if (data?.name) setGitUser(data.name) })
966
- .catch(() => {})
967
-
968
- // Always fetch live branch list from server API
969
- fetch(`${apiBase}/_storyboard/worktrees`).then(r => r.ok ? r.json() : null)
970
- .then(data => { if (Array.isArray(data) && data.length > 0) setBranches(data) })
971
- .catch(() => {})
972
- }, [])
973
-
974
- const currentBranch = useMemo(() => {
975
- const m = (basePath || '').match(/\/branch--([^/]+)\/?$/)
976
- return m ? m[1] : 'main'
977
- }, [basePath])
978
-
979
- const branchBasePath = (basePath || '/').replace(/\/branch--[^/]*\/$/, '/')
980
-
981
- return { branches, currentBranch, branchBasePath, gitUser }
982
- }
983
-
984
- function BranchDropdown({ basePath }) {
985
- // Dev: hide dropdown — use CLI to switch branches
951
+ function BranchNav({ basePath }) {
986
952
  const isLocalDev = typeof window !== 'undefined' && window.__SB_LOCAL_DEV__ === true
987
- if (isLocalDev) return null
988
-
989
- const { branches, currentBranch, branchBasePath, gitUser } = useBranches(basePath)
990
- const [showAll, setShowAll] = useState(false)
953
+ const { branches, currentBranch, branchBasePath } = useBranches(basePath)
991
954
  const [switching, setSwitching] = useState(null)
992
- const [switchError, setSwitchError] = useState(null)
993
955
 
994
956
  if (!branches || branches.length === 0) return null
995
957
 
996
- const twoWeeksAgo = Date.now() - 14 * 24 * 60 * 60 * 1000
958
+ const branchNames = branches.map(b => b.branch)
997
959
 
998
- // Split into "my branches" vs others
999
- const myBranches = gitUser
1000
- ? branches.filter(b => b.author === gitUser || b.branch === currentBranch)
1001
- : branches.filter(b => b.branch === currentBranch)
960
+ const navigate = async (branch) => {
961
+ if (switching) return
962
+ const target = branches.find(b => b.branch === branch)
963
+ const folder = target?.folder || (branch === 'main' ? '' : `branch--${branch}/`)
964
+ const directUrl = `${branchBasePath}${folder}`
1002
965
 
1003
- const otherBranches = branches.filter(b => !myBranches.some(m => m.branch === b.branch))
1004
-
1005
- // Recent = last 2 weeks (or all if showAll)
1006
- const recentBranches = showAll
1007
- ? [...otherBranches].sort((a, b) => (a.branch || '').localeCompare(b.branch || ''))
1008
- : otherBranches
1009
- .filter(b => !b.lastModified || new Date(b.lastModified).getTime() > twoWeeksAgo)
1010
- .sort((a, b) => (a.branch || '').localeCompare(b.branch || ''))
966
+ if (!isLocalDev) {
967
+ window.location.href = directUrl
968
+ return
969
+ }
1011
970
 
1012
- const switchBranch = (branch) => {
971
+ // Local dev: ask server to spin up the branch, then navigate
1013
972
  setSwitching(branch)
1014
- const target = branches?.find(b => b.branch === branch)
1015
- window.location.href = `${branchBasePath}${target?.folder || (branch === 'main' ? '' : `branch--${branch}/`)}`
973
+ const apiBase = (basePath || '/').replace(/\/$/, '')
974
+ try {
975
+ const res = await fetch(`${apiBase}/_storyboard/switch-branch`, {
976
+ method: 'POST',
977
+ headers: { 'Content-Type': 'application/json' },
978
+ body: JSON.stringify({ branch }),
979
+ })
980
+ const data = await res.json()
981
+ window.location.href = (res.ok && data.url) ? data.url : directUrl
982
+ } catch {
983
+ window.location.href = directUrl
984
+ }
1016
985
  }
1017
986
 
1018
987
  return (
1019
- <Menu.Root>
1020
- <Menu.Trigger className={css.branchBtn} disabled={!!switching}>
988
+ <>
989
+ <div className={css.branchNav}>
1021
990
  <GitBranchIcon size={14} />
1022
- <span className={css.branchBtnText}>{switching ? `Switching to ${switching}…` : currentBranch}</span>
1023
- {!switching && <ChevronDownIcon size={12} />}
1024
- </Menu.Trigger>
1025
- <Menu.Portal>
1026
- <Menu.Positioner className={css.branchPositioner} side="bottom" align="end" sideOffset={4}>
1027
- <Menu.Popup className={css.branchPopup}>
1028
- {myBranches.length > 0 && (
1029
- <>
1030
- <div className={css.branchSectionLabel}>My branches</div>
1031
- {myBranches.map(b => (
1032
- <Menu.Item
1033
- key={b.branch}
1034
- className={`${css.branchItem}${b.branch === currentBranch ? ` ${css.branchItemActive}` : ''}`}
1035
- onClick={() => switchBranch(b.branch)}
1036
- >
1037
- <GitBranchIcon size={12} />
1038
- {b.branch}
1039
- </Menu.Item>
1040
- ))}
1041
- </>
1042
- )}
1043
-
1044
- {myBranches.length > 0 && recentBranches.length > 0 && (
1045
- <div className={css.branchSeparator} />
1046
- )}
1047
-
1048
- {recentBranches.length > 0 && (
1049
- <>
1050
- <div className={css.branchSectionLabel}>
1051
- {showAll ? 'All branches' : 'Recent branches'}
1052
- </div>
1053
- <Menu.Viewport className={css.branchViewport}>
1054
- {recentBranches.map(b => (
1055
- <Menu.Item
1056
- key={b.branch}
1057
- className={`${css.branchItem}${b.branch === currentBranch ? ` ${css.branchItemActive}` : ''}`}
1058
- onClick={() => switchBranch(b.branch)}
1059
- >
1060
- <GitBranchIcon size={12} />
1061
- {b.branch}
1062
- </Menu.Item>
1063
- ))}
1064
- </Menu.Viewport>
1065
- </>
1066
- )}
1067
-
1068
- {!showAll && otherBranches.length > recentBranches.length && (
1069
- <>
1070
- <div className={css.branchSeparator} />
1071
- <button
1072
- className={css.branchShowAll}
1073
- onClick={(e) => { e.stopPropagation(); setShowAll(true) }}
1074
- >
1075
- See all branches ({otherBranches.length})
1076
- </button>
1077
- </>
1078
- )}
1079
- </Menu.Popup>
1080
- </Menu.Positioner>
1081
- </Menu.Portal>
1082
- </Menu.Root>
991
+ <BranchSelect
992
+ branches={branchNames}
993
+ value={currentBranch}
994
+ onChange={(e) => navigate(e.target.value)}
995
+ disabled={!!switching}
996
+ />
997
+ </div>
998
+ {switching && <div className={css.switchOverlay}>
999
+ <div className={css.switchSpinner} />
1000
+ <span>Starting {switching}…</span>
1001
+ </div>}
1002
+ </>
1083
1003
  )
1084
1004
  }
1085
1005
 
@@ -1153,7 +1073,7 @@ function UserSettingsDialog({ open, onOpenChange, user, onRemoveToken }) {
1153
1073
 
1154
1074
  /* ─── Main Component ─── */
1155
1075
 
1156
- export default function Viewfinder({
1076
+ export default function Workspace({
1157
1077
  pageModules = {},
1158
1078
  basePath,
1159
1079
  title = 'Storyboard',
@@ -1179,7 +1099,7 @@ export default function Viewfinder({
1179
1099
  const knownRoutes = useMemo(() =>
1180
1100
  Object.keys(pageModules)
1181
1101
  .map(p => p.replace('/src/prototypes/', '').replace('.jsx', ''))
1182
- .filter(n => !n.startsWith('_') && n !== 'index' && n !== 'viewfinder'),
1102
+ .filter(n => !n.startsWith('_') && n !== 'index' && n !== 'workspace' && n !== 'viewfinder'),
1183
1103
  [pageModules],
1184
1104
  )
1185
1105
 
@@ -1392,7 +1312,7 @@ export default function Viewfinder({
1392
1312
  </div>
1393
1313
  </div>
1394
1314
  <div className={css.topActions}>
1395
- <BranchDropdown basePath={basePath} />
1315
+ <BranchNav basePath={basePath} />
1396
1316
  {isLocalDev && (
1397
1317
  <Menu.Root open={showCreate} onOpenChange={setShowCreate}>
1398
1318
  <Menu.Trigger className={css.createBtn}>
@@ -81,110 +81,39 @@
81
81
 
82
82
  /* Branch dropdown */
83
83
 
84
- .branchBtn {
84
+ .branchNav {
85
85
  display: flex;
86
86
  align-items: center;
87
87
  gap: 6px;
88
- padding: 7px 14px;
89
- background: var(--bgColor-default, #fff);
90
88
  color: var(--fgColor-muted, #555);
91
- border: 1px solid var(--borderColor-default, #e5e5e5);
92
- border-radius: 8px;
93
- font-size: 16px;
94
- font-weight: 500;
95
- cursor: pointer;
96
- transition: all 0.15s;
97
- font-family: inherit;
98
- }
99
-
100
- .branchBtn:hover {
101
- background: var(--bgColor-muted, #f5f5f5);
102
- border-color: var(--borderColor-muted);
103
- }
104
-
105
- .branchBtnText {
106
- max-width: 160px;
107
- overflow: hidden;
108
- text-overflow: ellipsis;
109
- white-space: nowrap;
110
- }
111
-
112
- .branchPositioner {
113
- z-index: 200;
114
- }
115
-
116
- .branchPopup {
117
- background: var(--bgColor-default, #fff);
118
- border: 1px solid var(--borderColor-default, #e5e5e5);
119
- border-radius: 10px;
120
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
121
- min-width: 240px;
122
- max-width: 320px;
123
- padding: 6px 0;
124
- font-family: 'Mona Sans', -apple-system, BlinkMacSystemFont, sans-serif;
125
- }
126
-
127
- .branchSectionLabel {
128
- padding: 8px 14px 4px;
129
- font-size: 14px;
130
- font-weight: 600;
131
- color: var(--fgColor-muted, #999);
132
- text-transform: uppercase;
133
- letter-spacing: 0.4px;
134
89
  }
135
90
 
136
- .branchItem {
91
+ .switchOverlay {
92
+ position: fixed;
93
+ inset: 0;
94
+ z-index: 9999;
137
95
  display: flex;
96
+ flex-direction: column;
138
97
  align-items: center;
139
- gap: 8px;
140
- padding: 7px 14px;
141
- font-size: 16px;
98
+ justify-content: center;
99
+ gap: 12px;
100
+ background: var(--bgColor-default, #fff);
142
101
  color: var(--fgColor-muted, #555);
143
- cursor: pointer;
144
- transition: background 0.1s;
145
- border: none;
146
- background: none;
147
- width: 100%;
148
- text-align: left;
149
- }
150
-
151
- .branchItem:hover,
152
- .branchItem[data-highlighted] {
153
- background: var(--bgColor-muted, #f5f5f5);
154
- }
155
-
156
- .branchItemActive {
157
- composes: branchItem;
158
- color: var(--fgColor-default, #1a1a1a);
159
- font-weight: 600;
160
- }
161
-
162
- .branchSeparator {
163
- height: 1px;
164
- background: var(--borderColor-default, #e5e5e5);
165
- margin: 4px 10px;
166
- }
167
-
168
- .branchViewport {
169
- max-height: 280px;
170
- overflow-y: auto;
102
+ font-size: 15px;
103
+ font-weight: 500;
171
104
  }
172
105
 
173
- .branchShowAll {
174
- display: block;
175
- width: 100%;
176
- padding: 8px 14px;
177
- font-size: 16px;
178
- color: var(--fgColor-accent, #2563eb);
179
- background: none;
180
- border: none;
181
- cursor: pointer;
182
- text-align: left;
183
- font-family: inherit;
106
+ .switchSpinner {
107
+ width: 24px;
108
+ height: 24px;
109
+ border: 3px solid var(--borderColor-default, #e5e5e5);
110
+ border-top-color: var(--fgColor-accent, #2563eb);
111
+ border-radius: 50%;
112
+ animation: branchSpin 0.6s linear infinite;
184
113
  }
185
114
 
186
- .branchShowAll:hover {
187
- background: var(--bgColor-muted, #f5f5f5);
115
+ @keyframes branchSpin {
116
+ to { transform: rotate(360deg); }
188
117
  }
189
118
 
190
119
  /* ─── Body: sidebar + content ─── */
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Workspace — canonical entry point.
3
+ *
4
+ * Re-exports from Viewfinder.jsx (legacy filename kept for backwards compat).
5
+ * New code should import from this file.
6
+ */
7
+ export { default, default as Workspace } from './Viewfinder.jsx'