astro-tractstack 2.0.0-rc.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 (427) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +56 -0
  3. package/astro.d.ts +64 -0
  4. package/bin/create-tractstack.js +483 -0
  5. package/dist/config.js +80 -0
  6. package/dist/index.js +2129 -0
  7. package/package.json +89 -0
  8. package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
  9. package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
  10. package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
  11. package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
  12. package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
  13. package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
  14. package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
  15. package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
  16. package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
  17. package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
  18. package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
  19. package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
  20. package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
  21. package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
  22. package/templates/artpacks/kCz/dragon_600px.webp +0 -0
  23. package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
  24. package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
  25. package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
  26. package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
  27. package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
  28. package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
  29. package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
  30. package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
  31. package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
  32. package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
  33. package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
  34. package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
  35. package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
  36. package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
  37. package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
  38. package/templates/artpacks/kCz/snake_1080px.webp +0 -0
  39. package/templates/artpacks/kCz/snake_1920px.webp +0 -0
  40. package/templates/artpacks/kCz/snake_600px.webp +0 -0
  41. package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
  42. package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
  43. package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
  44. package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
  45. package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
  46. package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
  47. package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
  48. package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
  49. package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
  50. package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
  51. package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
  52. package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
  53. package/templates/artpacks/t8k/beach_1080px.webp +0 -0
  54. package/templates/artpacks/t8k/beach_1920px.webp +0 -0
  55. package/templates/artpacks/t8k/beach_600px.webp +0 -0
  56. package/templates/artpacks/t8k/blast_1080px.webp +0 -0
  57. package/templates/artpacks/t8k/blast_1920px.webp +0 -0
  58. package/templates/artpacks/t8k/blast_600px.webp +0 -0
  59. package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
  60. package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
  61. package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
  62. package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
  63. package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
  64. package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
  65. package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
  66. package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
  67. package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
  68. package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
  69. package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
  70. package/templates/artpacks/t8k/explosion_600px.webp +0 -0
  71. package/templates/artpacks/t8k/floral_1080px.webp +0 -0
  72. package/templates/artpacks/t8k/floral_1920px.webp +0 -0
  73. package/templates/artpacks/t8k/floral_600px.webp +0 -0
  74. package/templates/artpacks/t8k/flower_1080px.webp +0 -0
  75. package/templates/artpacks/t8k/flower_1920px.webp +0 -0
  76. package/templates/artpacks/t8k/flower_600px.webp +0 -0
  77. package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
  78. package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
  79. package/templates/artpacks/t8k/foliage_600px.webp +0 -0
  80. package/templates/artpacks/t8k/mist_1080px.webp +0 -0
  81. package/templates/artpacks/t8k/mist_1920px.webp +0 -0
  82. package/templates/artpacks/t8k/mist_600px.webp +0 -0
  83. package/templates/artpacks/t8k/portal_1080px.webp +0 -0
  84. package/templates/artpacks/t8k/portal_1920px.webp +0 -0
  85. package/templates/artpacks/t8k/portal_600px.webp +0 -0
  86. package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
  87. package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
  88. package/templates/artpacks/t8k/storytime_600px.webp +0 -0
  89. package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
  90. package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
  91. package/templates/artpacks/t8k/tacky_600px.webp +0 -0
  92. package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
  93. package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
  94. package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
  95. package/templates/brand/favicon.ico +0 -0
  96. package/templates/brand/logo.svg +19 -0
  97. package/templates/brand/static.jpg +0 -0
  98. package/templates/brand/wordmark.svg +4 -0
  99. package/templates/css/custom.css +51 -0
  100. package/templates/css/frontend.css +3519 -0
  101. package/templates/css/storykeep.css +92872 -0
  102. package/templates/custom/minimal/CodeHook.astro +53 -0
  103. package/templates/custom/minimal/CustomRoutes.astro +46 -0
  104. package/templates/custom/with-examples/CodeHook.astro +49 -0
  105. package/templates/custom/with-examples/CustomHero.astro +13 -0
  106. package/templates/custom/with-examples/CustomRoutes.astro +39 -0
  107. package/templates/custom/with-examples/pages/Collections.astro +110 -0
  108. package/templates/env.example +8 -0
  109. package/templates/fonts/Inter-Black.woff2 +0 -0
  110. package/templates/fonts/Inter-Bold.woff2 +0 -0
  111. package/templates/fonts/Inter-Regular.woff2 +0 -0
  112. package/templates/icons/h2.svg +1 -0
  113. package/templates/icons/h3.svg +1 -0
  114. package/templates/icons/h4.svg +1 -0
  115. package/templates/icons/h5.svg +1 -0
  116. package/templates/icons/image.svg +7 -0
  117. package/templates/icons/text.svg +6 -0
  118. package/templates/socials/codepen.svg +1 -0
  119. package/templates/socials/discord.svg +1 -0
  120. package/templates/socials/facebook.svg +1 -0
  121. package/templates/socials/github.svg +1 -0
  122. package/templates/socials/instagram.svg +1 -0
  123. package/templates/socials/linkedin.svg +1 -0
  124. package/templates/socials/mail.svg +1 -0
  125. package/templates/socials/rumble.svg +1 -0
  126. package/templates/socials/tiktok.svg +1 -0
  127. package/templates/socials/twitch.svg +1 -0
  128. package/templates/socials/twitter.svg +1 -0
  129. package/templates/socials/x.svg +1 -0
  130. package/templates/socials/youtube.svg +1 -0
  131. package/templates/src/client/analytics-events.ts +213 -0
  132. package/templates/src/client/belief-events.ts +205 -0
  133. package/templates/src/client/sse.ts +667 -0
  134. package/templates/src/components/Footer.astro +246 -0
  135. package/templates/src/components/Fragment.astro +70 -0
  136. package/templates/src/components/Header.astro +458 -0
  137. package/templates/src/components/Menu.tsx +196 -0
  138. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
  139. package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
  140. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
  141. package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
  142. package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
  143. package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
  144. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
  145. package/templates/src/components/codehooks/ListContent.astro +460 -0
  146. package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
  147. package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
  148. package/templates/src/components/compositor/Compositor.tsx +144 -0
  149. package/templates/src/components/compositor/Node.tsx +415 -0
  150. package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
  151. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
  152. package/templates/src/components/compositor/elements/Belief.tsx +148 -0
  153. package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
  154. package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
  155. package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
  156. package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
  157. package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
  158. package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
  159. package/templates/src/components/compositor/elements/Svg.tsx +33 -0
  160. package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
  161. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
  162. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
  163. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
  164. package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
  165. package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
  166. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
  167. package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
  168. package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
  169. package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
  170. package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
  171. package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
  172. package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
  173. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
  174. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
  175. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
  176. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
  177. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
  178. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
  179. package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
  180. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
  181. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
  182. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
  183. package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
  184. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
  185. package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
  186. package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
  187. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
  188. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
  189. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
  190. package/templates/src/components/edit/Header.tsx +181 -0
  191. package/templates/src/components/edit/PanelSwitch.tsx +446 -0
  192. package/templates/src/components/edit/SettingsPanel.tsx +70 -0
  193. package/templates/src/components/edit/ToolBar.tsx +101 -0
  194. package/templates/src/components/edit/ToolMode.tsx +121 -0
  195. package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
  196. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
  197. package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
  198. package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
  199. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
  200. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
  201. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
  202. package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
  203. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
  204. package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
  205. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
  206. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
  207. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
  208. package/templates/src/components/edit/pane/PageGen.tsx +485 -0
  209. package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
  210. package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
  211. package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
  212. package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
  213. package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
  214. package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
  215. package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
  216. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
  217. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
  218. package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
  219. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
  220. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
  221. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
  222. package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
  223. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
  224. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
  225. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
  226. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
  227. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
  228. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
  229. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
  230. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
  231. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
  232. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
  233. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
  234. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
  235. package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
  236. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
  237. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
  238. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
  239. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
  240. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
  241. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
  242. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
  243. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
  244. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
  245. package/templates/src/components/edit/state/SaveModal.tsx +811 -0
  246. package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
  247. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
  248. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
  249. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
  250. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
  251. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
  252. package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
  253. package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
  254. package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
  255. package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
  256. package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
  257. package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
  258. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
  259. package/templates/src/components/fields/ArtpackImage.tsx +480 -0
  260. package/templates/src/components/fields/BackgroundImage.tsx +530 -0
  261. package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
  262. package/templates/src/components/fields/BooleanParam.tsx +67 -0
  263. package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
  264. package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
  265. package/templates/src/components/fields/ImageUpload.tsx +405 -0
  266. package/templates/src/components/fields/MultiParam.tsx +75 -0
  267. package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
  268. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
  269. package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
  270. package/templates/src/components/fields/SingleParam.tsx +73 -0
  271. package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
  272. package/templates/src/components/form/ActionBuilderField.tsx +282 -0
  273. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
  274. package/templates/src/components/form/BooleanToggle.tsx +94 -0
  275. package/templates/src/components/form/ColorPicker.tsx +153 -0
  276. package/templates/src/components/form/DateTimeInput.tsx +638 -0
  277. package/templates/src/components/form/EnumSelect.tsx +88 -0
  278. package/templates/src/components/form/FileUpload.tsx +465 -0
  279. package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
  280. package/templates/src/components/form/NumberInput.tsx +101 -0
  281. package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
  282. package/templates/src/components/form/StringArrayInput.tsx +163 -0
  283. package/templates/src/components/form/StringInput.tsx +88 -0
  284. package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
  285. package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
  286. package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
  287. package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
  288. package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
  289. package/templates/src/components/form/brand/SEOSection.tsx +101 -0
  290. package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
  291. package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
  292. package/templates/src/components/profile/ProfileConsent.tsx +65 -0
  293. package/templates/src/components/profile/ProfileCreate.tsx +462 -0
  294. package/templates/src/components/profile/ProfileEdit.tsx +409 -0
  295. package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
  296. package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
  297. package/templates/src/components/storykeep/Dashboard.tsx +160 -0
  298. package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
  299. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
  300. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
  301. package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
  302. package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
  303. package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
  304. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
  305. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
  306. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
  307. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
  308. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
  309. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
  310. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
  311. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
  312. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
  313. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
  314. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
  315. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
  316. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
  317. package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
  318. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
  319. package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
  320. package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
  321. package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
  322. package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
  323. package/templates/src/components/widgets/Impression.tsx +102 -0
  324. package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
  325. package/templates/src/constants/beliefs.ts +61 -0
  326. package/templates/src/constants/brandThemes.ts +133 -0
  327. package/templates/src/constants/prompts.json +55 -0
  328. package/templates/src/constants/shapes.ts +556 -0
  329. package/templates/src/constants/stopWords.ts +116 -0
  330. package/templates/src/constants/tailwindColors.json +344 -0
  331. package/templates/src/constants.ts +274 -0
  332. package/templates/src/hooks/useFormState.ts +203 -0
  333. package/templates/src/layouts/Layout.astro +290 -0
  334. package/templates/src/lib/session.ts +126 -0
  335. package/templates/src/lib/storyData.ts +56 -0
  336. package/templates/src/middleware.ts +52 -0
  337. package/templates/src/pages/404.astro +54 -0
  338. package/templates/src/pages/[...slug]/edit.astro +216 -0
  339. package/templates/src/pages/[...slug].astro +148 -0
  340. package/templates/src/pages/api/auth/decode.ts +101 -0
  341. package/templates/src/pages/api/auth/login.ts +122 -0
  342. package/templates/src/pages/api/auth/logout.ts +37 -0
  343. package/templates/src/pages/api/auth/profile.ts +76 -0
  344. package/templates/src/pages/api/orphan-analysis.ts +106 -0
  345. package/templates/src/pages/api/tailwind.ts +116 -0
  346. package/templates/src/pages/collections/[param1].astro +65 -0
  347. package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
  348. package/templates/src/pages/context/[...contextSlug].astro +161 -0
  349. package/templates/src/pages/llms.txt.ts +122 -0
  350. package/templates/src/pages/maint.astro +183 -0
  351. package/templates/src/pages/media/[...slug].astro +67 -0
  352. package/templates/src/pages/robots.txt.ts +36 -0
  353. package/templates/src/pages/sandbox/activate.astro +258 -0
  354. package/templates/src/pages/sandbox/register.astro +44 -0
  355. package/templates/src/pages/sandbox/success.astro +179 -0
  356. package/templates/src/pages/sitemap.xml.ts +119 -0
  357. package/templates/src/pages/storykeep/advanced.astro +69 -0
  358. package/templates/src/pages/storykeep/branding.astro +57 -0
  359. package/templates/src/pages/storykeep/content.astro +71 -0
  360. package/templates/src/pages/storykeep/init.astro +36 -0
  361. package/templates/src/pages/storykeep/login.astro +266 -0
  362. package/templates/src/pages/storykeep/logout.astro +84 -0
  363. package/templates/src/pages/storykeep/profile.astro +98 -0
  364. package/templates/src/pages/storykeep.astro +81 -0
  365. package/templates/src/stores/analytics.ts +171 -0
  366. package/templates/src/stores/backend.ts +16 -0
  367. package/templates/src/stores/navigation.ts +149 -0
  368. package/templates/src/stores/nodes.ts +2390 -0
  369. package/templates/src/stores/nodesHistory.ts +85 -0
  370. package/templates/src/stores/notificationSystem.ts +41 -0
  371. package/templates/src/stores/orphanAnalysis.ts +409 -0
  372. package/templates/src/stores/storykeep.ts +247 -0
  373. package/templates/src/types/astro.ts +86 -0
  374. package/templates/src/types/compositorTypes.ts +456 -0
  375. package/templates/src/types/formTypes.ts +281 -0
  376. package/templates/src/types/multiTenant.ts +77 -0
  377. package/templates/src/types/nodeProps.ts +66 -0
  378. package/templates/src/types/tractstack.ts +445 -0
  379. package/templates/src/utils/aai/getTitleSlug.ts +72 -0
  380. package/templates/src/utils/actions/actionButton.ts +101 -0
  381. package/templates/src/utils/actions/lispLexer.ts +57 -0
  382. package/templates/src/utils/actions/preParse_Action.ts +85 -0
  383. package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
  384. package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
  385. package/templates/src/utils/actions/preParse_Impression.ts +71 -0
  386. package/templates/src/utils/api/advancedConfig.ts +66 -0
  387. package/templates/src/utils/api/advancedHelpers.ts +134 -0
  388. package/templates/src/utils/api/beliefConfig.ts +87 -0
  389. package/templates/src/utils/api/beliefHelpers.ts +196 -0
  390. package/templates/src/utils/api/brandConfig.ts +126 -0
  391. package/templates/src/utils/api/brandHelpers.ts +155 -0
  392. package/templates/src/utils/api/fileHelpers.ts +306 -0
  393. package/templates/src/utils/api/menuConfig.ts +57 -0
  394. package/templates/src/utils/api/menuHelpers.ts +156 -0
  395. package/templates/src/utils/api/resourceConfig.ts +158 -0
  396. package/templates/src/utils/api/resourceHelpers.ts +72 -0
  397. package/templates/src/utils/api/tenantConfig.ts +97 -0
  398. package/templates/src/utils/api/tenantHelpers.ts +172 -0
  399. package/templates/src/utils/api.ts +183 -0
  400. package/templates/src/utils/auth.ts +150 -0
  401. package/templates/src/utils/backend.ts +243 -0
  402. package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
  403. package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
  404. package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
  405. package/templates/src/utils/compositor/allowInsert.ts +100 -0
  406. package/templates/src/utils/compositor/domHelpers.ts +37 -0
  407. package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
  408. package/templates/src/utils/compositor/nodesHelper.ts +491 -0
  409. package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
  410. package/templates/src/utils/compositor/processMarkdown.ts +431 -0
  411. package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
  412. package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
  413. package/templates/src/utils/compositor/tailwindColors.ts +227 -0
  414. package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
  415. package/templates/src/utils/compositor/typeGuards.ts +193 -0
  416. package/templates/src/utils/etl/extractor.ts +119 -0
  417. package/templates/src/utils/etl/index.ts +88 -0
  418. package/templates/src/utils/etl/loader.ts +36 -0
  419. package/templates/src/utils/etl/transformer.ts +286 -0
  420. package/templates/src/utils/helpers.ts +435 -0
  421. package/templates/src/utils/layout.ts +209 -0
  422. package/templates/src/utils/profileStorage.ts +306 -0
  423. package/templates/src/utils/useInterval.ts +27 -0
  424. package/templates/tailwind.config.cjs +169 -0
  425. package/utils/create-resolver.ts +10 -0
  426. package/utils/inject-files.ts +2140 -0
  427. package/utils/validate-config.ts +43 -0
