@drawnagency/primitives 0.1.113 → 0.1.115

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 (534) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/cookies.d.ts +39 -0
  3. package/dist/auth/cookies.d.ts.map +1 -1
  4. package/dist/auth/index.js +3 -3
  5. package/dist/chunk-4WQ36ZP7.js +83 -0
  6. package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
  7. package/dist/chunk-5GFACGSN.js +6 -0
  8. package/dist/{chunk-AHALSCQH.js → chunk-67P34WNW.js} +76 -131
  9. package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
  10. package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
  11. package/dist/chunk-APIJPTFG.js +64 -0
  12. package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
  13. package/dist/chunk-CDBURDPX.js +90 -0
  14. package/dist/chunk-DFKTF3CH.js +0 -0
  15. package/dist/chunk-DIMDRBFK.js +11 -0
  16. package/dist/chunk-DJABADOO.js +131 -0
  17. package/dist/{chunk-NHN2LSIU.js → chunk-DJOGPSRU.js} +31 -68
  18. package/dist/{chunk-KR2MHTNJ.js → chunk-FHTKSELX.js} +610 -62
  19. package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
  20. package/dist/chunk-GNF6KFQ7.js +18 -0
  21. package/dist/{chunk-FRE2CCLV.js → chunk-HHNPSKPT.js} +5 -79
  22. package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
  23. package/dist/chunk-JR24U7FJ.js +120 -0
  24. package/dist/{chunk-BDFTMGCL.js → chunk-JXATNKLV.js} +11 -10
  25. package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
  26. package/dist/{chunk-6BZ2KNTP.js → chunk-NX7TFHA4.js} +195 -65
  27. package/dist/{chunk-VWWY5TL5.js → chunk-O37AV27P.js} +14 -75
  28. package/dist/chunk-RIITGTV3.js +275 -0
  29. package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
  30. package/dist/chunk-S7QRHUWO.js +8 -0
  31. package/dist/{chunk-Z7W3DQTR.js → chunk-T26HAKW3.js} +424 -42
  32. package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
  33. package/dist/{chunk-GPPVYLEI.js → chunk-UUUSJDIG.js} +1 -1
  34. package/dist/chunk-VY3IFGE3.js +70 -0
  35. package/dist/chunk-WYOFQHKI.js +176 -0
  36. package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
  37. package/dist/{chunk-DS4GCYV2.js → chunk-YV3YSZEB.js} +223 -16
  38. package/dist/collab/rebase.d.ts +2 -1
  39. package/dist/collab/rebase.d.ts.map +1 -1
  40. package/dist/collab/rebase.js +24 -9
  41. package/dist/components/brandguide/ColorsEditable.d.ts.map +1 -1
  42. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  43. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  44. package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
  45. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
  46. package/dist/components/editor/ConflictDialog.d.ts.map +1 -1
  47. package/dist/components/editor/DragHandle.d.ts +12 -2
  48. package/dist/components/editor/DragHandle.d.ts.map +1 -1
  49. package/dist/components/editor/DropEdgeIndicator.d.ts +15 -6
  50. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
  51. package/dist/components/editor/NavGroupRow.d.ts +4 -1
  52. package/dist/components/editor/NavGroupRow.d.ts.map +1 -1
  53. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  54. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  55. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  56. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  57. package/dist/components/editor/TagIndicator.d.ts +15 -0
  58. package/dist/components/editor/TagIndicator.d.ts.map +1 -0
  59. package/dist/components/editor/index.js +150 -79
  60. package/dist/components/primitives/DocumentDropZone.d.ts +1 -1
  61. package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
  62. package/dist/components/primitives/EditableList.d.ts.map +1 -1
  63. package/dist/components/primitives/HeadingSection.d.ts +4 -1
  64. package/dist/components/primitives/HeadingSection.d.ts.map +1 -1
  65. package/dist/components/primitives/ResolvedMedia.d.ts +5 -1
  66. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  67. package/dist/components/primitives/{defineHeadingSection.d.ts → define-heading-section.d.ts} +1 -1
  68. package/dist/components/primitives/define-heading-section.d.ts.map +1 -0
  69. package/dist/components/primitives/{gridDrop.d.ts → grid-drop.d.ts} +1 -1
  70. package/dist/components/primitives/grid-drop.d.ts.map +1 -0
  71. package/dist/components/primitives/index.d.ts +1 -1
  72. package/dist/components/primitives/index.d.ts.map +1 -1
  73. package/dist/components/primitives/index.js +22 -154
  74. package/dist/components/sections/Button/CTAButton.d.ts.map +1 -1
  75. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +2 -6
  76. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -1
  77. package/dist/components/sections/DoDontMedia/index.d.ts +8 -0
  78. package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
  79. package/dist/components/sections/IconList/IconList.d.ts +2 -1
  80. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  81. package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
  82. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  83. package/dist/components/sections/IconList/index.d.ts +1 -0
  84. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  85. package/dist/components/sections/Media/MediaBlock.d.ts +2 -1
  86. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  87. package/dist/components/sections/Media/index.d.ts +8 -0
  88. package/dist/components/sections/Media/index.d.ts.map +1 -1
  89. package/dist/components/sections/Reference/ReferenceList.d.ts +35 -0
  90. package/dist/components/sections/Reference/ReferenceList.d.ts.map +1 -0
  91. package/dist/components/sections/Reference/ReferenceSettings.d.ts +12 -0
  92. package/dist/components/sections/Reference/ReferenceSettings.d.ts.map +1 -0
  93. package/dist/components/sections/Reference/index.d.ts +9 -0
  94. package/dist/components/sections/Reference/index.d.ts.map +1 -0
  95. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts +38 -0
  96. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts.map +1 -0
  97. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts +20 -0
  98. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts.map +1 -0
  99. package/dist/components/sections/SectionSummary/index.d.ts +33 -0
  100. package/dist/components/sections/SectionSummary/index.d.ts.map +1 -0
  101. package/dist/components/sections/all-sections.d.ts +53 -0
  102. package/dist/components/sections/all-sections.d.ts.map +1 -1
  103. package/dist/components/sections/brand-guide.d.ts +41 -2
  104. package/dist/components/sections/brand-guide.d.ts.map +1 -1
  105. package/dist/components/sections/brand-guide.js +17 -10
  106. package/dist/components/sections/core-sections.d.ts +14 -0
  107. package/dist/components/sections/core-sections.d.ts.map +1 -1
  108. package/dist/components/sections/register-schemas.js +25 -18
  109. package/dist/components/sections/register.js +25 -18
  110. package/dist/components/shared/BrandSwatchesContext.d.ts +9 -0
  111. package/dist/components/shared/BrandSwatchesContext.d.ts.map +1 -0
  112. package/dist/components/shared/Button.d.ts +11 -3
  113. package/dist/components/shared/Button.d.ts.map +1 -1
  114. package/dist/components/shared/Checkbox.d.ts +6 -0
  115. package/dist/components/shared/Checkbox.d.ts.map +1 -1
  116. package/dist/components/shared/ConfirmDialog.d.ts +25 -0
  117. package/dist/components/shared/ConfirmDialog.d.ts.map +1 -0
  118. package/dist/components/shared/ConfirmInline.d.ts +25 -0
  119. package/dist/components/shared/ConfirmInline.d.ts.map +1 -0
  120. package/dist/components/shared/ErrorBoundary.d.ts.map +1 -1
  121. package/dist/components/shared/Fab.d.ts +11 -2
  122. package/dist/components/shared/Fab.d.ts.map +1 -1
  123. package/dist/components/shared/FabStack.d.ts +22 -0
  124. package/dist/components/shared/FabStack.d.ts.map +1 -0
  125. package/dist/components/shared/HistoryChangesModal.d.ts.map +1 -1
  126. package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
  127. package/dist/components/shared/ImageBackgroundField.d.ts +9 -0
  128. package/dist/components/shared/ImageBackgroundField.d.ts.map +1 -0
  129. package/dist/components/shared/{UploadRejectionAlert.d.ts → InlineAlert.d.ts} +4 -2
  130. package/dist/components/shared/InlineAlert.d.ts.map +1 -0
  131. package/dist/components/shared/Input.d.ts +10 -0
  132. package/dist/components/shared/Input.d.ts.map +1 -1
  133. package/dist/components/shared/Navigation.d.ts.map +1 -1
  134. package/dist/components/shared/Notice.d.ts +10 -0
  135. package/dist/components/shared/Notice.d.ts.map +1 -0
  136. package/dist/components/shared/Popover.d.ts.map +1 -1
  137. package/dist/components/shared/ReferenceSourcesContext.d.ts +25 -0
  138. package/dist/components/shared/ReferenceSourcesContext.d.ts.map +1 -0
  139. package/dist/components/shared/Select.d.ts +3 -2
  140. package/dist/components/shared/Select.d.ts.map +1 -1
  141. package/dist/components/shared/SplitButton.d.ts.map +1 -1
  142. package/dist/components/shared/SummaryAnchorsContext.d.ts +10 -0
  143. package/dist/components/shared/SummaryAnchorsContext.d.ts.map +1 -0
  144. package/dist/components/shared/Textarea.d.ts +7 -0
  145. package/dist/components/shared/Textarea.d.ts.map +1 -1
  146. package/dist/components/shared/ToolbarFrame.d.ts +17 -0
  147. package/dist/components/shared/ToolbarFrame.d.ts.map +1 -0
  148. package/dist/components/shared/copy.d.ts +13 -0
  149. package/dist/components/shared/copy.d.ts.map +1 -0
  150. package/dist/components/shared/icons.d.ts +0 -12
  151. package/dist/components/shared/icons.d.ts.map +1 -1
  152. package/dist/components/shell/AudienceDetailsPanel.d.ts +7 -1
  153. package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
  154. package/dist/components/shell/AudienceRow.d.ts.map +1 -1
  155. package/dist/components/shell/BugReportFAB.d.ts +7 -1
  156. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  157. package/dist/components/shell/CredentialList.d.ts.map +1 -1
  158. package/dist/components/shell/DocInspector.d.ts.map +1 -1
  159. package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -1
  160. package/dist/components/shell/EditorContext.d.ts +4 -0
  161. package/dist/components/shell/EditorContext.d.ts.map +1 -1
  162. package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
  163. package/dist/components/shell/EditorModal.d.ts.map +1 -1
  164. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  165. package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
  166. package/dist/components/shell/MediaFolderTile.d.ts +4 -1
  167. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
  168. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  169. package/dist/components/shell/RestoreModal.d.ts.map +1 -1
  170. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  171. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  172. package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
  173. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  174. package/dist/components/shell/SiteSettingsTags.d.ts +13 -0
  175. package/dist/components/shell/SiteSettingsTags.d.ts.map +1 -0
  176. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
  177. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
  178. package/dist/components/shell/{blockMoveDispatch.d.ts → block-move-dispatch.d.ts} +1 -1
  179. package/dist/components/shell/block-move-dispatch.d.ts.map +1 -0
  180. package/dist/components/shell/reference-sources.d.ts +22 -0
  181. package/dist/components/shell/reference-sources.d.ts.map +1 -0
  182. package/dist/deck/index.d.ts +2 -2
  183. package/dist/deck/index.d.ts.map +1 -1
  184. package/dist/deck/index.js +1 -3
  185. package/dist/deck-builder/index.js +10 -17
  186. package/dist/document/index.d.ts +2 -2
  187. package/dist/document/index.d.ts.map +1 -1
  188. package/dist/hooks/useBlockDnd.d.ts +11 -0
  189. package/dist/hooks/useBlockDnd.d.ts.map +1 -1
  190. package/dist/hooks/useBuildStatus.d.ts +1 -1
  191. package/dist/hooks/useBuildStatus.d.ts.map +1 -1
  192. package/dist/hooks/useDocumentUploads.d.ts +1 -1
  193. package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
  194. package/dist/hooks/useEditorPersistence.d.ts +5 -1
  195. package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
  196. package/dist/hooks/useEditorPublish.d.ts +6 -3
  197. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  198. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  199. package/dist/hooks/useNotice.d.ts +19 -0
  200. package/dist/hooks/useNotice.d.ts.map +1 -0
  201. package/dist/hooks/useSortableNavRow.d.ts +11 -0
  202. package/dist/hooks/useSortableNavRow.d.ts.map +1 -1
  203. package/dist/hooks/useSortableRow.d.ts +11 -0
  204. package/dist/hooks/useSortableRow.d.ts.map +1 -1
  205. package/dist/hooks/useStackedEscape.d.ts +32 -0
  206. package/dist/hooks/useStackedEscape.d.ts.map +1 -0
  207. package/dist/index.js +95 -48
  208. package/dist/lib/audience-secret.js +2 -2
  209. package/dist/lib/block-dnd.d.ts +5 -0
  210. package/dist/lib/block-dnd.d.ts.map +1 -1
  211. package/dist/lib/block-move.d.ts.map +1 -1
  212. package/dist/lib/brand-swatches.d.ts +12 -0
  213. package/dist/lib/brand-swatches.d.ts.map +1 -0
  214. package/dist/lib/container-ops.d.ts +3 -2
  215. package/dist/lib/container-ops.d.ts.map +1 -1
  216. package/dist/lib/dexie.d.ts +7 -1
  217. package/dist/lib/dexie.d.ts.map +1 -1
  218. package/dist/lib/dexie.js +12 -3
  219. package/dist/lib/env.d.ts +4 -0
  220. package/dist/lib/env.d.ts.map +1 -1
  221. package/dist/lib/env.js +5 -3
  222. package/dist/lib/events.d.ts +1 -2
  223. package/dist/lib/events.d.ts.map +1 -1
  224. package/dist/lib/image-background.d.ts +8 -0
  225. package/dist/lib/image-background.d.ts.map +1 -0
  226. package/dist/lib/image-variant-plan.d.ts +42 -0
  227. package/dist/lib/image-variant-plan.d.ts.map +1 -0
  228. package/dist/lib/image-variant-plan.js +53 -0
  229. package/dist/lib/index-ops.d.ts.map +1 -1
  230. package/dist/lib/index-ops.js +21 -17
  231. package/dist/lib/index.d.ts +7 -4
  232. package/dist/lib/index.d.ts.map +1 -1
  233. package/dist/lib/index.js +55 -26
  234. package/dist/lib/loader.d.ts +25 -12
  235. package/dist/lib/loader.d.ts.map +1 -1
  236. package/dist/lib/media-ops.d.ts +73 -0
  237. package/dist/lib/media-ops.d.ts.map +1 -0
  238. package/dist/lib/media-ops.js +60 -0
  239. package/dist/lib/nav-tree.d.ts +7 -0
  240. package/dist/lib/nav-tree.d.ts.map +1 -1
  241. package/dist/lib/nav.d.ts +38 -0
  242. package/dist/lib/nav.d.ts.map +1 -1
  243. package/dist/lib/pages.d.ts +2 -1
  244. package/dist/lib/pages.d.ts.map +1 -1
  245. package/dist/lib/platform-broker.d.ts +4 -2
  246. package/dist/lib/platform-broker.d.ts.map +1 -1
  247. package/dist/lib/platform-broker.js +21 -13
  248. package/dist/lib/reference-type.d.ts +2 -0
  249. package/dist/lib/reference-type.d.ts.map +1 -0
  250. package/dist/lib/reference-validation.d.ts +31 -0
  251. package/dist/lib/reference-validation.d.ts.map +1 -0
  252. package/dist/lib/reference-validation.js +39 -0
  253. package/dist/lib/references.d.ts +74 -0
  254. package/dist/lib/references.d.ts.map +1 -0
  255. package/dist/lib/registry.d.ts +35 -0
  256. package/dist/lib/registry.d.ts.map +1 -1
  257. package/dist/lib/{safeRedirect.d.ts → safe-redirect.d.ts} +1 -1
  258. package/dist/lib/safe-redirect.d.ts.map +1 -0
  259. package/dist/lib/sidecar-diagnostics.d.ts +20 -0
  260. package/dist/lib/sidecar-diagnostics.d.ts.map +1 -0
  261. package/dist/lib/sidecars.d.ts +118 -0
  262. package/dist/lib/sidecars.d.ts.map +1 -0
  263. package/dist/lib/sidecars.js +23 -0
  264. package/dist/lib/slug.d.ts +2 -0
  265. package/dist/lib/slug.d.ts.map +1 -0
  266. package/dist/lib/storage-tier.d.ts +17 -0
  267. package/dist/lib/storage-tier.d.ts.map +1 -0
  268. package/dist/lib/storage-tier.js +29 -0
  269. package/dist/lib/tags.d.ts +64 -0
  270. package/dist/lib/tags.d.ts.map +1 -0
  271. package/dist/lib/theme-vars.d.ts +9 -0
  272. package/dist/lib/theme-vars.d.ts.map +1 -1
  273. package/dist/media/index.d.ts +1 -1
  274. package/dist/media/index.d.ts.map +1 -1
  275. package/dist/media/index.js +18 -14
  276. package/dist/media/queue.d.ts +2 -0
  277. package/dist/media/queue.d.ts.map +1 -1
  278. package/dist/media/utils.d.ts +2 -0
  279. package/dist/media/utils.d.ts.map +1 -1
  280. package/dist/media/{videoPoster.d.ts → video-poster.d.ts} +1 -1
  281. package/dist/media/video-poster.d.ts.map +1 -0
  282. package/dist/media/worker.d.ts +1 -35
  283. package/dist/media/worker.d.ts.map +1 -1
  284. package/dist/schemas/audience.js +2 -2
  285. package/dist/schemas/index.d.ts +1 -0
  286. package/dist/schemas/index.d.ts.map +1 -1
  287. package/dist/schemas/index.js +25 -13
  288. package/dist/schemas/media.d.ts +4 -0
  289. package/dist/schemas/media.d.ts.map +1 -1
  290. package/dist/schemas/shared.d.ts +26 -0
  291. package/dist/schemas/shared.d.ts.map +1 -1
  292. package/dist/schemas/site-config.d.ts +6 -0
  293. package/dist/schemas/site-config.d.ts.map +1 -1
  294. package/dist/schemas/tags.d.ts +27 -0
  295. package/dist/schemas/tags.d.ts.map +1 -0
  296. package/dist/storage/index.js +2 -1
  297. package/dist/storage/memory.d.ts +21 -0
  298. package/dist/storage/memory.d.ts.map +1 -0
  299. package/dist/storage/memory.js +129 -0
  300. package/dist/storage/types.d.ts +9 -0
  301. package/dist/storage/types.d.ts.map +1 -1
  302. package/dist/types/database.d.ts.map +1 -1
  303. package/package.json +26 -1
  304. package/src/auth/cookies.ts +111 -0
  305. package/src/collab/rebase.ts +29 -9
  306. package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
  307. package/src/components/brandguide/ColorsEditable.tsx +1 -0
  308. package/src/components/chat/ChatPanel.tsx +2 -3
  309. package/src/components/chat/ChatWidget.tsx +2 -3
  310. package/src/components/docfile/DocFilePane.tsx +4 -12
  311. package/src/components/editor/ChildBlockWrapper.tsx +13 -19
  312. package/src/components/editor/ConflictDialog.tsx +27 -35
  313. package/src/components/editor/DeleteButton.tsx +2 -2
  314. package/src/components/editor/DragHandle.tsx +16 -3
  315. package/src/components/editor/DropEdgeIndicator.tsx +36 -10
  316. package/src/components/editor/InsertButton.tsx +2 -2
  317. package/src/components/editor/NavGroupRow.tsx +43 -14
  318. package/src/components/editor/PagesModal.tsx +99 -26
  319. package/src/components/editor/SectionWrapper.tsx +64 -23
  320. package/src/components/editor/SettingsButton.tsx +2 -2
  321. package/src/components/editor/SettingsForm.tsx +19 -2
  322. package/src/components/editor/StatusIndicator.tsx +4 -4
  323. package/src/components/editor/TagIndicator.tsx +80 -0
  324. package/src/components/primitives/DocumentDropZone.tsx +1 -1
  325. package/src/components/primitives/EditableGrid.tsx +15 -14
  326. package/src/components/primitives/EditableList.tsx +27 -74
  327. package/src/components/primitives/HeadingSection.tsx +5 -2
  328. package/src/components/primitives/ResolvedMedia.tsx +13 -0
  329. package/src/components/primitives/{defineHeadingSection.tsx → define-heading-section.tsx} +1 -0
  330. package/src/components/primitives/index.ts +1 -1
  331. package/src/components/primitives/useEditablePlainText.ts +5 -5
  332. package/src/components/sections/Button/CTAButton.tsx +3 -1
  333. package/src/components/sections/DoDontMedia/DoDontMediaBlock.tsx +4 -4
  334. package/src/components/sections/DoDontMedia/index.tsx +12 -3
  335. package/src/components/sections/IconList/DoDontToggle.tsx +2 -2
  336. package/src/components/sections/IconList/IconList.tsx +70 -37
  337. package/src/components/sections/IconList/IconListSettings.tsx +55 -17
  338. package/src/components/sections/IconList/index.tsx +10 -3
  339. package/src/components/sections/LinkHeading/index.tsx +1 -1
  340. package/src/components/sections/Media/MediaBlock.tsx +13 -2
  341. package/src/components/sections/Media/index.tsx +12 -4
  342. package/src/components/sections/Reference/ReferenceList.tsx +214 -0
  343. package/src/components/sections/Reference/ReferenceSettings.tsx +159 -0
  344. package/src/components/sections/Reference/index.tsx +47 -0
  345. package/src/components/sections/SectionSummary/SectionSummaryBlock.tsx +476 -0
  346. package/src/components/sections/SectionSummary/SectionSummarySettings.tsx +126 -0
  347. package/src/components/sections/SectionSummary/index.tsx +149 -0
  348. package/src/components/sections/SubHeading/index.tsx +1 -1
  349. package/src/components/sections/SubSubHeading/index.tsx +1 -1
  350. package/src/components/sections/brand-guide.ts +4 -3
  351. package/src/components/sections/core-sections.ts +2 -0
  352. package/src/components/shared/BrandSwatchesContext.ts +13 -0
  353. package/src/components/shared/Button.tsx +47 -34
  354. package/src/components/shared/Checkbox.tsx +21 -12
  355. package/src/components/shared/ConfirmDialog.tsx +87 -0
  356. package/src/components/shared/ConfirmInline.tsx +90 -0
  357. package/src/components/shared/ErrorBoundary.tsx +6 -18
  358. package/src/components/shared/Fab.tsx +15 -5
  359. package/src/components/shared/FabStack.tsx +28 -0
  360. package/src/components/shared/HistoryChangesModal.tsx +43 -41
  361. package/src/components/shared/HistoryPopover.tsx +7 -20
  362. package/src/components/shared/ImageBackgroundField.tsx +92 -0
  363. package/src/components/shared/{UploadRejectionAlert.tsx → InlineAlert.tsx} +21 -5
  364. package/src/components/shared/Input.tsx +23 -7
  365. package/src/components/shared/Navigation.tsx +22 -17
  366. package/src/components/shared/Notice.tsx +59 -0
  367. package/src/components/shared/Popover.tsx +7 -11
  368. package/src/components/shared/ReferenceSourcesContext.ts +27 -0
  369. package/src/components/shared/Select.tsx +5 -2
  370. package/src/components/shared/SplitButton.tsx +11 -17
  371. package/src/components/shared/SummaryAnchorsContext.ts +11 -0
  372. package/src/components/shared/Textarea.tsx +17 -2
  373. package/src/components/shared/ToolbarFrame.tsx +28 -0
  374. package/src/components/shared/VideoSourceField.tsx +1 -1
  375. package/src/components/shared/copy.ts +18 -0
  376. package/src/components/shared/icons.tsx +1 -17
  377. package/src/components/shell/AudienceAddForm.tsx +2 -2
  378. package/src/components/shell/AudienceDetailsPanel.tsx +23 -5
  379. package/src/components/shell/AudienceRow.tsx +22 -15
  380. package/src/components/shell/BugReportFAB.tsx +50 -40
  381. package/src/components/shell/CredentialList.tsx +33 -13
  382. package/src/components/shell/DocInspector.tsx +3 -12
  383. package/src/components/shell/DocumentEditorOverlay.tsx +33 -23
  384. package/src/components/shell/EditorContext.tsx +7 -0
  385. package/src/components/shell/EditorLoginForm.tsx +26 -36
  386. package/src/components/shell/EditorModal.tsx +8 -10
  387. package/src/components/shell/EditorShell.tsx +501 -338
  388. package/src/components/shell/HistoryToolbar.tsx +24 -26
  389. package/src/components/shell/MediaFolderTile.tsx +8 -3
  390. package/src/components/shell/MediaLibraryModal.tsx +59 -60
  391. package/src/components/shell/ProcessingIndicator.tsx +2 -2
  392. package/src/components/shell/RestoreModal.tsx +17 -16
  393. package/src/components/shell/SetPasswordForm.tsx +3 -3
  394. package/src/components/shell/SiteSettingsChatbot.tsx +61 -42
  395. package/src/components/shell/SiteSettingsDisplay.tsx +10 -20
  396. package/src/components/shell/SiteSettingsModal.tsx +24 -2
  397. package/src/components/shell/SiteSettingsTags.tsx +143 -0
  398. package/src/components/shell/SiteSettingsUsers.tsx +36 -26
  399. package/src/components/shell/SiteSettingsViewerAccess.tsx +2 -2
  400. package/src/components/shell/ViewerLoginForm.tsx +5 -4
  401. package/src/components/shell/reference-sources.ts +36 -0
  402. package/src/deck/index.ts +2 -2
  403. package/src/document/index.ts +2 -2
  404. package/src/hooks/useBlockDnd.ts +11 -0
  405. package/src/hooks/useBuildStatus.ts +53 -6
  406. package/src/hooks/useDocumentUploads.ts +1 -1
  407. package/src/hooks/useEditorPersistence.ts +141 -35
  408. package/src/hooks/useEditorPublish.ts +27 -31
  409. package/src/hooks/useMediaPipeline.ts +30 -24
  410. package/src/hooks/useNotice.ts +45 -0
  411. package/src/hooks/useSortableNavRow.ts +11 -0
  412. package/src/hooks/useSortableRow.ts +22 -2
  413. package/src/hooks/useStackedEscape.ts +62 -0
  414. package/src/lib/block-dnd.ts +6 -1
  415. package/src/lib/block-move.ts +3 -0
  416. package/src/lib/brand-swatches.ts +32 -0
  417. package/src/lib/container-ops.ts +5 -3
  418. package/src/lib/dexie.ts +24 -2
  419. package/src/lib/env.ts +9 -0
  420. package/src/lib/events.ts +1 -2
  421. package/src/lib/image-background.ts +31 -0
  422. package/src/lib/image-variant-plan.ts +92 -0
  423. package/src/lib/index-ops.ts +9 -18
  424. package/src/lib/index.ts +20 -4
  425. package/src/lib/loader.ts +28 -88
  426. package/src/lib/media-ops.ts +112 -0
  427. package/src/lib/nav-tree.ts +104 -4
  428. package/src/lib/nav.ts +196 -36
  429. package/src/lib/pages.ts +2 -3
  430. package/src/lib/platform-broker.ts +32 -14
  431. package/src/lib/reference-type.ts +3 -0
  432. package/src/lib/reference-validation.ts +69 -0
  433. package/src/lib/references.ts +130 -0
  434. package/src/lib/registry.ts +37 -0
  435. package/src/lib/sidecar-diagnostics.ts +29 -0
  436. package/src/lib/sidecars.ts +221 -0
  437. package/src/lib/slug.ts +6 -0
  438. package/src/lib/storage-tier.ts +38 -0
  439. package/src/lib/tags.ts +165 -0
  440. package/src/lib/theme-vars.ts +23 -0
  441. package/src/media/index.ts +1 -1
  442. package/src/media/queue.ts +3 -0
  443. package/src/media/utils.ts +19 -1
  444. package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
  445. package/src/media/worker.ts +17 -8
  446. package/src/schemas/index.ts +1 -0
  447. package/src/schemas/media.ts +7 -2
  448. package/src/schemas/shared.ts +24 -1
  449. package/src/schemas/site-config.ts +6 -1
  450. package/src/schemas/tags.ts +33 -0
  451. package/src/storage/memory.ts +178 -0
  452. package/src/storage/types.ts +16 -3
  453. package/src/types/database.ts +2 -0
  454. package/dist/chunk-DNY72JZN.js +0 -86
  455. package/dist/chunk-F2LYQPXV.js +0 -77
  456. package/dist/chunk-JH762VUH.js +0 -128
  457. package/dist/chunk-K7DYZXVU.js +0 -138
  458. package/dist/components/primitives/defineHeadingSection.d.ts.map +0 -1
  459. package/dist/components/primitives/gridDrop.d.ts.map +0 -1
  460. package/dist/components/shared/UploadRejectionAlert.d.ts.map +0 -1
  461. package/dist/components/shell/BuildStatusIndicator.d.ts +0 -7
  462. package/dist/components/shell/BuildStatusIndicator.d.ts.map +0 -1
  463. package/dist/components/shell/blockMoveDispatch.d.ts.map +0 -1
  464. package/dist/hooks/index.d.ts +0 -9
  465. package/dist/hooks/index.d.ts.map +0 -1
  466. package/dist/lib/grid.d.ts +0 -2
  467. package/dist/lib/grid.d.ts.map +0 -1
  468. package/dist/lib/safeRedirect.d.ts.map +0 -1
  469. package/dist/media/videoPoster.d.ts.map +0 -1
  470. package/src/components/brandguide/ColorSwatchSettings.d.ts.map +0 -1
  471. package/src/components/brandguide/Colors.d.ts.map +0 -1
  472. package/src/components/primitives/CustomParagraph.d.ts.map +0 -1
  473. package/src/components/primitives/EditableGrid.d.ts.map +0 -1
  474. package/src/components/primitives/EditablePlainText.d.ts.map +0 -1
  475. package/src/components/primitives/EditableRichText.d.ts.map +0 -1
  476. package/src/components/primitives/HeadingSection.d.ts.map +0 -1
  477. package/src/components/primitives/IconPicker.d.ts.map +0 -1
  478. package/src/components/primitives/LinkPopover.d.ts.map +0 -1
  479. package/src/components/primitives/MediaSettingsForms.d.ts.map +0 -1
  480. package/src/components/primitives/ResolvedMedia.d.ts.map +0 -1
  481. package/src/components/primitives/RichTextToolbar.d.ts.map +0 -1
  482. package/src/components/primitives/tiptap-presets.d.ts.map +0 -1
  483. package/src/components/primitives/useEditableCollection.d.ts.map +0 -1
  484. package/src/components/primitives/useEditablePlainText.d.ts.map +0 -1
  485. package/src/components/primitives/useEditableRichText.d.ts.map +0 -1
  486. package/src/components/sections/Button/CTAButton.d.ts.map +0 -1
  487. package/src/components/sections/Button/index.d.ts.map +0 -1
  488. package/src/components/sections/Colors/index.d.ts.map +0 -1
  489. package/src/components/sections/IconList/IconList.d.ts.map +0 -1
  490. package/src/components/sections/IconList/IconListSettings.d.ts.map +0 -1
  491. package/src/components/sections/IconList/index.d.ts.map +0 -1
  492. package/src/components/sections/LinkHeading/index.d.ts.map +0 -1
  493. package/src/components/sections/Prose/Prose.d.ts.map +0 -1
  494. package/src/components/sections/Prose/index.d.ts.map +0 -1
  495. package/src/components/sections/SubHeading/index.d.ts.map +0 -1
  496. package/src/components/sections/SubSubHeading/index.d.ts.map +0 -1
  497. package/src/components/sections/register-schemas.d.ts.map +0 -1
  498. package/src/components/shared/Button.d.ts.map +0 -1
  499. package/src/components/shared/Checkbox.d.ts.map +0 -1
  500. package/src/components/shared/FormLabel.d.ts.map +0 -1
  501. package/src/components/shared/IconButton.d.ts.map +0 -1
  502. package/src/components/shared/Input.d.ts.map +0 -1
  503. package/src/components/shared/Select.d.ts.map +0 -1
  504. package/src/components/shared/icons.d.ts.map +0 -1
  505. package/src/components/shell/BuildStatusIndicator.tsx +0 -59
  506. package/src/components/shell/MediaLibraryContext.d.ts.map +0 -1
  507. package/src/hooks/index.ts +0 -8
  508. package/src/hooks/useResolvedMedia.d.ts.map +0 -1
  509. package/src/lib/cn.d.ts.map +0 -1
  510. package/src/lib/grid.d.ts.map +0 -1
  511. package/src/lib/grid.ts +0 -7
  512. package/src/lib/icons.d.ts.map +0 -1
  513. package/src/lib/loader.d.ts.map +0 -1
  514. package/src/lib/nav.d.ts.map +0 -1
  515. package/src/lib/registry.d.ts.map +0 -1
  516. package/src/lib/sanitize.d.ts.map +0 -1
  517. package/src/media/github.d.ts.map +0 -1
  518. package/src/media/index.d.ts.map +0 -1
  519. package/src/media/queue.d.ts.map +0 -1
  520. package/src/media/resolve.d.ts.map +0 -1
  521. package/src/media/types.d.ts.map +0 -1
  522. package/src/media/utils.d.ts.map +0 -1
  523. package/src/media/videoPoster.d.ts.map +0 -1
  524. package/src/schemas/auth.d.ts.map +0 -1
  525. package/src/schemas/media-grid-options.d.ts.map +0 -1
  526. package/src/schemas/media.d.ts.map +0 -1
  527. package/src/schemas/sections.d.ts.map +0 -1
  528. package/src/schemas/shared.d.ts.map +0 -1
  529. package/src/schemas/site-config.d.ts.map +0 -1
  530. /package/dist/{chunk-B7VBSE5X.js → chunk-3JDTXOIG.js} +0 -0
  531. /package/dist/{chunk-CXZ5YJG4.js → chunk-L45X7DKX.js} +0 -0
  532. /package/src/components/primitives/{gridDrop.ts → grid-drop.ts} +0 -0
  533. /package/src/components/shell/{blockMoveDispatch.ts → block-move-dispatch.ts} +0 -0
  534. /package/src/lib/{safeRedirect.ts → safe-redirect.ts} +0 -0
