@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
package/src/lib/nav.ts CHANGED
@@ -4,7 +4,9 @@ import { audiencePasses, pageDisplayTitle } from "./pages";
4
4
  import { navEntries } from "./nav-tree";
5
5
  import { getContainerChildren } from "./container-ops";
6
6
  import type { SiteIndex } from "../schemas/site-config";
7
+ import type { Section } from "../schemas/sections";
7
8
  import type { SiteDocument } from "./documents";
9
+ import { expandReferences, referenceItems, REFERENCE_TYPE, type ReferenceContent, type ReferenceQuery } from "./references";
8
10
 
9
11
  export interface NavItem {
10
12
  href: string;
@@ -41,35 +43,114 @@ function flattenForNav(sections: LoadedSection[]): LoadedSection[] {
41
43
  const walk = (loaded: LoadedSection) => {
42
44
  flat.push(loaded);
43
45
  for (const child of getContainerChildren(loaded.section)) {
44
- walk({ section: child, meta: loaded.meta });
46
+ walk({ section: child, meta: loaded.meta, referencedBy: loaded.referencedBy });
45
47
  }
46
48
  };
47
49
  sections.forEach(walk);
48
50
  return flat;
49
51
  }
50
52
 
51
- export function generateNavLinks(
52
- sections: LoadedSection[],
53
- registry?: SectionRegistry,
54
- ): NavItem[] {
53
+ /**
54
+ * The TL;DR / Summary section (`section_summary`) is the one non-heading type
55
+ * that can put itself in the nav, as a Heading (h1 role) or Subheading (h2 role)
56
+ * under its own label. Deliberately special-cased by type rather than added to
57
+ * the registry: the nav-entry option is scoped to this section only.
58
+ */
59
+ const SUMMARY_TYPE = "section_summary";
60
+
61
+ export type SummaryNavLevel = "off" | "heading" | "subheading";
62
+
63
+ function summaryNav(section: LoadedSection["section"]): { role: "h1" | "h2"; label: string } | null {
64
+ if (section.type !== SUMMARY_TYPE) return null;
65
+ const options = ("options" in section ? section.options : undefined) as
66
+ | { nav?: SummaryNavLevel; navLabel?: string }
67
+ | undefined;
68
+ const label = options?.navLabel?.trim() ?? "";
69
+ // The label is required while nav is on; without one there is nothing to show.
70
+ if (!label || (options?.nav !== "heading" && options?.nav !== "subheading")) return null;
71
+ return { role: options.nav === "heading" ? "h1" : "h2", label };
72
+ }
73
+
74
+ type RoleLookup = (type: string) => "h1" | "h2" | "h3" | undefined;
75
+
76
+ function roleLookup(registry?: SectionRegistry): RoleLookup {
77
+ return (type) => (registry ? registry.getSection(type) : getSection(type))?.navRole;
78
+ }
79
+
80
+ /**
81
+ * Every anchor the page renders, aligned with `flat` (undefined = none).
82
+ *
83
+ * Headings render id={toSectionId(heading)} unconditionally (even when excluded
84
+ * from nav) and never move, so every native heading anchor is reserved first.
85
+ * Summary anchors are derived from the nav label, so they can collide with a
86
+ * heading's or another summary's: summaries claim theirs next, in document
87
+ * order, suffixing -2, -3… on collision. Copies a Reference pulled in
88
+ * (`referencedBy`) claim last — `<anchor>-<reference id>`, then -2, -3… — for
89
+ * their headings and nav-on summaries alike, so adding a Reference never moves
90
+ * an anchor the page already had. Keying on the Reference rather than a bare
91
+ * suffix keeps two References showing the same section apart, and keeps each
92
+ * copy's anchor stable when References are reordered.
93
+ *
94
+ * Positional rather than keyed by section id: a Reference repeats its sources'
95
+ * ids, so one page can hold the same section twice.
96
+ */
97
+ function assignAnchors(flat: LoadedSection[], lookupRole: RoleLookup): (string | undefined)[] {
98
+ const anchors: (string | undefined)[] = new Array(flat.length);
99
+ const taken = new Set<string>();
100
+ const claim = (i: number, base: string) => {
101
+ let anchor = base;
102
+ for (let n = 2; taken.has(anchor); n++) anchor = `${base}-${n}`;
103
+ taken.add(anchor);
104
+ anchors[i] = anchor;
105
+ };
106
+ const headingOf = (section: LoadedSection["section"]) => {
107
+ const heading = (section.content as { heading?: string }).heading;
108
+ return heading && lookupRole(section.type) ? heading : undefined;
109
+ };
110
+
111
+ flat.forEach(({ section, referencedBy }, i) => {
112
+ const heading = headingOf(section);
113
+ if (referencedBy || !heading) return;
114
+ anchors[i] = toSectionId(heading);
115
+ taken.add(anchors[i]);
116
+ });
117
+ flat.forEach(({ section, referencedBy }, i) => {
118
+ const entry = summaryNav(section);
119
+ if (!referencedBy && entry) claim(i, toSectionId(entry.label));
120
+ });
121
+ flat.forEach(({ section, referencedBy }, i) => {
122
+ const text = summaryNav(section)?.label ?? headingOf(section);
123
+ if (referencedBy && text) claim(i, `${toSectionId(text)}-${toSectionId(referencedBy)}`);
124
+ });
125
+ return anchors;
126
+ }
127
+
128
+ function buildNavLinks(flat: LoadedSection[], lookupRole: RoleLookup, anchors: (string | undefined)[]): NavItem[] {
55
129
  const nav: NavItem[] = [];
56
130
  let currentParent: NavItem | null = null;
57
131
  let currentChild: NavItem | null = null;
58
132
 
59
- const lookupRole = (type: string) => {
60
- const def = registry ? registry.getSection(type) : getSection(type);
61
- return def?.navRole;
62
- };
63
-
64
- for (const { section, meta } of flattenForNav(sections)) {
65
- const content = section.content as { heading?: string; excludeFromNav?: boolean };
66
- if (!content.heading) continue;
133
+ for (const [i, { section, meta }] of flat.entries()) {
134
+ let role: "h1" | "h2" | "h3" | undefined;
135
+ let label: string;
136
+ let excluded = false;
67
137
 
68
- const role = lookupRole(section.type);
69
- if (!role) continue;
138
+ const summary = summaryNav(section);
139
+ if (summary) {
140
+ role = summary.role;
141
+ label = summary.label;
142
+ } else {
143
+ const content = section.content as { heading?: string; excludeFromNav?: boolean };
144
+ if (!content.heading) continue;
145
+ role = lookupRole(section.type);
146
+ label = content.heading;
147
+ excluded = !!content.excludeFromNav;
148
+ }
149
+ const anchor = anchors[i];
150
+ if (!role || !anchor) continue;
70
151
 
71
152
  if (role === "h1") {
72
- if (content.excludeFromNav) {
153
+ if (excluded) {
73
154
  currentParent = null;
74
155
  // Clear the child cursor too: leaving it pointing into the PREVIOUS
75
156
  // h1's subtree made an h3 following an excluded h1 nest under that
@@ -77,44 +158,123 @@ export function generateNavLinks(
77
158
  currentChild = null;
78
159
  continue;
79
160
  }
80
- currentParent = {
81
- href: `#${toSectionId(content.heading)}`,
82
- label: content.heading,
83
- status: meta.status,
84
- children: [],
85
- };
161
+ currentParent = { href: `#${anchor}`, label, status: meta.status, children: [] };
86
162
  currentChild = null;
87
163
  nav.push(currentParent);
88
164
  } else if (role === "h2") {
89
165
  // Same cursor reset as the excluded h1 above — an h3 under an excluded
90
166
  // h2 belongs nowhere, not under the preceding h2.
91
- if (content.excludeFromNav) {
167
+ if (excluded) {
92
168
  currentChild = null;
93
169
  continue;
94
170
  }
95
171
  if (!currentParent) continue;
96
- currentChild = {
97
- href: `#${toSectionId(content.heading)}`,
98
- label: content.heading,
99
- status: meta.status,
100
- children: [],
101
- };
172
+ currentChild = { href: `#${anchor}`, label, status: meta.status, children: [] };
102
173
  currentParent.children.push(currentChild);
103
174
  } else if (role === "h3") {
104
- if (content.excludeFromNav) continue;
175
+ if (excluded) continue;
105
176
  if (!currentChild) continue;
106
- currentChild.children.push({
107
- href: `#${toSectionId(content.heading)}`,
108
- label: content.heading,
109
- status: meta.status,
110
- children: [],
111
- });
177
+ currentChild.children.push({ href: `#${anchor}`, label, status: meta.status, children: [] });
112
178
  }
113
179
  }
114
180
 
115
181
  return nav;
116
182
  }
117
183
 
184
+ export function generateNavLinks(
185
+ sections: LoadedSection[],
186
+ registry?: SectionRegistry,
187
+ ): NavItem[] {
188
+ const flat = flattenForNav(sections);
189
+ const lookupRole = roleLookup(registry);
190
+ return buildNavLinks(flat, lookupRole, assignAnchors(flat, lookupRole));
191
+ }
192
+
193
+ export interface SummaryNavAnchor {
194
+ anchor: string;
195
+ /** False for a Subheading with no Link Heading above it: it renders its
196
+ * anchor but is dropped from the nav, so the editor hints about it. */
197
+ inNav: boolean;
198
+ }
199
+
200
+ /** Per summary section id (nav on, label set): the anchor it renders and
201
+ * whether it made it into the nav. Same derivation generateNavLinks uses. */
202
+ export function summaryNavAnchors(
203
+ sections: LoadedSection[],
204
+ registry?: SectionRegistry,
205
+ ): Record<string, SummaryNavAnchor> {
206
+ const flat = flattenForNav(sections);
207
+ const lookupRole = roleLookup(registry);
208
+ const anchors = assignAnchors(flat, lookupRole);
209
+ const hrefs = new Set<string>();
210
+ const collect = (items: NavItem[]) => items.forEach((i) => { hrefs.add(i.href); collect(i.children); });
211
+ collect(buildNavLinks(flat, lookupRole, anchors));
212
+ const out: Record<string, SummaryNavAnchor> = {};
213
+ flat.forEach(({ section, referencedBy }, i) => {
214
+ const anchor = anchors[i];
215
+ if (!referencedBy && anchor && summaryNav(section)) out[section.id] = { anchor, inNav: hrefs.has(`#${anchor}`) };
216
+ });
217
+ return out;
218
+ }
219
+
220
+ /**
221
+ * Render-only copy of the page's sections with `navAnchor` stamped on every
222
+ * section whose rendered anchor it cannot derive alone — nav-bearing summaries
223
+ * and everything a Reference pulled in (container children included) — for
224
+ * renderers that can't share React context with the page: the SSR viewer
225
+ * renders each section as its own React tree. Never written back: the editor
226
+ * reads summary anchors from SummaryAnchorsContext instead, so stored content
227
+ * stays clean.
228
+ *
229
+ * `navAnchor` is not part of any section schema: it survives only because the
230
+ * viewer's later passes (media resolve, sanitizeLoadedSections) spread sections.
231
+ * A Zod re-parse after this point would strip it and leave the section without
232
+ * the id its nav entry links to — stamp after any parse, never before.
233
+ */
234
+ export function withNavAnchors(sections: LoadedSection[], registry?: SectionRegistry): LoadedSection[] {
235
+ const anchors = assignAnchors(flattenForNav(sections), roleLookup(registry));
236
+ // Walks in flattenForNav's order (each section, then its children), so the
237
+ // running position lines up with `anchors`.
238
+ let at = 0;
239
+ const stamp = (section: Section, referenced: boolean): Section => {
240
+ const anchor = anchors[at++];
241
+ const children = getContainerChildren(section);
242
+ let out = section;
243
+ if (children.length > 0) {
244
+ out = { ...out, content: { ...(out.content as object), children: children.map((c) => stamp(c, referenced)) } } as Section;
245
+ }
246
+ return anchor && (referenced || summaryNav(section)) ? ({ ...out, navAnchor: anchor } as Section) : out;
247
+ };
248
+ return sections.map((s) => ({ ...s, section: stamp(s.section, !!s.referencedBy) }));
249
+ }
250
+
251
+ /**
252
+ * Every copy the editor renders for each Reference on the page, keyed by
253
+ * Reference id then source id, with anchors stamped over the WHOLE page — so
254
+ * the editor nav (built from expandReferences) links to exactly these ids, and
255
+ * two References showing one section never share a DOM id. Copies the
256
+ * published site drops (drafts, archived pages) claim after the rest, so a
257
+ * greyed copy can't take an anchor a published one would have.
258
+ * Lives here rather than in references.ts because it needs withNavAnchors, and
259
+ * references.ts importing nav.ts would cycle.
260
+ */
261
+ export function pageReferenceCopies(
262
+ pageSections: LoadedSection[],
263
+ opts: Omit<ReferenceQuery, "referenceId" | "content">,
264
+ ): Record<string, Record<string, Section>> {
265
+ const greyed = pageSections
266
+ .filter((s) => s.section.type === REFERENCE_TYPE)
267
+ .flatMap((r) =>
268
+ referenceItems({ ...opts, referenceId: r.section.id, content: r.section.content as unknown as ReferenceContent, audiences: [] })
269
+ .flatMap((i) => (i.source && i.excluded ? [{ ...i.source, referencedBy: r.section.id }] : [])),
270
+ );
271
+ const out: Record<string, Record<string, Section>> = {};
272
+ for (const s of withNavAnchors([...expandReferences(pageSections, { ...opts, audience: null }), ...greyed])) {
273
+ if (s.referencedBy) (out[s.referencedBy] ??= {})[s.section.id] = s.section;
274
+ }
275
+ return out;
276
+ }
277
+
118
278
  export interface PageNavItem {
119
279
  id: string;
120
280
  title: string;
@@ -2,6 +2,10 @@ import { env } from "./env";
2
2
 
3
3
  const ATTEMPTS = 3;
4
4
  const RETRY_BASE_MS = 150;
5
+ const REQUEST_TIMEOUT_MS = 10_000;
6
+ // Gateway failures mean the request never reached a handler, so even a write
7
+ // is safe to replay. A 500 may have failed after the write landed; it isn't.
8
+ const GATEWAY_STATUSES = new Set([502, 503, 504]);
5
9
 
6
10
  function sleep(ms: number): Promise<void> {
7
11
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -16,8 +20,8 @@ function sleep(ms: number): Promise<void> {
16
20
  * request never got a response at all.
17
21
  */
18
22
  export class PlatformBrokerError extends Error {
19
- constructor(public readonly status: number | null, message: string) {
20
- super(message);
23
+ constructor(public readonly status: number | null, message: string, options?: ErrorOptions) {
24
+ super(message, options);
21
25
  this.name = "PlatformBrokerError";
22
26
  }
23
27
  }
@@ -31,7 +35,9 @@ export function isPlatformMode(): boolean {
31
35
  * Call a platform broker endpoint scoped to this site. The platform authorizes
32
36
  * the op against the site that owns PLATFORM_API_KEY — the client never names
33
37
  * another tenant. Retries network/5xx (transient); 4xx is a real auth/config
34
- * error and is surfaced immediately. Mirrors fetchInstallationToken().
38
+ * error and is surfaced immediately. Writes are retried only on a gateway
39
+ * status: an unconfirmed write (timeout, dropped connection, 500) may already
40
+ * have landed. Mirrors fetchInstallationToken().
35
41
  */
36
42
  export async function platformBroker<T = unknown>(
37
43
  path: string,
@@ -52,15 +58,18 @@ export async function platformBroker<T = unknown>(
52
58
  serializedBody = JSON.stringify(opts.body);
53
59
  }
54
60
 
55
- let lastError: unknown;
56
61
  for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
57
62
  const isLast = attempt === ATTEMPTS - 1;
58
63
  let res: Response;
59
64
  try {
60
- res = await fetch(url, { method, headers, body: serializedBody });
65
+ res = await fetch(url, { method, headers, body: serializedBody, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
61
66
  } catch (err) {
62
- lastError = err;
63
- if (isLast) throw err;
67
+ // A timed-out write may already have landed. Do not automatically replay it.
68
+ if (method !== "GET" || isLast) {
69
+ throw new PlatformBrokerError(null, method === "GET"
70
+ ? "Platform request failed — check your connection and try again."
71
+ : "Platform request wasn't confirmed — check its status before trying again.", { cause: err });
72
+ }
64
73
  await sleep(RETRY_BASE_MS * 2 ** attempt);
65
74
  continue;
66
75
  }
@@ -68,14 +77,17 @@ export async function platformBroker<T = unknown>(
68
77
  if (res.ok) {
69
78
  if (res.status === 204) return undefined as T;
70
79
  try {
71
- return (await res.json()) as T;
72
- } catch {
73
- return undefined as T;
80
+ // Some endpoints (e.g. bug-report's 201) succeed with an empty body.
81
+ const text = await res.text();
82
+ return (text ? JSON.parse(text) : undefined) as T;
83
+ } catch (err) {
84
+ throw new PlatformBrokerError(null, method === "GET"
85
+ ? "Platform response couldn't be read — try again."
86
+ : "Platform response couldn't be read — check the operation's status before trying again.", { cause: err });
74
87
  }
75
88
  }
76
89
 
77
- if (res.status >= 500 && !isLast) {
78
- lastError = new PlatformBrokerError(res.status, `Platform broker error (${res.status})`);
90
+ if (!isLast && (method === "GET" ? res.status >= 500 : GATEWAY_STATUSES.has(res.status))) {
79
91
  await sleep(RETRY_BASE_MS * 2 ** attempt);
80
92
  continue;
81
93
  }
@@ -83,7 +95,8 @@ export async function platformBroker<T = unknown>(
83
95
  throw new PlatformBrokerError(res.status, `Platform broker error (${res.status}): ${await res.text()}`);
84
96
  }
85
97
 
86
- throw lastError instanceof Error ? lastError : new PlatformBrokerError(null, "Platform broker unreachable");
98
+ // Unreachable: every final attempt returns or throws above.
99
+ throw new PlatformBrokerError(null, "Platform broker unreachable");
87
100
  }
88
101
 
89
102
  /**
@@ -104,18 +117,23 @@ export async function platformBrokerStream(
104
117
  let lastError: unknown;
105
118
  for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
106
119
  let res: Response;
120
+ // Bound only connection establishment; an active LLM stream may take longer.
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
107
123
  try {
108
124
  res = await fetch(url, {
125
+ signal: controller.signal,
109
126
  method: "POST",
110
127
  headers: { "x-api-key": apiKey, "x-site-id": siteId, "Content-Type": "application/json" },
111
128
  body: JSON.stringify(body),
112
129
  });
113
130
  } catch (err) {
114
131
  lastError = err;
132
+ if (controller.signal.aborted) throw new PlatformBrokerError(null, "Chat connection timed out — try again.");
115
133
  if (attempt === ATTEMPTS - 1) throw err;
116
134
  await sleep(RETRY_BASE_MS * 2 ** attempt);
117
135
  continue;
118
- }
136
+ } finally { clearTimeout(timer); }
119
137
  if (!res.ok || !res.body) throw new Error(`Platform broker error (${res.status})`);
120
138
  return res.body;
121
139
  }
@@ -0,0 +1,3 @@
1
+ // A leaf of its own so modules that only need to recognise a Reference (drag
2
+ // rules, container ops, save validation) don't import the resolution engine.
3
+ export const REFERENCE_TYPE = "reference" as const;
@@ -0,0 +1,69 @@
1
+ import type { SiteIndex } from "../schemas/site-config";
2
+ import { REFERENCE_TYPE } from "./reference-type";
3
+
4
+ /** What one Reference names that it can never show. */
5
+ export interface ReferenceProblem {
6
+ referenceId: string;
7
+ unknownTagIds: string[];
8
+ /** Not a top-level section on any page: deleted, or a container's child. */
9
+ unplacedSectionIds: string[];
10
+ /** Picks that are themselves References, which are never shown. */
11
+ referencePickIds: string[];
12
+ picksItself: boolean;
13
+ }
14
+
15
+ const strings = (v: unknown): string[] =>
16
+ Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : [];
17
+
18
+ /**
19
+ * The dangling-id check for Reference sections — what save_sections rejects on,
20
+ * and what validate_site and `authoring validate` warn about.
21
+ *
22
+ * Deliberately NOT part of applyContentWrite or the schema: on the viewer and in
23
+ * the editor a dangling pick or a deleted tag is ordinary (someone deleted the
24
+ * source), so it drops silently and an editor save must keep succeeding. This
25
+ * is for whoever just chose the ids, where a bad one is a mistake.
26
+ *
27
+ * `sections` are full section bodies; anything that is not a Reference (by body
28
+ * type or index type) is ignored. `tagIds` null means the registry could not
29
+ * be read whole, so tag ids go unchecked rather than being misreported.
30
+ */
31
+ export function referenceProblems(
32
+ sections: readonly { id: string; content: unknown }[],
33
+ index: Pick<SiteIndex, "pages" | "sections">,
34
+ tagIds: ReadonlySet<string> | null,
35
+ ): ReferenceProblem[] {
36
+ const onPage = new Set(index.pages.flatMap((p) => p.order));
37
+ const problems: ReferenceProblem[] = [];
38
+ for (const { id, content } of sections) {
39
+ const body = content as { type?: unknown; content?: { tagIds?: unknown; sectionIds?: unknown } } | null;
40
+ if (body?.type !== REFERENCE_TYPE && index.sections[id]?.type !== REFERENCE_TYPE) continue;
41
+ const picks = strings(body?.content?.sectionIds).filter((pick) => pick !== id);
42
+ const problem: ReferenceProblem = {
43
+ referenceId: id,
44
+ unknownTagIds: tagIds ? strings(body?.content?.tagIds).filter((tag) => !tagIds.has(tag)) : [],
45
+ unplacedSectionIds: picks.filter((pick) => !onPage.has(pick)),
46
+ referencePickIds: picks.filter((pick) => onPage.has(pick) && index.sections[pick]?.type === REFERENCE_TYPE),
47
+ picksItself: strings(body?.content?.sectionIds).includes(id),
48
+ };
49
+ if (
50
+ problem.unknownTagIds.length || problem.unplacedSectionIds.length ||
51
+ problem.referencePickIds.length || problem.picksItself
52
+ ) problems.push(problem);
53
+ }
54
+ return problems;
55
+ }
56
+
57
+ /** One line per Reference, naming every bad id. */
58
+ export function describeReferenceProblem(problem: ReferenceProblem): string {
59
+ const list = (ids: string[]) => ids.map((id) => `"${id}"`).join(", ");
60
+ const issues = [
61
+ ...(problem.unknownTagIds.length ? [`tagIds ${list(problem.unknownTagIds)} are not tags on this site`] : []),
62
+ ...(problem.unplacedSectionIds.length
63
+ ? [`sectionIds ${list(problem.unplacedSectionIds)} are not top-level sections on any page (a container's child cannot be picked — pick the container)`]
64
+ : []),
65
+ ...(problem.referencePickIds.length ? [`sectionIds ${list(problem.referencePickIds)} are References, which a Reference never shows`] : []),
66
+ ...(problem.picksItself ? ["sectionIds includes the Reference itself"] : []),
67
+ ];
68
+ return `Reference "${problem.referenceId}": ${issues.join("; ")}.`;
69
+ }
@@ -0,0 +1,130 @@
1
+ import type { LoadedSection } from "./loader";
2
+ import type { Page, SiteIndex } from "../schemas/site-config";
3
+ import { audiencePasses } from "./pages";
4
+ import { pagesInNavOrder } from "./nav-tree";
5
+ import { siteTags } from "./tags";
6
+ import { REFERENCE_TYPE } from "./reference-type";
7
+
8
+ export { REFERENCE_TYPE };
9
+
10
+ export interface ReferenceContent {
11
+ tagIds: string[];
12
+ sectionIds: string[];
13
+ }
14
+
15
+ /** Why the published site leaves a referenced section out entirely. */
16
+ export type ReferenceExclusion =
17
+ /** Deleted, its file failed to load, or not a top-level section. */
18
+ | "missing"
19
+ /** Another Reference — never nested. */
20
+ | "reference"
21
+ | "archived-page"
22
+ /** A draft or archived section. */
23
+ | "not-live";
24
+
25
+ /** What a Reference is resolved against. */
26
+ export interface ReferenceQuery {
27
+ referenceId: string;
28
+ content: ReferenceContent;
29
+ index: SiteIndex;
30
+ allSections: LoadedSection[];
31
+ }
32
+
33
+ export interface ReferenceItem {
34
+ sectionId: string;
35
+ /** The page the section sits on; null when it is on none (deleted, or an
36
+ * id that was never a top-level section). */
37
+ page: Page | null;
38
+ source: LoadedSection | null;
39
+ excluded: ReferenceExclusion | null;
40
+ /** Of the audiences asked about, those who can't see it on its own page.
41
+ * Always empty for an excluded item. */
42
+ hiddenFrom: string[];
43
+ }
44
+
45
+ /**
46
+ * Everything a Reference points at, as the editor lists it: in site order,
47
+ * with what the viewer would silently drop kept and marked `excluded`, and
48
+ * with `hiddenFrom` naming which of `audiences` its source page and section
49
+ * keep it from. Picks that are on no page trail, in stored order. Only the
50
+ * Reference itself is left out. resolveReference is this list filtered, so
51
+ * the two can never disagree about what a Reference shows.
52
+ */
53
+ export function referenceItems(opts: ReferenceQuery & { audiences: string[] }): ReferenceItem[] {
54
+ const { referenceId, content, index, allSections, audiences } = opts;
55
+
56
+ const wanted = new Set(content.sectionIds);
57
+ const tagIds = new Set(content.tagIds);
58
+ for (const tag of siteTags(index)) {
59
+ if (tagIds.has(tag.id)) for (const id of tag.sectionIds) wanted.add(id);
60
+ }
61
+ wanted.delete(referenceId);
62
+ if (wanted.size === 0) return [];
63
+
64
+ const byId = new Map(allSections.map((s) => [s.section.id, s]));
65
+ const item = (sectionId: string, page: Page | null): ReferenceItem => {
66
+ const source = byId.get(sectionId) ?? null;
67
+ const excluded: ReferenceExclusion | null =
68
+ !source || !page ? "missing"
69
+ : source.section.type === REFERENCE_TYPE ? "reference"
70
+ : page.status !== "live" ? "archived-page"
71
+ : source.meta.status !== "live" ? "not-live"
72
+ : null;
73
+ const hiddenFrom = excluded || !source || !page ? [] : audiences.filter(
74
+ (a) => !audiencePasses(page.access, a) || !audiencePasses(source.meta.access, a),
75
+ );
76
+ return { sectionId, page, source, excluded, hiddenFrom };
77
+ };
78
+
79
+ const items: ReferenceItem[] = [];
80
+ const placed = new Set<string>();
81
+ for (const page of pagesInNavOrder(index)) {
82
+ for (const id of page.order) {
83
+ if (!wanted.has(id)) continue;
84
+ items.push(item(id, page));
85
+ placed.add(id);
86
+ }
87
+ }
88
+ for (const id of wanted) if (!placed.has(id)) items.push(item(id, null));
89
+ return items;
90
+ }
91
+
92
+ /**
93
+ * The sections a Reference shows: the union of its tagged and picked sections,
94
+ * resolved against the content it is given (the published content, on every
95
+ * viewer path) in site order — pages in nav order, then position on the page.
96
+ *
97
+ * A Reference stores pointers rather than copies because the index puts each
98
+ * section id on exactly one page. Resolving them is where every exposure rule
99
+ * lives, so a Reference can never widen what a viewer sees:
100
+ * - an item must pass its SOURCE page's access and its source section's access
101
+ * for `audience` (the viewer has already passed the referencing page's and
102
+ * the Reference's own, so the result is the intersection of all four);
103
+ * - only live sections on live pages — hidden (not-in-nav) pages count,
104
+ * archived ones do not;
105
+ * - References in the source set are skipped, the Reference itself included,
106
+ * so there are no cycles. Containers come whole, children and all.
107
+ * Dangling picks and deleted tags drop silently. Items are the source entries
108
+ * marked with `referencedBy`, which is what gives their anchors the
109
+ * `-<reference id>` suffix (lib/nav.ts).
110
+ */
111
+ export function resolveReference(opts: ReferenceQuery & { audience: string | null }): LoadedSection[] {
112
+ const { audience, ...rest } = opts;
113
+ return referenceItems({ ...rest, audiences: audience === null ? [] : [audience] }).flatMap((i) =>
114
+ i.source && !i.excluded && i.hiddenFrom.length === 0 ? [{ ...i.source, referencedBy: opts.referenceId }] : [],
115
+ );
116
+ }
117
+
118
+ /** A page's sections with each Reference replaced, in place, by the copies it
119
+ * shows `audience` — what the viewer renders and what the page nav is built
120
+ * from, on both the viewer and the editor. */
121
+ export function expandReferences(
122
+ pageSections: LoadedSection[],
123
+ opts: Omit<ReferenceQuery, "referenceId" | "content"> & { audience: string | null },
124
+ ): LoadedSection[] {
125
+ return pageSections.flatMap((s) =>
126
+ s.section.type === REFERENCE_TYPE
127
+ ? resolveReference({ ...opts, referenceId: s.section.id, content: s.section.content as unknown as ReferenceContent })
128
+ : [s],
129
+ );
130
+ }
@@ -2,7 +2,9 @@ import type { ZodType, z } from "zod";
2
2
  import type { ComponentType, ReactNode } from "react";
3
3
  import type { Audience } from "../schemas/auth";
4
4
  import type { LinkValue } from "../schemas/link";
5
+ import type { ImageBackground } from "../schemas/shared";
5
6
  import type { SectionContent } from "../schemas/sections";
7
+ import type { TagEntry } from "../schemas/tags";
6
8
  import type { VideoSource } from "../components/sections/Video/VideoBlock";
7
9
 
8
10
  // --- Settings field types ---
@@ -71,6 +73,16 @@ export type SettingsFieldDef =
71
73
  label: string;
72
74
  default: { documentId: string };
73
75
  target?: "content" | "options";
76
+ }
77
+ | {
78
+ /** Per-placement backdrop behind an image (ImageBackgroundField). Leave
79
+ * `default` unset: an absent value is "None" and must stay absent on save. */
80
+ type: "image-background";
81
+ label: string;
82
+ default?: ImageBackground;
83
+ target?: "content" | "options";
84
+ /** Hide the field for the current values (e.g. when the ref is a video). */
85
+ hidden?: (values: Record<string, unknown>) => boolean;
74
86
  };
75
87
 
76
88
  export type SettingsSchema = Record<string, SettingsFieldDef>;
@@ -105,6 +117,20 @@ export interface SectionProps<T = unknown> {
105
117
  onDelete?: () => void;
106
118
  }
107
119
 
120
+ /** The site's tag registry and its edits, as the editor shell hands them to
121
+ * the canvas and Site settings. */
122
+ export interface SiteTagging {
123
+ /** Already reconciled (siteTags). */
124
+ tags: TagEntry[];
125
+ /** False when the registry was never loaded or loaded degraded: a save would
126
+ * wholesale-replace tags.json with what this session couldn't fully read. */
127
+ editable: boolean;
128
+ onCreate: (label: string, sectionId?: string) => void;
129
+ onRename: (tagId: string, label: string) => void;
130
+ onDelete: (tagId: string) => void;
131
+ onToggle: (sectionId: string, tagId: string, tagged: boolean) => void;
132
+ }
133
+
108
134
  export interface WrapperProps {
109
135
  sectionId: string;
110
136
  sectionType: string;
@@ -112,6 +138,8 @@ export interface WrapperProps {
112
138
  dirty?: boolean;
113
139
  index: number;
114
140
  isLast: boolean;
141
+ /** Total sections on the page — Move up/down's "N of M" and end-disabling. */
142
+ sectionCount?: number;
115
143
  containerId: string;
116
144
  isContainerBlock: boolean;
117
145
  definition: SectionDefinition;
@@ -119,6 +147,15 @@ export interface WrapperProps {
119
147
  audiences: Audience[];
120
148
  access: string[];
121
149
  onAccessChange?: (access: string[]) => void;
150
+ /** Section tags (the tags.json sidecar). Only root sections get a
151
+ * SectionWrapper — container children are not index sections and cannot be
152
+ * tagged. Absent handlers make the Tags pill read-only. */
153
+ tagging: {
154
+ tags: TagEntry[];
155
+ assignedIds: string[];
156
+ onToggle?: (tagId: string, tagged: boolean) => void;
157
+ onCreate?: (label: string) => void;
158
+ };
122
159
  onStatusChange?: (status: "draft" | "live" | "archived") => void;
123
160
  onSectionChange?: (options: Record<string, unknown>) => void;
124
161
  onReorder?: (fromIndex: number, toIndex: number) => void;