@@ -0,0 +1,811 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Dialog } from '@ark-ui/react/dialog';
3
+ import { Portal } from '@ark-ui/react/portal';
4
+ import { navigate } from 'astro:transitions/client';
5
+ import { getCtx } from '@/stores/nodes';
6
+ import {
7
+ transformLivePaneForSave,
8
+ transformStoryFragmentForSave,
9
+ } from '@/utils/etl/index';
10
+ import {
11
+ fullContentMapStore,
12
+ getPendingImageOperation,
13
+ clearPendingImageOperation,
14
+ } from '@/stores/storykeep';
15
+ import { startLoadingAnimation } from '@/utils/helpers';
16
+ import type {
17
+ BaseNode,
18
+ PaneNode,
19
+ StoryFragmentNode,
20
+ } from '@/types/compositorTypes';
21
+
22
+ type SaveStage =
23
+ | 'PREPARING'
24
+ | 'SAVING_PENDING_FILES'
25
+ | 'PROCESSING_OG_IMAGES'
26
+ | 'SAVING_PANES'
27
+ | 'SAVING_STORY_FRAGMENTS'
28
+ | 'LINKING_FILES'
29
+ | 'PROCESSING_STYLES'
30
+ | 'COMPLETED'
31
+ | 'ERROR';
32
+
33
+ interface SaveStageProgress {
34
+ currentStep: number;
35
+ totalSteps: number;
36
+ }
37
+
38
+ interface SaveModalProps {
39
+ show: boolean;
40
+ slug: string;
41
+ isContext: boolean;
42
+ onClose: () => void;
43
+ }
44
+
45
+ export default function SaveModal({
46
+ show,
47
+ slug,
48
+ isContext,
49
+ onClose,
50
+ }: SaveModalProps) {
51
+ const [stage, setStage] = useState<SaveStage>('PREPARING');
52
+ const [progress, setProgress] = useState(0);
53
+ const [stageProgress, setStageProgress] = useState<SaveStageProgress>({
54
+ currentStep: 0,
55
+ totalSteps: 0,
56
+ });
57
+ const [error, setError] = useState<string | null>(null);
58
+ const [showDebug, setShowDebug] = useState(false);
59
+ const [debugMessages, setDebugMessages] = useState<string[]>([]);
60
+ const isSaving = useRef(false);
61
+ const [isNavigating, setIsNavigating] = useState(false);
62
+
63
+ // Determine if we're in create mode
64
+ const isCreateMode = slug === 'create';
65
+
66
+ const contentMap = fullContentMapStore.get();
67
+
68
+ // Get backend URL
69
+ const goBackend =
70
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
71
+ const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
72
+
73
+ const addDebugMessage = (message: string) => {
74
+ const timestamp = new Date().toLocaleTimeString();
75
+ setDebugMessages((prev) => [...prev, `${timestamp}: ${message}`]);
76
+ };
77
+
78
+ // Main save process
79
+ useEffect(() => {
80
+ // Reset state when modal is hidden or if save is already running
81
+ if (!show) {
82
+ setStage('PREPARING');
83
+ setProgress(0);
84
+ setDebugMessages([]);
85
+ setShowDebug(false);
86
+ isSaving.current = false;
87
+ return;
88
+ }
89
+
90
+ if (isSaving.current) return;
91
+
92
+ const runSaveProcess = async () => {
93
+ isSaving.current = true;
94
+ const ctx = getCtx();
95
+ const allDirtyNodes = ctx.getDirtyNodes();
96
+
97
+ try {
98
+ setStage('PREPARING');
99
+ setProgress(5);
100
+ addDebugMessage(
101
+ `Starting save process... (${isContext ? 'Context' : 'StoryFragment'} mode, ${isCreateMode ? 'CREATE' : 'UPDATE'})`
102
+ );
103
+
104
+ // Filter nodes based on context mode
105
+ let dirtyPanes = allDirtyNodes.filter(
106
+ (node) => node.nodeType === 'Pane'
107
+ );
108
+ let dirtyStoryFragments = allDirtyNodes.filter(
109
+ (node) => node.nodeType === 'StoryFragment'
110
+ );
111
+
112
+ // In context mode, we only care about panes, not story fragments
113
+ if (isContext) {
114
+ dirtyStoryFragments = [];
115
+ addDebugMessage('Context mode: Ignoring StoryFragment nodes');
116
+ }
117
+
118
+ const nodesWithPendingFiles = allDirtyNodes.filter(
119
+ (node): node is BaseNode & { base64Data?: string } =>
120
+ 'base64Data' in node && !!node.base64Data
121
+ );
122
+
123
+ // Check for story fragments with pending OG image operations
124
+ const storyFragmentsWithPendingImages = dirtyStoryFragments.filter(
125
+ (fragment) => {
126
+ const pendingOp = getPendingImageOperation(fragment.id);
127
+ return pendingOp && pendingOp.type === 'upload';
128
+ }
129
+ );
130
+
131
+ const relevantNodeCount =
132
+ dirtyPanes.length + dirtyStoryFragments.length;
133
+ addDebugMessage(
134
+ `Found ${relevantNodeCount} relevant dirty nodes to save (${dirtyPanes.length} Panes, ${dirtyStoryFragments.length} StoryFragments)`
135
+ );
136
+ addDebugMessage(
137
+ `Found ${storyFragmentsWithPendingImages.length} story fragments with pending OG image operations`
138
+ );
139
+ addDebugMessage(
140
+ `Found ${nodesWithPendingFiles.length} nodes with pending file uploads`
141
+ );
142
+
143
+ if (
144
+ relevantNodeCount === 0 &&
145
+ nodesWithPendingFiles.length === 0 &&
146
+ storyFragmentsWithPendingImages.length === 0
147
+ ) {
148
+ addDebugMessage('No changes to save');
149
+ setStage('COMPLETED');
150
+ setProgress(100);
151
+ return;
152
+ }
153
+
154
+ const totalSteps =
155
+ nodesWithPendingFiles.length +
156
+ storyFragmentsWithPendingImages.length +
157
+ dirtyPanes.length +
158
+ dirtyStoryFragments.length +
159
+ 2; // +1 for file linking, +1 for styles
160
+
161
+ addDebugMessage(
162
+ `Save plan: ${nodesWithPendingFiles.length} files, ${storyFragmentsWithPendingImages.length} og images, ${dirtyPanes.length} panes, ${dirtyStoryFragments.length} story fragments, 1 file linking, 1 styles = ${totalSteps} total steps`
163
+ );
164
+
165
+ let completedSteps = 1;
166
+
167
+ // PHASE 1: Upload all pending files and OG images first
168
+ const uploadedOGPaths: Record<string, string> = {};
169
+
170
+ // Handle pending files
171
+ if (nodesWithPendingFiles.length > 0) {
172
+ setStage('SAVING_PENDING_FILES');
173
+ setStageProgress({
174
+ currentStep: 0,
175
+ totalSteps: nodesWithPendingFiles.length,
176
+ });
177
+
178
+ for (let i = 0; i < nodesWithPendingFiles.length; i++) {
179
+ const fileNode = nodesWithPendingFiles[i];
180
+ const endpoint = `${goBackend}/api/v1/nodes/files/create`;
181
+ addDebugMessage(
182
+ `Processing file ${i + 1}/${nodesWithPendingFiles.length}: ${fileNode.id} -> POST ${endpoint}`
183
+ );
184
+
185
+ try {
186
+ const response = await fetch(endpoint, {
187
+ method: 'POST',
188
+ headers: {
189
+ 'Content-Type': 'application/json',
190
+ 'X-Tenant-ID': tenantId,
191
+ },
192
+ credentials: 'include',
193
+ body: JSON.stringify({ base64Data: fileNode.base64Data }), // FIXED: only send base64Data
194
+ });
195
+
196
+ if (!response.ok) {
197
+ throw new Error(`HTTP error! status: ${response.status}`);
198
+ }
199
+
200
+ const result = await response.json();
201
+
202
+ // Update tree with response data - handle different node types properly
203
+ const updatedNode = { ...fileNode, isChanged: true };
204
+
205
+ // Remove base64Data and add file properties
206
+ if ('base64Data' in updatedNode) {
207
+ delete updatedNode.base64Data;
208
+ }
209
+
210
+ // Add file properties - these properties already exist in FlatNode and BgImageNode types
211
+ if ('fileId' in updatedNode) {
212
+ updatedNode.fileId = result.fileId;
213
+ }
214
+ if ('src' in updatedNode) {
215
+ updatedNode.src = result.src;
216
+ }
217
+ if ('srcSet' in updatedNode && result.srcSet) {
218
+ updatedNode.srcSet = result.srcSet;
219
+ }
220
+
221
+ ctx.modifyNodes([updatedNode]);
222
+
223
+ addDebugMessage(
224
+ `File ${fileNode.id} uploaded successfully - got fileId: ${result.fileId}`
225
+ );
226
+ } catch (error) {
227
+ const errorMsg =
228
+ error instanceof Error ? error.message : 'Unknown error';
229
+ addDebugMessage(`File ${fileNode.id} upload failed: ${errorMsg}`);
230
+ throw new Error(
231
+ `Failed to upload file ${fileNode.id}: ${errorMsg}`
232
+ );
233
+ }
234
+
235
+ setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
236
+ completedSteps++;
237
+ setProgress((completedSteps / totalSteps) * 80);
238
+ }
239
+ }
240
+
241
+ // Handle OG image uploads
242
+ if (storyFragmentsWithPendingImages.length > 0) {
243
+ setStage('PROCESSING_OG_IMAGES');
244
+ setStageProgress({
245
+ currentStep: 0,
246
+ totalSteps: storyFragmentsWithPendingImages.length,
247
+ });
248
+ for (let i = 0; i < storyFragmentsWithPendingImages.length; i++) {
249
+ const fragment = storyFragmentsWithPendingImages[i];
250
+ const pendingOp = getPendingImageOperation(fragment.id);
251
+
252
+ if (pendingOp && pendingOp.type === 'upload' && pendingOp.data) {
253
+ const ogUploadEndpoint = `${goBackend}/api/v1/nodes/images/og`;
254
+ addDebugMessage(
255
+ `Processing OG image ${i + 1}/${storyFragmentsWithPendingImages.length}: ${fragment.id} -> POST ${ogUploadEndpoint}`
256
+ );
257
+
258
+ const uploadPayload = {
259
+ data: pendingOp.data,
260
+ filename:
261
+ pendingOp.filename || `${fragment.id}-${Date.now()}.png`,
262
+ };
263
+
264
+ try {
265
+ const response = await fetch(ogUploadEndpoint, {
266
+ method: 'POST',
267
+ headers: {
268
+ 'Content-Type': 'application/json',
269
+ 'X-Tenant-ID': tenantId,
270
+ },
271
+ credentials: 'include',
272
+ body: JSON.stringify(uploadPayload),
273
+ });
274
+
275
+ if (!response.ok) {
276
+ throw new Error(`HTTP error! status: ${response.status}`);
277
+ }
278
+
279
+ const result = await response.json();
280
+
281
+ uploadedOGPaths[fragment.id] = result.path;
282
+ addDebugMessage(
283
+ `OG image uploaded successfully: ${result.path}`
284
+ );
285
+ } catch (error) {
286
+ const errorMsg =
287
+ error instanceof Error ? error.message : 'Unknown error';
288
+ addDebugMessage(
289
+ `OG image upload failed for ${fragment.id}: ${errorMsg}`
290
+ );
291
+ throw new Error(
292
+ `Failed to upload OG image for ${fragment.id}: ${errorMsg}`
293
+ );
294
+ }
295
+ }
296
+
297
+ setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
298
+ completedSteps++;
299
+ setProgress((completedSteps / totalSteps) * 80);
300
+ }
301
+ }
302
+
303
+ // Handle panes
304
+ if (dirtyPanes.length > 0) {
305
+ setStage('SAVING_PANES');
306
+ setStageProgress({
307
+ currentStep: 0,
308
+ totalSteps: dirtyPanes.length,
309
+ });
310
+ for (let i = 0; i < dirtyPanes.length; i++) {
311
+ const paneNode = dirtyPanes[i];
312
+
313
+ try {
314
+ const payload = transformLivePaneForSave(
315
+ ctx,
316
+ paneNode.id,
317
+ isContext
318
+ );
319
+
320
+ // Check if this pane exists or is new
321
+ const paneExistsInBackend = contentMap.some(
322
+ (item) => item.type === 'Pane' && item.id === paneNode.id
323
+ );
324
+ const isCreatePaneMode = !paneExistsInBackend;
325
+ const endpoint = isCreatePaneMode
326
+ ? `${goBackend}/api/v1/nodes/panes/create`
327
+ : `${goBackend}/api/v1/nodes/panes/${payload.id}`;
328
+ const method = isCreatePaneMode ? 'POST' : 'PUT';
329
+
330
+ addDebugMessage(
331
+ `Processing pane ${i + 1}/${dirtyPanes.length}: ${paneNode.id} -> ${method} ${endpoint}`
332
+ );
333
+
334
+ const response = await fetch(endpoint, {
335
+ method,
336
+ headers: {
337
+ 'Content-Type': 'application/json',
338
+ 'X-Tenant-ID': tenantId,
339
+ },
340
+ credentials: 'include',
341
+ body: JSON.stringify(payload),
342
+ });
343
+
344
+ if (!response.ok) {
345
+ throw new Error(`HTTP error! status: ${response.status}`);
346
+ }
347
+
348
+ //const result =
349
+ await response.json();
350
+ addDebugMessage(`Pane ${paneNode.id} saved successfully`);
351
+ } catch (etlError) {
352
+ const errorMsg =
353
+ etlError instanceof Error ? etlError.message : 'Unknown error';
354
+ addDebugMessage(`Pane ${paneNode.id} ETL failed: ${errorMsg}`);
355
+ throw new Error(
356
+ `Failed to save pane ${paneNode.id}: ${errorMsg}`
357
+ );
358
+ }
359
+
360
+ setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
361
+ completedSteps++;
362
+ setProgress((completedSteps / totalSteps) * 80);
363
+ }
364
+ }
365
+
366
+ // Handle story fragments
367
+ if (!isContext && dirtyStoryFragments.length > 0) {
368
+ setStage('SAVING_STORY_FRAGMENTS');
369
+ setStageProgress({
370
+ currentStep: 0,
371
+ totalSteps: dirtyStoryFragments.length,
372
+ });
373
+ for (let i = 0; i < dirtyStoryFragments.length; i++) {
374
+ const fragment = dirtyStoryFragments[i];
375
+
376
+ try {
377
+ const payload = await transformStoryFragmentForSave(
378
+ ctx,
379
+ fragment.id,
380
+ window.TRACTSTACK_CONFIG?.tenantId || 'default'
381
+ );
382
+
383
+ // If we uploaded an OG image for this fragment, use that path
384
+ if (uploadedOGPaths[fragment.id]) {
385
+ payload.socialImagePath = uploadedOGPaths[fragment.id];
386
+ }
387
+
388
+ const endpoint = isCreateMode
389
+ ? `${goBackend}/api/v1/nodes/storyfragments/create`
390
+ : `${goBackend}/api/v1/nodes/storyfragments/${payload.id}/complete`;
391
+ const method = isCreateMode ? 'POST' : 'PUT';
392
+
393
+ addDebugMessage(
394
+ `Processing story fragment ${i + 1}/${dirtyStoryFragments.length}: ${fragment.id} -> ${method} ${endpoint}`
395
+ );
396
+
397
+ const response = await fetch(endpoint, {
398
+ method,
399
+ headers: {
400
+ 'Content-Type': 'application/json',
401
+ 'X-Tenant-ID': tenantId,
402
+ },
403
+ credentials: 'include',
404
+ body: JSON.stringify(payload),
405
+ });
406
+
407
+ if (!response.ok) {
408
+ throw new Error(`HTTP error! status: ${response.status}`);
409
+ }
410
+
411
+ //const result =
412
+ await response.json();
413
+ addDebugMessage(
414
+ `StoryFragment ${fragment.id} saved successfully`
415
+ );
416
+
417
+ // Clear pending image operation after successful save
418
+ if (uploadedOGPaths[fragment.id]) {
419
+ clearPendingImageOperation(fragment.id);
420
+ addDebugMessage(
421
+ `Cleared pending image operation for ${fragment.id}`
422
+ );
423
+ }
424
+ } catch (etlError) {
425
+ const errorMsg =
426
+ etlError instanceof Error ? etlError.message : 'Unknown error';
427
+ addDebugMessage(
428
+ `StoryFragment ${fragment.id} ETL failed: ${errorMsg}`
429
+ );
430
+ throw new Error(
431
+ `Failed to save story fragment ${fragment.id}: ${errorMsg}`
432
+ );
433
+ }
434
+
435
+ setStageProgress((prev) => ({ ...prev, currentStep: i + 1 }));
436
+ completedSteps++;
437
+ setProgress((completedSteps / totalSteps) * 80);
438
+ }
439
+ }
440
+
441
+ // PHASE 3: Link file-pane relationships
442
+ if (dirtyPanes.length > 0) {
443
+ setStage('LINKING_FILES');
444
+ addDebugMessage('Starting file-pane relationship linking...');
445
+
446
+ // Extract pane<>file relationships from saved panes
447
+ const relationships = [];
448
+ for (const paneNode of dirtyPanes) {
449
+ const fileIds = ctx.getPaneImageFileIds(paneNode.id);
450
+ relationships.push({
451
+ paneId: paneNode.id,
452
+ fileIds: fileIds,
453
+ });
454
+ }
455
+
456
+ if (relationships.some((rel) => rel.fileIds.length > 0)) {
457
+ try {
458
+ const bulkEndpoint = `${goBackend}/api/v1/nodes/panes/files/bulk`;
459
+ addDebugMessage(
460
+ `Linking relationships: ${JSON.stringify(relationships)}`
461
+ );
462
+
463
+ const response = await fetch(bulkEndpoint, {
464
+ method: 'POST',
465
+ headers: {
466
+ 'Content-Type': 'application/json',
467
+ 'X-Tenant-ID': tenantId,
468
+ },
469
+ credentials: 'include',
470
+ body: JSON.stringify({ relationships }),
471
+ });
472
+
473
+ if (!response.ok) {
474
+ throw new Error(`HTTP error! status: ${response.status}`);
475
+ }
476
+
477
+ const result = await response.json();
478
+ addDebugMessage(
479
+ `File-pane relationships linked successfully: ${result.message}`
480
+ );
481
+ } catch (error) {
482
+ const errorMsg =
483
+ error instanceof Error ? error.message : 'Unknown error';
484
+ addDebugMessage(
485
+ `Failed to link file-pane relationships: ${errorMsg}`
486
+ );
487
+ throw new Error(
488
+ `Failed to link file-pane relationships: ${errorMsg}`
489
+ );
490
+ }
491
+ } else {
492
+ addDebugMessage('No file relationships to link');
493
+ }
494
+
495
+ completedSteps++;
496
+ setProgress((completedSteps / totalSteps) * 90);
497
+ }
498
+
499
+ // PHASE 4: Styles processing (2-step process)
500
+ setStage('PROCESSING_STYLES');
501
+ setProgress(95);
502
+ addDebugMessage(`Processing styles...`);
503
+
504
+ try {
505
+ const { dirtyPaneIds, classes: dirtyClasses } =
506
+ ctx.getDirtyNodesClassData();
507
+
508
+ if (dirtyClasses.length === 0) {
509
+ addDebugMessage(
510
+ 'No dirty classes to process, skipping Tailwind update'
511
+ );
512
+ } else {
513
+ // STEP 1: Generate CSS using Astro API
514
+ const astroEndpoint = `/api/tailwind`;
515
+ const astroPayload = { dirtyPaneIds, dirtyClasses };
516
+ const astroResponse = await fetch(astroEndpoint, {
517
+ method: 'POST',
518
+ headers: {
519
+ 'Content-Type': 'application/json',
520
+ 'X-Tenant-ID': tenantId,
521
+ },
522
+ credentials: 'include',
523
+ body: JSON.stringify(astroPayload),
524
+ });
525
+
526
+ if (!astroResponse.ok) {
527
+ throw new Error(
528
+ `CSS generation failed! status: ${astroResponse.status}`
529
+ );
530
+ }
531
+
532
+ const astroResult = await astroResponse.json();
533
+
534
+ if (!astroResult.success || !astroResult.generatedCss) {
535
+ throw new Error('CSS generation failed: no CSS returned');
536
+ }
537
+
538
+ addDebugMessage(
539
+ `CSS generated: ${astroResult.generatedCss.length} bytes for ${dirtyClasses.length} classes`
540
+ );
541
+
542
+ // STEP 2: Save CSS to Go backend
543
+ const goEndpoint = `${goBackend}/api/v1/tailwind/update`;
544
+ const goPayload = { frontendCss: astroResult.generatedCss };
545
+ const goResponse = await fetch(goEndpoint, {
546
+ method: 'POST',
547
+ headers: {
548
+ 'Content-Type': 'application/json',
549
+ 'X-Tenant-ID': tenantId,
550
+ },
551
+ credentials: 'include',
552
+ body: JSON.stringify(goPayload),
553
+ });
554
+
555
+ if (!goResponse.ok) {
556
+ throw new Error(`CSS save failed! status: ${goResponse.status}`);
557
+ }
558
+
559
+ const goResult = await goResponse.json();
560
+ addDebugMessage(
561
+ `CSS saved successfully: stylesVer ${goResult.stylesVer}`
562
+ );
563
+ }
564
+ } catch (error) {
565
+ const errorMsg =
566
+ error instanceof Error ? error.message : 'Unknown error';
567
+ addDebugMessage(`Styles processing failed: ${errorMsg}`);
568
+ throw new Error(`Failed to process styles: ${errorMsg}`);
569
+ }
570
+
571
+ // Success!
572
+ setStage('COMPLETED');
573
+ setProgress(100);
574
+ addDebugMessage('Save process completed successfully!');
575
+ } catch (err) {
576
+ setStage('ERROR');
577
+ const errorMessage =
578
+ err instanceof Error && err.message
579
+ ? err.message
580
+ : 'Unknown error occurred';
581
+ setError(errorMessage);
582
+ addDebugMessage(`Save process failed: ${errorMessage}`);
583
+ } finally {
584
+ isSaving.current = false;
585
+ }
586
+ };
587
+
588
+ runSaveProcess();
589
+ }, [show, slug, isContext, isCreateMode, goBackend, tenantId]);
590
+
591
+ const getStageDescription = () => {
592
+ const getProgressText = () =>
593
+ stageProgress.totalSteps > 0
594
+ ? ` (${stageProgress.currentStep}/${stageProgress.totalSteps})`
595
+ : '';
596
+
597
+ const modeText = isContext ? 'Context Pane' : 'Story Fragment';
598
+ const actionText = isCreateMode ? 'Creating' : 'Updating';
599
+
600
+ switch (stage) {
601
+ case 'PREPARING':
602
+ return `Preparing ${actionText.toLowerCase()} ${modeText.toLowerCase()}...`;
603
+ case 'SAVING_PENDING_FILES':
604
+ return `Uploading files...${getProgressText()}`;
605
+ case 'PROCESSING_OG_IMAGES':
606
+ return `Processing OG images...${getProgressText()}`;
607
+ case 'SAVING_PANES':
608
+ return `${actionText} pane content...${getProgressText()}`;
609
+ case 'SAVING_STORY_FRAGMENTS':
610
+ return `${actionText} story fragments...${getProgressText()}`;
611
+ case 'LINKING_FILES':
612
+ return 'Linking file relationships...';
613
+ case 'PROCESSING_STYLES':
614
+ return 'Processing styles...';
615
+ case 'COMPLETED':
616
+ return `${actionText} ${modeText.toLowerCase()} completed successfully!`;
617
+ case 'ERROR':
618
+ return `Error: ${error}`;
619
+ default:
620
+ return '';
621
+ }
622
+ };
623
+
624
+ const handleOpenChange = (details: { open: boolean }) => {
625
+ if (!details.open && (stage === 'COMPLETED' || stage === 'ERROR')) {
626
+ onClose();
627
+ }
628
+ };
629
+
630
+ const handleSuccessClose = async () => {
631
+ if (stage === 'COMPLETED') {
632
+ startLoadingAnimation();
633
+ setIsNavigating(true);
634
+
635
+ if (isCreateMode) {
636
+ let actualSlug: string;
637
+
638
+ if (isContext) {
639
+ // For context mode, get slug from the saved pane
640
+ const ctx = getCtx();
641
+ const allDirtyNodes = ctx.getDirtyNodes();
642
+ const dirtyPanes = allDirtyNodes.filter(
643
+ (node): node is PaneNode => node.nodeType === 'Pane'
644
+ );
645
+ actualSlug = dirtyPanes[0].slug;
646
+ } else {
647
+ // For storyfragment mode, get slug from the saved storyfragment
648
+ const ctx = getCtx();
649
+ const allDirtyNodes = ctx.getDirtyNodes();
650
+ const dirtyStoryFragments = allDirtyNodes.filter(
651
+ (node): node is StoryFragmentNode =>
652
+ node.nodeType === 'StoryFragment'
653
+ );
654
+ actualSlug = dirtyStoryFragments[0].slug;
655
+ }
656
+
657
+ const editUrl = isContext
658
+ ? `/context/${actualSlug}/edit`
659
+ : `/${actualSlug}/edit`;
660
+ await navigate(editUrl);
661
+ } else {
662
+ const currentUrl = isContext
663
+ ? `/context/${slug}/edit`
664
+ : `/${slug}/edit`;
665
+ await navigate(currentUrl);
666
+ }
667
+ }
668
+ };
669
+
670
+ const visitPageUrl = (() => {
671
+ startLoadingAnimation();
672
+ const ctx = getCtx();
673
+ const allDirtyNodes = ctx.getDirtyNodes();
674
+
675
+ if (isContext) {
676
+ const dirtyPanes = allDirtyNodes.filter(
677
+ (node): node is PaneNode => node.nodeType === 'Pane'
678
+ );
679
+ const currentSlug = dirtyPanes[0]?.slug || slug;
680
+ return `/context/${currentSlug}`;
681
+ } else {
682
+ const dirtyStoryFragments = allDirtyNodes.filter(
683
+ (node): node is StoryFragmentNode => node.nodeType === 'StoryFragment'
684
+ );
685
+ const currentSlug = dirtyStoryFragments[0]?.slug || slug;
686
+ return `/${currentSlug}`;
687
+ }
688
+ })();
689
+
690
+ return (
691
+ <Dialog.Root
692
+ open={show}
693
+ onOpenChange={handleOpenChange}
694
+ modal={true}
695
+ preventScroll={true}
696
+ >
697
+ <Portal>
698
+ <Dialog.Backdrop
699
+ className="fixed inset-0 bg-black bg-opacity-75"
700
+ style={{ zIndex: 9005 }}
701
+ />
702
+ <Dialog.Positioner
703
+ className="fixed inset-0 flex items-center justify-center p-4"
704
+ style={{ zIndex: 9005 }}
705
+ >
706
+ <Dialog.Content
707
+ className="w-full max-w-2xl overflow-hidden rounded-lg bg-white shadow-xl"
708
+ style={{ maxHeight: '90vh' }}
709
+ >
710
+ <div className="border-b border-gray-200 px-6 py-4">
711
+ <div className="flex items-center justify-between">
712
+ <Dialog.Title className="text-xl font-bold text-gray-900">
713
+ {isCreateMode ? 'Creating' : 'Saving'}{' '}
714
+ {isContext ? 'Context Pane' : 'Story Fragment'}
715
+ </Dialog.Title>
716
+ <button
717
+ onClick={() => setShowDebug(!showDebug)}
718
+ className="text-sm text-gray-500 hover:text-gray-700"
719
+ >
720
+ {showDebug ? 'Hide Debug' : 'Show Debug'}
721
+ </button>
722
+ </div>
723
+ </div>
724
+
725
+ <div className="p-6">
726
+ <div className="mb-4">
727
+ <div className="mb-2 flex items-center justify-between">
728
+ <span className="text-sm text-gray-700">
729
+ {getStageDescription()}
730
+ </span>
731
+ {stage !== 'ERROR' && (
732
+ <span className="text-sm text-gray-500">
733
+ {Math.round(progress)}%
734
+ </span>
735
+ )}
736
+ </div>
737
+ <div className="h-2 w-full rounded-full bg-gray-200">
738
+ <div
739
+ className={`h-2 rounded-full transition-all duration-300 ${
740
+ stage === 'ERROR' ? 'bg-red-500' : 'bg-green-500'
741
+ }`}
742
+ style={{ width: `${progress}%` }}
743
+ />
744
+ </div>
745
+ </div>
746
+
747
+ {showDebug && (
748
+ <div className="mb-4 max-h-40 overflow-y-auto rounded border bg-gray-50 p-3">
749
+ <div className="text-xs text-gray-600">
750
+ {debugMessages.map((message, index) => (
751
+ <div key={index} className="mb-1">
752
+ {message}
753
+ </div>
754
+ ))}
755
+ </div>
756
+ </div>
757
+ )}
758
+
759
+ {stage === 'COMPLETED' && (
760
+ <div className="mb-4 rounded bg-green-50 p-3 text-green-800">
761
+ Save completed successfully!
762
+ </div>
763
+ )}
764
+
765
+ {stage === 'ERROR' && (
766
+ <div className="mb-4 rounded bg-red-50 p-3 text-red-800">
767
+ <div className="font-medium">Save failed</div>
768
+ <div className="mt-1 text-sm">{error}</div>
769
+ </div>
770
+ )}
771
+
772
+ {(stage === 'COMPLETED' || stage === 'ERROR') && (
773
+ <div className="flex justify-end gap-2">
774
+ {stage === 'COMPLETED' && (
775
+ <>
776
+ <a
777
+ href={visitPageUrl}
778
+ className={`rounded bg-cyan-600 px-4 py-2 text-white transition-colors hover:bg-cyan-700`}
779
+ >
780
+ Visit Page
781
+ </a>
782
+ <button
783
+ onClick={handleSuccessClose}
784
+ disabled={isNavigating}
785
+ className={`rounded px-4 py-2 text-white transition-colors ${
786
+ isNavigating
787
+ ? 'cursor-not-allowed bg-gray-400'
788
+ : 'bg-gray-600 hover:bg-gray-700'
789
+ }`}
790
+ >
791
+ Keep Editing
792
+ </button>
793
+ </>
794
+ )}
795
+ {stage === 'ERROR' && (
796
+ <button
797
+ onClick={onClose}
798
+ className="rounded bg-gray-600 px-4 py-2 text-white transition-colors hover:bg-gray-700"
799
+ >
800
+ Close
801
+ </button>
802
+ )}
803
+ </div>
804
+ )}
805
+ </div>
806
+ </Dialog.Content>
807
+ </Dialog.Positioner>
808
+ </Portal>
809
+ </Dialog.Root>
810
+ );
811
+ }