@drawnagency/primitives 0.1.117 → 0.1.119

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 (263) hide show
  1. package/dist/{chunk-O37AV27P.js → chunk-5JYHTHVR.js} +28 -0
  2. package/dist/{chunk-W72BPGR3.js → chunk-BZKHHUIN.js} +44 -8
  3. package/dist/{chunk-CEFFP3CH.js → chunk-G6O3TYPJ.js} +297 -83
  4. package/dist/{chunk-L7WTERU5.js → chunk-HFHM4MNK.js} +33 -10
  5. package/dist/{chunk-RIITGTV3.js → chunk-LQHBC7C6.js} +37 -2
  6. package/dist/{chunk-VY3IFGE3.js → chunk-QAMAIDXZ.js} +1 -1
  7. package/dist/{chunk-LNQUUC2S.js → chunk-RIN2QCYJ.js} +9 -11
  8. package/dist/{chunk-HUOW3ZV2.js → chunk-UNFPATDE.js} +8 -10
  9. package/dist/{chunk-MSBHMY3C.js → chunk-YKKGVSJH.js} +35 -37
  10. package/dist/{chunk-67P34WNW.js → chunk-ZKVQMIMJ.js} +3 -20
  11. package/dist/collab/rebase.js +4 -5
  12. package/dist/components/chat/ChatComposer.d.ts +0 -4
  13. package/dist/components/chat/ChatComposer.d.ts.map +1 -1
  14. package/dist/components/chat/ChatPanel.d.ts +2 -3
  15. package/dist/components/chat/ChatPanel.d.ts.map +1 -1
  16. package/dist/components/chat/markdown.d.ts +2 -3
  17. package/dist/components/chat/markdown.d.ts.map +1 -1
  18. package/dist/components/editor/PagesModal.d.ts +0 -2
  19. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  20. package/dist/components/editor/index.js +15 -19
  21. package/dist/components/primitives/EditablePlainText.d.ts.map +1 -1
  22. package/dist/components/primitives/index.js +11 -15
  23. package/dist/components/sections/brand-guide.js +10 -13
  24. package/dist/components/sections/register-schemas.js +16 -21
  25. package/dist/components/sections/register.js +16 -21
  26. package/dist/components/shell/AudienceChips.d.ts +1 -3
  27. package/dist/components/shell/AudienceChips.d.ts.map +1 -1
  28. package/dist/components/shell/AudienceDetailsPanel.d.ts +3 -1
  29. package/dist/components/shell/AudienceDetailsPanel.d.ts.map +1 -1
  30. package/dist/components/shell/EditorShell.d.ts +0 -34
  31. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  32. package/dist/components/shell/SiteSettingsModal.d.ts.map +1 -1
  33. package/dist/hooks/useSortableNavRow.d.ts +1 -1
  34. package/dist/hooks/useSortableRow.d.ts +1 -1
  35. package/dist/index.js +83 -90
  36. package/dist/lib/index-ops.js +5 -6
  37. package/dist/lib/index.js +27 -32
  38. package/dist/lib/pages.d.ts +0 -2
  39. package/dist/lib/pages.d.ts.map +1 -1
  40. package/dist/lib/sidecars.js +2 -2
  41. package/dist/lib/site-validation.js +13 -14
  42. package/dist/lib/storage-tier.js +4 -4
  43. package/dist/schemas/index.js +42 -45
  44. package/dist/schemas/site-config.d.ts +1 -277
  45. package/dist/schemas/site-config.d.ts.map +1 -1
  46. package/package.json +2 -42
  47. package/src/components/chat/ChatComposer.tsx +0 -4
  48. package/src/components/chat/ChatPanel.tsx +2 -3
  49. package/src/components/chat/markdown.ts +2 -3
  50. package/src/components/editor/PagesModal.tsx +1 -15
  51. package/src/components/editor/StatusIndicator.tsx +4 -4
  52. package/src/components/primitives/EditablePlainText.tsx +27 -3
  53. package/src/components/shell/AudienceChips.tsx +1 -3
  54. package/src/components/shell/AudienceDetailsPanel.tsx +6 -4
  55. package/src/components/shell/EditorShell.tsx +18 -114
  56. package/src/components/shell/SiteSettingsModal.tsx +0 -11
  57. package/src/lib/pages.ts +0 -22
  58. package/src/schemas/index.ts +0 -4
  59. package/src/schemas/site-config.ts +4 -32
  60. package/dist/chunk-4TZJZU2T.js +0 -92
  61. package/dist/chunk-DIMDRBFK.js +0 -11
  62. package/dist/chunk-FV7JQRCN.js +0 -94
  63. package/dist/chunk-IIDKW7AV.js +0 -41
  64. package/dist/chunk-JCDZAFX2.js +0 -224
  65. package/dist/chunk-MM7KZS5C.js +0 -30
  66. package/dist/chunk-QQ3S3P5P.js +0 -38
  67. package/dist/chunk-TEOAKGZA.js +0 -784
  68. package/dist/chunk-VAYZJKVR.js +0 -209
  69. package/dist/components/shell/DocInspector.d.ts +0 -18
  70. package/dist/components/shell/DocInspector.d.ts.map +0 -1
  71. package/dist/components/shell/DocumentEditorOverlay.d.ts +0 -29
  72. package/dist/components/shell/DocumentEditorOverlay.d.ts.map +0 -1
  73. package/dist/components/shell/DocumentPresentOverlay.d.ts +0 -30
  74. package/dist/components/shell/DocumentPresentOverlay.d.ts.map +0 -1
  75. package/dist/components/shell/SiteSettingsDecks.d.ts +0 -13
  76. package/dist/components/shell/SiteSettingsDecks.d.ts.map +0 -1
  77. package/dist/deck/DeckComponentView.d.ts +0 -13
  78. package/dist/deck/DeckComponentView.d.ts.map +0 -1
  79. package/dist/deck/DeckPlayer.d.ts +0 -26
  80. package/dist/deck/DeckPlayer.d.ts.map +0 -1
  81. package/dist/deck/DeckSlideView.d.ts +0 -17
  82. package/dist/deck/DeckSlideView.d.ts.map +0 -1
  83. package/dist/deck/DeckStage.d.ts +0 -16
  84. package/dist/deck/DeckStage.d.ts.map +0 -1
  85. package/dist/deck/components/DeckImage.d.ts +0 -14
  86. package/dist/deck/components/DeckImage.d.ts.map +0 -1
  87. package/dist/deck/components/DeckImageGrid.d.ts +0 -27
  88. package/dist/deck/components/DeckImageGrid.d.ts.map +0 -1
  89. package/dist/deck/components/DeckRichText.d.ts +0 -22
  90. package/dist/deck/components/DeckRichText.d.ts.map +0 -1
  91. package/dist/deck/components/DeckVideo.d.ts +0 -15
  92. package/dist/deck/components/DeckVideo.d.ts.map +0 -1
  93. package/dist/deck/editing.d.ts +0 -50
  94. package/dist/deck/editing.d.ts.map +0 -1
  95. package/dist/deck/flip.d.ts +0 -11
  96. package/dist/deck/flip.d.ts.map +0 -1
  97. package/dist/deck/index.d.ts +0 -24
  98. package/dist/deck/index.d.ts.map +0 -1
  99. package/dist/deck/index.js +0 -51
  100. package/dist/deck/layouts/CenteredTextLayout.d.ts +0 -12
  101. package/dist/deck/layouts/CenteredTextLayout.d.ts.map +0 -1
  102. package/dist/deck/layouts/ColumnSplitLayout.d.ts +0 -14
  103. package/dist/deck/layouts/ColumnSplitLayout.d.ts.map +0 -1
  104. package/dist/deck/layouts/FullBleedImageLayout.d.ts +0 -14
  105. package/dist/deck/layouts/FullBleedImageLayout.d.ts.map +0 -1
  106. package/dist/deck/media.d.ts +0 -33
  107. package/dist/deck/media.d.ts.map +0 -1
  108. package/dist/deck/tokens.d.ts +0 -22
  109. package/dist/deck/tokens.d.ts.map +0 -1
  110. package/dist/deck/useStageScale.d.ts +0 -10
  111. package/dist/deck/useStageScale.d.ts.map +0 -1
  112. package/dist/deck-builder/DeckBuilderApp.d.ts +0 -30
  113. package/dist/deck-builder/DeckBuilderApp.d.ts.map +0 -1
  114. package/dist/deck-builder/DeckBuilderTopBar.d.ts +0 -24
  115. package/dist/deck-builder/DeckBuilderTopBar.d.ts.map +0 -1
  116. package/dist/deck-builder/DeckChatPanel.d.ts +0 -45
  117. package/dist/deck-builder/DeckChatPanel.d.ts.map +0 -1
  118. package/dist/deck-builder/DeckEditor.d.ts +0 -37
  119. package/dist/deck-builder/DeckEditor.d.ts.map +0 -1
  120. package/dist/deck-builder/DeckLibrary.d.ts +0 -22
  121. package/dist/deck-builder/DeckLibrary.d.ts.map +0 -1
  122. package/dist/deck-builder/DeckSlideSurface.d.ts +0 -31
  123. package/dist/deck-builder/DeckSlideSurface.d.ts.map +0 -1
  124. package/dist/deck-builder/deck-api.d.ts +0 -76
  125. package/dist/deck-builder/deck-api.d.ts.map +0 -1
  126. package/dist/deck-builder/deck-chat-client.d.ts +0 -44
  127. package/dist/deck-builder/deck-chat-client.d.ts.map +0 -1
  128. package/dist/deck-builder/deck-chat-history.d.ts +0 -10
  129. package/dist/deck-builder/deck-chat-history.d.ts.map +0 -1
  130. package/dist/deck-builder/index.d.ts +0 -13
  131. package/dist/deck-builder/index.d.ts.map +0 -1
  132. package/dist/deck-builder/index.js +0 -1498
  133. package/dist/deck-builder/starter-deck.d.ts +0 -23
  134. package/dist/deck-builder/starter-deck.d.ts.map +0 -1
  135. package/dist/deck-builder/types.d.ts +0 -29
  136. package/dist/deck-builder/types.d.ts.map +0 -1
  137. package/dist/deck-builder/useSortableSlide.d.ts +0 -29
  138. package/dist/deck-builder/useSortableSlide.d.ts.map +0 -1
  139. package/dist/document/assets/fonts/albra-grotesk-bold.woff +0 -0
  140. package/dist/document/assets/fonts/albra-grotesk-regular.woff +0 -0
  141. package/dist/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
  142. package/dist/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
  143. package/dist/document/components/DocCoverPage.d.ts +0 -11
  144. package/dist/document/components/DocCoverPage.d.ts.map +0 -1
  145. package/dist/document/components/DocPage.d.ts +0 -10
  146. package/dist/document/components/DocPage.d.ts.map +0 -1
  147. package/dist/document/components/DocRail.d.ts +0 -28
  148. package/dist/document/components/DocRail.d.ts.map +0 -1
  149. package/dist/document/components/DocSectionPage.d.ts +0 -12
  150. package/dist/document/components/DocSectionPage.d.ts.map +0 -1
  151. package/dist/document/components/DocViewer.d.ts +0 -21
  152. package/dist/document/components/DocViewer.d.ts.map +0 -1
  153. package/dist/document/components/Wordmark.d.ts +0 -4
  154. package/dist/document/components/Wordmark.d.ts.map +0 -1
  155. package/dist/document/components/measure.d.ts +0 -7
  156. package/dist/document/components/measure.d.ts.map +0 -1
  157. package/dist/document/components/useDocPages.d.ts +0 -12
  158. package/dist/document/components/useDocPages.d.ts.map +0 -1
  159. package/dist/document/engine/blocks.d.ts +0 -2
  160. package/dist/document/engine/blocks.d.ts.map +0 -1
  161. package/dist/document/engine/harvest.d.ts +0 -3
  162. package/dist/document/engine/harvest.d.ts.map +0 -1
  163. package/dist/document/engine/masonry.d.ts +0 -26
  164. package/dist/document/engine/masonry.d.ts.map +0 -1
  165. package/dist/document/engine/paginate.d.ts +0 -43
  166. package/dist/document/engine/paginate.d.ts.map +0 -1
  167. package/dist/document/engine/settings.d.ts +0 -91
  168. package/dist/document/engine/settings.d.ts.map +0 -1
  169. package/dist/document/engine/types.d.ts +0 -44
  170. package/dist/document/engine/types.d.ts.map +0 -1
  171. package/dist/document/index.d.ts +0 -13
  172. package/dist/document/index.d.ts.map +0 -1
  173. package/dist/document/index.js +0 -1030
  174. package/dist/document/styles/document.css +0 -217
  175. package/dist/lib/deck-access.d.ts +0 -86
  176. package/dist/lib/deck-access.d.ts.map +0 -1
  177. package/dist/lib/deck-access.js +0 -58
  178. package/dist/lib/deck-dexie.d.ts +0 -88
  179. package/dist/lib/deck-dexie.d.ts.map +0 -1
  180. package/dist/lib/deck-dexie.js +0 -22
  181. package/dist/lib/deck-edit.d.ts +0 -67
  182. package/dist/lib/deck-edit.d.ts.map +0 -1
  183. package/dist/lib/deck-media-assets.d.ts +0 -27
  184. package/dist/lib/deck-media-assets.d.ts.map +0 -1
  185. package/dist/lib/deck-media-assets.js +0 -25
  186. package/dist/lib/deck-media-refs.d.ts +0 -35
  187. package/dist/lib/deck-media-refs.d.ts.map +0 -1
  188. package/dist/lib/deck-media-refs.js +0 -36
  189. package/dist/lib/deck-tool.d.ts +0 -25
  190. package/dist/lib/deck-tool.d.ts.map +0 -1
  191. package/dist/lib/deck-tool.js +0 -58
  192. package/dist/lib/document-input.d.ts +0 -25
  193. package/dist/lib/document-input.d.ts.map +0 -1
  194. package/dist/schemas/deck-meta.d.ts +0 -85
  195. package/dist/schemas/deck-meta.d.ts.map +0 -1
  196. package/dist/schemas/deck-meta.js +0 -64
  197. package/dist/schemas/deck.d.ts +0 -954
  198. package/dist/schemas/deck.d.ts.map +0 -1
  199. package/dist/schemas/deck.js +0 -65
  200. package/src/components/shell/DocInspector.tsx +0 -314
  201. package/src/components/shell/DocumentEditorOverlay.tsx +0 -179
  202. package/src/components/shell/DocumentPresentOverlay.tsx +0 -87
  203. package/src/components/shell/SiteSettingsDecks.tsx +0 -41
  204. package/src/deck/DeckComponentView.tsx +0 -28
  205. package/src/deck/DeckPlayer.tsx +0 -166
  206. package/src/deck/DeckSlideView.tsx +0 -38
  207. package/src/deck/DeckStage.tsx +0 -39
  208. package/src/deck/components/DeckImage.tsx +0 -38
  209. package/src/deck/components/DeckImageGrid.tsx +0 -254
  210. package/src/deck/components/DeckRichText.tsx +0 -111
  211. package/src/deck/components/DeckVideo.tsx +0 -81
  212. package/src/deck/editing.tsx +0 -201
  213. package/src/deck/flip.ts +0 -70
  214. package/src/deck/index.ts +0 -30
  215. package/src/deck/layouts/CenteredTextLayout.tsx +0 -29
  216. package/src/deck/layouts/ColumnSplitLayout.tsx +0 -69
  217. package/src/deck/layouts/FullBleedImageLayout.tsx +0 -58
  218. package/src/deck/media.tsx +0 -46
  219. package/src/deck/tokens.ts +0 -57
  220. package/src/deck/useStageScale.ts +0 -44
  221. package/src/deck-builder/DeckBuilderApp.tsx +0 -572
  222. package/src/deck-builder/DeckBuilderTopBar.tsx +0 -62
  223. package/src/deck-builder/DeckChatPanel.tsx +0 -334
  224. package/src/deck-builder/DeckEditor.tsx +0 -258
  225. package/src/deck-builder/DeckLibrary.tsx +0 -195
  226. package/src/deck-builder/DeckSlideSurface.tsx +0 -55
  227. package/src/deck-builder/deck-api.ts +0 -177
  228. package/src/deck-builder/deck-chat-client.ts +0 -129
  229. package/src/deck-builder/deck-chat-history.ts +0 -62
  230. package/src/deck-builder/index.ts +0 -12
  231. package/src/deck-builder/starter-deck.ts +0 -86
  232. package/src/deck-builder/types.ts +0 -24
  233. package/src/deck-builder/useSortableSlide.ts +0 -102
  234. package/src/document/assets/fonts/albra-grotesk-bold.woff +0 -0
  235. package/src/document/assets/fonts/albra-grotesk-regular.woff +0 -0
  236. package/src/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
  237. package/src/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
  238. package/src/document/components/DocCoverPage.tsx +0 -45
  239. package/src/document/components/DocPage.tsx +0 -40
  240. package/src/document/components/DocRail.tsx +0 -189
  241. package/src/document/components/DocSectionPage.tsx +0 -48
  242. package/src/document/components/DocViewer.tsx +0 -313
  243. package/src/document/components/Wordmark.tsx +0 -18
  244. package/src/document/components/measure.tsx +0 -133
  245. package/src/document/components/useDocPages.ts +0 -95
  246. package/src/document/engine/blocks.ts +0 -116
  247. package/src/document/engine/harvest.ts +0 -95
  248. package/src/document/engine/masonry.ts +0 -158
  249. package/src/document/engine/paginate.ts +0 -232
  250. package/src/document/engine/settings.ts +0 -125
  251. package/src/document/engine/types.ts +0 -64
  252. package/src/document/index.ts +0 -18
  253. package/src/document/styles/document.css +0 -217
  254. package/src/lib/deck-access.ts +0 -132
  255. package/src/lib/deck-dexie.ts +0 -190
  256. package/src/lib/deck-edit.ts +0 -196
  257. package/src/lib/deck-media-assets.ts +0 -57
  258. package/src/lib/deck-media-refs.ts +0 -67
  259. package/src/lib/deck-tool.ts +0 -95
  260. package/src/lib/document-input.ts +0 -57
  261. package/src/schemas/deck-meta.ts +0 -88
  262. package/src/schemas/deck.ts +0 -478
  263. package/dist/{chunk-DJABADOO.js → chunk-AJ2QOAZI.js} +3 -3