@@ -1,12 +1,14 @@
1
- import { useCallback, useMemo, useRef, useEffect } from "react";
1
+ import { useCallback, useMemo, useRef, useEffect, useState } from "react";
2
2
  import type { SectionContent } from "../schemas/sections";
3
3
  import type { SiteIndex, SiteConfig } from "../schemas/site-config";
4
- import { persistAll, persistSiteConfig, deleteSectionRows } from "../lib/dexie";
4
+ import { persistAll, deleteSectionRows } from "../lib/dexie";
5
5
 
6
6
  interface PersistenceState {
7
7
  pendingSections: Map<string, SectionContent>;
8
8
  indexDirty: boolean;
9
9
  configDirty: boolean;
10
+ pendingConfig: SiteConfig | null;
11
+ pendingDeletedRows: Set<string>;
10
12
  // Durable "unsaved index structure exists" signal. Unlike indexDirty — which is
11
13
  // flush-PENDING state and self-clears ~500ms after an edit — this mirrors the
12
14
  // lifetime of the Dexie siteIndex row: set on every structural edit, NOT
@@ -43,6 +45,22 @@ interface PersistenceOptions {
43
45
  deletedSectionsRef?: React.RefObject<Set<string>>;
44
46
  }
45
47
 
48
+ const PERSIST_FAILED_COPY = "Your latest edits couldn't be stored in this browser. Keep this tab open and try saving again.";
49
+ // How long Save/Discard wait on an in-flight IndexedDB write before giving up.
50
+ // A hung transaction (Safari private mode, a quota prompt) never settles.
51
+ const FLUSH_WAIT_MS = 10_000;
52
+
53
+ /** Resolves true once `flush` settles (either way), false if it is still running after FLUSH_WAIT_MS. */
54
+ async function settlesInTime(flush: Promise<void>): Promise<boolean> {
55
+ let timer: ReturnType<typeof setTimeout> | undefined;
56
+ const timedOut = new Promise<false>((resolve) => { timer = setTimeout(() => resolve(false), FLUSH_WAIT_MS); });
57
+ try {
58
+ return await Promise.race([flush.then(() => true, () => true), timedOut]);
59
+ } finally {
60
+ clearTimeout(timer);
61
+ }
62
+ }
63
+
46
64
  export function useEditorPersistence(
47
65
  siteIndexRef: React.RefObject<SiteIndex>,
48
66
  options?: PersistenceOptions,
@@ -51,67 +69,130 @@ export function useEditorPersistence(
51
69
  pendingSections: new Map(),
52
70
  indexDirty: false,
53
71
  configDirty: false,
72
+ pendingConfig: null,
73
+ pendingDeletedRows: new Set(),
54
74
  unsavedIndex: false,
55
75
  generation: 0,
56
76
  indexGeneration: 0,
57
77
  sectionGenerations: new Map(),
58
78
  });
79
+ const [persistenceError, setPersistenceError] = useState<string | null>(null);
80
+ const activeFlush = useRef<Promise<void> | null>(null);
81
+ // Writes given up on after FLUSH_WAIT_MS; their late outcome must not touch state.
82
+ const abandonedFlushes = useRef(new WeakSet<Promise<void>>());
83
+ // Bumped by discardPending so an in-flight flush can't restore discarded edits.
84
+ const discardEpoch = useRef(0);
59
85
  const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
60
86
  const onFlushRef = useRef(options?.onFlush);
61
87
  useEffect(() => { onFlushRef.current = options?.onFlush; });
62
88
  const deletedSectionsRef = useRef(options?.deletedSectionsRef);
63
89
  useEffect(() => { deletedSectionsRef.current = options?.deletedSectionsRef; });
64
90
 
65
- const flushToDexie = useCallback(async () => {
91
+ const flushToDexie = useCallback(async (opts?: { exit?: boolean }) => {
92
+ // Saves wait for an already-running flush before reading Dexie; its failure
93
+ // restored its entries into state, so this flush retries them. A write that
94
+ // never settles is abandoned so the editor can recover without a reload.
95
+ // A tab-close flush can't wait: the unloading page may never resume it, and
96
+ // IndexedDB applies its later transaction after the in-flight one anyway.
97
+ while (!opts?.exit && activeFlush.current) {
98
+ const inFlight = activeFlush.current;
99
+ if (!(await settlesInTime(inFlight))) abandonedFlushes.current.add(inFlight);
100
+ if (activeFlush.current === inFlight) activeFlush.current = null;
101
+ }
66
102
  const s = state.current;
67
- if (s.pendingSections.size === 0 && !s.indexDirty) return;
103
+ if (s.pendingSections.size === 0 && !s.indexDirty && !s.pendingConfig && !s.pendingDeletedRows.size) return;
68
104
 
69
105
  const entries = Array.from(s.pendingSections.entries()).map(([sectionId, content]) => ({
70
106
  sectionId,
71
107
  content,
72
108
  }));
109
+ const entryGenerations = new Map(s.sectionGenerations);
73
110
  const wasIndexDirty = s.indexDirty;
111
+ const config = s.pendingConfig;
112
+ const deletedRows = s.pendingDeletedRows;
113
+ const epoch = discardEpoch.current;
74
114
 
75
115
  // Clear optimistically so new edits during the await go into a fresh set
76
116
  s.pendingSections = new Map();
77
117
  s.indexDirty = false;
118
+ s.pendingConfig = null;
119
+ s.pendingDeletedRows = new Set();
78
120
 
79
- try {
80
- // The deletion set is cumulative in shell state (cleared only on save or
81
- // discard), so every flush rewrites the whole current set — a later flush
82
- // can never narrow an earlier record.
83
- const pendingDeletions = deletedSectionsRef.current?.current;
84
- await persistAll(
85
- entries,
86
- wasIndexDirty ? siteIndexRef.current : undefined,
87
- pendingDeletions && pendingDeletions.size > 0 ? [...pendingDeletions] : undefined,
88
- );
89
- // Fire only after a successful persist — broadcasting an edit that failed
90
- // to persist would let peers diverge from what this editor can recover.
91
- onFlushRef.current?.(entries, wasIndexDirty);
92
- } catch (err) {
93
- // Restore: merge back any entries that weren't re-dirtied during the await
121
+ // Merge back what this flush took, unless Discard ran since (it would
122
+ // resurrect discarded edits) or a newer edit superseded it.
123
+ const restore = () => {
124
+ if (discardEpoch.current !== epoch) return;
94
125
  for (const { sectionId, content } of entries) {
95
- if (!s.pendingSections.has(sectionId)) {
126
+ if (!s.pendingSections.has(sectionId) && s.sectionGenerations.get(sectionId) === entryGenerations.get(sectionId)) {
96
127
  s.pendingSections.set(sectionId, content);
97
128
  }
98
129
  }
99
130
  if (wasIndexDirty) s.indexDirty = true;
100
- console.error("Failed to flush to Dexie:", err);
131
+ if (config && !s.pendingConfig) s.pendingConfig = config;
132
+ for (const id of deletedRows) s.pendingDeletedRows.add(id);
133
+ };
134
+
135
+ let pending: Promise<void>;
136
+ const write = async () => {
137
+ try {
138
+ if (deletedRows.size) await deleteSectionRows([...deletedRows]);
139
+ // The deletion set is cumulative in shell state (cleared only on save or
140
+ // discard), so every flush rewrites the whole current set — a later flush
141
+ // can never narrow an earlier record.
142
+ const pendingDeletions = deletedSectionsRef.current?.current;
143
+ await persistAll(
144
+ entries,
145
+ wasIndexDirty ? siteIndexRef.current : undefined,
146
+ pendingDeletions && pendingDeletions.size > 0 ? [...pendingDeletions] : undefined,
147
+ config ?? undefined,
148
+ );
149
+ } catch (err) {
150
+ console.error("Failed to flush to Dexie:", err);
151
+ // An abandoned write was already restored when it was given up on.
152
+ if (!abandonedFlushes.current.has(pending)) {
153
+ restore();
154
+ setPersistenceError(PERSIST_FAILED_COPY);
155
+ }
156
+ throw new Error(PERSIST_FAILED_COPY, { cause: err });
157
+ }
158
+ if (abandonedFlushes.current.has(pending)) return;
159
+ setPersistenceError(null);
160
+ // Fire only after a successful persist — broadcasting an edit that failed
161
+ // to persist would let peers diverge from what this editor can recover.
162
+ // Config-only and row-delete-only flushes have nothing to broadcast.
163
+ if (entries.length > 0 || wasIndexDirty) onFlushRef.current?.(entries, wasIndexDirty);
164
+ };
165
+ pending = write();
166
+ activeFlush.current = pending;
167
+ try {
168
+ if (!(await settlesInTime(pending))) {
169
+ abandonedFlushes.current.add(pending);
170
+ restore();
171
+ setPersistenceError(PERSIST_FAILED_COPY);
172
+ throw new Error(PERSIST_FAILED_COPY);
173
+ }
174
+ await pending;
175
+ } finally {
176
+ if (activeFlush.current === pending) activeFlush.current = null;
101
177
  }
102
178
  }, [siteIndexRef]);
103
179
 
180
+ // Background flushes surface their error in the banner; explicit saves reject.
181
+ const flushInBackground = useCallback(() => {
182
+ void flushToDexie().catch(() => {});
183
+ }, [flushToDexie]);
184
+
104
185
  const scheduleFlush = useCallback(() => {
105
186
  if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
106
- flushTimerRef.current = setTimeout(flushToDexie, 500);
107
- }, [flushToDexie]);
187
+ flushTimerRef.current = setTimeout(flushInBackground, 500);
188
+ }, [flushInBackground]);
108
189
 
109
190
  useEffect(() => {
110
191
  return () => {
111
192
  if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
112
- void flushToDexie();
193
+ flushInBackground();
113
194
  };
114
- }, [flushToDexie]);
195
+ }, [flushInBackground]);
115
196
 
116
197
  // Flush pending edits when the tab is hidden or unloaded. The unmount cleanup
117
198
  // above is fire-and-forget and does not reliably complete during real document
@@ -126,7 +207,7 @@ export function useEditorPersistence(
126
207
  clearTimeout(flushTimerRef.current);
127
208
  flushTimerRef.current = null;
128
209
  }
129
- void flushToDexie();
210
+ void flushToDexie({ exit: true }).catch(() => {});
130
211
  };
