@drawnagency/primitives 0.1.113 → 0.1.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (534) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/cookies.d.ts +39 -0
  3. package/dist/auth/cookies.d.ts.map +1 -1
  4. package/dist/auth/index.js +3 -3
  5. package/dist/chunk-4WQ36ZP7.js +83 -0
  6. package/dist/{chunk-REDDTTGJ.js → chunk-4Y2JQQZL.js} +19 -1
  7. package/dist/chunk-5GFACGSN.js +6 -0
  8. package/dist/{chunk-AHALSCQH.js → chunk-67P34WNW.js} +76 -131
  9. package/dist/{chunk-S2L3BPLS.js → chunk-7SX42OP4.js} +1 -1
  10. package/dist/{chunk-GGXZZ77Z.js → chunk-AASEGXNT.js} +18 -1
  11. package/dist/chunk-APIJPTFG.js +64 -0
  12. package/dist/{chunk-WKZ7OENC.js → chunk-BQ5TZZUE.js} +5 -1
  13. package/dist/chunk-CDBURDPX.js +90 -0
  14. package/dist/chunk-DFKTF3CH.js +0 -0
  15. package/dist/chunk-DIMDRBFK.js +11 -0
  16. package/dist/chunk-DJABADOO.js +131 -0
  17. package/dist/{chunk-NHN2LSIU.js → chunk-DJOGPSRU.js} +31 -68
  18. package/dist/{chunk-KR2MHTNJ.js → chunk-FHTKSELX.js} +610 -62
  19. package/dist/{chunk-WUJ3XNFI.js → chunk-GGYY3JQJ.js} +19 -24
  20. package/dist/chunk-GNF6KFQ7.js +18 -0
  21. package/dist/{chunk-FRE2CCLV.js → chunk-HHNPSKPT.js} +5 -79
  22. package/dist/{chunk-IINIZBQB.js → chunk-JCDZAFX2.js} +30 -10
  23. package/dist/chunk-JR24U7FJ.js +120 -0
  24. package/dist/{chunk-BDFTMGCL.js → chunk-JXATNKLV.js} +11 -10
  25. package/dist/{chunk-SW6PCQQD.js → chunk-KQA772WQ.js} +2 -1
  26. package/dist/{chunk-6BZ2KNTP.js → chunk-NX7TFHA4.js} +195 -65
  27. package/dist/{chunk-VWWY5TL5.js → chunk-O37AV27P.js} +14 -75
  28. package/dist/chunk-RIITGTV3.js +275 -0
  29. package/dist/{chunk-HBULBDGP.js → chunk-S37DZPHW.js} +1 -1
  30. package/dist/chunk-S7QRHUWO.js +8 -0
  31. package/dist/{chunk-Z7W3DQTR.js → chunk-T26HAKW3.js} +424 -42
  32. package/dist/{chunk-4I5OO5UB.js → chunk-TEOAKGZA.js} +0 -1
  33. package/dist/{chunk-GPPVYLEI.js → chunk-UUUSJDIG.js} +1 -1
  34. package/dist/chunk-VY3IFGE3.js +70 -0
  35. package/dist/chunk-WYOFQHKI.js +176 -0
  36. package/dist/{chunk-4RNF2DRG.js → chunk-XDEPXTBY.js} +0 -2
  37. package/dist/{chunk-DS4GCYV2.js → chunk-YV3YSZEB.js} +223 -16
  38. package/dist/collab/rebase.d.ts +2 -1
  39. package/dist/collab/rebase.d.ts.map +1 -1
  40. package/dist/collab/rebase.js +24 -9
  41. package/dist/components/brandguide/ColorsEditable.d.ts.map +1 -1
  42. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  43. package/dist/components/chat/ChatWidget.d.ts.map +1 -1
  44. package/dist/components/docfile/DocFilePane.d.ts.map +1 -1
  45. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
  46. package/dist/components/editor/ConflictDialog.d.ts.map +1 -1
  47. package/dist/components/editor/DragHandle.d.ts +12 -2
  48. package/dist/components/editor/DragHandle.d.ts.map +1 -1
  49. package/dist/components/editor/DropEdgeIndicator.d.ts +15 -6
  50. package/dist/components/editor/DropEdgeIndicator.d.ts.map +1 -1
  51. package/dist/components/editor/NavGroupRow.d.ts +4 -1
  52. package/dist/components/editor/NavGroupRow.d.ts.map +1 -1
  53. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  54. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  55. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  56. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  57. package/dist/components/editor/TagIndicator.d.ts +15 -0
  58. package/dist/components/editor/TagIndicator.d.ts.map +1 -0
  59. package/dist/components/editor/index.js +150 -79
  60. package/dist/components/primitives/DocumentDropZone.d.ts +1 -1
  61. package/dist/components/primitives/EditableGrid.d.ts.map +1 -1
  62. package/dist/components/primitives/EditableList.d.ts.map +1 -1
  63. package/dist/components/primitives/HeadingSection.d.ts +4 -1
  64. package/dist/components/primitives/HeadingSection.d.ts.map +1 -1
  65. package/dist/components/primitives/ResolvedMedia.d.ts +5 -1
  66. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  67. package/dist/components/primitives/{defineHeadingSection.d.ts → define-heading-section.d.ts} +1 -1
  68. package/dist/components/primitives/define-heading-section.d.ts.map +1 -0
  69. package/dist/components/primitives/{gridDrop.d.ts → grid-drop.d.ts} +1 -1
  70. package/dist/components/primitives/grid-drop.d.ts.map +1 -0
  71. package/dist/components/primitives/index.d.ts +1 -1
  72. package/dist/components/primitives/index.d.ts.map +1 -1
  73. package/dist/components/primitives/index.js +22 -154
  74. package/dist/components/sections/Button/CTAButton.d.ts.map +1 -1
  75. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts +2 -6
  76. package/dist/components/sections/DoDontMedia/DoDontMediaBlock.d.ts.map +1 -1
  77. package/dist/components/sections/DoDontMedia/index.d.ts +8 -0
  78. package/dist/components/sections/DoDontMedia/index.d.ts.map +1 -1
  79. package/dist/components/sections/IconList/IconList.d.ts +2 -1
  80. package/dist/components/sections/IconList/IconList.d.ts.map +1 -1
  81. package/dist/components/sections/IconList/IconListSettings.d.ts +2 -1
  82. package/dist/components/sections/IconList/IconListSettings.d.ts.map +1 -1
  83. package/dist/components/sections/IconList/index.d.ts +1 -0
  84. package/dist/components/sections/IconList/index.d.ts.map +1 -1
  85. package/dist/components/sections/Media/MediaBlock.d.ts +2 -1
  86. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  87. package/dist/components/sections/Media/index.d.ts +8 -0
  88. package/dist/components/sections/Media/index.d.ts.map +1 -1
  89. package/dist/components/sections/Reference/ReferenceList.d.ts +35 -0
  90. package/dist/components/sections/Reference/ReferenceList.d.ts.map +1 -0
  91. package/dist/components/sections/Reference/ReferenceSettings.d.ts +12 -0
  92. package/dist/components/sections/Reference/ReferenceSettings.d.ts.map +1 -0
  93. package/dist/components/sections/Reference/index.d.ts +9 -0
  94. package/dist/components/sections/Reference/index.d.ts.map +1 -0
  95. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts +38 -0
  96. package/dist/components/sections/SectionSummary/SectionSummaryBlock.d.ts.map +1 -0
  97. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts +20 -0
  98. package/dist/components/sections/SectionSummary/SectionSummarySettings.d.ts.map +1 -0
  99. package/dist/components/sections/SectionSummary/index.d.ts +33 -0
  100. package/dist/components/sections/SectionSummary/index.d.ts.map +1 -0
  101. package/dist/components/sections/all-sections.d.ts +53 -0
  102. package/dist/components/sections/all-sections.d.ts.map +1 -1
  103. package/dist/components/sections/brand-guide.d.ts +41 -2
  104. package/dist/components/sections/brand-guide.d.ts.map +1 -1
  105. package/dist/components/sections/brand-guide.js +17 -10
  106. package/dist/components/sections/core-sections.d.ts +14 -0
  107. package/dist/components/sections/core-sections.d.ts.map +1 -1
  108. package/dist/components/sections/register-schemas.js +25 -18
  109. package/dist/components/sections/register.js +25 -18
  110. package/dist/components/shared/BrandSwatchesContext.d.ts +9 -0
  111. package/dist/components/shared/BrandSwatchesContext.d.ts.map +1 -0
  112. package/dist/components/shared/Button.d.ts +11 -3
  113. package/dist/components/shared/Button.d.ts.map +1 -1
  114. package/dist/components/shared/Checkbox.d.ts +6 -0
  115. package/dist/components/shared/Checkbox.d.ts.map +1 -1
  116. package/dist/components/shared/ConfirmDialog.d.ts +25 -0
  117. package/dist/components/shared/ConfirmDialog.d.ts.map +1 -0
  118. package/dist/components/shared/ConfirmInline.d.ts +25 -0
  119. package/dist/components/shared/ConfirmInline.d.ts.map +1 -0
  120. package/dist/components/shared/ErrorBoundary.d.ts.map +1 -1
  121. package/dist/components/shared/Fab.d.ts +11 -2
  122. package/dist/components/shared/Fab.d.ts.map +1 -1
  123. package/dist/components/shared/FabStack.d.ts +22 -0
  124. package/dist/components/shared/FabStack.d.ts.map +1 -0
  125. package/dist/components/shared/HistoryChangesModal.d.ts.map +1 -1
  126. package/dist/components/shared/HistoryPopover.d.ts.map +1 -1
  127. package/dist/components/shared/ImageBackgroundField.d.ts +9 -0
  128. package/dist/components/shared/ImageBackgroundField.d.ts.map +1 -0
  129. package/dist/components/shared/{UploadRejectionAlert.d.ts → InlineAlert.d.ts} +4 -2
  130. package/dist/components/shared/InlineAlert.d.ts.map +1 -0
  131. package/dist/components/shared/Input.d.ts +10 -0
  132. package/dist/components/shared/Input.d.ts.map +1 -1
  133. package/dist/components/shared/Navigation.d.ts.map +1 -1
  134. package/dist/components/shared/Notice.d.ts +10 -0
  135. package/dist/components/shared/Notice.d.ts.map +1 -0
  136. package/dist/components/shared/Popover.d.ts.map +1 -1
  137. package/dist/components/shared/ReferenceSourcesContext.d.ts +25 -0
  138. package/dist/components/shared/ReferenceSourcesContext.d.ts.map +1 -0
  139. package/dist/components/shared/Select.d.ts +3 -2
  140. package/dist/components/shared/Select.d.ts.map +1 -1
  141. package/dist/components/shared/SplitButton.d.ts.map +1 -1
  142. package/dist/components/shared/SummaryAnchorsContext.d.ts +10 -0
  143. package/dist/components/shared/SummaryAnchorsContext.d.ts.map +1 -0
  144. package/dist/components/shared/Textarea.d.ts +7 -0
  145. package/dist/components/shared/Textarea.d.ts.map +1 -1
  146. package/dist/components/shared/ToolbarFrame.d.ts +17 -0
  147. package/dist/components/shared/ToolbarFrame.d.ts.map +1 -0
  148. package/dist/components/shared/copy.d.ts +13 -0
  149. package/dist/components/shared/copy.d.ts.map +1 -0
  150. package/dist/components/shared/icons.d.ts +0 -12
  151. package/dist/components/shared/icons.d.ts.map +1 -1
  152. package/dist/components/shell/AudienceDetailsPanel.d.ts +7 -1
  153. package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
  154. package/dist/components/shell/AudienceRow.d.ts.map +1 -1
  155. package/dist/components/shell/BugReportFAB.d.ts +7 -1
  156. package/dist/components/shell/BugReportFAB.d.ts.map +1 -1
  157. package/dist/components/shell/CredentialList.d.ts.map +1 -1
  158. package/dist/components/shell/DocInspector.d.ts.map +1 -1
  159. package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -1
  160. package/dist/components/shell/EditorContext.d.ts +4 -0
  161. package/dist/components/shell/EditorContext.d.ts.map +1 -1
  162. package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
  163. package/dist/components/shell/EditorModal.d.ts.map +1 -1
  164. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  165. package/dist/components/shell/HistoryToolbar.d.ts.map +1 -1
  166. package/dist/components/shell/MediaFolderTile.d.ts +4 -1
  167. package/dist/components/shell/MediaFolderTile.d.ts.map +1 -1
  168. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  169. package/dist/components/shell/RestoreModal.d.ts.map +1 -1
  170. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  171. package/dist/components/shell/SiteSettingsDisplay.d.ts.map +1 -1
  172. package/dist/components/shell/SiteSettingsModal.d.ts +3 -1
  173. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  174. package/dist/components/shell/SiteSettingsTags.d.ts +13 -0
  175. package/dist/components/shell/SiteSettingsTags.d.ts.map +1 -0
  176. package/dist/components/shell/SiteSettingsUsers.d.ts.map +1 -1
  177. package/dist/components/shell/ViewerLoginForm.d.ts.map +1 -1
  178. package/dist/components/shell/{blockMoveDispatch.d.ts → block-move-dispatch.d.ts} +1 -1
  179. package/dist/components/shell/block-move-dispatch.d.ts.map +1 -0
  180. package/dist/components/shell/reference-sources.d.ts +22 -0
  181. package/dist/components/shell/reference-sources.d.ts.map +1 -0
  182. package/dist/deck/index.d.ts +2 -2
  183. package/dist/deck/index.d.ts.map +1 -1
  184. package/dist/deck/index.js +1 -3
  185. package/dist/deck-builder/index.js +10 -17
  186. package/dist/document/index.d.ts +2 -2
  187. package/dist/document/index.d.ts.map +1 -1
  188. package/dist/hooks/useBlockDnd.d.ts +11 -0
  189. package/dist/hooks/useBlockDnd.d.ts.map +1 -1
  190. package/dist/hooks/useBuildStatus.d.ts +1 -1
  191. package/dist/hooks/useBuildStatus.d.ts.map +1 -1
  192. package/dist/hooks/useDocumentUploads.d.ts +1 -1
  193. package/dist/hooks/useDocumentUploads.d.ts.map +1 -1
  194. package/dist/hooks/useEditorPersistence.d.ts +5 -1
  195. package/dist/hooks/useEditorPersistence.d.ts.map +1 -1
  196. package/dist/hooks/useEditorPublish.d.ts +6 -3
  197. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  198. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  199. package/dist/hooks/useNotice.d.ts +19 -0
  200. package/dist/hooks/useNotice.d.ts.map +1 -0
  201. package/dist/hooks/useSortableNavRow.d.ts +11 -0
  202. package/dist/hooks/useSortableNavRow.d.ts.map +1 -1
  203. package/dist/hooks/useSortableRow.d.ts +11 -0
  204. package/dist/hooks/useSortableRow.d.ts.map +1 -1
  205. package/dist/hooks/useStackedEscape.d.ts +32 -0
  206. package/dist/hooks/useStackedEscape.d.ts.map +1 -0
  207. package/dist/index.js +95 -48
  208. package/dist/lib/audience-secret.js +2 -2
  209. package/dist/lib/block-dnd.d.ts +5 -0
  210. package/dist/lib/block-dnd.d.ts.map +1 -1
  211. package/dist/lib/block-move.d.ts.map +1 -1
  212. package/dist/lib/brand-swatches.d.ts +12 -0
  213. package/dist/lib/brand-swatches.d.ts.map +1 -0
  214. package/dist/lib/container-ops.d.ts +3 -2
  215. package/dist/lib/container-ops.d.ts.map +1 -1
  216. package/dist/lib/dexie.d.ts +7 -1
  217. package/dist/lib/dexie.d.ts.map +1 -1
  218. package/dist/lib/dexie.js +12 -3
  219. package/dist/lib/env.d.ts +4 -0
  220. package/dist/lib/env.d.ts.map +1 -1
  221. package/dist/lib/env.js +5 -3
  222. package/dist/lib/events.d.ts +1 -2
  223. package/dist/lib/events.d.ts.map +1 -1
  224. package/dist/lib/image-background.d.ts +8 -0
  225. package/dist/lib/image-background.d.ts.map +1 -0
  226. package/dist/lib/image-variant-plan.d.ts +42 -0
  227. package/dist/lib/image-variant-plan.d.ts.map +1 -0
  228. package/dist/lib/image-variant-plan.js +53 -0
  229. package/dist/lib/index-ops.d.ts.map +1 -1
  230. package/dist/lib/index-ops.js +21 -17
  231. package/dist/lib/index.d.ts +7 -4
  232. package/dist/lib/index.d.ts.map +1 -1
  233. package/dist/lib/index.js +55 -26
  234. package/dist/lib/loader.d.ts +25 -12
  235. package/dist/lib/loader.d.ts.map +1 -1
  236. package/dist/lib/media-ops.d.ts +73 -0
  237. package/dist/lib/media-ops.d.ts.map +1 -0
  238. package/dist/lib/media-ops.js +60 -0
  239. package/dist/lib/nav-tree.d.ts +7 -0
  240. package/dist/lib/nav-tree.d.ts.map +1 -1
  241. package/dist/lib/nav.d.ts +38 -0
  242. package/dist/lib/nav.d.ts.map +1 -1
  243. package/dist/lib/pages.d.ts +2 -1
  244. package/dist/lib/pages.d.ts.map +1 -1
  245. package/dist/lib/platform-broker.d.ts +4 -2
  246. package/dist/lib/platform-broker.d.ts.map +1 -1
  247. package/dist/lib/platform-broker.js +21 -13
  248. package/dist/lib/reference-type.d.ts +2 -0
  249. package/dist/lib/reference-type.d.ts.map +1 -0
  250. package/dist/lib/reference-validation.d.ts +31 -0
  251. package/dist/lib/reference-validation.d.ts.map +1 -0
  252. package/dist/lib/reference-validation.js +39 -0
  253. package/dist/lib/references.d.ts +74 -0
  254. package/dist/lib/references.d.ts.map +1 -0
  255. package/dist/lib/registry.d.ts +35 -0
  256. package/dist/lib/registry.d.ts.map +1 -1
  257. package/dist/lib/{safeRedirect.d.ts → safe-redirect.d.ts} +1 -1
  258. package/dist/lib/safe-redirect.d.ts.map +1 -0
  259. package/dist/lib/sidecar-diagnostics.d.ts +20 -0
  260. package/dist/lib/sidecar-diagnostics.d.ts.map +1 -0
  261. package/dist/lib/sidecars.d.ts +118 -0
  262. package/dist/lib/sidecars.d.ts.map +1 -0
  263. package/dist/lib/sidecars.js +23 -0
  264. package/dist/lib/slug.d.ts +2 -0
  265. package/dist/lib/slug.d.ts.map +1 -0
  266. package/dist/lib/storage-tier.d.ts +17 -0
  267. package/dist/lib/storage-tier.d.ts.map +1 -0
  268. package/dist/lib/storage-tier.js +29 -0
  269. package/dist/lib/tags.d.ts +64 -0
  270. package/dist/lib/tags.d.ts.map +1 -0
  271. package/dist/lib/theme-vars.d.ts +9 -0
  272. package/dist/lib/theme-vars.d.ts.map +1 -1
  273. package/dist/media/index.d.ts +1 -1
  274. package/dist/media/index.d.ts.map +1 -1
  275. package/dist/media/index.js +18 -14
  276. package/dist/media/queue.d.ts +2 -0
  277. package/dist/media/queue.d.ts.map +1 -1
  278. package/dist/media/utils.d.ts +2 -0
  279. package/dist/media/utils.d.ts.map +1 -1
  280. package/dist/media/{videoPoster.d.ts → video-poster.d.ts} +1 -1
  281. package/dist/media/video-poster.d.ts.map +1 -0
  282. package/dist/media/worker.d.ts +1 -35
  283. package/dist/media/worker.d.ts.map +1 -1
  284. package/dist/schemas/audience.js +2 -2
  285. package/dist/schemas/index.d.ts +1 -0
  286. package/dist/schemas/index.d.ts.map +1 -1
  287. package/dist/schemas/index.js +25 -13
  288. package/dist/schemas/media.d.ts +4 -0
  289. package/dist/schemas/media.d.ts.map +1 -1
  290. package/dist/schemas/shared.d.ts +26 -0
  291. package/dist/schemas/shared.d.ts.map +1 -1
  292. package/dist/schemas/site-config.d.ts +6 -0
  293. package/dist/schemas/site-config.d.ts.map +1 -1
  294. package/dist/schemas/tags.d.ts +27 -0
  295. package/dist/schemas/tags.d.ts.map +1 -0
  296. package/dist/storage/index.js +2 -1
  297. package/dist/storage/memory.d.ts +21 -0
  298. package/dist/storage/memory.d.ts.map +1 -0
  299. package/dist/storage/memory.js +129 -0
  300. package/dist/storage/types.d.ts +9 -0
  301. package/dist/storage/types.d.ts.map +1 -1
  302. package/dist/types/database.d.ts.map +1 -1
  303. package/package.json +26 -1
  304. package/src/auth/cookies.ts +111 -0
  305. package/src/collab/rebase.ts +29 -9
  306. package/src/components/brandguide/ColorSwatchSettings.tsx +1 -1
  307. package/src/components/brandguide/ColorsEditable.tsx +1 -0
  308. package/src/components/chat/ChatPanel.tsx +2 -3
  309. package/src/components/chat/ChatWidget.tsx +2 -3
  310. package/src/components/docfile/DocFilePane.tsx +4 -12
  311. package/src/components/editor/ChildBlockWrapper.tsx +13 -19
  312. package/src/components/editor/ConflictDialog.tsx +27 -35
  313. package/src/components/editor/DeleteButton.tsx +2 -2
  314. package/src/components/editor/DragHandle.tsx +16 -3
  315. package/src/components/editor/DropEdgeIndicator.tsx +36 -10
  316. package/src/components/editor/InsertButton.tsx +2 -2
  317. package/src/components/editor/NavGroupRow.tsx +43 -14
  318. package/src/components/editor/PagesModal.tsx +99 -26
  319. package/src/components/editor/SectionWrapper.tsx +64 -23
  320. package/src/components/editor/SettingsButton.tsx +2 -2
  321. package/src/components/editor/SettingsForm.tsx +19 -2
  322. package/src/components/editor/StatusIndicator.tsx +4 -4
  323. package/src/components/editor/TagIndicator.tsx +80 -0
  324. package/src/components/primitives/DocumentDropZone.tsx +1 -1
  325. package/src/components/primitives/EditableGrid.tsx +15 -14
  326. package/src/components/primitives/EditableList.tsx +27 -74
  327. package/src/components/primitives/HeadingSection.tsx +5 -2
  328. package/src/components/primitives/ResolvedMedia.tsx +13 -0
  329. package/src/components/primitives/{defineHeadingSection.tsx → define-heading-section.tsx} +1 -0
  330. package/src/components/primitives/index.ts +1 -1
  331. package/src/components/primitives/useEditablePlainText.ts +5 -5
  332. package/src/components/sections/Button/CTAButton.tsx +3 -1
  333. package/src/components/sections/DoDontMedia/DoDontMediaBlock.tsx +4 -4
  334. package/src/components/sections/DoDontMedia/index.tsx +12 -3
  335. package/src/components/sections/IconList/DoDontToggle.tsx +2 -2
  336. package/src/components/sections/IconList/IconList.tsx +70 -37
  337. package/src/components/sections/IconList/IconListSettings.tsx +55 -17
  338. package/src/components/sections/IconList/index.tsx +10 -3
  339. package/src/components/sections/LinkHeading/index.tsx +1 -1
  340. package/src/components/sections/Media/MediaBlock.tsx +13 -2
  341. package/src/components/sections/Media/index.tsx +12 -4
  342. package/src/components/sections/Reference/ReferenceList.tsx +214 -0
  343. package/src/components/sections/Reference/ReferenceSettings.tsx +159 -0
  344. package/src/components/sections/Reference/index.tsx +47 -0
  345. package/src/components/sections/SectionSummary/SectionSummaryBlock.tsx +476 -0
  346. package/src/components/sections/SectionSummary/SectionSummarySettings.tsx +126 -0
  347. package/src/components/sections/SectionSummary/index.tsx +149 -0
  348. package/src/components/sections/SubHeading/index.tsx +1 -1
  349. package/src/components/sections/SubSubHeading/index.tsx +1 -1
  350. package/src/components/sections/brand-guide.ts +4 -3
  351. package/src/components/sections/core-sections.ts +2 -0
  352. package/src/components/shared/BrandSwatchesContext.ts +13 -0
  353. package/src/components/shared/Button.tsx +47 -34
  354. package/src/components/shared/Checkbox.tsx +21 -12
  355. package/src/components/shared/ConfirmDialog.tsx +87 -0
  356. package/src/components/shared/ConfirmInline.tsx +90 -0
  357. package/src/components/shared/ErrorBoundary.tsx +6 -18
  358. package/src/components/shared/Fab.tsx +15 -5
  359. package/src/components/shared/FabStack.tsx +28 -0
  360. package/src/components/shared/HistoryChangesModal.tsx +43 -41
  361. package/src/components/shared/HistoryPopover.tsx +7 -20
  362. package/src/components/shared/ImageBackgroundField.tsx +92 -0
  363. package/src/components/shared/{UploadRejectionAlert.tsx → InlineAlert.tsx} +21 -5
  364. package/src/components/shared/Input.tsx +23 -7
  365. package/src/components/shared/Navigation.tsx +22 -17
  366. package/src/components/shared/Notice.tsx +59 -0
  367. package/src/components/shared/Popover.tsx +7 -11
  368. package/src/components/shared/ReferenceSourcesContext.ts +27 -0
  369. package/src/components/shared/Select.tsx +5 -2
  370. package/src/components/shared/SplitButton.tsx +11 -17
  371. package/src/components/shared/SummaryAnchorsContext.ts +11 -0
  372. package/src/components/shared/Textarea.tsx +17 -2
  373. package/src/components/shared/ToolbarFrame.tsx +28 -0
  374. package/src/components/shared/VideoSourceField.tsx +1 -1
  375. package/src/components/shared/copy.ts +18 -0
  376. package/src/components/shared/icons.tsx +1 -17
  377. package/src/components/shell/AudienceAddForm.tsx +2 -2
  378. package/src/components/shell/AudienceDetailsPanel.tsx +23 -5
  379. package/src/components/shell/AudienceRow.tsx +22 -15
  380. package/src/components/shell/BugReportFAB.tsx +50 -40
  381. package/src/components/shell/CredentialList.tsx +33 -13
  382. package/src/components/shell/DocInspector.tsx +3 -12
  383. package/src/components/shell/DocumentEditorOverlay.tsx +33 -23
  384. package/src/components/shell/EditorContext.tsx +7 -0
  385. package/src/components/shell/EditorLoginForm.tsx +26 -36
  386. package/src/components/shell/EditorModal.tsx +8 -10
  387. package/src/components/shell/EditorShell.tsx +501 -338
  388. package/src/components/shell/HistoryToolbar.tsx +24 -26
  389. package/src/components/shell/MediaFolderTile.tsx +8 -3
  390. package/src/components/shell/MediaLibraryModal.tsx +59 -60
  391. package/src/components/shell/ProcessingIndicator.tsx +2 -2
  392. package/src/components/shell/RestoreModal.tsx +17 -16
  393. package/src/components/shell/SetPasswordForm.tsx +3 -3
  394. package/src/components/shell/SiteSettingsChatbot.tsx +61 -42
  395. package/src/components/shell/SiteSettingsDisplay.tsx +10 -20
  396. package/src/components/shell/SiteSettingsModal.tsx +24 -2
  397. package/src/components/shell/SiteSettingsTags.tsx +143 -0
  398. package/src/components/shell/SiteSettingsUsers.tsx +36 -26
  399. package/src/components/shell/SiteSettingsViewerAccess.tsx +2 -2
  400. package/src/components/shell/ViewerLoginForm.tsx +5 -4
  401. package/src/components/shell/reference-sources.ts +36 -0
  402. package/src/deck/index.ts +2 -2
  403. package/src/document/index.ts +2 -2
  404. package/src/hooks/useBlockDnd.ts +11 -0
  405. package/src/hooks/useBuildStatus.ts +53 -6
  406. package/src/hooks/useDocumentUploads.ts +1 -1
  407. package/src/hooks/useEditorPersistence.ts +141 -35
  408. package/src/hooks/useEditorPublish.ts +27 -31
  409. package/src/hooks/useMediaPipeline.ts +30 -24
  410. package/src/hooks/useNotice.ts +45 -0
  411. package/src/hooks/useSortableNavRow.ts +11 -0
  412. package/src/hooks/useSortableRow.ts +22 -2
  413. package/src/hooks/useStackedEscape.ts +62 -0
  414. package/src/lib/block-dnd.ts +6 -1
  415. package/src/lib/block-move.ts +3 -0
  416. package/src/lib/brand-swatches.ts +32 -0
  417. package/src/lib/container-ops.ts +5 -3
  418. package/src/lib/dexie.ts +24 -2
  419. package/src/lib/env.ts +9 -0
  420. package/src/lib/events.ts +1 -2
  421. package/src/lib/image-background.ts +31 -0
  422. package/src/lib/image-variant-plan.ts +92 -0
  423. package/src/lib/index-ops.ts +9 -18
  424. package/src/lib/index.ts +20 -4
  425. package/src/lib/loader.ts +28 -88
  426. package/src/lib/media-ops.ts +112 -0
  427. package/src/lib/nav-tree.ts +104 -4
  428. package/src/lib/nav.ts +196 -36
  429. package/src/lib/pages.ts +2 -3
  430. package/src/lib/platform-broker.ts +32 -14
  431. package/src/lib/reference-type.ts +3 -0
  432. package/src/lib/reference-validation.ts +69 -0
  433. package/src/lib/references.ts +130 -0
  434. package/src/lib/registry.ts +37 -0
  435. package/src/lib/sidecar-diagnostics.ts +29 -0
  436. package/src/lib/sidecars.ts +221 -0
  437. package/src/lib/slug.ts +6 -0
  438. package/src/lib/storage-tier.ts +38 -0
  439. package/src/lib/tags.ts +165 -0
  440. package/src/lib/theme-vars.ts +23 -0
  441. package/src/media/index.ts +1 -1
  442. package/src/media/queue.ts +3 -0
  443. package/src/media/utils.ts +19 -1
  444. package/src/media/{videoPoster.ts → video-poster.ts} +2 -2
  445. package/src/media/worker.ts +17 -8
  446. package/src/schemas/index.ts +1 -0
  447. package/src/schemas/media.ts +7 -2
  448. package/src/schemas/shared.ts +24 -1
  449. package/src/schemas/site-config.ts +6 -1
  450. package/src/schemas/tags.ts +33 -0
  451. package/src/storage/memory.ts +178 -0
  452. package/src/storage/types.ts +16 -3
  453. package/src/types/database.ts +2 -0
  454. package/dist/chunk-DNY72JZN.js +0 -86
  455. package/dist/chunk-F2LYQPXV.js +0 -77
  456. package/dist/chunk-JH762VUH.js +0 -128
  457. package/dist/chunk-K7DYZXVU.js +0 -138
  458. package/dist/components/primitives/defineHeadingSection.d.ts.map +0 -1
  459. package/dist/components/primitives/gridDrop.d.ts.map +0 -1
  460. package/dist/components/shared/UploadRejectionAlert.d.ts.map +0 -1
  461. package/dist/components/shell/BuildStatusIndicator.d.ts +0 -7
  462. package/dist/components/shell/BuildStatusIndicator.d.ts.map +0 -1
  463. package/dist/components/shell/blockMoveDispatch.d.ts.map +0 -1
  464. package/dist/hooks/index.d.ts +0 -9
  465. package/dist/hooks/index.d.ts.map +0 -1
  466. package/dist/lib/grid.d.ts +0 -2
  467. package/dist/lib/grid.d.ts.map +0 -1
  468. package/dist/lib/safeRedirect.d.ts.map +0 -1
  469. package/dist/media/videoPoster.d.ts.map +0 -1
  470. package/src/components/brandguide/ColorSwatchSettings.d.ts.map +0 -1
  471. package/src/components/brandguide/Colors.d.ts.map +0 -1
  472. package/src/components/primitives/CustomParagraph.d.ts.map +0 -1
  473. package/src/components/primitives/EditableGrid.d.ts.map +0 -1
  474. package/src/components/primitives/EditablePlainText.d.ts.map +0 -1
  475. package/src/components/primitives/EditableRichText.d.ts.map +0 -1
  476. package/src/components/primitives/HeadingSection.d.ts.map +0 -1
  477. package/src/components/primitives/IconPicker.d.ts.map +0 -1
  478. package/src/components/primitives/LinkPopover.d.ts.map +0 -1
  479. package/src/components/primitives/MediaSettingsForms.d.ts.map +0 -1
  480. package/src/components/primitives/ResolvedMedia.d.ts.map +0 -1
  481. package/src/components/primitives/RichTextToolbar.d.ts.map +0 -1
  482. package/src/components/primitives/tiptap-presets.d.ts.map +0 -1
  483. package/src/components/primitives/useEditableCollection.d.ts.map +0 -1
  484. package/src/components/primitives/useEditablePlainText.d.ts.map +0 -1
  485. package/src/components/primitives/useEditableRichText.d.ts.map +0 -1
  486. package/src/components/sections/Button/CTAButton.d.ts.map +0 -1
  487. package/src/components/sections/Button/index.d.ts.map +0 -1
  488. package/src/components/sections/Colors/index.d.ts.map +0 -1
  489. package/src/components/sections/IconList/IconList.d.ts.map +0 -1
  490. package/src/components/sections/IconList/IconListSettings.d.ts.map +0 -1
  491. package/src/components/sections/IconList/index.d.ts.map +0 -1
  492. package/src/components/sections/LinkHeading/index.d.ts.map +0 -1
  493. package/src/components/sections/Prose/Prose.d.ts.map +0 -1
  494. package/src/components/sections/Prose/index.d.ts.map +0 -1
  495. package/src/components/sections/SubHeading/index.d.ts.map +0 -1
  496. package/src/components/sections/SubSubHeading/index.d.ts.map +0 -1
  497. package/src/components/sections/register-schemas.d.ts.map +0 -1
  498. package/src/components/shared/Button.d.ts.map +0 -1
  499. package/src/components/shared/Checkbox.d.ts.map +0 -1
  500. package/src/components/shared/FormLabel.d.ts.map +0 -1
  501. package/src/components/shared/IconButton.d.ts.map +0 -1
  502. package/src/components/shared/Input.d.ts.map +0 -1
  503. package/src/components/shared/Select.d.ts.map +0 -1
  504. package/src/components/shared/icons.d.ts.map +0 -1
  505. package/src/components/shell/BuildStatusIndicator.tsx +0 -59
  506. package/src/components/shell/MediaLibraryContext.d.ts.map +0 -1
  507. package/src/hooks/index.ts +0 -8
  508. package/src/hooks/useResolvedMedia.d.ts.map +0 -1
  509. package/src/lib/cn.d.ts.map +0 -1
  510. package/src/lib/grid.d.ts.map +0 -1
  511. package/src/lib/grid.ts +0 -7
  512. package/src/lib/icons.d.ts.map +0 -1
  513. package/src/lib/loader.d.ts.map +0 -1
  514. package/src/lib/nav.d.ts.map +0 -1
  515. package/src/lib/registry.d.ts.map +0 -1
  516. package/src/lib/sanitize.d.ts.map +0 -1
  517. package/src/media/github.d.ts.map +0 -1
  518. package/src/media/index.d.ts.map +0 -1
  519. package/src/media/queue.d.ts.map +0 -1
  520. package/src/media/resolve.d.ts.map +0 -1
  521. package/src/media/types.d.ts.map +0 -1
  522. package/src/media/utils.d.ts.map +0 -1
  523. package/src/media/videoPoster.d.ts.map +0 -1
  524. package/src/schemas/auth.d.ts.map +0 -1
  525. package/src/schemas/media-grid-options.d.ts.map +0 -1
  526. package/src/schemas/media.d.ts.map +0 -1
  527. package/src/schemas/sections.d.ts.map +0 -1
  528. package/src/schemas/shared.d.ts.map +0 -1
  529. package/src/schemas/site-config.d.ts.map +0 -1
  530. /package/dist/{chunk-B7VBSE5X.js → chunk-3JDTXOIG.js} +0 -0
  531. /package/dist/{chunk-CXZ5YJG4.js → chunk-L45X7DKX.js} +0 -0
  532. /package/src/components/primitives/{gridDrop.ts → grid-drop.ts} +0 -0
  533. /package/src/components/shell/{blockMoveDispatch.ts → block-move-dispatch.ts} +0 -0
  534. /package/src/lib/{safeRedirect.ts → safe-redirect.ts} +0 -0