@@ -63,9 +63,8 @@ const ERROR_COPY: Record<string, string> = {
63
63
  };
64
64
  const RETRYABLE = new Set(["rate_limited", "upstream_error"]);
65
65
 
66
- /** Re-exported from ./markdown, where the rendering rules now live so the deck
67
- * assistant shares them. Kept here because tests and callers import it from
68
- * this module. */
66
+ /** Re-exported from ./markdown, where the rendering rules live. Kept here
67
+ * because tests and callers import it from this module. */
69
68
  export { assistantHtml };
70
69
 
71
70
  /** Parse our /api/chat SSE frames out of a fetch body. Exported for tests. */
@@ -2,9 +2,8 @@ import { marked } from "marked";
2
2
  import { sanitizeHtml } from "../../lib/sanitize";
3
3
 
4
4
  /**
5
- * The assistant-message treatment, shared by every chat surface (brand chat's
6
- * ChatPanel and the Deck Builder's docked assistant). Extracted so a second
7
- * chat pane is a second CALLER, not a second copy of the rendering rules —
5
+ * The assistant-message treatment, shared by every chat surface. A new chat
6
+ * pane is a second CALLER, not a second copy of the rendering rules —
8
7
  * particularly the sanitizer gate below, which is a security property.
9
8
  */