131
212
  const onVisibilityChange = () => {
132
213
  if (document.visibilityState === "hidden") flushOnExit();
@@ -165,9 +246,7 @@ export function useEditorPersistence(
165
246
  state.current.unsavedIndex = true;
166
247
  state.current.indexGeneration = ++state.current.generation;
167
248
  state.current.sectionGenerations.delete(sectionId);
168
- deleteSectionRows([sectionId]).catch((err) => {
169
- console.error("Failed to delete section row from Dexie:", err);
170
- });
249
+ state.current.pendingDeletedRows.add(sectionId);
171
250
  scheduleFlush();
172
251
  }, [scheduleFlush]);
173
252
 
@@ -190,8 +269,10 @@ export function useEditorPersistence(
190
269
 
191
270
  const persistConfig = useCallback(async (config: SiteConfig) => {
192
271
  state.current.configDirty = true;
193
- await persistSiteConfig(config);
194
- }, []);
272
+ state.current.pendingConfig = config;
273
+ // Resolves once written; a failure surfaces in the persistence banner.
274
+ await flushToDexie().catch(() => {});
275
+ }, [flushToDexie]);
195
276
 
196
277
  const cancelPendingFlush = useCallback(() => {
197
278
  if (flushTimerRef.current) {
@@ -200,6 +281,28 @@ export function useEditorPersistence(
200
281
  }
201
282
  }, []);
202
283
 
284
+ const discardPending = useCallback(async () => {
285
+ cancelPendingFlush();
286
+ discardEpoch.current++;
287
+ // Finish an in-flight write before the caller clears Dexie, or it could
288
+ // recreate the rows after Discard has removed them. A write that never
289
+ // settles is abandoned rather than hanging Discard; if it lands later it can
290
+ // still write discarded rows, which no browser API lets us cancel.
291
+ while (activeFlush.current) {
292
+ const inFlight = activeFlush.current;
293
+ if (!(await settlesInTime(inFlight))) abandonedFlushes.current.add(inFlight);
294
+ if (activeFlush.current === inFlight) activeFlush.current = null;
295
+ }
296
+ state.current.pendingSections.clear();
297
+ state.current.pendingConfig = null;
298
+ state.current.pendingDeletedRows.clear();
299
+ state.current.indexDirty = false;
300
+ state.current.configDirty = false;
301
+ state.current.unsavedIndex = false;
302
+ state.current.sectionGenerations.clear();
303
+ setPersistenceError(null);
304
+ }, [cancelPendingFlush]);
305
+
203
306
  const isConfigDirty = useCallback(() => state.current.configDirty, []);
204
307
  const clearConfigDirty = useCallback(() => { state.current.configDirty = false; }, []);
205
308
  // Reflects whether an index change is pending flush to Dexie. NOTE: this is
@@ -236,14 +339,16 @@ export function useEditorPersistence(
236
339
  [],
237
340
  );
238
341
 
239
- // Memoized, and every member above is already identity-stable, so this object
240
- // is too. A fresh literal per render changed `persistence`'s identity on every
342
+ // Memoized, and every member above is identity-stable except persistenceError,
343
+ // which changes only when a flush fails or recovers — so this object is stable
344
+ // across ordinary edits. A fresh literal per render changed `persistence`'s identity on every
241
345
  // EditorShell render — which churned onBlockMove, getEditIntent,
242
346
  // onSectionChange, onAddSection, performDeleteSection, onReorderSections and
243
347
  // all twelve page/nav handlers, and made EditorContent tear down and
244
348
  // re-register its atlaskit drag monitors on every keystroke.
245
349
  return useMemo(
246
350
  () => ({
351
+ persistenceError,
247
352
  markSectionDirty,
248
353
  markIndexDirty,
249
354
  removeSection,
@@ -252,6 +357,7 @@ export function useEditorPersistence(
252
357
  persistConfig,
253
358
  flushNow: flushToDexie,
254
359
  cancelPendingFlush,
360
+ discardPending,
255
361
  isConfigDirty,
256
362
  clearConfigDirty,
257
363
  isIndexDirty,
@@ -262,8 +368,8 @@ export function useEditorPersistence(
262
368
  sectionDirtiedAfter,
263
369
  }),
264
370
  [
265
- markSectionDirty, markIndexDirty, removeSection, dropPendingSection, hasPendingSection,
266
- persistConfig, flushToDexie, cancelPendingFlush, isConfigDirty, clearConfigDirty,
371
+ persistenceError, markSectionDirty, markIndexDirty, removeSection, dropPendingSection, hasPendingSection,
372
+ persistConfig, flushToDexie, cancelPendingFlush, discardPending, isConfigDirty, clearConfigDirty,
267
373
  isIndexDirty, hasUnsavedIndex, clearUnsavedIndex, noteUnsavedIndex, currentGeneration,
268
374
  sectionDirtiedAfter,
269
375
  ],
@@ -1,4 +1,7 @@
1
- import { useState, useCallback, useRef, useEffect } from "react";
1
+ import { useState, useCallback, useRef } from "react";
2
+ import { useNotice } from "./useNotice";
3
+ import { COPY } from "../components/shared/copy";
4
+ import type { NoticeTone } from "../components/shared/Notice";
2
5
  import type { SiteIndex, SiteConfig } from "../schemas/site-config";
3
6
  import type { SectionContent } from "../schemas/sections";
4
7
  import type { LoadedSection } from "../lib/loader";
@@ -7,6 +10,7 @@ import { getDirtySectionRows, hasLocalChanges, discardSavedChanges, cacheContent
7
10
  import { packUploads, packDocumentUploads, type MediaUploadPayload, type DocumentUploadPayload } from "../lib/save-chunks";
8
11
  import type { DocumentManifest, DocumentManifestEntry } from "../schemas/documents";
9
12
  import { assembleSavedManifest } from "../lib/media-folders";
13
+ import { imageVariantPath } from "../lib/image-variant-plan";
10
14
  import { buildCommittedBaseline } from "../collab/recovery";
11
15
 
12
16
  function blobToBase64(blob: Blob): Promise<string> {
@@ -60,6 +64,9 @@ interface PublishDeps {
60
64
  clearConfigDirty: () => void;
61
65
  siteIndexRef: React.RefObject<SiteIndex>;
62
66
  siteConfig: SiteConfig | null;
67
+ /** This session's tags read was degraded; the cache re-primes record tags
68
+ * as not loaded (see cacheContent). */
69
+ tagsDegraded: boolean;
63
70
  sections: LoadedSection[];
64
71
  deletedSectionIds?: string[];
65
72
  /** Receives the ids that were actually committed this save (so the caller can
@@ -81,7 +88,7 @@ interface PublishDeps {
81
88
  pendingMediaDeletions: string[];
82
89
  onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
83
90
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
84
- onPublishComplete?: () => void;
91
+ onPublishComplete?: (sha: string) => void;
85
92
  // The draft version this editor is based on (saved-branch head, or null when
86
93
  // no draft exists yet). Sent with saves so the server can 409 on a concurrent
87
94
  // edit instead of silently overwriting it. Read at save time (latest value).
@@ -145,6 +152,7 @@ export function useEditorPublish({
145
152
  clearConfigDirty,
146
153
  siteIndexRef,
147
154
  siteConfig,
155
+ tagsDegraded,
148
156
  sections,
149
157
  deletedSectionIds,
150
158
  onSuccess,
@@ -171,23 +179,10 @@ export function useEditorPublish({
171
179
  onDocumentsDeleted,
172
180
  }: PublishDeps) {
173
181
  const [publishAction, setPublishAction] = useState<PublishAction>("idle");
174
- const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
182
+ const { notice: publishFeedback, show: showFeedback, clear: clearPublishFeedback } = useNotice();
175
183
  const [saveProgress, setSaveProgress] = useState<string | null>(null);
176
- const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
177
184
  const inFlightRef = useRef(false);
178
185
 
179
- useEffect(() => {
180
- return () => {
181
- if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
182
- };
183
- }, []);
184
-
185
- const showFeedback = useCallback((message: string, duration: number) => {
186
- setPublishFeedback(message);
187
- if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
188
- feedbackTimerRef.current = setTimeout(() => setPublishFeedback(null), duration);
189
- }, []);
190
-
191
186
  async function gatherMediaPayload(
192
187
  pendingItems: MediaItem[],
193
188
  pendingDeletions: string[],
@@ -218,7 +213,7 @@ export function useEditorPublish({
218
213
  const ext = mimeExt[item.mimeType] ?? "bin";
219
214
  blobPayloads.push({ path: `assets/images/${item.folder}/original.${ext}`, base64 });
220
215
  } else {
221
- blobPayloads.push({ path: `assets/images/${item.folder}/${key}.webp`, base64 });
216
+ blobPayloads.push({ path: imageVariantPath(item.folder, key), base64 });
222
217
  }
223
218
  }
224
219
 
@@ -591,7 +586,7 @@ export function useEditorPublish({
591
586
  recoveredRef.current = false;
592
587
  editTokenRef.current = getEditGeneration?.() ?? Number.MAX_SAFE_INTEGER;
593
588
  setPublishAction("saving");
594
- setPublishFeedback(null);
589
+ clearPublishFeedback();
595
590
  return true;
596
591
  }
597
592
 
@@ -675,12 +670,13 @@ export function useEditorPublish({
675
670
  // M1 honesty: only claim the view was updated when recovery actually
676
671
  // rebased it this invocation (i.e. the RETRY 409'd). A failed/absent/
677
672
  // skipped recovery left the view untouched — say so.
673
+ const tone: NoticeTone = isConflict && recoveredRef.current ? "warning" : "error";
678
674
  const message = isConflict
679
675
  ? recoveredRef.current
680
676
  ? "Someone else saved while you were editing — your view has been updated; try saving again."
681
677
  : "This site was changed elsewhere — reload to get the latest, then re-apply your edits."
682
678
  : error instanceof Error && error.message ? error.message : `${failLabel} failed`;
683
- showFeedback(message, isConflict ? 8000 : 6000);
679
+ showFeedback(tone, message, { ttlMs: isConflict ? 8000 : 6000 });
684
680
  }
685
681
 
686
682
  const handleSave = useCallback(async () => {
@@ -726,11 +722,11 @@ export function useEditorPublish({
726
722
  if (committedDocumentDeletions.length > 0) onDocumentsDeleted?.(committedDocumentDeletions);
727
723
  } catch (cleanupError) {
728
724
  console.error("Save succeeded but local cleanup failed:", cleanupError);
729
- showFeedback("Saved, but the local cache couldn't be updated — reload the editor.", 8000);
725
+ showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
730
726
  return;
731
727
  }
732
728
 
733
- showFeedback("Saved", 3000);
729
+ showFeedback("success", COPY.publish.saved, { ttlMs: 3000 });
734
730
  } catch (error) {
735
731
  reportSaveError(error, "Save");
736
732
  } finally {
@@ -748,7 +744,7 @@ export function useEditorPublish({
748
744
  if (inFlightRef.current) return;
749
745
  inFlightRef.current = true;
750
746
  setPublishAction("publishing");
751
- setPublishFeedback(null);
747
+ clearPublishFeedback();
752
748
 
753
749
  try {
754
750
  const response = await fetch("/api/publish", {
@@ -772,10 +768,10 @@ export function useEditorPublish({
772
768
  // restored on a later cache hit. (Save & Publish already re-caches instead.)
773
769
  await clearContentCache();
774
770
  onShasUpdated(null, sha);
775
- onPublishComplete?.();
771
+ onPublishComplete?.(sha);
776
772
  } catch (error) {
777
773
  console.error("Publish failed:", error);
778
- showFeedback("Publish failed", 5000);
774
+ showFeedback("error", COPY.publish.publishFailed, { ttlMs: 5000 });
779
775
  } finally {
780
776
  inFlightRef.current = false;
781
777
  setPublishAction("idle");
@@ -824,7 +820,7 @@ export function useEditorPublish({
824
820
  await cacheContent(saved.sha, baselineSections, saved.sentIndex, siteConfig!, {
825
821
  media: saved.committedManifest,
826
822
  documents: saved.committedDocumentManifest,
827
- });
823
+ }, tagsDegraded);
828
824
  finishSave(saved.uploadedItems, saved.savedSections.map((r) => r.sectionId));
829
825
  if (saved.committedDocumentIds.length > 0) onDocumentsPublished?.(saved.committedDocumentIds);
830
826
  if (saved.committedDocumentDeletions.length > 0) onDocumentsDeleted?.(saved.committedDocumentDeletions);
@@ -833,7 +829,7 @@ export function useEditorPublish({
833
829
  // top of unknown local state would compound it — stop and tell the
834
830
  // editor to reload.
835
831
  console.error("Save succeeded but local cleanup failed:", cleanupError);
836
- showFeedback("Saved, but the local cache couldn't be updated — reload the editor before publishing.", 8000);
832
+ showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor before publishing.", { ttlMs: 8000 });
837
833
  return;
838
834
  }
839
835
  }
@@ -866,18 +862,18 @@ export function useEditorPublish({
866
862
  await cacheContent(sha, baselineSections, sentIndex, siteConfig!, {
867
863
  media: committedManifest!,
868
864
  documents: committedDocumentManifest!,
869
- });
865
+ }, tagsDegraded);
870
866
  } catch (cleanupError) {
871
867
  console.error("Publish succeeded but the cache re-key failed:", cleanupError);
872
868
  onShasUpdated(null, sha);
873
- onPublishComplete?.();
874
- showFeedback("Published, but the local cache couldn't be updated — reload the editor.", 8000);
869
+ onPublishComplete?.(sha);
870
+ showFeedback("warning", "Published, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
875
871
  return;
876
872
  }
877
873
  }
878
874
 
879
875
  onShasUpdated(null, sha);
880
- onPublishComplete?.();
876
+ onPublishComplete?.(sha);
881
877
  } catch (error) {
882
878
  reportSaveError(error, "Publish");
883
879
  } finally {
@@ -886,7 +882,7 @@ export function useEditorPublish({
886
882
  setPublishAction("idle");
887
883
  setSaveProgress(null);
888
884
  }
889
- }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions]);
885
+ }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback, onPublishComplete, recoverConflict, saveChunkBudget, getEditGeneration, pendingDocumentDeletions, tagsDegraded]);
890
886
 
891
887
  return { publishAction, publishFeedback, saveProgress, handleSave, handlePublish, handleSaveAndPublish };
892
888
  }
@@ -1,5 +1,6 @@
1
1
  import { useState, useCallback, useEffect, useRef, useMemo } from "react";
2
- import { hashFileBuffer, sanitizeMediaName } from "../media/utils";
2
+ import { hashFileBuffer } from "../media/utils";
3
+ import { mediaIdentity, imageManifestEntry } from "../lib/image-variant-plan";
3
4
  import { ProcessingQueue, type QueueItem, type QueueEvent } from "../media/queue";
4
5
  import type { MediaManifest, MediaItem as LibraryMediaItem } from "../media/types";
5
6
  import type { SiteConfig } from "../schemas/site-config";
@@ -12,7 +13,7 @@ import {
12
13
  markPendingMediaDeleted,
13
14
  removePendingMediaDeletion,
14
15
  } from "../lib/dexie";
15
- import { generateVideoPoster } from "../media/videoPoster";
16
+ import { generateVideoPoster } from "../media/video-poster";
16
17
  import { ASSET_CACHE_CONTROL } from "../storage/types";
17
18
  import { tallyImageIds, collectImageIds, clearImageIds, applyLogoDeletion } from "../lib/image-refs";
18
19
  import { clearDocumentRefs, tallyDocumentIds } from "../lib/document-refs";
@@ -235,7 +236,16 @@ export function useMediaPipeline({
235
236
  if (event.type === "complete" && event.item.result) {
236
237
  const result = event.item.result;
237
238
  const kind = event.item.kind;
238
- const sanitizedName = sanitizeMediaName(event.item.originalName);
239
+ // A still image's manifest entry comes only from the plan the worker
240
+ // rendered. One without a plan (a stale cached worker after a deploy)
241
+ // is a failed upload, never a quiet entry with no variants.
242
+ if (kind === "image" && !result.plan) {
243
+ uploadTargetsRef.current.delete(event.item.hash);
244
+ hiddenUploadsRef.current.delete(event.item.hash);
245
+ uploadCallbacksRef.current.delete(event.item.hash);
246
+ onUploadErrorRef.current?.(`"${event.item.originalName}" couldn't be processed — reload the editor and try again.`);
247
+ return;
248
+ }
239
249
 
240
250
  const finalize = async (
241
251
  localUrls: Record<string, string>,
@@ -245,23 +255,23 @@ export function useMediaPipeline({
245
255
  ) => {
246
256
  if (destroyedRef.current) return;
247
257
  const targetFolderId = uploadTargetsRef.current.get(event.item.hash) ?? null;
258
+ // A still image's entry comes from the plan the worker rendered;
259
+ // animated and video items have no variants, only the identity.
260
+ const base: LibraryMediaItem = result.plan
261
+ ? imageManifestEntry(result.plan, result.variants, result.primaryBlob.size)
262
+ : {
263
+ ...mediaIdentity(event.item.hash, event.item.originalName),
264
+ kind,
265
+ width,
266
+ height,
267
+ mimeType: event.item.mimeType,
268
+ size: result.primaryBlob.size,
269
+ variants: [],
270
+ alt: "",
271
+ storage: "github",
272
+ };
248
273
  const item: LibraryMediaItem = {
249
- id: event.item.hash,
250
- hash: event.item.hash,
251
- kind,
252
- originalName: sanitizedName,
253
- width,
254
- height,
255
- mimeType: kind === "image" ? "image/webp" : event.item.mimeType,
256
- size: result.primaryBlob.size,
257
- folder: `${sanitizedName}-${event.item.hash}`,
258
- variants: result.variants.map((v) => ({
259
- width: v.width,
260
- height: v.height,
261
- size: v.size,
262
- })),
263
- alt: "",
264
- storage: "github",
274
+ ...base,
265
275
  ...(targetFolderId ? { folderId: targetFolderId } : {}),
266
276
  ...(hiddenUploadsRef.current.get(event.item.hash) ? { hidden: true } : {}),
267
277
  };
@@ -408,17 +418,13 @@ export function useMediaPipeline({
408
418
 
409
419
  try {
410
420
  const { posterBlob, width, height, duration } = await generateVideoPoster(file, mediaConfig.quality);
411
- const sanitizedName = sanitizeMediaName(file.name);
412
421
  const item: LibraryMediaItem = {
413
- id: hash,
414
- hash,
422
+ ...mediaIdentity(hash, file.name),
415
423
  kind: "video",
416
- originalName: sanitizedName,
417
424
  width,
418
425
  height,
419
426
  mimeType: file.type,
420
427
  size: file.size,
421
- folder: `${sanitizedName}-${hash}`,
422
428
  variants: [],
423
429
  alt: "",
424
430
  storage: "assets",
@@ -0,0 +1,45 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import type { NoticeTone } from "../components/shared/Notice";
3
+
4
+ export interface NoticeState {
5
+ tone: NoticeTone;
6
+ message: string;
7
+ }
8
+
9
+ const DEFAULT_TTL_MS = 5000;
10
+
11
+ /**
12
+ * One notice, one timer. `error` is sticky by default (no ttl) since it
13
+ * usually needs a deliberate dismiss; every other tone auto-clears.
14
+ * Call a second instance for a notice that must stay visible independently
15
+ * of another (e.g. toolbar feedback vs. a collab toast).
16
+ */
17
+ export function useNotice() {
18
+ const [notice, setNotice] = useState<NoticeState | null>(null);
19
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
20
+
21
+ useEffect(() => {
22
+ return () => {
23
+ if (timerRef.current) clearTimeout(timerRef.current);
24
+ };
25
+ }, []);
26
+
27
+ const clear = useCallback(() => {
28
+ if (timerRef.current) clearTimeout(timerRef.current);
29
+ setNotice(null);
30
+ }, []);
31
+
32
+ const show = useCallback(
33
+ (tone: NoticeTone, message: string, options?: { ttlMs?: number }) => {
34
+ if (timerRef.current) clearTimeout(timerRef.current);
35
+ setNotice({ tone, message });
36
+ const ttlMs = options?.ttlMs ?? (tone === "error" ? undefined : DEFAULT_TTL_MS);
37
+ if (ttlMs !== undefined) {
38
+ timerRef.current = setTimeout(() => setNotice(null), ttlMs);
39
+ }
40
+ },
41
+ [],
42
+ );
43
+
44
+ return { notice, show, clear };
45
+ }
@@ -15,6 +15,17 @@ interface Options {
15
15
  const DRAG_TYPE = "nav-tree-row";
16
16
 
17
17
  /**
18
+ * One of three sibling drag-and-drop position hooks, split by location type
19
+ * rather than unified behind a generic `useSortable({ axis })`: this one
20
+ * (NavLocation — the Pages modal's group-aware nav tree), `useSortableRow`
21
+ * (flat index — section-ordering lists), and `useBlockDnd` (block/container id
22
+ * + index — the section canvas, root and nested). A fourth location type
23
+ * earns its own hook on the same shape (rowRef/handleRef, lazy atlaskit
24
+ * import, closest-edge state, a ref for the latest callback) rather than a
25
+ * parameter bag bolted onto an existing one — see docs/superpowers/specs/
26
+ * 2026-09-04-editor-ui-primitives-consolidation.md § D6 for why a shared
27
+ * `useSortable` was deferred.
28
+ *
18
29
  * Closest-edge drag wiring for one Pages-modal nav row. A sibling of
19
30
  * useSortableRow that speaks NavLocation instead of a flat index; the section
20
31
  * modal keeps using the original, so this cannot regress it.