@drawnagency/primitives 0.1.55 → 0.1.56

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 (43) hide show
  1. package/dist/auth/cookies.d.ts.map +1 -1
  2. package/dist/auth/index.js +1 -1
  3. package/dist/{chunk-PUNXQK4M.js → chunk-7IAWF7LE.js} +1 -1
  4. package/dist/{chunk-KDGYHU36.js → chunk-EU6NZ4GS.js} +13 -2
  5. package/dist/{chunk-24SUF2BC.js → chunk-KGYWQDBB.js} +4 -2
  6. package/dist/{chunk-B5VYSTPB.js → chunk-XTK4BR27.js} +1 -1
  7. package/dist/components/sections/all-sections.d.ts +214 -0
  8. package/dist/components/sections/all-sections.d.ts.map +1 -0
  9. package/dist/components/sections/register-schemas.d.ts.map +1 -1
  10. package/dist/components/sections/register.d.ts.map +1 -1
  11. package/dist/components/shell/EditorShell.d.ts +2 -1
  12. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  13. package/dist/hooks/useEditorPublish.d.ts +2 -1
  14. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  15. package/dist/index.js +17 -4
  16. package/dist/lib/dexie.d.ts +12 -1
  17. package/dist/lib/dexie.d.ts.map +1 -1
  18. package/dist/lib/dexie.js +15 -3
  19. package/dist/lib/index.js +2 -2
  20. package/dist/lib/registry.d.ts +0 -2
  21. package/dist/lib/registry.d.ts.map +1 -1
  22. package/dist/lib/sanitize.d.ts.map +1 -1
  23. package/dist/schemas/index.js +4 -2
  24. package/dist/schemas/shared.d.ts +1 -0
  25. package/dist/schemas/shared.d.ts.map +1 -1
  26. package/dist/storage/index.d.ts +1 -0
  27. package/dist/storage/index.d.ts.map +1 -1
  28. package/dist/storage/types.d.ts +13 -1
  29. package/dist/storage/types.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/auth/cookies.ts +6 -1
  32. package/src/components/sections/all-sections.ts +38 -0
  33. package/src/components/sections/register-schemas.ts +9 -17
  34. package/src/components/sections/register.ts +3 -17
  35. package/src/components/shell/EditorShell.tsx +42 -5
  36. package/src/hooks/useEditorPublish.ts +17 -4
  37. package/src/lib/dexie.ts +25 -0
  38. package/src/lib/registry.ts +0 -2
  39. package/src/lib/sanitize.ts +22 -1
  40. package/src/schemas/shared.ts +10 -0
  41. package/src/schemas/site-config.ts +3 -3
  42. package/src/storage/index.ts +1 -0
  43. package/src/storage/types.ts +17 -0
@@ -1 +1 @@
1
- {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/auth/cookies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGnD,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,uBAAuB,QAAe,CAAC;AACpD,eAAO,MAAM,wBAAwB,QAAoB,CAAC;AAE1D,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAe/E;AAMD,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMzE;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS/E;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EACnB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,OAAO,GACpB,IAAI,CAQN"}
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/auth/cookies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGnD,eAAO,MAAM,cAAc,eAAe,CAAC;AAC3C,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAC7C,eAAO,MAAM,uBAAuB,QAAe,CAAC;AAMpD,eAAO,MAAM,wBAAwB,QAAe,CAAC;AAErD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAe/E;AAMD,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMzE;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS/E;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EACnB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,OAAO,GACpB,IAAI,CAQN"}
@@ -9,7 +9,7 @@ import {
9
9
  signSessionToken,
10
10
  verifyAudienceToken,
11
11
  verifySessionToken
12
- } from "../chunk-B5VYSTPB.js";
12
+ } from "../chunk-XTK4BR27.js";
13
13
  import {
14
14
  isSameOriginRequest,
15
15
  requireSessionSecret,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HexColorSchema
3
- } from "./chunk-24SUF2BC.js";
3
+ } from "./chunk-KGYWQDBB.js";
4
4
 
5
5
  // src/schemas/audience.ts
6
6
  import { z } from "zod";
@@ -3,7 +3,7 @@ import {
3
3
  getAllSchemas,
4
4
  getSection,
5
5
  getSectionSchema
6
- } from "./chunk-24SUF2BC.js";
6
+ } from "./chunk-KGYWQDBB.js";
7
7
  import {
8
8
  safeNextPath
9
9
  } from "./chunk-S2L3BPLS.js";
@@ -96,9 +96,20 @@ if (typeof window !== "undefined") {
96
96
  return mod;
97
97
  });
98
98
  }
99
+ var sanitizeCache = /* @__PURE__ */ new Map();
100
+ var SANITIZE_CACHE_LIMIT = 500;
99
101
  function sanitizeHtml(html) {
100
102
  if (!html) return "";
101
- return purifier ? purifier(html) : html;
103
+ if (!purifier) return html;
104
+ const cached = sanitizeCache.get(html);
105
+ if (cached !== void 0) return cached;
106
+ const clean = purifier(html);
107
+ if (sanitizeCache.size >= SANITIZE_CACHE_LIMIT) {
108
+ const oldest = sanitizeCache.keys().next().value;
109
+ if (oldest !== void 0) sanitizeCache.delete(oldest);
110
+ }
111
+ sanitizeCache.set(html, clean);
112
+ return clean;
102
113
  }