10
9
 
@@ -2,10 +2,6 @@ import { useCallback, useState, useEffect, useId } from "react";
2
2
  import { Check, ChevronDown, ChevronUp, Home } from "lucide-react";
3
3
  import type { SiteIndex, Page } from "../../schemas/site-config";
4
4
  import type { Audience } from "../../auth/types";
5
- // Sanctioned outside->inside import: document/ is a sealed subtree, but the
6
- // Document mode toggle needs the patch shape it edits (mirrors the same
7
- // sanctioned import in schemas/site-config.ts and lib/pages.ts).
8
- import type { DocumentOptions } from "../../document/engine/settings";
9
5
  import { Button } from "../shared/Button";
10
6
  import { ConfirmDialog } from "../shared/ConfirmDialog";
11
7
  import { IconButton } from "../shared/IconButton";
@@ -37,7 +33,6 @@ export interface PagesModalProps {
37
33
  onSetArchived: (pageId: string, archived: boolean) => void;
38
34
  onSetFields: (pageId: string, patch: Partial<Pick<Page, "title" | "slug" | "showInNav">>) => void;
39
35
  onSetAudience: (pageId: string, access: string[]) => void;
40
- onSetDocument: (pageId: string, patch: Partial<DocumentOptions>) => void;
41
36
  onConfirmDelete: (pageId: string) => void;
42
37
  }