@@ -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;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What a Sidecar reconcile changed. Reconcile repairs silently on every read, so
3
+ * these are only ever warnings: a validator reports them, nothing rejects on them.
4
+ * - dangling: ids of pages/sections the index does not have, dropped
5
+ * - duplicate-id: a group/tag id used again, later uses dropped
6
+ * - duplicate-member: a page placed twice / a section tagged twice, later placements dropped
7
+ * - unlisted: pages missing from the nav, appended to its end (nav only)
8
+ *
9
+ * Its own leaf so nav-tree and tags can report without importing the Sidecar
10
+ * module that imports them.
11
+ */
12
+ export type SidecarDiagnostic = {
13
+ code: "dangling" | "duplicate-id" | "duplicate-member" | "unlisted";
14
+ ids: string[];
15
+ };
16
+
17
+ export function diagnosticsCollector() {
18
+ const byCode = new Map<SidecarDiagnostic["code"], Set<string>>();
19
+ return {
20
+ add(code: SidecarDiagnostic["code"], id: string) {
21
+ const ids = byCode.get(code) ?? new Set<string>();
22
+ ids.add(id);
23
+ byCode.set(code, ids);
24
+ },
25
+ list(): SidecarDiagnostic[] {
26
+ return [...byCode].map(([code, ids]) => ({ code, ids: [...ids] }));
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,221 @@
1
+ import type { ZodType } from "zod";
2
+ import { NavEntrySchema, NavFileSchema, type NavEntry } from "../schemas/nav";
3
+ import { TagEntrySchema, TagsFileSchema, type TagEntry } from "../schemas/tags";
4
+ import type { SiteIndex } from "../schemas/site-config";
5
+ import { reconcileNavReport } from "./nav-tree";
6
+ import { mergeTags, reconcileTagsReport } from "./tags";
7
+ import type { SidecarDiagnostic } from "./sidecar-diagnostics";
8
+
9
+ export type { SidecarDiagnostic };
10
+
11
+ /**
12
+ * One definition per Sidecar — a content file kept beside index.json rather than
13
+ * on it, so a writer that predates it cannot drop it (CONTEXT.md). Every rule a
14
+ * caller would otherwise re-implement per file lives behind these: lenient
15
+ * parse, lifting the wire key off an index before IndexSchema strips it, and the
16
+ * two places the Sidecars genuinely differ — how a list is repaired against the
17
+ * index, and how concurrent edits rebase. Pure and browser-safe: the editor uses
18
+ * it during hydration, and callers do their own I/O.
19
+ */
20
+ export interface SidecarDefinition<E> {
21
+ /** Also the key the Sidecar travels under on the wire (`index.nav`, `index.tags`). */
22
+ name: "nav" | "tags";
23
+ path: string;
24
+ /** The key holding the entry list inside the file. */
25
+ fileKey: "entries" | "tags";
26
+ entrySchema: ZodType<E>;
27
+ fileSchema: ZodType<unknown>;
28
+ /** Total, pure and idempotent; also reports what it repaired. */
29
+ reconcile(entries: E[], index: SiteIndex): { entries: E[]; diagnostics: SidecarDiagnostic[] };
30
+ /** The list after a collab rebase; `undefined` when no side carries one. */
31
+ rebase(args: { base: E[] | undefined; local: E[] | undefined; remote: E[] | undefined }): E[] | undefined;
32
+ /** Words for a declined write's warning. */
33
+ words: {
34
+ /** What the file holds: "navigation entries". */
35
+ entries: string;
36
+ /** What did not land: "Page grouping changes". */
37
+ changes: string;
38
+ /** What a valid payload is: "a valid navigation tree". */
39
+ valid: string;
40
+ /** The expected wire shape, for an agent that sent the wrong one. */
41
+ shape: string;
42
+ };
43
+ }
44
+
45
+ export const NAV: SidecarDefinition<NavEntry> = {
46
+ name: "nav",
47
+ path: "src/content/nav.json",
48
+ fileKey: "entries",
49
+ entrySchema: NavEntrySchema,
50
+ fileSchema: NavFileSchema,
51
+ reconcile: (entries, index) => reconcileNavReport(entries, index.pages),
52
+ // Grouping is edited rarely and has no dirty tracking, so persisted remote
53
+ // state beats unsaved local edits. A remote without the Sidecar (an older
54
+ // server) keeps ours rather than flattening it.
55
+ rebase: ({ local, remote }) => (remote !== undefined ? remote : local),
56
+ words: {
57
+ entries: "navigation entries",
58
+ changes: "Page grouping changes",
59
+ valid: "a valid navigation tree",
60
+ shape: 'an array of {kind:"page",pageId} / {kind:"group",id,label,startCollapsed,pageIds} entries',
61
+ },
62
+ };
63
+
64
+ export const TAGS: SidecarDefinition<TagEntry> = {
65
+ name: "tags",
66
+ path: "src/content/tags.json",
67
+ fileKey: "tags",
68
+ entrySchema: TagEntrySchema,
69
+ fileSchema: TagsFileSchema,
70
+ reconcile: (entries, index) => reconcileTagsReport(entries, index.sections),
71
+ // Tagging is edited from every section's chrome, so remote-wins would drop
72
+ // unsaved tagging whenever a peer saved anything. See mergeTags.
73
+ rebase: mergeTags,
74
+ words: {
75
+ entries: "tag entries",
76
+ changes: "Tag changes",
77
+ valid: "a valid tag list",
78
+ shape: "an array of {id,label,sectionIds} entries",
79
+ },
80
+ };
81
+
82
+ // Widened once here: the definitions are invariant in their entry type, so a
83
+ // loop over both needs the common type.
84
+ export const SIDECARS: readonly SidecarDefinition<unknown>[] = [NAV, TAGS] as readonly SidecarDefinition<unknown>[];
85
+
86
+ /**
87
+ * A Sidecar as read. `degraded` means the file was present but held something
88
+ * this package version could not represent, so `entries` is missing it: the read
89
+ * is a Degraded read, never to be cached, posted back, or written over the file.
90
+ */
91
+ export type SidecarRead<E> = { entries: E[]; degraded: boolean };
92
+
93
+ /**
94
+ * Tolerance is per entry: one unknown entry kind (a newer package's addition, an
95
+ * authoring typo) drops that entry alone, not every group or tag on the site.
96
+ */
97
+ function readEntries<E>(def: SidecarDefinition<E>, raw: unknown): SidecarRead<E> {
98
+ if (!Array.isArray(raw)) {
99
+ console.warn(`[sidecars] ${def.name}: entry list is not a list; reading it as empty and degraded`);
100
+ return { entries: [], degraded: true };
101
+ }
102
+ const entries: E[] = [];
103
+ let dropped = 0;
104
+ for (const item of raw) {
105
+ const parsed = def.entrySchema.safeParse(item);
106
+ if (parsed.success) entries.push(parsed.data);
107
+ else dropped++;
108
+ }
109
+ if (dropped > 0) {
110
+ console.warn(`[sidecars] ${def.name}: dropped ${dropped} entr${dropped === 1 ? "y" : "ies"} this package version cannot represent; the read is degraded`);
111
+ }
112
+ return { entries, degraded: dropped > 0 };
113
+ }
114
+
115
+ /** Read a parsed Sidecar file. Absent (null/undefined) is an empty list, not degraded. */
116
+ export function readSidecar<E>(def: SidecarDefinition<E>, file: unknown): SidecarRead<E> {
117
+ if (file === undefined || file === null) return { entries: [], degraded: false };
118
+ return readEntries(def, (file as Record<string, unknown>)[def.fileKey]);
119
+ }
120
+
121
+ /**
122
+ * Read a Sidecar file's raw text. Present-but-unparseable JSON is degraded, not
123
+ * absent: a site whose nav.json is corrupt still has groups, and treating it as
124
+ * "never had any" would let a writer replace them.
125
+ */
126
+ export function readSidecarText<E>(def: SidecarDefinition<E>, text: string | undefined): SidecarRead<E> {
127
+ if (text === undefined) return { entries: [], degraded: false };
128
+ let file: unknown;
129
+ try {
130
+ file = JSON.parse(text);
131
+ } catch {
132
+ console.warn(`[sidecars] ${def.name}: ${def.path} is not valid JSON; the read is degraded`);
133
+ return { entries: [], degraded: true };
134
+ }
135
+ return readSidecar(def, file);
136
+ }
137
+
138
+ /**
139
+ * Lift the Sidecars off an index as it arrives on the wire, BEFORE anything
140
+ * parses it with IndexSchema — the parse strips unknown keys, and an index
141
+ * round-tripped through it has lost its Sidecars. A key the index does not carry
142
+ * is `undefined` (omitted: writers leave the file alone); a carried key is read
143
+ * with the file's per-entry leniency, into fresh objects that never alias the
144
+ * caller's.
145
+ */
146
+ export function liftSidecars(rawIndex: unknown): { nav?: SidecarRead<NavEntry>; tags?: SidecarRead<TagEntry> } {
147
+ const raw = rawIndex !== null && typeof rawIndex === "object" ? (rawIndex as Record<string, unknown>) : {};
148
+ return {
149
+ ...(raw.nav !== undefined ? { nav: readEntries(NAV, raw.nav) } : {}),
150
+ ...(raw.tags !== undefined ? { tags: readEntries(TAGS, raw.tags) } : {}),
151
+ };
152
+ }
153
+
154
+ /** The Sidecar file on the branch a write lands on, as the writer read it. */
155
+ export type SidecarFileState =
156
+ | { kind: "absent" }
157
+ | { kind: "present"; text: string }
158
+ /** The read itself failed, so nothing is known about the file. */
159
+ | { kind: "unreadable" };
160
+
161
+ export type SidecarWritePlan =
162
+ | { action: "untouched" }
163
+ | { action: "write"; path: string; content: string }
164
+ /** Declined. The rest of the write still lands, so the warning is what tells the caller. */
165
+ | { action: "skip"; warning: string };
166
+
167
+ /**
168
+ * What a write does to one Sidecar file, given what was posted (from
169
+ * liftSidecars) and the file on the branch. Every Sidecar write REPLACES the
170
+ * file, and the posted list was built from what this package version could
171
+ * READ — so a file holding anything this version cannot fully parse (an entry
172
+ * from a newer package, or a corrupt file) is read-only: replacing it would
173
+ * destroy those entries with a clean write. The same holds for a posted
174
+ * Degraded read.
175
+ *
176
+ * Emptiness is judged on the POSTED list, before reconcile: an empty list never
177
+ * creates a file (every editor save carries the Sidecars, and nav's reconcile
178
+ * would turn [] into the page list), while an empty list over an existing file
179
+ * replaces it wholesale. A written list is reconciled against the index being
180
+ * written, so it never names a page or section that write removed.
181
+ */
182
+ export function planSidecarWrite<E>(
183
+ def: SidecarDefinition<E>,
184
+ posted: SidecarRead<E> | undefined,
185
+ branch: SidecarFileState,
186
+ index: SiteIndex,
187
+ ): SidecarWritePlan {
188
+ if (posted === undefined) return { action: "untouched" };
189
+ const skip = (reason: string): SidecarWritePlan => ({
190
+ action: "skip",
191
+ warning: `${def.path} was left unchanged: ${reason} ${def.words.changes} from this save did not land. Upgrade the site's @drawnagency packages (or repair ${def.path} directly in the repo), then retry.`,
192
+ });
193
+
194
+ if (posted.degraded) {
195
+ // Leaving the file alone is the right action; saying nothing is not — an
196
+ // agent that posted a malformed list would otherwise believe it landed.
197
+ return {
198
+ action: "skip",
199
+ warning: `${def.name} was not ${def.words.valid}, so ${def.path} was left unchanged (it held entries this package version cannot read). Expected ${def.words.shape}.`,
200
+ };
201
+ }
202
+ if (branch.kind === "unreadable") {
203
+ return skip("the current file could not be read to confirm the replacement loses nothing.");
204
+ }
205
+ if (branch.kind === "present") {
206
+ let existing: unknown;
207
+ try {
208
+ existing = JSON.parse(branch.text);
209
+ } catch {
210
+ return skip("the current file is not valid JSON, so this version cannot prove a replacement keeps its entries.");
211
+ }
212
+ if (!def.fileSchema.safeParse(existing).success) {
213
+ return skip(`the current file holds ${def.words.entries} this package version cannot represent (likely written by a newer version).`);
214
+ }
215
+ } else if (posted.entries.length === 0) {
216
+ return { action: "untouched" };
217
+ }
218
+
219
+ const { entries } = def.reconcile(posted.entries, index);
220
+ return { action: "write", path: def.path, content: JSON.stringify({ [def.fileKey]: entries }, null, 2) };
221
+ }
@@ -0,0 +1,6 @@
1
+ // The one slugifier for page slugs and media names. Its own leaf, with no
2
+ // imports, so the media worker can use it without bundling the schemas
3
+ // lib/pages pulls in.
4
+ export function slugifyPageSlug(input: string): string {
5
+ return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
6
+ }
@@ -0,0 +1,38 @@
1
+ import { DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES } from "../schemas/documents";
2
+ import { MEDIA_GIT_MAX_BYTES, MEDIA_BUCKET_MAX_BYTES, type MediaConfig } from "../schemas/media";
3
+
4
+ // Where a file's bytes live, decided by its size: committed to the site repo
5
+ // (git), stored in the optional bucket, or refused. Every upload path and the
6
+ // write path's backstop decide this the same way, and every message quotes the
7
+ // limits through formatBytes, so a limit changes in one place.
8
+
9
+ export { DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES, MEDIA_GIT_MAX_BYTES, MEDIA_BUCKET_MAX_BYTES };
10
+
11
+ /**
12
+ * The MCP connector's own document ceiling, half the bucket cap. The binding
13
+ * constraint is memory, not time: a presigned PUT cannot stream, so the whole
14
+ * body is buffered (peak about 2x the file). The browser path has no such
15
+ * limit because those bytes never pass through a function.
16
+ */
17
+ export const DOCUMENT_CONNECTOR_MAX_BYTES = 104_857_600;
18
+
19
+ export type StorageTier = "git" | "bucket" | "too_large";
20
+
21
+ type MediaLimits = Pick<MediaConfig, "maxFileSize" | "maxAssetSize">;
22
+
23
+ /** Media limits come from the site's `media` config; documents' are fixed. */
24
+ export function tierFor(
25
+ kind: "document" | "media",
26
+ bytes: number,
27
+ media: MediaLimits = { maxFileSize: MEDIA_GIT_MAX_BYTES, maxAssetSize: MEDIA_BUCKET_MAX_BYTES },
28
+ ): StorageTier {
29
+ const [gitMax, bucketMax] =
30
+ kind === "document" ? [DOCUMENT_GIT_MAX_BYTES, DOCUMENT_BUCKET_MAX_BYTES] : [media.maxFileSize, media.maxAssetSize];
31
+ if (bytes <= gitMax) return "git";
32
+ return bytes <= bucketMax ? "bucket" : "too_large";
33
+ }
34
+
35
+ /** "2.5 MB", "100 MB" — one decimal at most, in binary megabytes. */
36
+ export function formatBytes(bytes: number): string {
37
+ return `${Math.round((bytes / 1024 / 1024) * 10) / 10} MB`;
38
+ }
@@ -0,0 +1,165 @@
1
+ import type { TagEntry } from "../schemas/tags";
2
+ import type { SiteIndex } from "../schemas/site-config";
3
+ import { diagnosticsCollector, type SidecarDiagnostic } from "./sidecar-diagnostics";
4
+
5
+ /**
6
+ * Repair the tag sidecar against the current section set.
7
+ *
8
+ * Total, pure, and idempotent — the same contract as reconcileNav, for the same
9
+ * reason: tags.json survives writers that do not know it exists, so it can name
10
+ * sections an older writer (or an agent) has since deleted, and the repair has
11
+ * to happen on read rather than by rejecting content. It is deliberately NOT a
12
+ * schema refinement: parse-rejecting content would leave the editor unable to
13
+ * load the site it needs to repair it.
14
+ *
15
+ * Drops assignments to sections absent from `sections`, duplicate section ids
16
+ * within a tag, duplicate tag ids (first wins), and tags with no usable id. A
17
+ * tag with no assignments left is KEPT — the registry outlives its sections.
18
+ * Every entry is rebuilt key by key, so the output always parses as a
19
+ * TagsFile and never shares structure with the input (the editor holds this in
20
+ * React state, where a shared array would let a later edit mutate prior state).
21
+ */
22
+ export function reconcileTags(entries: TagEntry[], sections: SiteIndex["sections"]): TagEntry[] {
23
+ return reconcileTagsReport(entries, sections).entries;
24
+ }
25
+
26
+ /** reconcileTags plus what the repair changed — what a validator reports. */
27
+ export function reconcileTagsReport(
28
+ entries: TagEntry[],
29
+ sections: SiteIndex["sections"],
30
+ ): { entries: TagEntry[]; diagnostics: SidecarDiagnostic[] } {
31
+ const report = diagnosticsCollector();
32
+ const entryList = Array.isArray(entries) ? entries : [];
33
+ const validIds = new Set(sections && typeof sections === "object" ? Object.keys(sections) : []);
34
+
35
+ const seenTagIds = new Set<string>();
36
+ const result: TagEntry[] = [];
37
+ for (const entry of entryList) {
38
+ if (!entry || typeof entry !== "object") continue;
39
+ if (typeof entry.id !== "string" || entry.id === "") continue;
40
+ if (seenTagIds.has(entry.id)) {
41
+ report.add("duplicate-id", entry.id);
42
+ continue;
43
+ }
44
+ seenTagIds.add(entry.id);
45
+
46
+ const claimed = Array.isArray(entry.sectionIds) ? entry.sectionIds : [];
47
+ const sectionIds: string[] = [];
48
+ for (const id of claimed) {
49
+ if (typeof id !== "string") continue;
50
+ if (!validIds.has(id)) report.add("dangling", id);
51
+ else if (sectionIds.includes(id)) report.add("duplicate-member", id);
52
+ else sectionIds.push(id);
53
+ }
54
+ result.push({ id: entry.id, label: typeof entry.label === "string" ? entry.label : "", sectionIds });
55
+ }
56
+ return { entries: result, diagnostics: report.list() };
57
+ }
58
+
59
+ /**
60
+ * The ONLY sanctioned way to read the tags. Always reconciles, so callers never
61
+ * reason about whether index.tags is present, stale, or names deleted sections.
62
+ */
63
+ export function siteTags(index: SiteIndex): TagEntry[] {
64
+ return reconcileTags(index.tags ?? [], index.sections);
65
+ }
66
+
67
+ /** Display fallback for blank tag labels — mirrors groupDisplayLabel. */
68
+ export function tagDisplayLabel(label: string): string {
69
+ return label || "Untitled tag";
70
+ }
71
+
72
+ /** The tag already using this name (case- and whitespace-insensitive) — what
73
+ * an inline create reuses, and what the registry refuses to duplicate. */
74
+ export function findTagByLabel(tags: TagEntry[], label: string): TagEntry | null {
75
+ const wanted = label.trim().toLowerCase();
76
+ if (!wanted) return null;
77
+ return tags.find((t) => t.label.trim().toLowerCase() === wanted) ?? null;
78
+ }
79
+
80
+ export function addTag(index: SiteIndex, id: string, label: string, sectionId?: string): SiteIndex {
81
+ const tags = siteTags(index);
82
+ if (tags.some((t) => t.id === id)) return { ...index, tags };
83
+ return { ...index, tags: reconcileTags([...tags, { id, label, sectionIds: sectionId ? [sectionId] : [] }], index.sections) };
84
+ }
85
+
86
+ export function renameTag(index: SiteIndex, id: string, label: string): SiteIndex {
87
+ return { ...index, tags: siteTags(index).map((t) => (t.id === id ? { ...t, label } : t)) };
88
+ }
89
+
90
+ export function deleteTag(index: SiteIndex, id: string): SiteIndex {
91
+ return { ...index, tags: siteTags(index).filter((t) => t.id !== id) };
92
+ }
93
+
94
+ export function setSectionTagged(index: SiteIndex, sectionId: string, tagId: string, tagged: boolean): SiteIndex {
95
+ return {
96
+ ...index,
97
+ tags: siteTags(index).map((t) => {
98
+ if (t.id !== tagId) return t;
99
+ const without = t.sectionIds.filter((id) => id !== sectionId);
100
+ return { ...t, sectionIds: tagged ? [...without, sectionId] : without };
101
+ }),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Three-way merge of the tag sidecar for the collab rebase.
107
+ *
108
+ * Nav's rebase rule is "the remote's persisted tree wins", which is safe there
109
+ * because grouping is edited rarely. Tags are edited from every section's
110
+ * chrome, so the same rule would silently drop unsaved tagging whenever a peer
111
+ * saved anything at all. Instead, local edits are diffed against the clean
112
+ * baseline (`base`) and replayed onto the remote:
113
+ * - a tag created locally is appended; one deleted locally is dropped;
114
+ * - a local rename wins, otherwise the remote label stands;
115
+ * - per (tag, section) pair, a local add or remove is replayed;
116
+ * - a tag the REMOTE deleted stays deleted, local assignments included —
117
+ * persisted state beats unsaved edits to a tag that no longer exists.
118
+ * With no baseline to diff against, the remote wins (nav's rule). A remote with
119
+ * no tags at all (an older server that does not attach the sidecar) leaves the
120
+ * local tags intact rather than wiping them. Inputs are never mutated; the
121
+ * result is not reconciled — readers do that through siteTags.
122
+ */
123
+ export function mergeTags(args: {
124
+ base: TagEntry[] | undefined;
125
+ local: TagEntry[] | undefined;
126
+ remote: TagEntry[] | undefined;
127
+ }): TagEntry[] | undefined {
128
+ const { base, local, remote } = args;
129
+ if (remote === undefined) return local?.map((t) => ({ ...t, sectionIds: [...t.sectionIds] }));
130
+ if (base === undefined || local === undefined) return remote.map((t) => ({ ...t, sectionIds: [...t.sectionIds] }));
131
+
132
+ // First occurrence wins, matching reconcileTags — `base` is an unreconciled
133
+ // server read, and a last-wins map would invent a diff against a duplicate.
134
+ const firstById = (tags: TagEntry[]) => {
135
+ const byId = new Map<string, TagEntry>();
136
+ for (const t of tags) if (!byId.has(t.id)) byId.set(t.id, t);
137
+ return byId;
138
+ };
139
+ const baseById = firstById(base);
140
+ const localById = firstById(local);
141
+ const remoteIds = new Set(remote.map((t) => t.id));
142
+
143
+ const result: TagEntry[] = [];
144
+ for (const r of remote) {
145
+ const b = baseById.get(r.id);
146
+ const l = localById.get(r.id);
147
+ if (b && !l) continue; // deleted locally
148
+ const members = [...r.sectionIds];
149
+ let label = r.label;
150
+ if (b && l) {
151
+ if (l.label !== b.label) label = l.label;
152
+ for (const id of l.sectionIds) if (!b.sectionIds.includes(id) && !members.includes(id)) members.push(id);
153
+ for (const id of b.sectionIds) {
154
+ if (l.sectionIds.includes(id)) continue;
155
+ const at = members.indexOf(id);
156
+ if (at !== -1) members.splice(at, 1);
157
+ }
158
+ }
159
+ result.push({ id: r.id, label, sectionIds: members });
160
+ }
161
+ for (const l of local) {
162
+ if (!remoteIds.has(l.id) && !baseById.has(l.id)) result.push({ ...l, sectionIds: [...l.sectionIds] });
163
+ }
164
+ return result;
165
+ }
@@ -1,5 +1,6 @@
1
1
  import type { SiteConfig } from "../schemas/site-config";
2
2
  import { suggestAccessibleOn } from "./contrast";
3
+ import { cornerRadiusValue } from "./corner-radius";
3
4
 
4
5
  /**
5
6
  * Computes the CSS value for the brand `--color-primary` / `--color-primary-contrast`
@@ -50,3 +51,25 @@ export function onPrimaryVarValue(c?: Partial<ThemeColorConfig> | null): string
50
51
  const dark = c?.primaryColorDark ? suggestAccessibleOn(c.primaryColorDark) : null;
51
52
  return lightDark(light, dark, c?.darkMode === "optional");
52
53
  }
54
+
55
+ /**
56
+ * The full inline `<html style>` string both `Layout.astro` and
57
+ * `LoginLayout.astro` set at SSR time — one assembly point for the 9 custom
58
+ * properties instead of the same template literal copied into both files.
59
+ * Byte-for-byte identical to what those files built inline before this
60
+ * existed; changing the property order or defaults here is a client-facing
61
+ * change to every site's `<html>` tag, not a refactor.
62
+ */
63
+ export function themeStyle(siteConfig?: SiteConfig | null): string {
64
+ return (
65
+ `--color-primary: ${primaryVarValue(siteConfig)}; ` +
66
+ `--color-primary-contrast: ${accentVarValue(siteConfig)}; ` +
67
+ `--color-on-primary: ${onPrimaryVarValue(siteConfig)}; ` +
68
+ `--font-heading: ${siteConfig?.headingFont ?? "system-ui"}, system-ui, sans-serif; ` +
69
+ `--font-body: ${siteConfig?.bodyFont ?? "system-ui"}, system-ui, sans-serif; ` +
70
+ `--heading-text-transform: ${siteConfig?.uppercaseHeadings !== false ? "uppercase" : "none"}; ` +
71
+ `--subheading-text-transform: ${siteConfig?.uppercaseSubheadings !== false ? "uppercase" : "none"}; ` +
72
+ `--nav-text-transform: ${siteConfig?.uppercaseNavHeadings !== false ? "uppercase" : "none"}; ` +
73
+ `--radius-outer: ${cornerRadiusValue(siteConfig?.cornerRadius)};`
74
+ );
75
+ }
@@ -5,7 +5,7 @@ export * from "./provider";
5
5
  export * from "./utils";
6
6
  export * from "./queue";
7
7
  export * from "./resolve";
8
- export { generateVideoPoster } from "./videoPoster";
8
+ export { generateVideoPoster } from "./video-poster";
9
9
  export { composePreviewImage, type PreviewImageInputs } from "./compose-preview";
10
10
 
11
11
  const MEDIA_KEY = Symbol.for("@drawnagency/primitives/media");
@@ -1,4 +1,5 @@
1
1
  import type { MediaKind } from "./types";
2
+ import type { ImageVariantPlan } from "../lib/image-variant-plan";
2
3
 
3
4
  export interface QueueInput {
4
5
  buffer: ArrayBuffer;
@@ -23,6 +24,7 @@ export interface QueueItem {
23
24
  posterBlob?: Blob;
24
25
  width: number;
25
26
  height: number;
27
+ plan?: ImageVariantPlan;
26
28
  };
27
29
  }
28
30
 
@@ -116,6 +118,7 @@ export class ProcessingQueue {
116
118
  posterBlob: msg.posterBlob,
117
119
  width: msg.width,
118
120
  height: msg.height,
121
+ plan: msg.plan,
119
122
  };
120
123
  this.options.onEvent({ type: "complete", item: { ...item } });
121
124
  // The event above carries the only reference anyone reads. Holding ours
@@ -1,4 +1,4 @@
1
- import { slugifyPageSlug } from "../lib/pages";
1
+ import { slugifyPageSlug } from "../lib/slug";
2
2
  import type { MediaItem } from "./types";
3
3
 
4
4
  export async function hashFileBuffer(buffer: ArrayBuffer): Promise<string> {
@@ -48,6 +48,24 @@ export function mimeToExt(mime: string): string {
48
48
  return MIME_TO_EXT[mime] ?? "bin";
49
49
  }
50
50
 
51
+ // Editor preview URL for a library item, matching what /api/media/[id] serves:
52
+ // SVGs carry kind "image" but have no webp variants, only original.svg; rasters
53
+ // get their smallest variant; animated and video items get their playable
54
+ // original (a <video> src, not a still). Null for a raster with no variants.
55
+ export function mediaThumbnailUrl(item: MediaItem): string | null {
56
+ if (item.mimeType === "image/svg+xml" || item.kind !== "image") {
57
+ return `/api/media/${item.id}/original.${mimeToExt(item.mimeType)}`;
58
+ }
59
+ if (item.variants.length === 0) return null;
60
+ const smallest = item.variants.reduce((a, b) => (a.width < b.width ? a : b));
61
+ return `/api/media/${item.id}/${smallest.width}.webp`;
62
+ }
63
+
64
+ // For <img>-only previews (cover, logo): null unless the item is a still image.
65
+ export function imageThumbnailUrl(item: MediaItem | undefined): string | null {
66
+ return item?.kind === "image" ? mediaThumbnailUrl(item) : null;
67
+ }
68
+
51
69
  export function displayFilenameExt(mime: string): string {
52
70
  const ext = MIME_TO_EXT[mime];
53
71
  return ext ? `.${ext}` : "";
@@ -26,7 +26,7 @@ export function generateVideoPoster(
26
26
  }, 10_000);
27
27
 
28
28
  video.addEventListener("error", () => {
29
- safeReject(new Error("Failed to load video for poster generation"));
29
+ safeReject(new Error("Couldn't load video for poster generation"));
30
30
  }, { once: true });
31
31
 
32
32
  video.muted = true;
@@ -56,7 +56,7 @@ export function generateVideoPoster(
56
56
  settled = true;
57
57
  cleanup();
58
58
  if (!posterBlob) {
59
- reject(new Error("Failed to create poster blob"));
59
+ reject(new Error("Couldn't create poster blob"));
60
60
  return;
61
61
  }
62
62
  resolve({ posterBlob, width: w, height: h, duration: video.duration });