103
114
  async function ensureSanitizer() {
104
115
  if (typeof window === "undefined") return;
@@ -62,6 +62,7 @@ var MediaReferenceSchema = z.discriminatedUnion("type", [
62
62
  LinkedImageRef
63
63
  ]);
64
64
  var HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color");
65
+ var FontNameSchema = z.string().max(120).regex(/^[a-zA-Z0-9 ,'"-]+$/, "contains invalid font-name characters");
65
66
  var ColorSpaceSchema = z.object({
66
67
  hex: HexColorSchema.optional(),
67
68
  rgb: z.string().optional(),
@@ -260,8 +261,8 @@ var SiteConfigSchema = z3.object({
260
261
  primaryColor: HexColorSchema.default("#009ca6"),
261
262
  primaryContrast: HexColorSchema.default("#f0f0f0"),
262
263
  darkMode: z3.enum(["light", "dark", "optional"]).default("light"),
263
- headingFont: z3.string().default("system-ui"),
264
- bodyFont: z3.string().default("system-ui"),
264
+ headingFont: FontNameSchema.default("system-ui"),
265
+ bodyFont: FontNameSchema.default("system-ui"),
265
266
  uppercaseHeadings: z3.boolean().default(true),
266
267
  uppercaseSubheadings: z3.boolean().default(true),
267
268
  uppercaseNavHeadings: z3.boolean().default(true),
@@ -276,6 +277,7 @@ export {
276
277
  TextLineSchema,
277
278
  MediaReferenceSchema,
278
279
  HexColorSchema,
280
+ FontNameSchema,
279
281
  ColorSpaceSchema,
280
282
  ColorItemSchema,
281
283
  defineSection,
@@ -7,7 +7,7 @@ import * as jose from "jose";
7
7
  var SESSION_COOKIE = "bp-session";
8
8
  var AUDIENCE_COOKIE = "bp-audience";
9
9
  var SESSION_MAX_AGE_SECONDS = 60 * 60 * 24;
10
- var AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
10
+ var AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24;
11
11
  async function signSessionToken(session) {
12
12
  return new jose.SignJWT({
13
13
  userId: session.userId,
@@ -0,0 +1,214 @@
1
+ /**
2
+ * The single ordered list of built-in section definitions.
3
+ *
4
+ * Both `register.ts` (full registration for rendering) and `register-schemas.ts`
5
+ * (schema-only registration for /api/save validation) derive from this one
6
+ * array, so the two can no longer drift — previously they hand-maintained
7
+ * parallel 11-entry lists, and a section added to one but not the other would
8
+ * make the editor render a type that save rejects with a 400 (or vice versa).
9
+ */
10
+ export declare const allSectionDefs: (import("../..").SectionDefinition<{
11
+ type: "link_heading";
12
+ content: {
13
+ heading: string;
14
+ };
15
+ }> | import("../..").SectionDefinition<{
16
+ type: "sub_heading";
17
+ content: {
18
+ heading: string;
19
+ excludeFromNav?: boolean | undefined;
20
+ };
21
+ }> | import("../..").SectionDefinition<{
22
+ type: "sub_sub_heading";
23
+ content: {
24
+ heading: string;
25
+ excludeFromNav?: boolean | undefined;
26
+ };
27
+ }> | import("../..").SectionDefinition<{
28
+ type: "prose";
29
+ content: {
30
+ body: string;
31
+ };
32
+ }> | import("../..").SectionDefinition<{
33
+ type: "media_grid";
34
+ content: {
35
+ columns: number;
36
+ media: ({
37
+ imageId: string;
38
+ type: "image";
39
+ caption?: string | string[] | undefined;
40
+ background?: string | undefined;
41
+ invertFrom?: string | undefined;
42
+ border?: boolean | undefined;
43
+ objectFit?: "cover" | "contain" | undefined;
44
+ } | {
45
+ imageId: string;
46
+ type: "video";
47
+ caption?: string | string[] | undefined;
48
+ background?: string | undefined;
49
+ invertFrom?: string | undefined;
50
+ border?: boolean | undefined;
51
+ objectFit?: "cover" | "contain" | undefined;
52
+ poster?: string | undefined;
53
+ autoplay?: boolean | undefined;
54
+ loop?: boolean | undefined;
55
+ muted?: boolean | undefined;
56
+ } | {
57
+ imageId: string;
58
+ type: "doDontImage";
59
+ doDont: "do" | "dont";
60
+ caption?: string | string[] | undefined;
61
+ background?: string | undefined;
62
+ invertFrom?: string | undefined;
63
+ border?: boolean | undefined;
64
+ objectFit?: "cover" | "contain" | undefined;
65
+ } | {
66
+ imageId: string;
67
+ type: "linkedImage";
68
+ href: string;
69
+ caption?: string | string[] | undefined;
70
+ background?: string | undefined;
71
+ invertFrom?: string | undefined;
72
+ border?: boolean | undefined;
73
+ objectFit?: "cover" | "contain" | undefined;
74
+ target?: string | undefined;
75
+ linkText?: string | undefined;
76
+ })[];
77
+ };
78
+ options: {
79
+ square?: boolean | undefined;
80
+ border?: boolean | undefined;
81
+ crop?: boolean | undefined;
82
+ showCaptions?: boolean | undefined;
83
+ };
84
+ }> | import("../..").SectionDefinition<{
85
+ type: "split_content";
86
+ content: {
87
+ body: string;
88
+ imageId?: string | undefined;
89
+ };
90
+ options?: {
91
+ border?: boolean | undefined;
92
+ imagePosition?: "left" | "right" | undefined;
93
+ } | undefined;
94
+ }> | import("../..").SectionDefinition<{
95
+ type: "button";
96
+ content: {
97
+ text: string;
98
+ link?: {
99
+ kind: "external";
100
+ href: string;
101
+ target: "_self" | "_blank";
102
+ } | {
103
+ kind: "internal";
104
+ pageId: string;
105
+ target: "_self" | "_blank";
106
+ anchorSectionId?: string | null | undefined;
107
+ } | undefined;
108
+ download?: boolean | undefined;
109
+ };
110
+ }> | import("../..").SectionDefinition<{
111
+ type: "colors";
112
+ content: {
113
+ colors: {
114
+ spaces: {
115
+ hex?: string | undefined;
116
+ rgb?: string | undefined;
117
+ cmyk?: string | undefined;
118
+ pantone?: string | undefined;
119
+ }[];
120
+ name?: string | undefined;
121
+ }[];
122
+ };
123
+ options?: {
124
+ label?: string | undefined;
125
+ columns?: number | undefined;
126
+ collapsing?: boolean | undefined;
127
+ showLabel?: boolean | undefined;
128
+ } | undefined;
129
+ }> | import("../..").SectionDefinition<{
130
+ type: "do_dont";
131
+ content: {
132
+ doItems: {
133
+ label: string;
134
+ text: string;
135
+ icon?: string | undefined;
136
+ }[];
137
+ dontItems: {
138
+ label: string;
139
+ text: string;
140
+ icon?: string | undefined;
141
+ }[];
142
+ };
143
+ options?: {
144
+ showLabel?: boolean | undefined;
145
+ stackText?: boolean | undefined;
146
+ } | undefined;
147
+ }> | import("../..").SectionDefinition<{
148
+ type: "do_dont_grid";
149
+ content: {
150
+ columns: number;
151
+ media: ({
152
+ imageId: string;
153
+ type: "image";
154
+ caption?: string | string[] | undefined;
155
+ background?: string | undefined;
156
+ invertFrom?: string | undefined;
157
+ border?: boolean | undefined;
158
+ objectFit?: "cover" | "contain" | undefined;
159
+ } | {
160
+ imageId: string;
161
+ type: "video";
162
+ caption?: string | string[] | undefined;
163
+ background?: string | undefined;
164
+ invertFrom?: string | undefined;
165
+ border?: boolean | undefined;
166
+ objectFit?: "cover" | "contain" | undefined;
167
+ poster?: string | undefined;
168
+ autoplay?: boolean | undefined;
169
+ loop?: boolean | undefined;
170
+ muted?: boolean | undefined;
171
+ } | {
172
+ imageId: string;
173
+ type: "doDontImage";
174
+ doDont: "do" | "dont";
175
+ caption?: string | string[] | undefined;
176
+ background?: string | undefined;
177
+ invertFrom?: string | undefined;
178
+ border?: boolean | undefined;
179
+ objectFit?: "cover" | "contain" | undefined;
180
+ } | {
181
+ imageId: string;
182
+ type: "linkedImage";
183
+ href: string;
184
+ caption?: string | string[] | undefined;
185
+ background?: string | undefined;
186
+ invertFrom?: string | undefined;
187
+ border?: boolean | undefined;
188
+ objectFit?: "cover" | "contain" | undefined;
189
+ target?: string | undefined;
190
+ linkText?: string | undefined;
191
+ })[];
192
+ };
193
+ options: {
194
+ square?: boolean | undefined;
195
+ border?: boolean | undefined;
196
+ crop?: boolean | undefined;
197
+ showCaptions?: boolean | undefined;
198
+ };
199
+ }> | import("../..").SectionDefinition<{
200
+ type: "icon_list";
201
+ content: {
202
+ items: {
203
+ label: string;
204
+ text: string;
205
+ icon?: string | undefined;
206
+ }[];
207
+ };
208
+ options?: {
209
+ icon?: string | null | undefined;
210
+ showLabel?: boolean | undefined;
211
+ stackText?: boolean | undefined;
212
+ } | undefined;
213
+ }>)[];
214
+ //# sourceMappingURL=all-sections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"all-sections.d.ts","sourceRoot":"","sources":["../../../src/components/sections/all-sections.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AAKH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAY1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"register-schemas.d.ts","sourceRoot":"","sources":["../../../src/components/sections/register-schemas.ts"],"names":[],"mappings":"AAkBA,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD"}
1
+ {"version":3,"file":"register-schemas.d.ts","sourceRoot":"","sources":["../../../src/components/sections/register-schemas.ts"],"names":[],"mappings":"AAUA,wBAAgB,uBAAuB,IAAI,MAAM,CAMhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/components/sections/register.ts"],"names":[],"mappings":"AAkBA,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/components/sections/register.ts"],"names":[],"mappings":"AAIA,wBAAgB,wBAAwB,IAAI,MAAM,CAMjD"}
@@ -2,6 +2,7 @@ import type { Audience } from "../../auth/types";
2
2
  export { useMediaLibrary } from "./MediaLibraryContext";
3
3
  interface Props {
4
4
  headSha: string;
5
+ draftHeadSha?: string;
5
6
  siteId: string;
6
7
  audiences: Audience[];
7
8
  capabilities: {
@@ -17,5 +18,5 @@ interface Props {
17
18
  role: "owner" | "editor";
18
19
  } | null;
19
20
  }
20
- export default function EditorShell({ headSha, siteId, audiences: initialAudiences, capabilities, currentUser, }: Props): import("react/jsx-runtime").JSX.Element;
21
+ export default function EditorShell({ headSha, draftHeadSha, siteId, audiences: initialAudiences, capabilities, currentUser, }: Props): import("react/jsx-runtime").JSX.Element;
21
22
  //# sourceMappingURL=EditorShell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAgEjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CA22BP"}
1
+ {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAgEjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;CACjE;AAED,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,GACZ,EAAE,KAAK,2CA24BP"}
@@ -19,9 +19,10 @@ interface PublishDeps {
19
19
  onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
20
20
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
21
21
  onPublishComplete?: () => void;
22
+ getBaseVersion: () => string | null;
22
23
  }
23
24
  export type PublishAction = "idle" | "saving" | "publishing";
24
- export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, }: PublishDeps): {
25
+ export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, getBaseVersion, }: PublishDeps): {
25
26
  publishAction: PublishAction;
26
27
  publishFeedback: string | null;
27
28
  handleSave: () => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,EAAE,WAAW;;;;;;EAgSb"}
1
+ {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAe/D,UAAU,WAAW;IACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,OAAO,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACzC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gBAAgB,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAI/B,cAAc,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACrC;AAQD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,cAAc,GACf,EAAE,WAAW;;;;;;EAwSb"}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  LinkValueSchema,
7
7
  MediaGridOptionsSchema,
8
8
  slugifyAudienceName
9
- } from "./chunk-PUNXQK4M.js";
9
+ } from "./chunk-7IAWF7LE.js";
10
10
  import {
11
11
  AudienceSchema,
12
12
  RoleSchema,
@@ -33,10 +33,11 @@ import {
33
33
  safeRedirect,
34
34
  sanitizeHtml,
35
35
  toSectionId
36
- } from "./chunk-KDGYHU36.js";
36
+ } from "./chunk-EU6NZ4GS.js";
37
37
  import {
38
38
  ColorItemSchema,
39
39
  ColorSpaceSchema,
40
+ FontNameSchema,
40
41
  HexColorSchema,
41
42
  IndexSchema,
42
43
  MediaReferenceSchema,
@@ -58,7 +59,7 @@ import {
58
59
  normalizeSiteIndex,
59
60
  registerSchema,
60
61
  registerSection
61
- } from "./chunk-24SUF2BC.js";
62
+ } from "./chunk-KGYWQDBB.js";
62
63
  import {
63
64
  AUDIENCE_COOKIE,
64
65
  AUDIENCE_MAX_AGE_SECONDS,
@@ -70,7 +71,7 @@ import {
70
71
  signSessionToken,
71
72
  verifyAudienceToken,
72
73
  verifySessionToken
73
- } from "./chunk-B5VYSTPB.js";
74
+ } from "./chunk-XTK4BR27.js";
74
75
  import {
75
76
  isSameOriginRequest,
76
77
  requireSessionSecret,
@@ -101,6 +102,16 @@ import {
101
102
  MediaItemSchema,
102
103
  VariantSchema
103
104
  } from "./chunk-DKOUFIP6.js";
105
+
106
+ // src/storage/types.ts
107
+ var StorageConflictError = class extends Error {
108
+ constructor(currentVersion) {
109
+ super("Draft has been modified since it was loaded");
110
+ this.currentVersion = currentVersion;
111
+ this.name = "StorageConflictError";
112
+ }
113
+ currentVersion;
114
+ };
104
115
  export {
105
116
  AUDIENCE_COOKIE,
106
117
  AUDIENCE_MAX_AGE_SECONDS,
@@ -111,6 +122,7 @@ export {
111
122
  ColorSpaceSchema,
112
123
  DEFAULT_LINK,
113
124
  EXT_TO_MIME,
125
+ FontNameSchema,
114
126
  HexColorSchema,
115
127
  ImageManifestSchema,
116
128
  IndexSchema,
@@ -133,6 +145,7 @@ export {
133
145
  SessionSchema,
134
146
  SiteConfigSchema,
135
147
  SiteUserSchema,
148
+ StorageConflictError,
136
149
  TextLineSchema,
137
150
  VariantSchema,
138
151
  buildGoogleFontsUrl,
@@ -57,13 +57,24 @@ export declare function getBranchShas(): Promise<{
57
57
  mainSha: string | null;
58
58
  } | null>;
59
59
  export declare function getDeletedSections(): Promise<string[]>;
60
- export declare function cacheContent(sha: string, sections: LoadedSection[], index: SiteIndex, siteConfig: Record<string, unknown>): Promise<void>;
60
+ export declare function cacheContent(sha: string, sections: LoadedSection[], index: SiteIndex, siteConfig: Record<string, unknown>, diff?: {
61
+ savedBranchSha: string | null;
62
+ changedSectionIds: string[];
63
+ mainIndex: SiteIndex | null;
64
+ }): Promise<void>;
61
65
  export declare function getCachedContent(): Promise<{
62
66
  sha: string;
63
67
  sections: LoadedSection[];
64
68
  index: SiteIndex;
65
69
  siteConfig: Record<string, unknown>;
70
+ savedBranchSha: string | null;
71
+ changedSectionIds: string[];
72
+ mainIndex: SiteIndex | null;
66
73
  } | null>;
74
+ /** Invalidate only the cached content snapshot (e.g. after a publish removes the
75
+ * draft), forcing the next editor load to refetch rather than serve stale diff
76
+ * state. Narrower than discardSavedChanges, which also clears index/meta/config. */
77
+ export declare function clearContentCache(): Promise<void>;
67
78
  export declare function persistMediaManifest(manifest: MediaManifest): Promise<void>;
68
79
  export declare function getMediaManifest(): Promise<MediaManifest | null>;
69
80
  export declare function addPendingMediaItem(item: MediaItem, localUrls?: Record<string, string>, blobs?: Record<string, Blob>): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dexie.d.ts","sourceRoot":"","sources":["../../src/lib/dexie.ts"],"names":[],"mappings":"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"}
package/dist/lib/dexie.js CHANGED
@@ -263,7 +263,7 @@ async function getDeletedSections() {
263
263
  const row = await getDb().siteIndex.get("current");
264
264
  return row?.deletedSections ?? [];
265
265
  }
266
- async function cacheContent(sha, sections, index, siteConfig) {
266
+ async function cacheContent(sha, sections, index, siteConfig, diff) {
267
267
  const now = (/* @__PURE__ */ new Date()).toISOString();
268
268
  await getDb().contentCache.put({
269
269
  key: "current",
@@ -271,7 +271,10 @@ async function cacheContent(sha, sections, index, siteConfig) {
271
271
  sections,
272
272
  index,
273
273
  siteConfig,
274
- updatedAt: now
274
+ updatedAt: now,
275
+ savedBranchSha: diff?.savedBranchSha ?? null,
276
+ changedSectionIds: diff?.changedSectionIds ?? [],
277
+ mainIndex: diff?.mainIndex ?? null
275
278
  });
276
279
  }
277
280
  async function getCachedContent() {
@@ -281,9 +284,17 @@ async function getCachedContent() {
281
284
  sha: row.sha,
282
285
  sections: row.sections,
283
286
  index: row.index,
284
- siteConfig: row.siteConfig
287
+ siteConfig: row.siteConfig,
288
+ savedBranchSha: row.savedBranchSha ?? null,
289
+ changedSectionIds: row.changedSectionIds ?? [],
290
+ // No stored mainIndex (e.g. legacy or post-publish cache) means "no draft",
291
+ // so the published index equals the current index.
292
+ mainIndex: row.mainIndex ?? row.index
285
293
  };
286
294
  }
295
+ async function clearContentCache() {
296
+ await getDb().contentCache.clear();
297
+ }
287
298
  async function persistMediaManifest(manifest) {
288
299
  const now = (/* @__PURE__ */ new Date()).toISOString();
289
300
  await getDb().mediaManifest.put({ key: "current", manifest, updatedAt: now });
@@ -341,6 +352,7 @@ export {
341
352
  addPendingMediaItem,
342
353
  cacheContent,
343
354
  checkForLocalChanges,
355
+ clearContentCache,
344
356
  clearPendingMedia,
345
357
  clearPendingMediaByIds,
346
358
  deleteSectionRows,
package/dist/lib/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  safeRedirect,
19
19
  sanitizeHtml,
20
20
  toSectionId
21
- } from "../chunk-KDGYHU36.js";
21
+ } from "../chunk-EU6NZ4GS.js";
22
22
  import {
23
23
  clearRegistry,
24
24
  createRegistry,
@@ -29,7 +29,7 @@ import {
29
29
  getSection,
30
30
  registerSchema,
31
31
  registerSection
32
- } from "../chunk-24SUF2BC.js";
32
+ } from "../chunk-KGYWQDBB.js";
33
33
  import "../chunk-S2L3BPLS.js";
34
34
  import {
35
35
  env
@@ -91,7 +91,6 @@ export interface SectionDefinition<T = unknown> {
91
91
  schema: ZodType<T>;
92
92
  component: ComponentType<SectionProps<T>>;
93
93
  defaults: () => T;
94
- wrapper?: ComponentType<WrapperProps>;
95
94
  settings?: SettingsSchema;
96
95
  settingsForm?: ComponentType<any>;
97
96
  getLabel?(content: T): string;
@@ -105,7 +104,6 @@ type DefineSectionInput<S extends ZodType> = {
105
104
  schema: S;
106
105
  component: ComponentType<SectionProps<z.infer<S>>>;
107
106
  defaults: () => z.infer<S>;
108
- wrapper?: ComponentType<WrapperProps>;
109
107
  settings?: SettingsSchema;
110
108
  settingsForm?: ComponentType<any>;
111
109
  getLabel?(content: z.infer<S>): string;
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/lib/registry.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/lib/sanitize.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBjD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD"}
@@ -6,7 +6,7 @@ import {
6
6
  LinkValueSchema,
7
7
  MediaGridOptionsSchema,
8
8
  slugifyAudienceName
9
- } from "../chunk-PUNXQK4M.js";
9
+ } from "../chunk-7IAWF7LE.js";
10
10
  import {
11
11
  AudienceSchema,
12
12
  RoleSchema,
@@ -16,6 +16,7 @@ import {
16
16
  import {
17
17
  ColorItemSchema,
18
18
  ColorSpaceSchema,
19
+ FontNameSchema,
19
20
  HexColorSchema,
20
21
  IndexSchema,
21
22
  MediaReferenceSchema,
@@ -28,7 +29,7 @@ import {
28
29
  getSectionContentSchema,
29
30
  getSectionSchema,
30
31
  normalizeSiteIndex
31
- } from "../chunk-24SUF2BC.js";
32
+ } from "../chunk-KGYWQDBB.js";
32
33
  import {
33
34
  ImageManifestSchema,
34
35
  MediaConfigSchema,
@@ -42,6 +43,7 @@ export {
42
43
  ColorItemSchema,
43
44
  ColorSpaceSchema,
44
45
  DEFAULT_LINK,
46
+ FontNameSchema,
45
47
  HexColorSchema,
46
48
  ImageManifestSchema,
47
49
  IndexSchema,
@@ -78,6 +78,7 @@ export declare const MediaReferenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
78
78
  }, z.core.$strip>], "type">;
79
79
  export type MediaReference = z.infer<typeof MediaReferenceSchema>;
80
80
  export declare const HexColorSchema: z.ZodString;
81
+ export declare const FontNameSchema: z.ZodString;
81
82
  export declare const ColorSpaceSchema: z.ZodObject<{
82
83
  hex: z.ZodOptional<z.ZodString>;
83
84
  rgb: z.ZodOptional<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAGlE,eAAO,MAAM,cAAc,aAEiC,CAAC;AAE7D,eAAO,MAAM,gBAAgB;;;;;iBAQ5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;iBAG1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/schemas/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwBxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;2BAKzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAoCtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAE/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAGlE,eAAO,MAAM,cAAc,aAEiC,CAAC;AAO7D,eAAO,MAAM,cAAc,aAG6C,CAAC;AAEzE,eAAO,MAAM,gBAAgB;;;;;iBAQ5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;iBAG1B,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export type { StorageProvider, FileWrite } from "./types";
2
+ export { StorageConflictError } from "./types";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC"}
@@ -1,5 +1,17 @@
1
+ /**
2
+ * Thrown by `writeFiles` when `options.baseVersion` no longer matches the
3
+ * draft's current version — i.e. another editor (or the same user on another
4
+ * device) committed since this editor loaded. Callers should surface a 409 and
5
+ * prompt a reload rather than clobbering the concurrent work (last-write-wins).
6
+ */
7
+ export declare class StorageConflictError extends Error {
8
+ readonly currentVersion: string | null;
9
+ constructor(currentVersion: string | null);
10
+ }
1
11
  export interface StorageProvider {
2
- writeFiles(files: FileWrite[], message: string): Promise<{
12
+ writeFiles(files: FileWrite[], message: string, options?: {
13
+ baseVersion?: string | null;
14
+ }): Promise<{
3
15
  version: string;
4
16
  }>;
5
17
  readFile(path: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/storage/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,cAAc,EAAE,MAAM,GAAG,IAAI;gBAA7B,cAAc,EAAE,MAAM,GAAG,IAAI;CAI1D;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CACR,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,EAAE,MAAM,EAIf,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GACxC,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEhC,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE9B,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9B,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC/E;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.55",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -5,7 +5,12 @@ import { requireSessionSecret } from "./security";
5
5
  export const SESSION_COOKIE = "bp-session";
6
6
  export const AUDIENCE_COOKIE = "bp-audience";
7
7
  export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24;
8
- export const AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
8
+ // 24h, matching the editor session. A viewer audience JWT carries no rotation
9
+ // token, so rotating a leaked viewer password can't actively revoke an
10
+ // outstanding cookie — a short lifetime bounds the leak window instead of the
11
+ // previous 30 days. (A `token_version` claim checked at verify time would give
12
+ // immediate revocation without forcing daily re-auth; see backlog.)
13
+ export const AUDIENCE_MAX_AGE_SECONDS = 60 * 60 * 24;
9
14
 
10
15
  export async function signSessionToken(session: Session): Promise<string> {
11
16
  return new jose.SignJWT({
@@ -0,0 +1,38 @@
1
+ import linkHeading from "./LinkHeading";
2
+ import subHeading from "./SubHeading";
3
+ import subSubHeading from "./SubSubHeading";
4
+ import prose from "./Prose";
5
+ import mediaGrid from "./MediaGrid";
6
+ import splitContent from "./SplitContent";
7
+ import button from "./Button";
8
+ import colors from "./Colors";
9
+ import doDontList from "./DoDontList";
10
+ import doDontImageGrid from "./DoDontMediaGrid";
11
+ import iconList from "./IconList";
12
+
13
+ /**
14
+ * The single ordered list of built-in section definitions.
15
+ *
16
+ * Both `register.ts` (full registration for rendering) and `register-schemas.ts`
17
+ * (schema-only registration for /api/save validation) derive from this one
18
+ * array, so the two can no longer drift — previously they hand-maintained
19
+ * parallel 11-entry lists, and a section added to one but not the other would
20
+ * make the editor render a type that save rejects with a 400 (or vice versa).
21
+ */
22
+ // Intentionally un-annotated: each section is SectionDefinition<its-own-shape>,
23
+ // and a wider `SectionDefinition<unknown>[]` annotation isn't assignable from
24
+ // them (generic param variance). The inferred union is what registerSection /
25
+ // registerSchema already accept.
26
+ export const allSectionDefs = [
27
+ linkHeading,
28
+ subHeading,
29
+ subSubHeading,
30
+ prose,
31
+ mediaGrid,
32
+ splitContent,
33
+ button,
34
+ colors,
35
+ doDontList,
36
+ doDontImageGrid,
37
+ iconList,
38
+ ];
@@ -1,25 +1,17 @@
1
1
  import { registerSchema } from "../../lib/registry";
2
- import linkHeading from "./LinkHeading";
3
- import subHeading from "./SubHeading";
4
- import subSubHeading from "./SubSubHeading";
5
- import prose from "./Prose";
6
- import mediaGrid from "./MediaGrid";
7
- import splitContent from "./SplitContent";
8
- import button from "./Button";
9
- import colors from "./Colors";
10
- import doDontList from "./DoDontList";
11
- import doDontImageGrid from "./DoDontMediaGrid";
12
- import iconList from "./IconList";
13
-
14
- const allDefs = [linkHeading, subHeading, subSubHeading, prose, mediaGrid,
15
- splitContent, button, colors, doDontList, doDontImageGrid, iconList,
16
- ];
2
+ import { allSectionDefs } from "./all-sections";
17
3
 
4
+ // NOTE: this still imports the full (component-bearing) section modules via the
5
+ // shared manifest, so /api/save's validation path currently evaluates React/
6
+ // TipTap. Decoupling that (schema-only sibling modules) is deferred — see
7
+ // docs/superpowers/specs/2026-06-13-custom-section-channel-design.md / backlog.
8
+ // The shared manifest already removes the original drift risk between the two
9
+ // registration lists, which was the correctness bug.
18
10
  let _ensured = false;
19
11
  export function ensureSchemasRegistered(): number {
20
12
  if (!_ensured) {
21
- allDefs.forEach((def) => registerSchema(def.type, def.schema));
13
+ allSectionDefs.forEach((def) => registerSchema(def.type, def.schema));
22
14
  _ensured = true;
23
15
  }
24
- return allDefs.length;
16
+ return allSectionDefs.length;
25
17
  }
@@ -1,25 +1,11 @@
1
1
  import { registerSection } from "../../lib/registry";
2
- import linkHeading from "./LinkHeading";
3
- import subHeading from "./SubHeading";
4
- import subSubHeading from "./SubSubHeading";
5
- import prose from "./Prose";
6
- import mediaGrid from "./MediaGrid";
7
- import splitContent from "./SplitContent";
8
- import button from "./Button";
9
- import colors from "./Colors";
10
- import doDontList from "./DoDontList";
11
- import doDontImageGrid from "./DoDontMediaGrid";
12
- import iconList from "./IconList";
13
-
14
- const allDefs = [linkHeading, subHeading, subSubHeading, prose, mediaGrid,
15
- splitContent, button, colors, doDontList, doDontImageGrid, iconList,
16
- ];
2
+ import { allSectionDefs } from "./all-sections";
17
3
 
18
4
  let _ensured = false;
19
5
  export function ensureSectionsRegistered(): number {
20
6
  if (!_ensured) {
21
- allDefs.forEach(registerSection);
7
+ allSectionDefs.forEach(registerSection);
22
8
  _ensured = true;
23
9
  }
24
- return allDefs.length;
10
+ return allSectionDefs.length;
25
11
  }
@@ -80,6 +80,10 @@ type ShellState =
80
80
 
81
81
  interface Props {
82
82
  headSha: string;
83
+ // Head of the branch the editor actually loads (draft `saved` if it exists,
84
+ // else main). The content cache is keyed on this, so a load with drafts can
85
+ // hit the cache instead of refetching every section. Falls back to headSha.
86
+ draftHeadSha?: string;
83
87
  siteId: string;
84
88
  audiences: Audience[];
85
89
  capabilities: {
@@ -95,6 +99,7 @@ interface Props {
95
99
 
96
100
  export default function EditorShell({
97
101
  headSha,
102
+ draftHeadSha,
98
103
  siteId,
99
104
  audiences: initialAudiences,
100
105
  capabilities,
@@ -117,6 +122,12 @@ export default function EditorShell({
117
122
  const [pendingDeleteSectionId, setPendingDeleteSectionId] = useState<string | null>(null);
118
123
  const [savedSha, setSavedSha] = useState<string | null>(null);
119
124
  const [mainSha, setMainSha] = useState<string | null>(null);
125
+ // Mirror savedSha into a ref so the save handler reads the latest draft
126
+ // version (the optimistic-concurrency base) without a stale closure.
127
+ const savedShaRef = useRef<string | null>(null);
128
+ useEffect(() => {
129
+ savedShaRef.current = savedSha;
130
+ }, [savedSha]);
120
131
  const [changedSectionIds, setChangedSectionIds] = useState<Set<string>>(new Set());
121
132
  const [mainIndex, setMainIndex] = useState<SiteIndex | null>(null);
122
133
  const [viewSections, setViewSections] = useState<LoadedSection[] | null>(null);
@@ -253,6 +264,7 @@ export default function EditorShell({
253
264
  setMainSha((prev) => newMainSha ?? prev);
254
265
  },
255
266
  onPublishComplete: buildStatus.startTracking,
267
+ getBaseVersion: () => savedShaRef.current,
256
268
  });
257
269
 
258
270
  const { buttonState } = useContentLifecycle({
@@ -350,14 +362,32 @@ export default function EditorShell({
350
362
  let loadedConfig: SiteConfig;
351
363
  let loadedManifest: MediaManifest = { images: {} };
352
364
 
365
+ // Key the content cache on the head of the branch we actually load (the
366
+ // draft `saved` branch if it exists, else main), provided by the SSR edit
367
+ // page. Previously this compared against main's head while the cache
368
+ // stored the saved-branch SHA, so any load with a draft missed and
369
+ // refetched ~2N+ files from GitHub every time.
370
+ //
371
+ // draftHeadSha is the server's view at page-load time (fresher than the
372
+ // client could get without a blocking round-trip). It can only be stale for
373
+ // the SSR->hydration window, and only if another writer moves the draft in
374
+ // that window AND this browser already cached the old SHA; the result is at
375
+ // worst momentarily-stale read content, self-corrected on reload and guarded
376
+ // against lost writes by the save-time 409 (optimistic concurrency). We
377
+ // deliberately don't block every load on a freshness fetch to validate this.
378
+ const cacheKeySha = draftHeadSha ?? headSha;
379
+
353
380
  const cached = await getCachedContent();
354
- if (cached && cached.sha === headSha) {
381
+ if (cached && cached.sha === cacheKeySha) {
355
382
  loadedSections = cached.sections;
356
383
  loadedIndex = cached.index;
357
384
  loadedConfig = SiteConfigSchema.parse(cached.siteConfig);
358
385
  const savedManifest = await getMediaManifest();
359
386
  if (savedManifest) loadedManifest = savedManifest;
360
- setMainIndex(loadedIndex);
387
+ setSavedSha(cached.savedBranchSha ?? null);
388
+ setMainSha(headSha);
389
+ setChangedSectionIds(new Set(cached.changedSectionIds ?? []));
390
+ setMainIndex(cached.mainIndex ?? loadedIndex);
361
391
  } else {
362
392
  const response = await fetch("/api/content?branch=saved");
363
393
  if (!response.ok) throw new Error(`Failed to load content: ${response.status}`);
@@ -365,7 +395,11 @@ export default function EditorShell({
365
395
  loadedSections = data.sections;
366
396
  loadedIndex = data.index;
367
397
  loadedConfig = SiteConfigSchema.parse(data.siteConfig);
368
- await cacheContent(data.sha, data.sections, data.index, data.siteConfig);
398
+ await cacheContent(data.sha, data.sections, data.index, data.siteConfig, {
399
+ savedBranchSha: data.savedBranchSha ?? null,
400
+ changedSectionIds: data.changedSectionIds ?? [],
401
+ mainIndex: data.mainIndex ?? data.index,
402
+ });
369
403
  if (data.mediaManifest) {
370
404
  loadedManifest = data.mediaManifest as MediaManifest;
371
405
  await persistMediaManifest(loadedManifest);
@@ -428,7 +462,7 @@ export default function EditorShell({
428
462
  setShellState({ phase: "error", message: err instanceof Error ? err.message : "Failed to load content" });
429
463
  });
430
464
  return () => { cancelled = true; };
431
- }, [headSha, siteId, applySiteConfigPreview]);
465
+ }, [headSha, draftHeadSha, siteId, applySiteConfigPreview]);
432
466
 
433
467
  // --- Recovery handlers ---
434
468
 
@@ -819,7 +853,10 @@ export default function EditorShell({
819
853
  onPagesClick={() => setShowPagesModal(true)}
820
854
  />
821
855
 
822
- <BugReportFAB />
856
+ {/* Bug reports persist via Supabase. On the password-only (zero-Supabase)
857
+ path there's no backend and the editor session has userId: null, so
858
+ the button would always 401 — hide it there. */}
859
+ {!capabilities.passwordOnly && <BugReportFAB />}
823
860
 
824
861
  {rejectedUploads.length > 0 && (
825
862
  <div className="sticky top-2 z-30 mx-auto w-full max-w-3xl px-4">
@@ -2,7 +2,7 @@ import { useState, useCallback, useRef, useEffect } from "react";
2
2
  import type { SiteIndex, SiteConfig } from "../schemas/site-config";
3
3
  import type { LoadedSection } from "../lib/loader";
4
4
  import type { MediaManifest, MediaItem } from "../media/types";
5
- import { getDirtySectionRows, hasLocalChanges, discardSavedChanges, cacheContent, getPendingMediaBlobs, clearPendingMediaByIds } from "../lib/dexie";
5
+ import { getDirtySectionRows, hasLocalChanges, discardSavedChanges, cacheContent, clearContentCache, getPendingMediaBlobs, clearPendingMediaByIds } from "../lib/dexie";
6
6
 
7
7
  function blobToBase64(blob: Blob): Promise<string> {
8
8
  return new Promise((resolve, reject) => {
@@ -34,6 +34,10 @@ interface PublishDeps {
34
34
  onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
35
35
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
36
36
  onPublishComplete?: () => void;
37
+ // The draft version this editor is based on (saved-branch head, or null when
38
+ // no draft exists yet). Sent with saves so the server can 409 on a concurrent
39
+ // edit instead of silently overwriting it. Read at save time (latest value).
40
+ getBaseVersion: () => string | null;
37
41
  }
38
42
 
39
43
  interface GatheredMedia {
@@ -62,6 +66,7 @@ export function useEditorPublish({
62
66
  onMediaPublished,
63
67
  onShasUpdated,
64
68
  onPublishComplete,
69
+ getBaseVersion,
65
70
  }: PublishDeps) {
66
71
  const [publishAction, setPublishAction] = useState<PublishAction>("idle");
67
72
  const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
@@ -172,6 +177,7 @@ export function useEditorPublish({
172
177
  content,
173
178
  })),
174
179
  siteIndex,
180
+ baseVersion: getBaseVersion(),
175
181
  ...(args.deletedSectionIds?.length ? { deletedSectionIds: args.deletedSectionIds } : {}),
176
182
  ...(args.isConfigDirty() ? { siteConfig: args.siteConfig } : {}),
177
183
  ...(gathered.hasMediaChanges ? {
@@ -189,7 +195,8 @@ export function useEditorPublish({
189
195
 
190
196
  if (!response.ok) {
191
197
  const errorBody = await response.json().catch(() => ({}));
192
- throw new Error(errorBody.error || "Save failed");
198
+ const message = errorBody.message || errorBody.error || "Save failed";
199
+ throw Object.assign(new Error(message), { status: response.status });
193
200
  }
194
201
 
195
202
  const responseData = await response.json().catch(() => null);
@@ -247,7 +254,8 @@ export function useEditorPublish({
247
254
  showFeedback("Saved", 3000);
248
255
  } catch (error) {
249
256
  console.error("Save failed:", error);
250
- showFeedback("Save failed", 5000);
257
+ const isConflict = (error as { status?: number })?.status === 409;
258
+ showFeedback(isConflict ? (error as Error).message : "Save failed", isConflict ? 8000 : 5000);
251
259
  } finally {
252
260
  inFlightRef.current = false;
253
261
  setPublishAction("idle");
@@ -275,6 +283,10 @@ export function useEditorPublish({
275
283
  }
276
284
  const { sha } = responseData;
277
285
 
286
+ // The draft branch is gone after publish — drop the cached content snapshot
287
+ // so its now-stale diff state (savedBranchSha/changedSectionIds) can't be
288
+ // restored on a later cache hit. (Save & Publish already re-caches instead.)
289
+ await clearContentCache();
278
290
  onShasUpdated(null, sha);
279
291
  onPublishComplete?.();
280
292
  } catch (error) {
@@ -342,7 +354,8 @@ export function useEditorPublish({
342
354
  onPublishComplete?.();
343
355
  } catch (error) {
344
356
  console.error("Publish failed:", error);
345
- showFeedback("Publish failed", 5000);
357
+ const isConflict = (error as { status?: number })?.status === 409;
358
+ showFeedback(isConflict ? (error as Error).message : "Publish failed", isConflict ? 8000 : 5000);
346
359
  } finally {
347
360
  inFlightRef.current = false;
348
361
  setPublishAction("idle");
package/src/lib/dexie.ts CHANGED
@@ -40,6 +40,12 @@ interface ContentCacheRow {
40
40
  index: SiteIndex;
41
41
  siteConfig: Record<string, unknown>;
42
42
  updatedAt: string;
43
+ // Draft-vs-published diff state, cached alongside the content so a cache hit
44
+ // (same sha) restores the editor's "changed since published" indicators
45
+ // without re-deriving them from GitHub. Optional for backward compatibility.
46
+ savedBranchSha?: string | null;
47
+ changedSectionIds?: string[];
48
+ mainIndex?: SiteIndex | null;
43
49
  }
44
50
 
45
51
  interface MediaManifestRow {
@@ -389,6 +395,7 @@ export async function cacheContent(
389
395
  sections: LoadedSection[],
390
396
  index: SiteIndex,
391
397
  siteConfig: Record<string, unknown>,
398
+ diff?: { savedBranchSha: string | null; changedSectionIds: string[]; mainIndex: SiteIndex | null },
392
399
  ): Promise<void> {
393
400
  const now = new Date().toISOString();
394
401
  await getDb().contentCache.put({
@@ -398,6 +405,9 @@ export async function cacheContent(
398
405
  index,
399
406
  siteConfig,
400
407
  updatedAt: now,
408
+ savedBranchSha: diff?.savedBranchSha ?? null,
409
+ changedSectionIds: diff?.changedSectionIds ?? [],
410
+ mainIndex: diff?.mainIndex ?? null,
401
411
  });
402
412
  }
403
413
 
@@ -406,6 +416,9 @@ export async function getCachedContent(): Promise<{
406
416
  sections: LoadedSection[];
407
417
  index: SiteIndex;
408
418
  siteConfig: Record<string, unknown>;
419
+ savedBranchSha: string | null;
420
+ changedSectionIds: string[];
421
+ mainIndex: SiteIndex | null;
409
422
  } | null> {
410
423
  const row = await getDb().contentCache.get("current");
411
424
  if (!row) return null;
@@ -414,9 +427,21 @@ export async function getCachedContent(): Promise<{
414
427
  sections: row.sections,
415
428
  index: row.index,
416
429
  siteConfig: row.siteConfig,
430
+ savedBranchSha: row.savedBranchSha ?? null,
431
+ changedSectionIds: row.changedSectionIds ?? [],
432
+ // No stored mainIndex (e.g. legacy or post-publish cache) means "no draft",
433
+ // so the published index equals the current index.
434
+ mainIndex: row.mainIndex ?? row.index,
417
435
  };
418
436
  }
419
437
 
438
+ /** Invalidate only the cached content snapshot (e.g. after a publish removes the
439
+ * draft), forcing the next editor load to refetch rather than serve stale diff
440
+ * state. Narrower than discardSavedChanges, which also clears index/meta/config. */
441
+ export async function clearContentCache(): Promise<void> {
442
+ await getDb().contentCache.clear();
443
+ }
444
+
420
445
  export async function persistMediaManifest(manifest: MediaManifest): Promise<void> {
421
446
  const now = new Date().toISOString();
422
447
  await getDb().mediaManifest.put({ key: "current", manifest, updatedAt: now });
@@ -105,7 +105,6 @@ export interface SectionDefinition<T = unknown> {
105
105
  schema: ZodType<T>;
106
106
  component: ComponentType<SectionProps<T>>;
107
107
  defaults: () => T;
108
- wrapper?: ComponentType<WrapperProps>;
109
108
  settings?: SettingsSchema;
110
109
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
110
  settingsForm?: ComponentType<any>;
@@ -123,7 +122,6 @@ type DefineSectionInput<S extends ZodType> = {
123
122
  schema: S;
124
123
  component: ComponentType<SectionProps<z.infer<S>>>;
125
124
  defaults: () => z.infer<S>;
126
- wrapper?: ComponentType<WrapperProps>;
127
125
  settings?: SettingsSchema;
128
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
127
  settingsForm?: ComponentType<any>;
@@ -9,6 +9,14 @@ if (typeof window !== "undefined") {
9
9
  });
10
10
  }
11
11
 
12
+ // Memoize sanitized output per input string. In the editor, every keystroke
13
+ // re-renders all sections, re-running DOMPurify (CPU-heavy HTML parsing) on
14
+ // every inactive rich-text block whose HTML hasn't changed. Caching by input
15
+ // value makes those repeats free. Bounded with FIFO eviction so it can't grow
16
+ // without limit across a long editing session.
17
+ const sanitizeCache = new Map<string, string>();
18
+ const SANITIZE_CACHE_LIMIT = 500;
19
+
12
20
  /**
13
21
  * Synchronous sanitizer — returns sanitized HTML if DOMPurify has loaded,
14
22
  * otherwise returns raw HTML. Call `ensureSanitizer()` during component mount
@@ -16,7 +24,20 @@ if (typeof window !== "undefined") {
16
24
  */
17
25
  export function sanitizeHtml(html: string): string {
18
26
  if (!html) return "";
19
- return purifier ? purifier(html) : html;
27
+ // Don't cache the pre-DOMPurify passthrough — once the purifier loads, the
28
+ // same input must produce sanitized output.
29
+ if (!purifier) return html;
30
+
31
+ const cached = sanitizeCache.get(html);
32
+ if (cached !== undefined) return cached;
33
+
34
+ const clean = purifier(html);
35
+ if (sanitizeCache.size >= SANITIZE_CACHE_LIMIT) {
36
+ const oldest = sanitizeCache.keys().next().value;
37
+ if (oldest !== undefined) sanitizeCache.delete(oldest);
38
+ }
39
+ sanitizeCache.set(html, clean);
40
+ return clean;
20
41
  }
21
42
 
22
43
  /**
@@ -76,6 +76,16 @@ export const HexColorSchema = z
76
76
  .string()
77
77
  .regex(/^#[0-9a-fA-F]{6}$/, "must be a 6-digit hex color");
78
78
 
79
+ // Font-family names are interpolated raw into the `style` attribute on <html>
80
+ // (`--font-heading: ${headingFont}, ...`). Astro HTML-escapes the attribute, so
81
+ // the residual risk is CSS-declaration injection via `;`/`{}`. Allow only the
82
+ // characters legitimate font names use (letters, digits, spaces, commas for
83
+ // fallback lists, quotes for quoted names, hyphens) and bound the length.
84
+ export const FontNameSchema = z
85
+ .string()
86
+ .max(120)
87
+ .regex(/^[a-zA-Z0-9 ,'"-]+$/, "contains invalid font-name characters");
88
+
79
89
  export const ColorSpaceSchema = z.object({
80
90
  hex: HexColorSchema.optional(),
81
91
  rgb: z.string().optional(),
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { MediaConfigSchema } from "./media";
3
- import { HexColorSchema } from "./shared";
3
+ import { HexColorSchema, FontNameSchema } from "./shared";
4
4
 
5
5
  const StatusSchema = z.enum(["draft", "live", "archived", "published"]).transform(
6
6
  (val) => val === "published" ? "live" as const : val,
@@ -137,8 +137,8 @@ export const SiteConfigSchema = z.object({
137
137
  primaryColor: HexColorSchema.default("#009ca6"),
138
138
  primaryContrast: HexColorSchema.default("#f0f0f0"),
139
139
  darkMode: z.enum(["light", "dark", "optional"]).default("light"),
140
- headingFont: z.string().default("system-ui"),
141
- bodyFont: z.string().default("system-ui"),
140
+ headingFont: FontNameSchema.default("system-ui"),
141
+ bodyFont: FontNameSchema.default("system-ui"),
142
142
  uppercaseHeadings: z.boolean().default(true),
143
143
  uppercaseSubheadings: z.boolean().default(true),
144
144
  uppercaseNavHeadings: z.boolean().default(true),
@@ -1 +1,2 @@
1
1
  export type { StorageProvider, FileWrite } from "./types";
2
+ export { StorageConflictError } from "./types";
@@ -1,7 +1,24 @@
1
+ /**
2
+ * Thrown by `writeFiles` when `options.baseVersion` no longer matches the
3
+ * draft's current version — i.e. another editor (or the same user on another
4
+ * device) committed since this editor loaded. Callers should surface a 409 and
5
+ * prompt a reload rather than clobbering the concurrent work (last-write-wins).
6
+ */
7
+ export class StorageConflictError extends Error {
8
+ constructor(public readonly currentVersion: string | null) {
9
+ super("Draft has been modified since it was loaded");
10
+ this.name = "StorageConflictError";
11
+ }
12
+ }
13
+
1
14
  export interface StorageProvider {
2
15
  writeFiles(
3
16
  files: FileWrite[],
4
17
  message: string,
18
+ // `baseVersion` is the draft version the edits were based on. When present,
19
+ // the write is rejected with StorageConflictError if the draft has moved
20
+ // since (null means "expected no draft to exist yet").
21
+ options?: { baseVersion?: string | null },
5
22
  ): Promise<{ version: string }>;
6
23
 
7
24
  readFile(