43
38
 
@@ -114,7 +109,6 @@ export function PagesModal(props: PagesModalProps) {
114
109
  }}
115
110
  onSetFields={(patch) => props.onSetFields(page.id, patch)}
116
111
  onSetAudience={(access) => props.onSetAudience(page.id, access)}
117
- onSetDocument={(patch) => props.onSetDocument(page.id, patch)}
118
112
  onRequestDelete={() =>
119
113
  page.order.length === 0 ? props.onConfirmDelete(page.id) : setDeletingId(page.id)
120
114
  }
@@ -222,7 +216,7 @@ export function PagesModal(props: PagesModalProps) {
222
216
  }
223
217
 
224
218
  function PageRow({
225
- page, audiences, expanded, isNew, otherSlugs, location, entryIndex, liveSiblingRawIndexes, onMove, onToggleExpand, onSetHome, onSetArchived, onSetFields, onSetAudience, onSetDocument, onRequestDelete,
219
+ page, audiences, expanded, isNew, otherSlugs, location, entryIndex, liveSiblingRawIndexes, onMove, onToggleExpand, onSetHome, onSetArchived, onSetFields, onSetAudience, onRequestDelete,
226
220
  }: {
227
221
  page: Page; audiences: Audience[]; expanded: boolean;
228
222
  isNew: boolean; otherSlugs: string[];
@@ -236,7 +230,6 @@ function PageRow({
236
230
  onToggleExpand: () => void; onSetHome: () => void; onSetArchived: (v: boolean) => void;
237
231
  onSetFields: (patch: Partial<Pick<Page, "title" | "slug" | "showInNav">>) => void;
238
232
  onSetAudience: (access: string[]) => void;
239
- onSetDocument: (patch: Partial<DocumentOptions>) => void;
240
233
  onRequestDelete: () => void;
241
234
  }) {
242
235
  const draggable = location !== undefined;
@@ -433,13 +426,6 @@ function PageRow({
433
426
  </div>
434
427
  <Toggle label="Show in menu" checked={page.showInNav} onChange={(v) => onSetFields({ showInNav: v })} />
435
428
  </div>
436
- <div className="flex items-center justify-between gap-4 px-3 py-2.5">
437
- <div className="min-w-0">
438
- <div className="text-sm text-base-contrast">Document mode</div>
439
- <div className="text-xs text-base-contrast/70">Adds a presentation view of this page.</div>
440
- </div>
441
- <Toggle label="Document mode" checked={page.document?.enabled ?? false} onChange={(enabled) => onSetDocument({ enabled })} />
442
- </div>
443
429
  </div>
444
430
  </div>
445
431
  <div className="flex items-center gap-2">
@@ -74,10 +74,10 @@ export function deriveStatusDisplay({ mainStatus, savedStatus, contentDiffers, i
74
74
  }
75
75
 
76
76
  const dotColorClasses: Record<StatusColor, string> = {
77
- live: "bg-status-live-text",
78
- draft: "bg-status-draft-text",
79
- archived: "bg-white border-status-draft-text",
80
- modified: "bg-status-modified-text",
77
+ live: "bg-status-live-dot",
78
+ draft: "bg-status-draft-dot",
79
+ archived: "bg-white border-status-archived-dot-border",
80
+ modified: "bg-status-modified-dot",
81
81
  };
82
82
 
83
83
 
@@ -18,6 +18,25 @@ interface EditablePlainTextProps
18
18
  placeholder?: string;
19
19
  }
20
20
 
21
+ function caretOffset(el: HTMLElement): number | null {
22
+ const sel = window.getSelection();
23
+ if (!sel || sel.rangeCount === 0) return null;
24
+ const range = sel.getRangeAt(0);
25
+ if (!el.contains(range.endContainer)) return null;
26
+ const before = document.createRange();
27
+ before.selectNodeContents(el);
28
+ before.setEnd(range.endContainer, range.endOffset);
29
+ return before.toString().length;
30
+ }
31
+
32
+ function placeCaret(el: HTMLElement, offset: number) {
33
+ const sel = window.getSelection();
34
+ if (!sel) return;
35
+ // textContent leaves a single text node, or none for an empty string.
36
+ if (el.firstChild) sel.collapse(el.firstChild, offset);
37
+ else sel.collapse(el, 0);
38
+ }
39
+
21
40
  export function EditablePlainText({
22
41
  tag,
23
42
  value,
@@ -48,9 +67,14 @@ export function EditablePlainText({
48
67
 
49
68
  // Set textContent via ref (safe — plain text only, no HTML injection)
50
69
  useEffect(() => {
51
- if (ref.current && ref.current.textContent !== localValue) {
52
- ref.current.textContent = localValue;
53
- }
70
+ const el = ref.current;
71
+ if (!el || el.textContent === localValue) return;
72
+ // A value that differs from the DOM while the field is focused (a peer
73
+ // save, a rebase or a 409 recovery landing mid-typing) replaces the text
74
+ // node, which collapses the caret to offset 0. Put it back, clamped.
75
+ const caret = document.activeElement === el ? caretOffset(el) : null;
76
+ el.textContent = localValue;
77
+ if (caret !== null) placeCaret(el, Math.min(caret, localValue.length));
54
78
  }, [localValue]);
55
79
 
56
80
  const onInput = useCallback(() => {
@@ -2,9 +2,7 @@ import { cn } from "../../lib/cn";
2
2
  import { contrastColor } from "../../lib/contrast";
3
3
  import type { Audience } from "../../auth/types";
4
4
 
5
- /** Multi-select audience chips — the site-settings idiom for "who sees this".
6
- * Shared by the chatbot and deck-builder opt-ins so the two gates, which have
7
- * identical strict-opt-in semantics, cannot come to look like different rules. */
5
+ /** Multi-select audience chips — the site-settings idiom for "who sees this". */
8
6
  export function AudienceChips({
9
7
  audiences,
10
8
  selected,
@@ -27,6 +27,8 @@ interface Props {
27
27
  * an audience-filtered site.
28
28
  */
29
29
  canImpersonate?: boolean;
30
+ /** Open the view in a new tab — set by the editor, so it keeps the editor tab. */
31
+ viewAsInNewTab?: boolean;
30
32
  }
31
33
 
32
34
  const PLACEHOLDER_COLOR = "#94a3b8";
@@ -174,7 +176,7 @@ function PageRow({ page }: { page: OverviewPage }) {
174
176
  );
175
177
  }
176
178
 
177
- function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean }) {
179
+ function AudienceBlock({ audience, revealSupported, canImpersonate, viewAsInNewTab }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean; viewAsInNewTab: boolean }) {
178
180
  const [open, setOpen] = useState(false);
179
181
  const { overview } = audience;
180
182
 
@@ -208,7 +210,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
208
210
  </button>
209
211
  {canImpersonate && (
210
212
  // A plain form POST: the route sets the cookie and 303s to the viewer site.
211
- <form method="post" action="/api/auth/impersonate" className="shrink-0">
213
+ <form method="post" action="/api/auth/impersonate" target={viewAsInNewTab ? "_blank" : undefined} className="shrink-0">
212
214
  <input type="hidden" name="audience" value={audience.name} />
213
215
  <Button type="submit" variant="secondary" size="sm" aria-label={`View the site as ${audience.displayName}`}>
214
216
  <span className="inline-flex items-center gap-1.5">
@@ -260,7 +262,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
260
262
  * Reachable by editors and the site's default audience (the client), so the
261
263
  * client can hand a new partner access without going through us.
262
264
  */
263
- export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false }: Props) {
265
+ export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false, viewAsInNewTab = false }: Props) {
264
266
  if (audiences.length === 0) {
265
267
  return (
266
268
  <p className="text-sm text-base-contrast-light">
@@ -272,7 +274,7 @@ export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonat
272
274
  return (
273
275
  <div className="flex flex-col gap-3">
274
276
  {audiences.map((a) => (
275
- <AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} />
277
+ <AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} viewAsInNewTab={viewAsInNewTab} />
276
278
  ))}
277
279
  </div>
278
280
  );
@@ -1,4 +1,4 @@
1
- import { Fragment, Suspense, lazy, useContext, useState, useCallback, useEffect, useRef, useMemo, type ReactNode } from "react";
1
+ import { Fragment, useContext, useState, useCallback, useEffect, useRef, useMemo, type ReactNode } from "react";
2
2
 
3
3
  import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
4
4
  import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
@@ -15,10 +15,6 @@ import { composePreviewImage, PREVIEW_COMPOSE_VERSION } from "../../media/compos
15
15
  import { derivePreviewInputs, servedPreviewSize } from "../../media/preview-inputs";
16
16
  import { resolveMedia } from "../../media/resolve";
17
17
  import { getMediaProvider } from "../../media";
18
- // Sanctioned outside->inside import: document/ is a sealed subtree, but the
19
- // Document mode toggle wiring needs the patch shape it edits (mirrors the same
20
- // sanctioned import in schemas/site-config.ts and lib/pages.ts).
21
- import type { DocumentOptions } from "../../document/engine/settings";
22
18
  import { SiteConfigSchema } from "../../schemas/site-config";
23
19
  import { ensureSanitizer } from "../../lib/sanitize";
24
20
  import { resolveChatbotName } from "../../lib/chatbot";
@@ -45,17 +41,6 @@ import { buildInsertMenuOptions, resolveInsertedContent } from "../../lib/insert
45
41
 
46
42
  ensureSectionsRegistered();
47
43
  import { BugReportFAB } from "./BugReportFAB";
48
- // Lazy boundary: the document editor/presentation overlays (and the whole
49
- // document engine they pull in) must not weigh /edit on sites that never use
50
- // document mode — they load only when a document-enabled page opens the overlay.
51
- // Named exports → adapt to the default-export shape React.lazy expects (mirrors
52
- // the ChatPanel lazy import convention in ChatWidget).
53
- const DocumentEditorOverlay = lazy(() =>
54
- import("./DocumentEditorOverlay").then((m) => ({ default: m.DocumentEditorOverlay })),
55
- );
56
- const DocumentPresentOverlay = lazy(() =>
57
- import("./DocumentPresentOverlay").then((m) => ({ default: m.DocumentPresentOverlay })),
58
- );
59
44
  import { SectionWrapper } from "../editor/SectionWrapper";
60
45
  import { SectionOrderingModal } from "../editor/SectionOrderingModal";
61
46
  import { MoveSectionModal } from "../editor/MoveSectionModal";
@@ -103,7 +88,7 @@ import type { ViewerCredential } from "../../schemas/audience";
103
88
  import {
104
89
  homePage, pageById, pageBySlug, pageDisplayTitle, addSectionToPage, removeSectionFromPages,
105
90
  reorderVisibleSectionInPage, audiencePasses,
106
- setHomePage, setPageArchived, setPageFields, setPageAudience, setPageDocument, deletePage, addPage,
91
+ setHomePage, setPageArchived, setPageFields, setPageAudience, deletePage, addPage,
107
92
  moveSection as moveSectionReducer,
108
93
  } from "../../lib/pages";
109
94
  import { PagesContext } from "../shared/PagesContext";
@@ -128,7 +113,7 @@ import { FabStack } from "../shared/FabStack";
128
113
  import { Tooltip } from "../shared/Tooltip";
129
114
  import { SegmentedControl } from "../shared/SegmentedControl";
130
115
  import { computeDisplaySections } from "./view-branch";
131
- import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, Settings, X, FileText, Presentation, Eye, Pencil } from "lucide-react";
116
+ import { ImageIcon, ListOrderedIcon, FilesIcon, JoystickIcon, Settings, X, Eye, Pencil } from "lucide-react";
132
117
  import { ErrorBoundary } from "../shared/ErrorBoundary";
133
118
  import { HistoryToolbar } from "./HistoryToolbar";
134
119
  import { RestoreModal } from "./RestoreModal";
@@ -299,7 +284,6 @@ export default function EditorShell({
299
284
  const [showRestoreModal, setShowRestoreModal] = useState(false);
300
285
  const [showOrderingModal, setShowOrderingModal] = useState(false);
301
286
  const [showPagesModal, setShowPagesModal] = useState(false);
302
- const [showDocumentEditor, setShowDocumentEditor] = useState(false);
303
287
  const [isRestoring, setIsRestoring] = useState(false);
304
288
  const [rejectedUploads, setRejectedUploads] = useState<{ name: string; reason: "size" | "type" }[]>([]);
305
289
  // Surfaces useMediaPipeline's onUploadError (oversized/disallowed files,
@@ -328,6 +312,9 @@ export default function EditorShell({
328
312
  // null = the auth provider has no named sign-ins (`.env` mode → route 501s),
329
313
  // which is also what turns the reveal buttons off.
330
314
  const [viewerCredentials, setViewerCredentials] = useState<ViewerCredential[] | null>(null);
315
+ // Gates "View as" in Audience Details: without password protection no visitor
316
+ // sees an audience-filtered site, and the impersonate route refuses.
317
+ const [passwordEnabled, setPasswordEnabled] = useState(false);
331
318
  const [movingSectionId, setMovingSectionId] = useState<string | null>(null);
332
319
  // Set by recoverConflict when a rebase surfaces overlapping edits; drives the
333
320
  // keep-mine/take-theirs dialog. `resolve` is the promise resolver recovery
@@ -485,26 +472,6 @@ export default function EditorShell({
485
472
  return audiences.filter((a) => resolvedActivePage.access.includes(a.name));
486
473
  }, [resolvedActivePage, audiences]);
487
474
 
488
- // The sections shown on the canvas when previewing the published ("live")
489
- // branch — the SAME list computeDisplaySections renders for viewBranch "live".
490
- // Only meaningful while viewSections !== null (branch view); the read-only
491
- // document presentation overlay renders exactly these so it mirrors the canvas.
492
- // (viewSections is set only for the live branch, so its non-null-ness IS the
493
- // "branch view" signal at this level.)
494
- const branchDisplaySections = useMemo(
495
- () =>
496
- viewSections && resolvedActivePage
497
- ? computeDisplaySections({
498
- isEditMode: false,
499
- viewBranch: "live",
500
- sections,
501
- viewSections,
502
- activePageSectionIds: resolvedActivePage.order,
503
- })
504
- : [],
505
- [viewSections, resolvedActivePage, sections],
506
- );
507
-
508
475
  const getPageHeadings = useCallback(
509
476
  (pageId: string) => {
510
477
  const page = pageById(siteIndex, pageId);
@@ -1221,6 +1188,15 @@ export default function EditorShell({
1221
1188
  if (!cancelled) setViewerCredentials(null);
1222
1189
  }
1223
1190
  })();
1191
+ (async () => {
1192
+ try {
1193
+ const res = await fetch("/api/auth/audiences");
1194
+ const enabled = res.ok && (await res.json()).passwordEnabled === true;
1195
+ if (!cancelled) setPasswordEnabled(enabled);
1196
+ } catch {
1197
+ if (!cancelled) setPasswordEnabled(false);
1198
+ }
1199
+ })();
1224
1200
  return () => { cancelled = true; };
1225
1201
  }, [showAudienceDetails]);
1226
1202
 
@@ -1855,11 +1831,6 @@ export default function EditorShell({
1855
1831
  persistence.markIndexDirty(); setLocalChangesExist(true);
1856
1832
  }, [persistence]);
1857
1833
 
1858
- const handleSetPageDocument = useCallback((id: string, patch: Partial<DocumentOptions>) => {
1859
- setSiteIndex((prev) => setPageDocument(prev, id, patch));
1860
- persistence.markIndexDirty(); setLocalChangesExist(true);
1861
- }, [persistence]);
1862
-
1863
1834
  const handleSetPageAudience = useCallback((id: string, access: string[]) => {
1864
1835
  const { index: next, resetSectionIds } = setPageAudience(siteIndexRef.current, id, access);
1865
1836
  setSiteIndex(next);
@@ -2164,55 +2135,12 @@ export default function EditorShell({
2164
2135
  audiences={audiences}
2165
2136
  authoringDefaults={authoringDefaults}
2166
2137
  onAuthoringDefaultsChange={onAuthoringDefaultsChange}
2167
- docEditEnabled={Boolean(resolvedActivePage?.document?.enabled)}
2168
- onDocEditClick={() => setShowDocumentEditor(true)}
2169
2138
  // Bug reports persist via Supabase. On the password-only
2170
2139
  // (zero-Supabase) path there's no backend and the editor session
2171
2140
  // has userId: null, so the button would always 401 — hide it there.
2172
2141
  showBugReport={!capabilities.passwordOnly}
2173
2142
  />
2174
2143
 
2175
- {/* Deck Builder (#34). Editors are super-admins over decks and are
2176
- never gated by deckBuilder.audiences, so this is unconditional.
2177
- An <a>, not a click handler: builder mode is its own route.
2178
- Standalone (not in EditorToolbar's FabStack): bottom-60 clears
2179
- the stack's three members with room to spare. Taking over the
2180
- editor top bar in place is #38 — this just goes there. */}
2181
- <Fab href="/decks?next=%2Fedit" label="Build a deck" column="editor" className="bottom-60">
2182
- <Presentation size={20} aria-hidden="true" />
2183
- </Fab>
2184
- {showDocumentEditor && resolvedActivePage?.document?.enabled && (
2185
- <Suspense fallback={<DocumentOverlayFallback />}>
2186
- {/* Branch/history view (viewSections !== null → previewing the
2187
- published branch): presentation only, never the editor. Drew's
2188
- product decision — old versions are the record, and we have no
2189
- way to edit them, so opening the editor over one would be a lie.
2190
- The draft view (viewSections === null) still opens the real
2191
- editor. NOTE: viewSections drives the "Published" branch preview
2192
- only; the separate historySelectEvent commit view
2193
- (EditorContext.historyState) is the OTHER read-only case — the
2194
- document FAB in EditorToolbar is gated on !historyState, which
2195
- keeps this overlay from ever being opened onto it in the
2196
- first place. */}
2197
- {viewSections !== null ? (
2198
- <DocumentPresentOverlay
2199
- page={resolvedActivePage}
2200
- sections={branchDisplaySections}
2201
- manifest={mediaManifest}
2202
- onClose={() => setShowDocumentEditor(false)}
2203
- />
2204
- ) : (
2205
- <DocumentEditorOverlay
2206
- page={resolvedActivePage}
2207
- sections={activeSections}
2208
- manifest={mediaManifest}
2209
- onUpdateDocument={(patch) => handleSetPageDocument(resolvedActivePage.id, patch)}
2210
- onClose={() => setShowDocumentEditor(false)}
2211
- />
2212
- )}
2213
- </Suspense>
2214
- )}
2215
-
2216
2144
  {/* Editor chat FAB: chat while editing, against the DRAFT chatbot
2217
2145
  config (tasks + prompt reflect unsaved changes; grounding is
2218
2146
  published content). Gated only on the platform switch — editors
@@ -2308,7 +2236,7 @@ export default function EditorShell({
2308
2236
  current working copy — add or remove sign-ins under Site settings → Audiences.
2309
2237
  </p>
2310
2238
  <div className="mt-6">
2311
- <AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} />
2239
+ <AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} canImpersonate={passwordEnabled} viewAsInNewTab />
2312
2240
  </div>
2313
2241
  </div>
2314
2242
  ) : (
@@ -2456,7 +2384,6 @@ export default function EditorShell({
2456
2384
  onSetArchived={handleSetArchived}
2457
2385
  onSetFields={handleSetPageFields}
2458
2386
  onSetAudience={handleSetPageAudience}
2459
- onSetDocument={handleSetPageDocument}
2460
2387
  onConfirmDelete={handleDeletePage}
2461
2388
  />
2462
2389
  </EditorModal>
@@ -2963,15 +2890,6 @@ function EditorContent({
2963
2890
  );
2964
2891
  }
2965
2892
 
2966
- // Suspense fallback while the lazy document overlay chunk loads.
2967
- function DocumentOverlayFallback() {
2968
- return (
2969
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-base" role="status" aria-live="polite">
2970
- <span className="text-sm text-base-contrast-light">Loading document editor…</span>
2971
- </div>
2972
- );
2973
- }
2974
-
2975
2893
  function GlobalModal() {
2976
2894
  const { modalState, closeModal } = useEditorModal();
2977
2895
 
@@ -3099,8 +3017,6 @@ function EditorToolbar({
3099
3017
  audiences,
3100
3018
  authoringDefaults,
3101
3019
  onAuthoringDefaultsChange,
3102
- docEditEnabled,
3103
- onDocEditClick,
3104
3020
  showBugReport,
3105
3021
  }: {
3106
3022
  buttonState: "synced" | "publish" | "saveAndPublish";
@@ -3127,11 +3043,6 @@ function EditorToolbar({
3127
3043
  audiences: Audience[];
3128
3044
  authoringDefaults: AuthoringDefaults;
3129
3045
  onAuthoringDefaultsChange: (next: AuthoringDefaults) => void;
3130
- /** Whether the active page has document mode on — gates the "Edit as
3131
- * document" FAB this toolbar stacks alongside its own mode-toggle FAB.
3132
- * Owned by EditorShell (resolvedActivePage lives above the Provider). */
3133
- docEditEnabled: boolean;
3134
- onDocEditClick: () => void;
3135
3046
  /** Whether to render BugReportFAB in this toolbar's FabStack — false on
3136
3047
  * the password-only (zero-Supabase) path, which has no backend for it. */
3137
3048
  showBugReport: boolean;
@@ -3255,17 +3166,10 @@ function EditorToolbar({
3255
3166
  <FabStack>
3256
3167
  {/* Bug reports persist via Supabase; hidden on the password-only
3257
3168
  path (showBugReport, threaded from EditorShell's capabilities).
3258
- History is presentation only, never editing (branch-preview
3259
- already routes to DocumentPresentOverlay; !historyState covers
3260
- the separate historySelectEvent commit view) — FabStack
3169
+ History is presentation only, never editing — FabStack
3261
3170
  unmounts with the rest of this block whenever history is
3262
- active, which hides all three FABs together. */}
3171
+ active, which hides its FABs together. */}
3263
3172
  {showBugReport && <BugReportFAB stacked />}
3264
- {docEditEnabled && (
3265
- <Fab stacked label="Edit as document" column="editor" onClick={onDocEditClick}>
3266
- <FileText size={20} aria-hidden="true" />
3267
- </Fab>
3268
- )}
3269
3173
  <Fab
3270
3174
  stacked
3271
3175
  label={isEditMode ? "Switch to view mode" : "Switch to edit mode"}
@@ -6,7 +6,6 @@ import { SiteSettingsViewerAccess } from "./SiteSettingsViewerAccess";
6
6
  import { SiteSettingsDisplay } from "./SiteSettingsDisplay";
7
7
  import { SiteSettingsUsers } from "./SiteSettingsUsers";
8
8
  import { SiteSettingsChatbot } from "./SiteSettingsChatbot";
9
- import { SiteSettingsDecks } from "./SiteSettingsDecks";
10
9
  import { SiteSettingsTags } from "./SiteSettingsTags";
11
10
  import type { SiteTagging } from "../../lib/registry";
12
11
  import type { SiteConfig } from "../../schemas/site-config";
@@ -88,16 +87,6 @@ export function SiteSettingsModal({
88
87
  </div>
89
88
  ),
90
89
  },
91
- {
92
- id: "decks",
93
- label: "Decks",
94
- show: true,
95
- content: (
96
- <div data-testid="site-settings-tab-panel" className="flex flex-1 flex-col overflow-y-auto px-6 py-4">
97
- <SiteSettingsDecks siteConfig={siteConfig} onSiteConfigChange={onSiteConfigChange} audiences={audiences} />
98
- </div>
99
- ),
100
- },
101
90
  {
102
91
  id: "tags",
103
92
  label: "Tags",
package/src/lib/pages.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import type { Page, SiteIndex, SectionMeta } from "../schemas/site-config";
2
2
  import { RESERVED_SLUGS } from "../schemas/site-config";
3
- // Sanctioned outside->inside import: document/ is a sealed subtree, but pages.ts
4
- // is allowed to reach into its engine to write the page-level document options
5
- // block (mirrors the same sanctioned import in schemas/site-config.ts).
6
- import { documentOptionsSchema, type DocumentOptions } from "../document/engine/settings";
7
3
  import { navEntries } from "./nav-tree";
8
4
  import { slugifyPageSlug } from "./slug";
9
5
 
@@ -101,24 +97,6 @@ export function setPageFields(
101
97
  return mapPages(index, (p) => (p.id === pageId ? { ...p, ...patch } : p));
102
98
  }
103
99
 
104
- export function setPageDocument(index: SiteIndex, pageId: string, patch: Partial<DocumentOptions>): SiteIndex {
105
- return {
106
- ...index,
107
- pages: index.pages.map((p) => {
108
- if (p.id !== pageId) return p;
109
- const current = p.document ?? documentOptionsSchema.parse({});
110
- return {
111
- ...p,
112
- document: documentOptionsSchema.parse({
113
- ...current,
114
- ...patch,
115
- sections: { ...current.sections, ...(patch.sections ?? {}) },
116
- }),
117
- };
118
- }),
119
- };
120
- }
121
-
122
100
  export function setPageAudience(
123
101
  index: SiteIndex,
124
102
  pageId: string,
@@ -9,7 +9,3 @@ export * from "./media-grid-options";
9
9
  export * from "./auth";
10
10
  export * from "./link";
11
11
  export * from "./documents";
12
- // ./deck is deliberately NOT re-exported here: this barrel is reached from the
13
- // primitives root export, which viewer pages pull in. Decks are not site
14
- // content and not a section type — reach the deck schema through its own
15
- // subpath export instead (see tests/deck/viewer-isolation.test.ts).
@@ -2,10 +2,6 @@ import { z } from "zod";
2
2
  import { MediaConfigSchema } from "./media";
3
3
  import { HexColorSchema, FontNameSchema } from "./shared";
4
4
  import { CORNER_RADIUS_OPTIONS } from "../lib/corner-radius";
5
- // Sanctioned outside->inside import: document/ is a sealed subtree, but
6
- // site-config.ts is allowed to reach into its engine to wire the page-level
7
- // document options block onto PageSchema.
8
- import { documentOptionsSchema } from "../document/engine/settings";
9
5
  import type { NavEntry } from "./nav";
10
6
  import type { TagEntry } from "./tags";
11
7
 
@@ -21,19 +17,19 @@ export const SectionMetaSchema = z.object({
21
17
 
22
18
  export type SectionMeta = z.infer<typeof SectionMetaSchema>;
23
19
 
24
- // Page slugs that would shadow a platform route. KEEP IN SYNC with the root,
25
- // /edit/*, and /present/* route patterns injected in
20
+ // Page slugs that would shadow a platform route. KEEP IN SYNC with the root
21
+ // and /edit/* route patterns injected in
26
22
  // packages/core/src/integration.ts — if a new root-level route is added there,
27
23
  // add its first segment here (the test in Step 1 guards the known set). This is
28
24
  // the single source of truth for reserved slugs; the schema, uniquePageSlug, and
29
25
  // /api/save all import it.
30
- export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404", "present", "print", "audiences", "decks"] as const;
26
+ export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404", "audiences"] as const;
31
27
 
32
28
  // Subset of RESERVED_SLUGS the index schema hard-rejects at parse time. Only
33
29
  // slugs reserved since before client sites existed may hard-fail here: parse-
34
30
  // rejecting a newly-reserved slug would brick an already-deployed index that
35
31
  // uses it (viewer AND editor refuse to load, leaving no in-product rename
36
- // path). Newly reserved slugs ("present") are enforced at authoring time only —
32
+ // path). Newly reserved slugs ("audiences") are enforced at authoring time only —
37
33
  // PagesModal and uniquePageSlug block new uses via RESERVED_SLUGS, and the
38
34
  // injected platform route simply shadows a grandfathered page until renamed.
39
35
  const PARSE_REJECTED_SLUGS = ["edit", "api", "login", "set-password", "404"] as const;
@@ -49,7 +45,6 @@ export const PageSchema = z.object({
49
45
  status: PageStatusSchema,
50
46
  access: z.array(z.string()),
51
47
  order: z.array(z.string()),
52
- document: documentOptionsSchema.optional(),
53
48
  });
54
49
 
55
50
  export type Page = z.infer<typeof PageSchema>;
@@ -238,26 +233,6 @@ export const ChatbotConfigSchema = z
238
233
  });
239
234
  export type ChatbotConfig = z.infer<typeof ChatbotConfigSchema>;
240
235
 
241
- /**
242
- * Deck Builder config (ADR 0001/0002). An OBJECT rather than a bare audience
243
- * array because `publicSharing` (#37) lands beside `audiences` — the same shape
244
- * ChatbotConfigSchema has, for the same reason.
245
- */
246
- export const DeckBuilderConfigSchema = z.object({
247
- // Audience names whose members may CREATE decks — a Deck Creator. Strict
248
- // opt-in exactly like chatbot.audiences: [] = no viewer may build a deck.
249
- // Editors are super-admins over decks and are never gated by this list.
250
- // Gates: [...slug].astro's FAB, /decks, and POST /api/decks (canCreateDeck).
251
- audiences: z
252
- .array(z.string())
253
- .default([])
254
- .describe(
255
- "Audience names allowed to create decks in the Deck Builder. Strict opt-in — an empty list means no viewer can. Editors always can.",
256
- ),
257
- });
258
-
259
- export type DeckBuilderConfig = z.infer<typeof DeckBuilderConfigSchema>;
260
-
261
236
  export const SiteConfigSchema = z.object({
262
237
  siteName: z.string().default("Brand Portal"),
263
238
  primaryColor: HexColorSchema.default("#009ca6"),
@@ -328,9 +303,6 @@ export const SiteConfigSchema = z.object({
328
303
  // sites table, not here). Every field defaults so pre-existing
329
304
  // site-config.json files keep parsing.
330
305
  chatbot: ChatbotConfigSchema.default({ enabled: false, name: "Portal Assistant", audienceNames: {}, thinkingPhrases: [], systemPrompt: "", audiences: [], tasks: [], audiencePrompts: {} }),
331
- // Deck Builder config (editor-owned). Defaults so pre-existing
332
- // site-config.json files keep parsing — and the default is closed.
333
- deckBuilder: DeckBuilderConfigSchema.default({ audiences: [] }),
334
306
  });
335
307
 
336
308
  export type SiteConfig = z.infer<typeof SiteConfigSchema>;