@drawnagency/primitives 0.1.113 → 0.1.114

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 (483) 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-GPPVYLEI.js → chunk-45FA7Z55.js} +1 -1
  6. package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
  7. package/dist/chunk-5GFACGSN.js +6 -0
  8. package/dist/chunk-5KSAS4VY.js +275 -0
  9. package/dist/{chunk-Z7W3DQTR.js → chunk-7SAAJVDE.js} +408 -28
  10. package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
  11. package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
  12. package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
  13. package/dist/chunk-DIMDRBFK.js +11 -0
  14. package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
  15. package/dist/{chunk-NHN2LSIU.js → chunk-IP2SEBAJ.js} +5 -1
  16. package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
  17. package/dist/chunk-JR24U7FJ.js +120 -0
  18. package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
  19. package/dist/{chunk-KR2MHTNJ.js → chunk-N3G7DOU3.js} +609 -61
  20. package/dist/chunk-OAGJBCSH.js +67 -0
  21. package/dist/{chunk-6BZ2KNTP.js → chunk-PLANHXS7.js} +191 -61
  22. package/dist/{chunk-VWWY5TL5.js → chunk-PNSCDBU7.js} +38 -56
  23. package/dist/{chunk-AHALSCQH.js → chunk-PTY2RG2K.js} +1 -1
  24. package/dist/{chunk-BDFTMGCL.js → chunk-PZPGJZRG.js} +9 -8
  25. package/dist/{chunk-FRE2CCLV.js → chunk-QHMKIODQ.js} +17 -4
  26. package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
  27. package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
  28. package/dist/chunk-VUYCRBJK.js +174 -0
  29. package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
  30. package/dist/{chunk-DS4GCYV2.js → chunk-Y5QECJX7.js} +223 -16
  31. package/dist/collab/rebase.d.ts +2 -1
  32. package/dist/collab/rebase.d.ts.map +1 -1
  33. package/dist/collab/rebase.js +20 -5
  34. package/dist/components/brandguide/ColorsEditable.d.ts.map +1 -1
  35. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  36. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  37. package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
  38. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
  39. package/dist/components/editor/ConflictDialog.d.ts.map +1 -1
  40. package/dist/components/editor/DragHandle.d.ts +12 -2
  41. package/dist/components/editor/DragHandle.d.ts.map +1 -1
  42. package/dist/components/editor/DropEdgeIndicator.d.ts +15 -6
  43. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
  44. package/dist/components/editor/NavGroupRow.d.ts +4 -1
  45. package/dist/components/editor/NavGroupRow.d.ts.map +1 -1
  46. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  47. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  48. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  49. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  50. package/dist/components/editor/TagIndicator.d.ts +15 -0
  51. package/dist/components/editor/TagIndicator.d.ts.map +1 -0
  52. package/dist/components/editor/index.js +148 -77
  53. package/dist/components/primitives/DocumentDropZone.d.ts +1 -1
  54. package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
  55. package/dist/components/primitives/EditableList.d.ts.map +1 -1
  56. package/dist/components/primitives/HeadingSection.d.ts +4 -1
  57. package/dist/components/primitives/HeadingSection.d.ts.map +1 -1
  58. package/dist/components/primitives/ResolvedMedia.d.ts +5 -1
  59. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  60. package/dist/components/primitives/{defineHeadingSection.d.ts → define-heading-section.d.ts} +1 -1
  61. package/dist/components/primitives/define-heading-section.d.ts.map +1 -0
  62. package/dist/components/primitives/{gridDrop.d.ts → grid-drop.d.ts} +1 -1
  63. package/dist/components/primitives/grid-drop.d.ts.map +1 -0
  64. package/dist/components/primitives/index.d.ts +1 -1
  65. package/dist/components/primitives/index.d.ts.map +1 -1
  66. package/dist/components/primitives/index.js +17 -154
  67. package/dist/components/sections/Button/CTAButton.d.ts.map +1 -1
  68. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +2 -6
  69. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -1
  70. package/dist/components/sections/DoDontMedia/index.d.ts +8 -0
  71. package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
  72. package/dist/components/sections/IconList/IconList.d.ts +2 -1
  73. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  74. package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
  75. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  76. package/dist/components/sections/IconList/index.d.ts +1 -0
  77. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  78. package/dist/components/sections/Media/MediaBlock.d.ts +2 -1
  79. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  80. package/dist/components/sections/Media/index.d.ts +8 -0
  81. package/dist/components/sections/Media/index.d.ts.map +1 -1
  82. package/dist/components/sections/Reference/ReferenceList.d.ts +35 -0
  83. package/dist/components/sections/Reference/ReferenceList.d.ts.map +1 -0
  84. package/dist/components/sections/Reference/ReferenceSettings.d.ts +12 -0
  85. package/dist/components/sections/Reference/ReferenceSettings.d.ts.map +1 -0
  86. package/dist/components/sections/Reference/index.d.ts +9 -0
  87. package/dist/components/sections/Reference/index.d.ts.map +1 -0
  88. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts +38 -0
  89. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts.map +1 -0
  90. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts +20 -0
  91. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts.map +1 -0
  92. package/dist/components/sections/SectionSummary/index.d.ts +33 -0
  93. package/dist/components/sections/SectionSummary/index.d.ts.map +1 -0
  94. package/dist/components/sections/all-sections.d.ts +53 -0
  95. package/dist/components/sections/all-sections.d.ts.map +1 -1
  96. package/dist/components/sections/brand-guide.d.ts +41 -2
  97. package/dist/components/sections/brand-guide.d.ts.map +1 -1
  98. package/dist/components/sections/brand-guide.js +12 -10
  99. package/dist/components/sections/core-sections.d.ts +14 -0
  100. package/dist/components/sections/core-sections.d.ts.map +1 -1
  101. package/dist/components/sections/register-schemas.js +19 -17
  102. package/dist/components/sections/register.js +19 -17
  103. package/dist/components/shared/BrandSwatchesContext.d.ts +9 -0
  104. package/dist/components/shared/BrandSwatchesContext.d.ts.map +1 -0
  105. package/dist/components/shared/Button.d.ts +11 -3
  106. package/dist/components/shared/Button.d.ts.map +1 -1
  107. package/dist/components/shared/Checkbox.d.ts +6 -0
  108. package/dist/components/shared/Checkbox.d.ts.map +1 -1
  109. package/dist/components/shared/ConfirmDialog.d.ts +25 -0
  110. package/dist/components/shared/ConfirmDialog.d.ts.map +1 -0
  111. package/dist/components/shared/ConfirmInline.d.ts +25 -0
  112. package/dist/components/shared/ConfirmInline.d.ts.map +1 -0
  113. package/dist/components/shared/ErrorBoundary.d.ts.map +1 -1
  114. package/dist/components/shared/Fab.d.ts +11 -2
  115. package/dist/components/shared/Fab.d.ts.map +1 -1
  116. package/dist/components/shared/FabStack.d.ts +22 -0
  117. package/dist/components/shared/FabStack.d.ts.map +1 -0
  118. package/dist/components/shared/HistoryChangesModal.d.ts.map +1 -1
  119. package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
  120. package/dist/components/shared/ImageBackgroundField.d.ts +9 -0
  121. package/dist/components/shared/ImageBackgroundField.d.ts.map +1 -0
  122. package/dist/components/shared/{UploadRejectionAlert.d.ts → InlineAlert.d.ts} +4 -2
  123. package/dist/components/shared/InlineAlert.d.ts.map +1 -0
  124. package/dist/components/shared/Input.d.ts +10 -0
  125. package/dist/components/shared/Input.d.ts.map +1 -1
  126. package/dist/components/shared/Navigation.d.ts.map +1 -1
  127. package/dist/components/shared/Notice.d.ts +10 -0
  128. package/dist/components/shared/Notice.d.ts.map +1 -0
  129. package/dist/components/shared/Popover.d.ts.map +1 -1
  130. package/dist/components/shared/ReferenceSourcesContext.d.ts +25 -0
  131. package/dist/components/shared/ReferenceSourcesContext.d.ts.map +1 -0
  132. package/dist/components/shared/Select.d.ts +3 -2
  133. package/dist/components/shared/Select.d.ts.map +1 -1
  134. package/dist/components/shared/SplitButton.d.ts.map +1 -1
  135. package/dist/components/shared/SummaryAnchorsContext.d.ts +10 -0
  136. package/dist/components/shared/SummaryAnchorsContext.d.ts.map +1 -0
  137. package/dist/components/shared/Textarea.d.ts +7 -0
  138. package/dist/components/shared/Textarea.d.ts.map +1 -1
  139. package/dist/components/shared/ToolbarFrame.d.ts +17 -0
  140. package/dist/components/shared/ToolbarFrame.d.ts.map +1 -0
  141. package/dist/components/shared/copy.d.ts +13 -0
  142. package/dist/components/shared/copy.d.ts.map +1 -0
  143. package/dist/components/shared/icons.d.ts +0 -12
  144. package/dist/components/shared/icons.d.ts.map +1 -1
  145. package/dist/components/shell/AudienceDetailsPanel.d.ts +7 -1
  146. package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
  147. package/dist/components/shell/AudienceRow.d.ts.map +1 -1
  148. package/dist/components/shell/BugReportFAB.d.ts +7 -1
  149. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  150. package/dist/components/shell/CredentialList.d.ts.map +1 -1
  151. package/dist/components/shell/DocInspector.d.ts.map +1 -1
  152. package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -1
  153. package/dist/components/shell/EditorContext.d.ts +4 -0
  154. package/dist/components/shell/EditorContext.d.ts.map +1 -1
  155. package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
  156. package/dist/components/shell/EditorModal.d.ts.map +1 -1
  157. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  158. package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
  159. package/dist/components/shell/MediaFolderTile.d.ts +4 -1
  160. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
  161. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  162. package/dist/components/shell/RestoreModal.d.ts.map +1 -1
  163. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  164. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  165. package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
  166. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  167. package/dist/components/shell/SiteSettingsTags.d.ts +13 -0
  168. package/dist/components/shell/SiteSettingsTags.d.ts.map +1 -0
  169. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
  170. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
  171. package/dist/components/shell/{blockMoveDispatch.d.ts → block-move-dispatch.d.ts} +1 -1
  172. package/dist/components/shell/block-move-dispatch.d.ts.map +1 -0
  173. package/dist/components/shell/reference-sources.d.ts +22 -0
  174. package/dist/components/shell/reference-sources.d.ts.map +1 -0
  175. package/dist/deck/index.d.ts +2 -2
  176. package/dist/deck/index.d.ts.map +1 -1
  177. package/dist/deck/index.js +1 -3
  178. package/dist/deck-builder/index.js +11 -18
  179. package/dist/document/index.d.ts +2 -2
  180. package/dist/document/index.d.ts.map +1 -1
  181. package/dist/hooks/useBlockDnd.d.ts +11 -0
  182. package/dist/hooks/useBlockDnd.d.ts.map +1 -1
  183. package/dist/hooks/useBuildStatus.d.ts +1 -1
  184. package/dist/hooks/useBuildStatus.d.ts.map +1 -1
  185. package/dist/hooks/useDocumentUploads.d.ts +1 -1
  186. package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
  187. package/dist/hooks/useEditorPersistence.d.ts +5 -1
  188. package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
  189. package/dist/hooks/useEditorPublish.d.ts +2 -2
  190. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  191. package/dist/hooks/useNotice.d.ts +19 -0
  192. package/dist/hooks/useNotice.d.ts.map +1 -0
  193. package/dist/hooks/useSortableNavRow.d.ts +11 -0
  194. package/dist/hooks/useSortableNavRow.d.ts.map +1 -1
  195. package/dist/hooks/useSortableRow.d.ts +11 -0
  196. package/dist/hooks/useSortableRow.d.ts.map +1 -1
  197. package/dist/hooks/useStackedEscape.d.ts +32 -0
  198. package/dist/hooks/useStackedEscape.d.ts.map +1 -0
  199. package/dist/index.js +67 -49
  200. package/dist/lib/audience-secret.js +2 -2
  201. package/dist/lib/block-dnd.d.ts +5 -0
  202. package/dist/lib/block-dnd.d.ts.map +1 -1
  203. package/dist/lib/block-move.d.ts.map +1 -1
  204. package/dist/lib/brand-swatches.d.ts +12 -0
  205. package/dist/lib/brand-swatches.d.ts.map +1 -0
  206. package/dist/lib/container-ops.d.ts +3 -2
  207. package/dist/lib/container-ops.d.ts.map +1 -1
  208. package/dist/lib/dexie.d.ts.map +1 -1
  209. package/dist/lib/dexie.js +9 -1
  210. package/dist/lib/env.d.ts +4 -0
  211. package/dist/lib/env.d.ts.map +1 -1
  212. package/dist/lib/env.js +5 -3
  213. package/dist/lib/events.d.ts +1 -2
  214. package/dist/lib/events.d.ts.map +1 -1
  215. package/dist/lib/image-background.d.ts +8 -0
  216. package/dist/lib/image-background.d.ts.map +1 -0
  217. package/dist/lib/index-ops.js +3 -3
  218. package/dist/lib/index.d.ts +6 -4
  219. package/dist/lib/index.d.ts.map +1 -1
  220. package/dist/lib/index.js +29 -21
  221. package/dist/lib/loader.d.ts +21 -7
  222. package/dist/lib/loader.d.ts.map +1 -1
  223. package/dist/lib/nav-tree.d.ts +1 -0
  224. package/dist/lib/nav-tree.d.ts.map +1 -1
  225. package/dist/lib/nav.d.ts +38 -0
  226. package/dist/lib/nav.d.ts.map +1 -1
  227. package/dist/lib/platform-broker.d.ts +4 -2
  228. package/dist/lib/platform-broker.d.ts.map +1 -1
  229. package/dist/lib/platform-broker.js +21 -13
  230. package/dist/lib/reference-type.d.ts +2 -0
  231. package/dist/lib/reference-type.d.ts.map +1 -0
  232. package/dist/lib/reference-validation.d.ts +31 -0
  233. package/dist/lib/reference-validation.d.ts.map +1 -0
  234. package/dist/lib/reference-validation.js +39 -0
  235. package/dist/lib/references.d.ts +74 -0
  236. package/dist/lib/references.d.ts.map +1 -0
  237. package/dist/lib/registry.d.ts +35 -0
  238. package/dist/lib/registry.d.ts.map +1 -1
  239. package/dist/lib/{safeRedirect.d.ts → safe-redirect.d.ts} +1 -1
  240. package/dist/lib/safe-redirect.d.ts.map +1 -0
  241. package/dist/lib/tags.d.ts +58 -0
  242. package/dist/lib/tags.d.ts.map +1 -0
  243. package/dist/lib/theme-vars.d.ts +9 -0
  244. package/dist/lib/theme-vars.d.ts.map +1 -1
  245. package/dist/media/index.d.ts +1 -1
  246. package/dist/media/index.d.ts.map +1 -1
  247. package/dist/media/index.js +8 -4
  248. package/dist/media/utils.d.ts +2 -0
  249. package/dist/media/utils.d.ts.map +1 -1
  250. package/dist/media/{videoPoster.d.ts → video-poster.d.ts} +1 -1
  251. package/dist/media/video-poster.d.ts.map +1 -0
  252. package/dist/schemas/audience.js +2 -2
  253. package/dist/schemas/index.d.ts +1 -0
  254. package/dist/schemas/index.d.ts.map +1 -1
  255. package/dist/schemas/index.js +14 -8
  256. package/dist/schemas/shared.d.ts +26 -0
  257. package/dist/schemas/shared.d.ts.map +1 -1
  258. package/dist/schemas/site-config.d.ts +6 -0
  259. package/dist/schemas/site-config.d.ts.map +1 -1
  260. package/dist/schemas/tags.d.ts +27 -0
  261. package/dist/schemas/tags.d.ts.map +1 -0
  262. package/dist/storage/index.js +1 -1
  263. package/dist/types/database.d.ts.map +1 -1
  264. package/package.json +6 -1
  265. package/src/auth/cookies.ts +111 -0
  266. package/src/collab/rebase.ts +26 -2
  267. package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
  268. package/src/components/brandguide/ColorsEditable.tsx +1 -0
  269. package/src/components/chat/ChatPanel.tsx +2 -3
  270. package/src/components/chat/ChatWidget.tsx +2 -3
  271. package/src/components/docfile/DocFilePane.tsx +4 -12
  272. package/src/components/editor/ChildBlockWrapper.tsx +13 -19
  273. package/src/components/editor/ConflictDialog.tsx +27 -35
  274. package/src/components/editor/DeleteButton.tsx +2 -2
  275. package/src/components/editor/DragHandle.tsx +16 -3
  276. package/src/components/editor/DropEdgeIndicator.tsx +36 -10
  277. package/src/components/editor/InsertButton.tsx +2 -2
  278. package/src/components/editor/NavGroupRow.tsx +43 -14
  279. package/src/components/editor/PagesModal.tsx +99 -26
  280. package/src/components/editor/SectionWrapper.tsx +64 -23
  281. package/src/components/editor/SettingsButton.tsx +2 -2
  282. package/src/components/editor/SettingsForm.tsx +19 -2
  283. package/src/components/editor/StatusIndicator.tsx +4 -4
  284. package/src/components/editor/TagIndicator.tsx +80 -0
  285. package/src/components/primitives/DocumentDropZone.tsx +1 -1
  286. package/src/components/primitives/EditableGrid.tsx +15 -14
  287. package/src/components/primitives/EditableList.tsx +27 -74
  288. package/src/components/primitives/HeadingSection.tsx +5 -2
  289. package/src/components/primitives/ResolvedMedia.tsx +13 -0
  290. package/src/components/primitives/{defineHeadingSection.tsx → define-heading-section.tsx} +1 -0
  291. package/src/components/primitives/index.ts +1 -1
  292. package/src/components/primitives/useEditablePlainText.ts +5 -5
  293. package/src/components/sections/Button/CTAButton.tsx +3 -1
  294. package/src/components/sections/DoDontMedia/DoDontMediaBlock.tsx +4 -4
  295. package/src/components/sections/DoDontMedia/index.tsx +12 -3
  296. package/src/components/sections/IconList/DoDontToggle.tsx +2 -2
  297. package/src/components/sections/IconList/IconList.tsx +70 -37
  298. package/src/components/sections/IconList/IconListSettings.tsx +55 -17
  299. package/src/components/sections/IconList/index.tsx +10 -3
  300. package/src/components/sections/LinkHeading/index.tsx +1 -1
  301. package/src/components/sections/Media/MediaBlock.tsx +13 -2
  302. package/src/components/sections/Media/index.tsx +12 -4
  303. package/src/components/sections/Reference/ReferenceList.tsx +214 -0
  304. package/src/components/sections/Reference/ReferenceSettings.tsx +159 -0
  305. package/src/components/sections/Reference/index.tsx +47 -0
  306. package/src/components/sections/SectionSummary/SectionSummaryBlock.tsx +476 -0
  307. package/src/components/sections/SectionSummary/SectionSummarySettings.tsx +126 -0
  308. package/src/components/sections/SectionSummary/index.tsx +149 -0
  309. package/src/components/sections/SubHeading/index.tsx +1 -1
  310. package/src/components/sections/SubSubHeading/index.tsx +1 -1
  311. package/src/components/sections/brand-guide.ts +4 -3
  312. package/src/components/sections/core-sections.ts +2 -0
  313. package/src/components/shared/BrandSwatchesContext.ts +13 -0
  314. package/src/components/shared/Button.tsx +47 -34
  315. package/src/components/shared/Checkbox.tsx +21 -12
  316. package/src/components/shared/ConfirmDialog.tsx +87 -0
  317. package/src/components/shared/ConfirmInline.tsx +90 -0
  318. package/src/components/shared/ErrorBoundary.tsx +6 -18
  319. package/src/components/shared/Fab.tsx +15 -5
  320. package/src/components/shared/FabStack.tsx +28 -0
  321. package/src/components/shared/HistoryChangesModal.tsx +43 -41
  322. package/src/components/shared/HistoryPopover.tsx +7 -20
  323. package/src/components/shared/ImageBackgroundField.tsx +92 -0
  324. package/src/components/shared/{UploadRejectionAlert.tsx → InlineAlert.tsx} +21 -5
  325. package/src/components/shared/Input.tsx +23 -7
  326. package/src/components/shared/Navigation.tsx +22 -17
  327. package/src/components/shared/Notice.tsx +59 -0
  328. package/src/components/shared/Popover.tsx +7 -11
  329. package/src/components/shared/ReferenceSourcesContext.ts +27 -0
  330. package/src/components/shared/Select.tsx +5 -2
  331. package/src/components/shared/SplitButton.tsx +11 -17
  332. package/src/components/shared/SummaryAnchorsContext.ts +11 -0
  333. package/src/components/shared/Textarea.tsx +17 -2
  334. package/src/components/shared/ToolbarFrame.tsx +28 -0
  335. package/src/components/shared/VideoSourceField.tsx +1 -1
  336. package/src/components/shared/copy.ts +18 -0
  337. package/src/components/shared/icons.tsx +1 -17
  338. package/src/components/shell/AudienceAddForm.tsx +2 -2
  339. package/src/components/shell/AudienceDetailsPanel.tsx +23 -5
  340. package/src/components/shell/AudienceRow.tsx +22 -15
  341. package/src/components/shell/BugReportFAB.tsx +50 -40
  342. package/src/components/shell/CredentialList.tsx +33 -13
  343. package/src/components/shell/DocInspector.tsx +3 -12
  344. package/src/components/shell/DocumentEditorOverlay.tsx +33 -23
  345. package/src/components/shell/EditorContext.tsx +7 -0
  346. package/src/components/shell/EditorLoginForm.tsx +26 -36
  347. package/src/components/shell/EditorModal.tsx +8 -10
  348. package/src/components/shell/EditorShell.tsx +499 -339
  349. package/src/components/shell/HistoryToolbar.tsx +24 -26
  350. package/src/components/shell/MediaFolderTile.tsx +8 -3
  351. package/src/components/shell/MediaLibraryModal.tsx +59 -60
  352. package/src/components/shell/ProcessingIndicator.tsx +2 -2
  353. package/src/components/shell/RestoreModal.tsx +17 -16
  354. package/src/components/shell/SetPasswordForm.tsx +3 -3
  355. package/src/components/shell/SiteSettingsChatbot.tsx +61 -42
  356. package/src/components/shell/SiteSettingsDisplay.tsx +10 -20
  357. package/src/components/shell/SiteSettingsModal.tsx +24 -2
  358. package/src/components/shell/SiteSettingsTags.tsx +143 -0
  359. package/src/components/shell/SiteSettingsUsers.tsx +36 -26
  360. package/src/components/shell/SiteSettingsViewerAccess.tsx +2 -2
  361. package/src/components/shell/ViewerLoginForm.tsx +5 -4
  362. package/src/components/shell/reference-sources.ts +36 -0
  363. package/src/deck/index.ts +2 -2
  364. package/src/document/index.ts +2 -2
  365. package/src/hooks/useBlockDnd.ts +11 -0
  366. package/src/hooks/useBuildStatus.ts +53 -6
  367. package/src/hooks/useDocumentUploads.ts +1 -1
  368. package/src/hooks/useEditorPersistence.ts +141 -35
  369. package/src/hooks/useEditorPublish.ts +18 -27
  370. package/src/hooks/useMediaPipeline.ts +1 -1
  371. package/src/hooks/useNotice.ts +45 -0
  372. package/src/hooks/useSortableNavRow.ts +11 -0
  373. package/src/hooks/useSortableRow.ts +22 -2
  374. package/src/hooks/useStackedEscape.ts +62 -0
  375. package/src/lib/block-dnd.ts +6 -1
  376. package/src/lib/block-move.ts +3 -0
  377. package/src/lib/brand-swatches.ts +32 -0
  378. package/src/lib/container-ops.ts +5 -3
  379. package/src/lib/dexie.ts +15 -1
  380. package/src/lib/env.ts +9 -0
  381. package/src/lib/events.ts +1 -2
  382. package/src/lib/image-background.ts +31 -0
  383. package/src/lib/index.ts +5 -4
  384. package/src/lib/loader.ts +54 -55
  385. package/src/lib/nav-tree.ts +71 -0
  386. package/src/lib/nav.ts +196 -36
  387. package/src/lib/platform-broker.ts +32 -14
  388. package/src/lib/reference-type.ts +3 -0
  389. package/src/lib/reference-validation.ts +69 -0
  390. package/src/lib/references.ts +130 -0
  391. package/src/lib/registry.ts +37 -0
  392. package/src/lib/tags.ts +148 -0
  393. package/src/lib/theme-vars.ts +23 -0
  394. package/src/media/index.ts +1 -1
  395. package/src/media/utils.ts +18 -0
  396. package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
  397. package/src/schemas/index.ts +1 -0
  398. package/src/schemas/shared.ts +24 -1
  399. package/src/schemas/site-config.ts +6 -1
  400. package/src/schemas/tags.ts +33 -0
  401. package/src/storage/types.ts +1 -1
  402. package/src/types/database.ts +2 -0
  403. package/dist/chunk-DNY72JZN.js +0 -86
  404. package/dist/chunk-F2LYQPXV.js +0 -77
  405. package/dist/chunk-JH762VUH.js +0 -128
  406. package/dist/chunk-K7DYZXVU.js +0 -138
  407. package/dist/components/primitives/defineHeadingSection.d.ts.map +0 -1
  408. package/dist/components/primitives/gridDrop.d.ts.map +0 -1
  409. package/dist/components/shared/UploadRejectionAlert.d.ts.map +0 -1
  410. package/dist/components/shell/BuildStatusIndicator.d.ts +0 -7
  411. package/dist/components/shell/BuildStatusIndicator.d.ts.map +0 -1
  412. package/dist/components/shell/blockMoveDispatch.d.ts.map +0 -1
  413. package/dist/hooks/index.d.ts +0 -9
  414. package/dist/hooks/index.d.ts.map +0 -1
  415. package/dist/lib/grid.d.ts +0 -2
  416. package/dist/lib/grid.d.ts.map +0 -1
  417. package/dist/lib/safeRedirect.d.ts.map +0 -1
  418. package/dist/media/videoPoster.d.ts.map +0 -1
  419. package/src/components/brandguide/ColorSwatchSettings.d.ts.map +0 -1
  420. package/src/components/brandguide/Colors.d.ts.map +0 -1
  421. package/src/components/primitives/CustomParagraph.d.ts.map +0 -1
  422. package/src/components/primitives/EditableGrid.d.ts.map +0 -1
  423. package/src/components/primitives/EditablePlainText.d.ts.map +0 -1
  424. package/src/components/primitives/EditableRichText.d.ts.map +0 -1
  425. package/src/components/primitives/HeadingSection.d.ts.map +0 -1
  426. package/src/components/primitives/IconPicker.d.ts.map +0 -1
  427. package/src/components/primitives/LinkPopover.d.ts.map +0 -1
  428. package/src/components/primitives/MediaSettingsForms.d.ts.map +0 -1
  429. package/src/components/primitives/ResolvedMedia.d.ts.map +0 -1
  430. package/src/components/primitives/RichTextToolbar.d.ts.map +0 -1
  431. package/src/components/primitives/tiptap-presets.d.ts.map +0 -1
  432. package/src/components/primitives/useEditableCollection.d.ts.map +0 -1
  433. package/src/components/primitives/useEditablePlainText.d.ts.map +0 -1
  434. package/src/components/primitives/useEditableRichText.d.ts.map +0 -1
  435. package/src/components/sections/Button/CTAButton.d.ts.map +0 -1
  436. package/src/components/sections/Button/index.d.ts.map +0 -1
  437. package/src/components/sections/Colors/index.d.ts.map +0 -1
  438. package/src/components/sections/IconList/IconList.d.ts.map +0 -1
  439. package/src/components/sections/IconList/IconListSettings.d.ts.map +0 -1
  440. package/src/components/sections/IconList/index.d.ts.map +0 -1
  441. package/src/components/sections/LinkHeading/index.d.ts.map +0 -1
  442. package/src/components/sections/Prose/Prose.d.ts.map +0 -1
  443. package/src/components/sections/Prose/index.d.ts.map +0 -1
  444. package/src/components/sections/SubHeading/index.d.ts.map +0 -1
  445. package/src/components/sections/SubSubHeading/index.d.ts.map +0 -1
  446. package/src/components/sections/register-schemas.d.ts.map +0 -1
  447. package/src/components/shared/Button.d.ts.map +0 -1
  448. package/src/components/shared/Checkbox.d.ts.map +0 -1
  449. package/src/components/shared/FormLabel.d.ts.map +0 -1
  450. package/src/components/shared/IconButton.d.ts.map +0 -1
  451. package/src/components/shared/Input.d.ts.map +0 -1
  452. package/src/components/shared/Select.d.ts.map +0 -1
  453. package/src/components/shared/icons.d.ts.map +0 -1
  454. package/src/components/shell/BuildStatusIndicator.tsx +0 -59
  455. package/src/components/shell/MediaLibraryContext.d.ts.map +0 -1
  456. package/src/hooks/index.ts +0 -8
  457. package/src/hooks/useResolvedMedia.d.ts.map +0 -1
  458. package/src/lib/cn.d.ts.map +0 -1
  459. package/src/lib/grid.d.ts.map +0 -1
  460. package/src/lib/grid.ts +0 -7
  461. package/src/lib/icons.d.ts.map +0 -1
  462. package/src/lib/loader.d.ts.map +0 -1
  463. package/src/lib/nav.d.ts.map +0 -1
  464. package/src/lib/registry.d.ts.map +0 -1
  465. package/src/lib/sanitize.d.ts.map +0 -1
  466. package/src/media/github.d.ts.map +0 -1
  467. package/src/media/index.d.ts.map +0 -1
  468. package/src/media/queue.d.ts.map +0 -1
  469. package/src/media/resolve.d.ts.map +0 -1
  470. package/src/media/types.d.ts.map +0 -1
  471. package/src/media/utils.d.ts.map +0 -1
  472. package/src/media/videoPoster.d.ts.map +0 -1
  473. package/src/schemas/auth.d.ts.map +0 -1
  474. package/src/schemas/media-grid-options.d.ts.map +0 -1
  475. package/src/schemas/media.d.ts.map +0 -1
  476. package/src/schemas/sections.d.ts.map +0 -1
  477. package/src/schemas/shared.d.ts.map +0 -1
  478. package/src/schemas/site-config.d.ts.map +0 -1
  479. /package/dist/{chunk-B7VBSE5X.js → chunk-3JDTXOIG.js} +0 -0
  480. /package/dist/{chunk-CXZ5YJG4.js → chunk-L45X7DKX.js} +0 -0
  481. /package/src/components/primitives/{gridDrop.ts → grid-drop.ts} +0 -0
  482. /package/src/components/shell/{blockMoveDispatch.ts → block-move-dispatch.ts} +0 -0
  483. /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";
