@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
@@ -1,88 +0,0 @@
1
- import { z } from "zod";
2
- import { DeckIdSchema, formatDeckIssues } from "./deck";
3
-
4
- /**
5
- * `decks/{deckId}/meta.json` — the sidecar beside a published deck on the
6
- * Decks Branch (ADR 0002). It carries everything a listing needs, so the list
7
- * route never reads a single `deck.json`.
8
- *
9
- * Ownership is dual-identity from day one (feasibility decision 9): a viewer
10
- * credential, an editor session, or — on env-mode standalone portals, which
11
- * have no named credentials — the audience alone. Retrofitting that later
12
- * would mean rewriting every deck's meta.
13
- */
14
-
15
- /** Bumped when the sidecar's shape changes in a way old readers can't parse. */
16
- export const DECK_META_SCHEMA_VERSION = 1;
17
-
18
- /**
19
- * Who owns a deck. `viewer` is the normal case (ownership is credential-keyed);
20
- * `editor` covers a deck an agency editor built; `audience` is the env-mode
21
- * standalone degradation, where a shared per-audience password means there is
22
- * no person to attribute to.
23
- */
24
- export const DeckOwnerSchema = z.discriminatedUnion("kind", [
25
- z.object({
26
- kind: z.literal("viewer"),
27
- /** The `viewer_credentials` username — the person-level key. */
28
- username: z.string().min(1).max(200),
29
- /** The audience that credential signed in as, for editor-side context. */
30
- audience: z.string().min(1).max(200),
31
- }),
32
- z.object({
33
- kind: z.literal("editor"),
34
- userId: z.string().min(1).max(200),
35
- }),
36
- z.object({
37
- kind: z.literal("audience"),
38
- audience: z.string().min(1).max(200),
39
- }),
40
- ]);
41
-
42
- /**
43
- * Deliberately a LOOSE object: grants and the Public Link token land in later
44
- * tickets (#36/#37), and a portal running an older package version must not
45
- * silently strip fields it doesn't know when it rewrites this file. This is the
46
- * same failure mode the nav sidecar exists to avoid — with unknown keys
47
- * preserved, an old writer round-trips them instead of deleting them.
48
- */
49
- export const DeckMetaSchema = z.looseObject({
50
- schemaVersion: z.literal(DECK_META_SCHEMA_VERSION),
51
- id: DeckIdSchema,
52
- /** Mirrors deck.title so a listing needs no deck body. */
53
- title: z.string().min(1).max(120),
54
- /** Mirrors deck.slides.length, for the same reason. */
55
- slideCount: z.number().int().min(1),
56
- owner: DeckOwnerSchema,
57
- createdAt: z.iso.datetime(),
58
- updatedAt: z.iso.datetime(),
59
- /**
60
- * Bucket hashes this deck's media resolved to when it was published, and the
61
- * whole reason publish-time GC can protect them (ADR 0002).
62
- *
63
- * A deck references media by MANIFEST ID, and an id is only resolvable to a
64
- * bucket hash while the manifest still carries the entry — which is exactly
65
- * what stops being true in the case the keep-set exists for: the editor
66
- * removes the media from the library, the entry goes, and the object then
67
- * looks unreferenced to every reader. Resolving at publish time and recording
68
- * the answer here is what survives that. Absent when the deck references no
69
- * bucket-backed media, which is every deck until the media components land.
70
- */
71
- assetHashes: z.array(z.string().min(1)).optional(),
72
- });
73
-
74
- export type DeckOwner = z.infer<typeof DeckOwnerSchema>;
75
- export type DeckMeta = z.infer<typeof DeckMetaSchema>;
76
-
77
- export type DeckMetaParseResult = { ok: true; meta: DeckMeta } | { ok: false; errors: string[] };
78
-
79
- /** Parse an untrusted meta sidecar, reporting every problem at once. */
80
- export function parseDeckMeta(input: unknown): DeckMetaParseResult {
81
- const result = DeckMetaSchema.safeParse(input);
82
- return result.success ? { ok: true, meta: result.data } : { ok: false, errors: formatDeckIssues(result.error) };
83
- }
84
-
85
- /** Pretty-printed with a trailing newline, matching every other repo-stored JSON. */
86
- export function serializeDeckMeta(meta: DeckMeta): string {
87
- return JSON.stringify(meta, null, 2) + "\n";
88
- }
@@ -1,478 +0,0 @@
1
- import { z } from "zod";
2
-
3
- /**
4
- * A Deck is a viewer-creatable 16:9 presentation, stored as a Zod-validated
5
- * JSON tree of Layouts and Deck Components (ADR 0001). It is NOT a section
6
- * type: decks never enter the section registry, never appear in index.json,
7
- * and are rendered by the deck runtime rather than by ViewRenderer.
8
- *
9
- * The tree is authored by an LLM one whole deck at a time, so every leaf
10
- * carries a `.describe()` (the shape the model is handed) and every refusal
11
- * has to name the thing that is wrong — see `parseDeck`, which is what a
12
- * validation-retry loop reads.
13
- */
14
-
15
- /**
16
- * The stage is a fixed logical canvas scaled into the viewport by transform,
17
- * so every size inside a slide is authored in px against these dimensions and
18
- * one renderer serves a thumbnail and a full-screen presentation identically.
19
- */
20
- export const DECK_STAGE_WIDTH = 1600;
21
- export const DECK_STAGE_HEIGHT = 900;
22
-
23
- /**
24
- * Bumped when a stored deck's shape changes in a way old readers can't parse.
25
- *
26
- * Growing the vocabulary is deliberately NOT such a change: a reader that meets
27
- * an unknown `type` refuses by name (see the unions below) rather than
28
- * mis-rendering, and no deck is stored anywhere yet — ADR 0002's decks branch is
29
- * still ahead. The version is for changes to the shape of what already exists.
30
- */
31
- export const DECK_SCHEMA_VERSION = 1;
32
-
33
- /**
34
- * Slide ceiling. Generous for a real presentation and still a bound on the two
35
- * places an unbounded deck hurts: the LLM re-emits the WHOLE deck every turn,
36
- * and every publish re-commits it to the decks branch. Deliberately not tight —
37
- * it exists to stop a runaway, not to shape a deck.
38
- */
39
- export const DECK_MAX_SLIDES = 40;
40
-
41
- // Capped because a deck id becomes a directory name on the decks branch
42
- // (ADR 0002, `decks/{deckId}/…`). Cheap to set while nothing is stored yet.
43
- // Exported so the meta sidecar and the storage routes validate an id against
44
- // this one rule — the regex is also what keeps an id out of a path it should
45
- // not reach, so a second copy of it is a second chance to get it wrong.
46
- export const DeckIdSchema = z
47
- .string()
48
- .max(64, "must be 64 characters or fewer — a deck id is also a directory name")
49
- .regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit");
50
-
51
- /**
52
- * The runtime's vocabulary. An author can only remix what the runtime provides
53
- * (ADR 0001), so a wrong `type` has to answer "then what are my options?".
54
- * Zod's own discriminator-miss message is a bare "Invalid input" at every union
55
- * size, hence the explicit `error` on each union below.
56
- */
57
- export const DECK_LAYOUT_TYPES = ["centeredText", "fullBleedImage", "columnSplit"] as const;
58
- export const DECK_COMPONENT_TYPES = ["richText", "image", "imageGrid", "video"] as const;
59
- export const DECK_MEDIA_KINDS = ["portal"] as const;
60
-
61
- /** Surfaces a Layout can declare. Components read the ink tokens, not this. */
62
- export const DECK_SURFACES = ["base", "brand"] as const;
63
- export const DECK_OVERLAY_PLACEMENTS = ["sideLeft", "sideRight", "bottomBand"] as const;
64
- export const DECK_SPLIT_RATIOS = ["1-2", "2-1"] as const;
65
-
66
- function vocabularyError(kind: string, types: readonly string[], field = "type"): string {
67
- return `unknown ${kind} ${field} — the deck runtime provides: ${types.map((t) => `"${t}"`).join(", ")}`;
68
- }
69
-
70
- /**
71
- * A media reference. A discriminated union from day one so a future
72
- * deck-scoped media library lands as a second member rather than a migration —
73
- * `portal` (the site's own media manifest) is the only MVP kind.
74
- */
75
- export const PortalMediaRefSchema = z.object({
76
- kind: z.literal("portal"),
77
- // Same charset as a deck id, for the same reason and one more: a media id
78
- // becomes a key into a plain object at resolve time, and `"__proto__"` parses
79
- // as a perfectly good string. Constrain it here rather than relying on every
80
- // resolver to be careful.
81
- id: z
82
- .string()
83
- .max(120, "must be 120 characters or fewer")
84
- .regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase letters, digits and hyphens, starting with a letter or digit")
85
- .describe("Media id from the portal's media library — the manifest key, not a filename, path or URL."),
86
- });
87
-
88
- export const DeckMediaRefSchema = z.discriminatedUnion("kind", [PortalMediaRefSchema], {
89
- error: vocabularyError("media reference", DECK_MEDIA_KINDS, "kind"),
90
- });
91
-
92
- export type DeckMediaRef = z.infer<typeof DeckMediaRefSchema>;
93
-
94
- /**
95
- * What a media reference resolves to at render time.
96
- *
97
- * Not Zod and never stored: it is the seam between the deck runtime and
98
- * whoever owns the media manifest — the Deck Builder in production, the dev
99
- * preview route today. Keeping it a function means the runtime needs no
100
- * manifest of its own and no knowledge of how a URL is minted.
101
- */
102
- export interface DeckMediaAsset {
103
- /** Image URL, or a video's playable source. */
104
- src: string;
105
- alt: string;
106
- /** Poster frame. Videos only. */
107
- poster?: string;
108
- /**
109
- * Intrinsic pixel dimensions, when the resolver knows them (the portal's
110
- * media manifest always does). Optional because a resolver may not, but
111
- * supplying them is what lets the image grid reserve aspect-correct space
112
- * before a byte loads, and what it packs its masonry columns from.
113
- */
114
- width?: number;
115
- height?: number;
116
- }
117
-
118
- /** `null` for media the site no longer has — the runtime renders a gap, not a broken image. */
119
- export type DeckMediaResolver = (ref: DeckMediaRef) => DeckMediaAsset | null;
120
-
121
- /** Longest a body line may run, in characters, whichever form it takes. */
122
- const PARAGRAPH_MAX = 400;
123
- const BULLET_MAX = 160;
124
- const MAX_SPANS = 12;
125
-
126
- /**
127
- * An inline span with its marks as flags.
128
- *
129
- * This is how a deck gets bold and italic without ever admitting markup: the
130
- * marks are booleans on a segment of literal text, so "no arbitrary script"
131
- * stays a property of the schema (ADR 0001) rather than of a sanitizer.
132
- */
133
- // Strict, uniquely in this file: the marks ARE the vocabulary here, so an
134
- // invented one (`underline`, `color`, `link`) has to be refused rather than
135
- // stripped on the way in — a dropped mark is invisible to the author.
136
- //
137
- // The rest of the tree stays loose ON PURPOSE, and the asymmetry is the
138
- // decision, not an oversight. Decks are stored content (ADR 0002's decks
139
- // branch) read by whatever package version a given deploy happens to be on: a
140
- // strictObject tree would make an older reader HARD-REFUSE a deck that a newer
141
- // writer saved with a new optional field, turning every additive schema change
142
- // into a coordinated-deploy problem. Forward-compatibility of stored content
143
- // wins, exactly as it does for the nav sidecar and the deck meta file. Spans
144
- // are the one place where that trade flips, because there the unknown key is
145
- // content the author can see is missing rather than a field they never set.
146
- // The #35 authoring loop is free to layer stricter validation at its own
147
- // boundary, where a rejection costs one LLM retry instead of a broken deck.
148
- export const DeckTextSpanSchema = z.strictObject({
149
- text: z
150
- .string()
151
- .min(1)
152
- .max(PARAGRAPH_MAX)
153
- .describe("The span's literal text. Never HTML or markdown — the marks below are the only formatting there is."),
154
- bold: z.boolean().optional().describe("Renders the span at full ink weight. For the one phrase in a line that carries it."),
155
- italic: z.boolean().optional().describe("Renders the span italic. For titles of works and genuine emphasis, not decoration."),
156
- });
157
-
158
- export type DeckTextSpan = z.infer<typeof DeckTextSpanSchema>;
159
-
160
- /** A body line: plain text, or a run of marked spans. */
161
- export type DeckInlineText = string | DeckTextSpan[];
162
-
163
- function inlineText(maxChars: number) {
164
- return z.union([z.string().min(1).max(maxChars), z.array(DeckTextSpanSchema).min(1).max(MAX_SPANS)], {
165
- error: "must be plain text, or an array of { text, bold?, italic? } spans — deck rich text is structured, never HTML or markdown",
166
- });
167
- }
168
-
169
- /** Characters in a body line, so one cap covers both representations. */
170
- function inlineLength(value: DeckInlineText): number {
171
- return typeof value === "string" ? value.length : value.reduce((total, span) => total + span.text.length, 0);
172
- }
173
-
174
- /**
175
- * Deck Component — rich text.
176
- *
177
- * Deliberately structured plain text rather than an HTML string: deck JSON is
178
- * authored by a model and served on the Public Link surface, and ADR 0001 makes
179
- * "a deck cannot contain arbitrary script" a property of the schema rather than
180
- * of a sanitizer. Typographic roles are fields; the runtime owns their sizing.
181
- */
182
- export const DeckRichTextSchema = z
183
- .object({
184
- type: z.literal("richText"),
185
- kicker: z
186
- .string()
187
- .min(1)
188
- .max(60)
189
- .optional()
190
- .describe("Short label above the heading, e.g. a campaign or section name. Rendered small, tracked and in the brand accent."),
191
- heading: z
192
- .string()
193
- .min(1)
194
- .max(120)
195
- .optional()
196
- .describe("The slide's primary line. Keep it to a phrase — it renders at display size on the 16:9 stage."),
197
- subheading: z
198
- .string()
199
- .min(1)
200
- .max(200)
201
- .optional()
202
- .describe("Secondary line under the heading. Optional; omit rather than repeating the heading."),
203
- paragraphs: z
204
- .array(inlineText(PARAGRAPH_MAX))
205
- .max(3)
206
- .optional()
207
- .describe(
208
- `Body copy, one entry per paragraph. Each is either a plain string or an array of { text, bold?, italic? } spans — use spans only where a phrase genuinely needs a mark. Never HTML or markdown; ${PARAGRAPH_MAX} characters per paragraph.`,
209
- ),
210
- bullets: z
211
- .array(inlineText(BULLET_MAX))
212
- .max(6)
213
- .optional()
214
- .describe(
215
- `Short bullet lines, plain strings or span arrays like paragraphs. Prefer six or fewer so they stay legible at presentation size; ${BULLET_MAX} characters per line.`,
216
- ),
217
- })
218
- .superRefine((value, ctx) => {
219
- const hasContent =
220
- value.kicker !== undefined ||
221
- value.heading !== undefined ||
222
- value.subheading !== undefined ||
223
- (value.paragraphs?.length ?? 0) > 0 ||
224
- (value.bullets?.length ?? 0) > 0;
225
- if (!hasContent) {
226
- ctx.addIssue({
227
- code: "custom",
228
- message: "a richText component needs at least one of kicker, heading, subheading, paragraphs or bullets",
229
- });
230
- }
231
-
232
- // The plain-string branch is capped by the schema; a span run has to be
233
- // measured, or marks would be a way around the layout's character budget.
234
- const capLines = (lines: DeckInlineText[] | undefined, max: number, field: "paragraphs" | "bullets") => {
235
- lines?.forEach((line, i) => {
236
- const length = inlineLength(line);
237
- if (length > max) {
238
- ctx.addIssue({
239
- code: "custom",
240
- path: [field, i],
241
- message: `spans total ${length} characters — keep a ${field === "bullets" ? "bullet" : "paragraph"} to ${max} or fewer`,
242
- });
243
- }
244
- });
245
- };
246
- capLines(value.paragraphs, PARAGRAPH_MAX, "paragraphs");
247
- capLines(value.bullets, BULLET_MAX, "bullets");
248
- });
249
-
250
- /**
251
- * Deck Component — image.
252
- *
253
- * The media itself is a reference, never a URL: a deck outlives the URL shape,
254
- * and the runtime has no manifest of its own (see `DeckMediaResolver`).
255
- */
256
- export const DeckImageSchema = z.object({
257
- type: z.literal("image"),
258
- media: DeckMediaRefSchema.describe("The image to show. It fills its slot and is cropped to fit."),
259
- caption: z
260
- .string()
261
- .min(1)
262
- .max(120)
263
- .optional()
264
- .describe("Caption printed on a solid brand block along the image's bottom edge. Omit rather than restating the slide."),
265
- });
266
-
267
- /**
268
- * Deck Component — image grid.
269
- *
270
- * Masonry columns, not a cell grid: every image keeps its own aspect ratio and
271
- * nothing is ever cropped, so a set of mixed portrait and landscape frames
272
- * reads as a contact sheet rather than as a set of bad crops. Carries the
273
- * runtime's signature interaction — a tile lifts on hover and expands in place
274
- * to fill the grid region, so a presenter can talk through one frame without
275
- * leaving the slide. Column count is the runtime's business.
276
- */
277
- export const DeckImageGridItemSchema = z.object({
278
- media: DeckMediaRefSchema.describe("The tile's image."),
279
- caption: z
280
- .string()
281
- .min(1)
282
- .max(80)
283
- .optional()
284
- .describe("Shown on the tile on hover, and while the tile is expanded. A few words, not a sentence."),
285
- });
286
-
287
- export const DeckImageGridSchema = z.object({
288
- type: z.literal("imageGrid"),
289
- items: z
290
- .array(DeckImageGridItemSchema)
291
- .min(2, "an image grid needs at least two images — use the image component for one")
292
- .max(12, "an image grid holds at most twelve images — past that a tile is too small to read at presentation size")
293
- .describe(
294
- "The tiles, in order of importance. Two to twelve. They pack into masonry columns at their own aspect ratios — nothing is cropped and nothing is resized to match its neighbours — and the columns clip at the bottom of the slot if the stack runs past it, so put the frames that must be seen first.",
295
- ),
296
- });
297
-
298
- /**
299
- * Deck Component — video.
300
- *
301
- * Poster and play affordance until activated, then the portal-hosted source
302
- * plays in place with native controls.
303
- */
304
- export const DeckVideoSchema = z.object({
305
- type: z.literal("video"),
306
- media: DeckMediaRefSchema.describe("The video to play. Must reference portal-hosted media — a deck cannot embed an external player."),
307
- title: z
308
- .string()
309
- .min(1)
310
- .max(120)
311
- .optional()
312
- .describe("Label on a brand block across the bottom of the poster, before playback starts."),
313
- });
314
-
315
- /** Every Deck Component the runtime can place in a Layout slot. */
316
- export const DeckComponentSchema = z.discriminatedUnion(
317
- "type",
318
- [DeckRichTextSchema, DeckImageSchema, DeckImageGridSchema, DeckVideoSchema],
319
- { error: vocabularyError("deck component", DECK_COMPONENT_TYPES) },
320
- );
321
-
322
- /**
323
- * Layout — centered text.
324
- *
325
- * A full-slide title card: one Deck Component, centered on both axes. Slots are
326
- * the Layout's business; the component only says what it says.
327
- */
328
- export const CenteredTextLayoutSchema = z.object({
329
- type: z.literal("centeredText"),
330
- background: z
331
- .enum(DECK_SURFACES)
332
- .default("base")
333
- .describe("`base` puts the slide on the site's surface color; `brand` fills it with the brand primary and flips the ink."),
334
- content: DeckComponentSchema.describe("The single Deck Component this Layout centers."),
335
- });
336
-
337
- /**
338
- * The overlay panel on a full-bleed slide: a solid brand-color block over the
339
- * image, holding text. Both placements exist because they read differently —
340
- * a side column is the editorial treatment, a bottom band lets the image carry
341
- * the slide. Neither is a scrim: legibility comes from the block, so the panel
342
- * declares the brand surface and the copy is opaque over it.
343
- */
344
- export const DeckOverlayPanelSchema = z.object({
345
- placement: z
346
- .enum(DECK_OVERLAY_PLACEMENTS)
347
- .default("sideLeft")
348
- .describe(
349
- "`sideLeft`/`sideRight` put the panel in a full-height column over that edge of the image; `bottomBand` runs it full width across the bottom. Pick the side the image is emptiest on.",
350
- ),
351
- content: DeckRichTextSchema.describe("The panel's text. It sits on the brand color, so keep it short — a kicker and a line or two."),
352
- });
353
-
354
- /**
355
- * Layout — full-bleed image.
356
- *
357
- * The image is the slide. An optional overlay panel is the only thing that may
358
- * sit on top of it.
359
- */
360
- export const FullBleedImageLayoutSchema = z.object({
361
- type: z.literal("fullBleedImage"),
362
- media: DeckMediaRefSchema.describe("The image filling the whole 16:9 stage. It is cropped to fit, so choose one that survives a wide crop."),
363
- overlay: DeckOverlayPanelSchema.optional().describe("Optional text panel over the image. Omit it for a slide that is purely the picture."),
364
- });
365
-
366
- /**
367
- * Layout — left/right column split.
368
- *
369
- * Two slots side by side at a deliberate imbalance: an even split reads as a
370
- * comparison, and this Layout is for one thing supporting another.
371
- */
372
- export const ColumnSplitLayoutSchema = z.object({
373
- type: z.literal("columnSplit"),
374
- ratio: z
375
- .enum(DECK_SPLIT_RATIOS)
376
- .default("1-2")
377
- .describe("`1-2` gives the left column a third of the stage and the right two thirds; `2-1` is the reverse. Give the media the larger side."),
378
- left: DeckComponentSchema.describe("The left column's Deck Component."),
379
- right: DeckComponentSchema.describe("The right column's Deck Component."),
380
- });
381
-
382
- export const DeckLayoutSchema = z.discriminatedUnion(
383
- "type",
384
- [CenteredTextLayoutSchema, FullBleedImageLayoutSchema, ColumnSplitLayoutSchema],
385
- { error: vocabularyError("layout", DECK_LAYOUT_TYPES) },
386
- );
387
-
388
- export const DeckSlideSchema = z.object({
389
- id: DeckIdSchema.describe("Stable slide id, unique within the deck. Reused across edits so revisions stay diffable."),
390
- title: z
391
- .string()
392
- .min(1)
393
- .max(80)
394
- .optional()
395
- .describe("Short label for the slide, used by the builder filmstrip and by slide-navigation labels. Not rendered on the stage."),
396
- layout: DeckLayoutSchema.describe("The full-slide arrangement. A slide must use exactly one Layout."),
397
- });
398
-
399
- export const DeckSchema = z
400
- .object({
401
- schemaVersion: z
402
- .literal(DECK_SCHEMA_VERSION)
403
- .describe(`Deck schema version. Always ${DECK_SCHEMA_VERSION}.`),
404
- id: DeckIdSchema.describe("Stable deck id — also the directory name on the decks branch."),
405
- title: z.string().min(1).max(120).describe("The deck's name, shown in the builder and in the deck's chrome."),
406
- slides: z
407
- .array(DeckSlideSchema)
408
- .min(1, "a deck needs at least one slide")
409
- .max(
410
- DECK_MAX_SLIDES,
411
- `a deck is capped at ${DECK_MAX_SLIDES} slides — split a longer story into several decks`,
412
- )
413
- .describe(
414
- `Slides in presentation order, at most ${DECK_MAX_SLIDES}. The whole deck is re-emitted on every authoring turn and re-committed on every publish, so the cap bounds both the model's output and the size of a stored deck.`,
415
- ),
416
- })
417
- .superRefine((deck, ctx) => {
418
- const seen = new Set<string>();
419
- deck.slides.forEach((slide, i) => {
420
- if (seen.has(slide.id)) {
421
- ctx.addIssue({
422
- code: "custom",
423
- path: ["slides", i, "id"],
424
- message: `duplicate slide id "${slide.id}" — slide ids must be unique within a deck`,
425
- });
426
- }
427
- seen.add(slide.id);
428
- });
429
- });
430
-
431
- export type DeckSurface = (typeof DECK_SURFACES)[number];
432
- export type DeckOverlayPlacement = (typeof DECK_OVERLAY_PLACEMENTS)[number];
433
- export type DeckSplitRatio = (typeof DECK_SPLIT_RATIOS)[number];
434
- export type DeckRichText = z.infer<typeof DeckRichTextSchema>;
435
- export type DeckImage = z.infer<typeof DeckImageSchema>;
436
- export type DeckImageGridItem = z.infer<typeof DeckImageGridItemSchema>;
437
- export type DeckImageGrid = z.infer<typeof DeckImageGridSchema>;
438
- export type DeckVideo = z.infer<typeof DeckVideoSchema>;
439
- export type DeckComponent = z.infer<typeof DeckComponentSchema>;
440
- export type CenteredTextLayout = z.infer<typeof CenteredTextLayoutSchema>;
441
- export type DeckOverlayPanel = z.infer<typeof DeckOverlayPanelSchema>;
442
- export type FullBleedImageLayout = z.infer<typeof FullBleedImageLayoutSchema>;
443
- export type ColumnSplitLayout = z.infer<typeof ColumnSplitLayoutSchema>;
444
- export type DeckLayout = z.infer<typeof DeckLayoutSchema>;
445
- export type DeckSlide = z.infer<typeof DeckSlideSchema>;
446
- export type Deck = z.infer<typeof DeckSchema>;
447
-
448
- export type DeckParseResult = { ok: true; deck: Deck } | { ok: false; errors: string[] };
449
-
450
- /** `slides[2].layout.content.heading` — the path an author can act on. */
451
- function formatPath(path: ReadonlyArray<PropertyKey>): string {
452
- return path.reduce<string>((acc, key) => {
453
- if (typeof key === "number") return `${acc}[${key}]`;
454
- return acc === "" ? String(key) : `${acc}.${String(key)}`;
455
- }, "");
456
- }
457
-
458
- /** One `path: message` line per issue, or `"<message>"` for a deck-level issue. */
459
- export function formatDeckIssues(error: z.ZodError): string[] {
460
- return error.issues.map((issue) => {
461
- const path = formatPath(issue.path);
462
- return path === "" ? issue.message : `${path}: ${issue.message}`;
463
- });
464
- }
465
-
466
- /**
467
- * Parse untrusted deck JSON. Returns every problem, not just the first, so a
468
- * validation-retry loop can hand the whole list back to the author in one turn.
469
- */
470
- export function parseDeck(input: unknown): DeckParseResult {
471
- const result = DeckSchema.safeParse(input);
472
- return result.success ? { ok: true, deck: result.data } : { ok: false, errors: formatDeckIssues(result.error) };
473
- }
474
-
475
- /** Pretty-printed with a trailing newline, matching every other repo-stored JSON. */
476
- export function serializeDeck(deck: Deck): string {
477
- return JSON.stringify(deck, null, 2) + "\n";
478
- }
@@ -1,10 +1,10 @@
1
- import {
2
- reconcileNavReport
3
- } from "./chunk-DJOGPSRU.js";
4
1
  import {
5
2
  mergeTags,
6
3
  reconcileTagsReport
7
4
  } from "./chunk-4WQ36ZP7.js";
5
+ import {
6
+ reconcileNavReport
7
+ } from "./chunk-DJOGPSRU.js";
8
8
  import {
9
9
  NavEntrySchema,
10
10
  NavFileSchema,