@@ -81,7 +84,7 @@ interface PublishDeps {
81
84
  pendingMediaDeletions: string[];
82
85
  onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
83
86
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
84
- onPublishComplete?: () => void;
87
+ onPublishComplete?: (sha: string) => void;
85
88
  // The draft version this editor is based on (saved-branch head, or null when
86
89
  // no draft exists yet). Sent with saves so the server can 409 on a concurrent
87
90
  // edit instead of silently overwriting it. Read at save time (latest value).
@@ -171,23 +174,10 @@ export function useEditorPublish({
171
174
  onDocumentsDeleted,
172
175
  }: PublishDeps) {
173
176
  const [publishAction, setPublishAction] = useState<PublishAction>("idle");
174
- const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
177
+ const { notice: publishFeedback, show: showFeedback, clear: clearPublishFeedback } = useNotice();
175
178
  const [saveProgress, setSaveProgress] = useState<string | null>(null);
176
- const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
177
179
  const inFlightRef = useRef(false);
178
180
 
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
181
  async function gatherMediaPayload(
192
182
  pendingItems: MediaItem[],
193
183
  pendingDeletions: string[],
@@ -591,7 +581,7 @@ export function useEditorPublish({
591
581
  recoveredRef.current = false;
592
582
  editTokenRef.current = getEditGeneration?.() ?? Number.MAX_SAFE_INTEGER;
593
583
  setPublishAction("saving");
594
- setPublishFeedback(null);
584
+ clearPublishFeedback();
595
585
  return true;
596
586
  }
597
587
 
@@ -675,12 +665,13 @@ export function useEditorPublish({
675
665
  // M1 honesty: only claim the view was updated when recovery actually
676
666
  // rebased it this invocation (i.e. the RETRY 409'd). A failed/absent/
677
667
  // skipped recovery left the view untouched — say so.
668
+ const tone: NoticeTone = isConflict && recoveredRef.current ? "warning" : "error";
678
669
  const message = isConflict
679
670
  ? recoveredRef.current
680
671
  ? "Someone else saved while you were editing — your view has been updated; try saving again."
681
672
  : "This site was changed elsewhere — reload to get the latest, then re-apply your edits."
682
673
  : error instanceof Error && error.message ? error.message : `${failLabel} failed`;
683
- showFeedback(message, isConflict ? 8000 : 6000);
674
+ showFeedback(tone, message, { ttlMs: isConflict ? 8000 : 6000 });
684
675
  }
685
676
 
686
677
  const handleSave = useCallback(async () => {
@@ -726,11 +717,11 @@ export function useEditorPublish({
726
717
  if (committedDocumentDeletions.length > 0) onDocumentsDeleted?.(committedDocumentDeletions);
727
718
  } catch (cleanupError) {
728
719
  console.error("Save succeeded but local cleanup failed:", cleanupError);
729
- showFeedback("Saved, but the local cache couldn't be updated — reload the editor.", 8000);
720
+ showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
730
721
  return;
731
722
  }
732
723
 
733
- showFeedback("Saved", 3000);
724
+ showFeedback("success", COPY.publish.saved, { ttlMs: 3000 });
734
725
  } catch (error) {
735
726
  reportSaveError(error, "Save");
736
727
  } finally {
@@ -748,7 +739,7 @@ export function useEditorPublish({
748
739
  if (inFlightRef.current) return;
749
740
  inFlightRef.current = true;
750
741
  setPublishAction("publishing");
751
- setPublishFeedback(null);
742
+ clearPublishFeedback();
752
743
 
753
744
  try {
754
745
  const response = await fetch("/api/publish", {
@@ -772,10 +763,10 @@ export function useEditorPublish({
772
763
  // restored on a later cache hit. (Save & Publish already re-caches instead.)
773
764
  await clearContentCache();
774
765
  onShasUpdated(null, sha);
775
- onPublishComplete?.();
766
+ onPublishComplete?.(sha);
776
767
  } catch (error) {
777
768
  console.error("Publish failed:", error);
778
- showFeedback("Publish failed", 5000);
769
+ showFeedback("error", COPY.publish.publishFailed, { ttlMs: 5000 });
779
770
  } finally {
780
771
  inFlightRef.current = false;
781
772
  setPublishAction("idle");
@@ -833,7 +824,7 @@ export function useEditorPublish({
833
824
  // top of unknown local state would compound it — stop and tell the
834
825
  // editor to reload.
835
826
  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);
827
+ showFeedback("warning", "Saved, but the local cache couldn't be updated — reload the editor before publishing.", { ttlMs: 8000 });
837
828
  return;
838
829
  }
839
830
  }
@@ -870,14 +861,14 @@ export function useEditorPublish({
870
861
  } catch (cleanupError) {
871
862
  console.error("Publish succeeded but the cache re-key failed:", cleanupError);
872
863
  onShasUpdated(null, sha);
873
- onPublishComplete?.();
874
- showFeedback("Published, but the local cache couldn't be updated — reload the editor.", 8000);
864
+ onPublishComplete?.(sha);
865
+ showFeedback("warning", "Published, but the local cache couldn't be updated — reload the editor.", { ttlMs: 8000 });
875
866
  return;
876
867
  }
877
868
  }
878
869
 
879
870
  onShasUpdated(null, sha);
880
- onPublishComplete?.();
871
+ onPublishComplete?.(sha);
881
872
  } catch (error) {
882
873
  reportSaveError(error, "Publish");
883
874
  } finally {
@@ -12,7 +12,7 @@ import {
12
12
  markPendingMediaDeleted,
13
13
  removePendingMediaDeletion,
14
14
  } from "../lib/dexie";
15
- import { generateVideoPoster } from "../media/videoPoster";
15
+ import { generateVideoPoster } from "../media/video-poster";
16
16
  import { ASSET_CACHE_CONTROL } from "../storage/types";
17
17
  import { tallyImageIds, collectImageIds, clearImageIds, applyLogoDeletion } from "../lib/image-refs";
18
18
  import { clearDocumentRefs, tallyDocumentIds } from "../lib/document-refs";
@@ -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.
@@ -11,6 +11,17 @@ interface UseSortableRowOptions {
11
11
  }
12
12
 
13
13
  /**
14
+ * One of three sibling drag-and-drop position hooks, split by location type
15
+ * rather than unified behind a generic `useSortable({ axis })`: this one (flat
16
+ * index — section-ordering lists, `EditableList`), `useSortableNavRow`
17
+ * (NavLocation — the Pages modal's group-aware nav tree), and `useBlockDnd`
18
+ * (block/container id + index — the section canvas, root and nested). A
19
+ * fourth location type earns its own hook on the same shape (rowRef/handleRef,
20
+ * lazy atlaskit import, closest-edge state, a ref for the latest callback)
21
+ * rather than a parameter bag bolted onto an existing one — see
22
+ * docs/superpowers/specs/2026-09-04-editor-ui-primitives-consolidation.md § D6
23
+ * for why a shared `useSortable` was deferred.
24
+ *
14
25
  * Closest-edge drag-to-reorder wiring for a single list row, shared by the
15
26
  * section- and page-ordering modals. Attach `rowRef` to the row element and
16
27
  * `handleRef` to the drag handle, and render
@@ -30,6 +41,15 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
30
41
  const [isDragging, setIsDragging] = useState(false);
31
42
  const [closestEdge, setClosestEdge] = useState<"top" | "bottom" | null>(null);
32
43
 
44
+ // Read through a ref so a changed `onReorder` identity doesn't re-register
45
+ // the atlaskit bindings mid-drag (which cancels the drag) — the same class
46
+ // of bug EditableList's `onReorderRef` fixed (2026-08-03). `index` stays a
47
+ // real effect dependency: a stale index would report the wrong
48
+ // `fromIndex`/`toIndex` on drop, the hazard useSortableNavRow's `locationOf`
49
+ // comment warns about for its own (non-re-subscribing) case.
50
+ const onReorderRef = useRef(onReorder);
51
+ onReorderRef.current = onReorder;
52
+
33
53
  useEffect(() => {
34
54
  if (!enabled) return;
35
55
  const row = rowRef.current;
@@ -79,7 +99,7 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
79
99
  const rawEdge = extractClosestEdge(self.data);
80
100
  const edge = rawEdge === "top" || rawEdge === "bottom" ? rawEdge : null;
81
101
  const toIndex = computeReorderTarget(fromIndex, index, edge);
82
- if (toIndex !== null) onReorder(fromIndex, toIndex);
102
+ if (toIndex !== null) onReorderRef.current(fromIndex, toIndex);
83
103
  },
84
104
  });
85
105
 
@@ -93,7 +113,7 @@ export function useSortableRow<R extends HTMLElement, H extends HTMLElement>({
93
113
  cancelled = true;
94
114
  cleanup?.();
95
115
  };
96
- }, [index, dragType, onReorder, enabled]);
116
+ }, [index, dragType, enabled]);
97
117
 
98
118
  return { rowRef, handleRef, isDragging, closestEdge };
99
119
  }
@@ -0,0 +1,62 @@
1
+ import { useEffect, useRef, type RefObject } from "react";
2
+
3
+ export interface UseStackedEscapeOptions {
4
+ enabled: boolean;
5
+ /**
6
+ * Runs first, before the layer check would otherwise fall through to
7
+ * `onEscape`; returning `true` swallows the keypress entirely (the document
8
+ * editor overlay's blur-a-focused-field-first step).
9
+ */
10
+ before?: (e: KeyboardEvent) => boolean;
11
+ }
12
+
13
+ /**
14
+ * Shared "a modal above me owns Escape" rule. `EditorModal` marks its
15
+ * outermost layer with `data-modal-layer`; anything that might render BELOW
16
+ * one — a `Popover`, the doc file pane, the document editor overlay, and
17
+ * `EditorModal` itself (relative to another `EditorModal` opened on top of
18
+ * it) — must not react to an Escape meant for that layer instead.
19
+ *
20
+ * Attach the returned ref to your own `data-modal-layer` element (only
21
+ * `EditorModal` does) so the check can exclude it — otherwise it would always
22
+ * find itself and never fire. Callers that aren't a layer themselves (Popover,
23
+ * DocFilePane, the overlay) just ignore the return value: any layer at all
24
+ * then means something is stacked above them.
25
+ *
26
+ * For a tagged caller, "above" means a layer that FOLLOWS its own layer in
27
+ * DOM order (`compareDocumentPosition` + `DOCUMENT_POSITION_FOLLOWING`), not
28
+ * just "any other layer" — modals are appended later in DOM order than the
29
+ * ones already open, so this is what lets two stacked `EditorModal`s (e.g.
30
+ * Site Settings opening the Media Library over itself) resolve Escape to the
31
+ * topmost one only, then the next one down once that one closes.
32
+ */
33
+ export function useStackedEscape<T extends HTMLElement = HTMLElement>(
34
+ onEscape: () => void,
35
+ { enabled, before }: UseStackedEscapeOptions,
36
+ ): RefObject<T | null> {
37
+ const layerRef = useRef<T | null>(null);
38
+
39
+ useEffect(() => {
40
+ if (!enabled) return;
41
+
42
+ const onKeyDown = (e: KeyboardEvent) => {
43
+ if (e.key !== "Escape") return;
44
+ const own = layerRef.current;
45
+ const layers = document.querySelectorAll<HTMLElement>("[data-modal-layer]");
46
+ const above = Array.from(layers).some((el) => {
47
+ if (el === own) return false;
48
+ if (!own) return true;
49
+ return Boolean(own.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING);
50
+ });
51
+ if (above) return;
52
+ if (before?.(e)) return;
53
+ e.preventDefault();
54
+ onEscape();
55
+ };
56
+
57
+ document.addEventListener("keydown", onKeyDown);
58
+ return () => document.removeEventListener("keydown", onKeyDown);
59
+ }, [enabled, onEscape, before]);
60
+
61
+ return layerRef;
62
+ }
@@ -11,6 +11,8 @@ export interface BlockDragData {
11
11
  index: number;
12
12
  /** True if the dragged block is itself a container (used for the depth guard). */
13
13
  isContainer: boolean;
14
+ /** True if the dragged block is a Reference, which only expands at the page root. */
15
+ isReference?: boolean;
14
16
  [key: string | symbol]: unknown;
15
17
  }
16
18
 
@@ -27,6 +29,7 @@ export function buildBlockDragData(args: {
27
29
  containerId: string;
28
30
  index: number;
29
31
  isContainer: boolean;
32
+ isReference?: boolean;
30
33
  }): BlockDragData {
31
34
  return { dragType: "block", ...args };
32
35
  }
@@ -43,11 +46,13 @@ export function isBlockDragData(data: Record<string | symbol, unknown>): data is
43
46
  * May `source` drop into `dropContainerId`?
44
47
  * - A container block may only land at the page root (v1 one-level nesting → no
45
48
  * container-in-container; MAX_BLOCK_DEPTH=2).
49
+ * - So may a Reference: it is expanded into its sources only at the page root,
50
+ * so inside a container it would render nothing.
46
51
  * - Nothing may drop onto itself.
47
52
  */
48
53
  export function canDropBlock(source: BlockDragData, dropContainerId: string): boolean {
49
54
  if (source.blockId === dropContainerId) return false;
50
- if (source.isContainer && dropContainerId !== ROOT_CONTAINER_ID) return false;
55
+ if ((source.isContainer || source.isReference) && dropContainerId !== ROOT_CONTAINER_ID) return false;
51
56
  return true;
52
57
  }
53
58
 
@@ -2,6 +2,7 @@ import type { LoadedSection } from "./loader";
2
2
  import type { Section } from "../schemas/sections";
3
3
  import type { SiteIndex, SectionMeta } from "../schemas/site-config";
4
4
  import { ROOT_CONTAINER_ID, canDropBlock } from "./block-dnd";
5
+ import { REFERENCE_TYPE } from "./reference-type";
5
6
  import { getContainerChildren, reorderChild, insertChildAt, removeChildAt, placeBlockAtColumn, trimTrailingSpacers } from "./container-ops";
6
7
  import { reorderVisibleSectionInPage, visibleInsertPosition, addSectionToPage, removeSectionFromPages } from "./pages";
7
8
 
@@ -96,6 +97,7 @@ export function applyBlockMove(
96
97
  containerId: fromContainerId,
97
98
  index: fromIndex,
98
99
  isContainer: isContainerSection(draggedSection),
100
+ isReference: draggedSection.type === REFERENCE_TYPE,
99
101
  };
100
102
  if (!canDropBlock(dragData, toContainerId)) return noop(state);
101
103
 
@@ -200,6 +202,7 @@ function applyColumnPlacement(
200
202
  containerId: fromContainerId,
201
203
  index: fromIndex,
202
204
  isContainer: isContainerSection(draggedSection),
205
+ isReference: draggedSection.type === REFERENCE_TYPE,
203
206
  };
204
207
  if (!canDropBlock(dragData, toContainerId)) return noop(state);
205
208