@drawnagency/primitives 0.1.116 → 0.1.117

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.
@@ -1 +1 @@
1
- {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA6DnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8BxD,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;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAQD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CAiuEP;AA8ED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
1
+ {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA6DnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8BxD,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;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAQD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CAsvEP;AA8ED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
@@ -38,6 +38,10 @@ interface PublishDeps {
38
38
  onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
39
39
  onPublishComplete?: (sha: string) => void;
40
40
  getBaseVersion: () => string | null;
41
+ /** The main commit this editor's content was read at. Sent as `mainVersion`
42
+ * with a no-draft save, so a publish landed by someone else since this tab
43
+ * loaded 409s into recoverConflict instead of committing a stale view. */
44
+ getMainVersion?: () => string | null;
41
45
  /** Injected by EditorShell. On a 409 (concurrent edit) the save calls this to
42
46
  * re-fetch the fresh draft, rebase local unsaved work on top, and refresh the
43
47
  * optimistic-concurrency base (savedShaRef). Returns true when recovery
@@ -77,6 +81,10 @@ interface PublishDeps {
77
81
  /** Committed document manifest, merged with the staged entries this save
78
82
  * commits. Optional so pre-documents callers keep compiling. */
79
83
  documentManifest?: DocumentManifest;
84
+ /** Optional ref mirror of `documentManifest`, read at save time — same
85
+ * contract as mediaManifestRef: the 409 retry sees recoverConflict's
86
+ * refreshed sidecar instead of this render's pre-conflict copy. */
87
+ documentManifestRef?: React.RefObject<DocumentManifest | null>;
80
88
  pendingDocuments?: {
81
89
  id: string;
82
90
  entry: DocumentManifestEntry;
@@ -92,7 +100,7 @@ interface PublishDeps {
92
100
  onDocumentsDeleted?: (ids: string[]) => void;
93
101
  }
94
102
  export type PublishAction = "idle" | "saving" | "publishing";
95
- export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, tagsDegraded, sections, deletedSectionIds, onSuccess, mediaManifest, mediaManifestRef, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, getBaseVersion, recoverConflict, onBaselineCommitted, onSaveWarnings, saveChunkBudget, savingRef, getEditGeneration, documentManifest, pendingDocuments, pendingDocumentDeletions, onDocumentsPublished, onDocumentsDeleted, }: PublishDeps): {
103
+ export declare function useEditorPublish({ flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, tagsDegraded, sections, deletedSectionIds, onSuccess, mediaManifest, mediaManifestRef, manifestDirty, clearManifestDirty, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, onPublishComplete, getBaseVersion, getMainVersion, recoverConflict, onBaselineCommitted, onSaveWarnings, saveChunkBudget, savingRef, getEditGeneration, documentManifest, documentManifestRef, pendingDocuments, pendingDocumentDeletions, onDocumentsPublished, onDocumentsDeleted, }: PublishDeps): {
96
104
  publishAction: PublishAction;
97
105
  publishFeedback: import("./useNotice").NoticeState | null;
98
106
  saveProgress: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useEditorPublish.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorPublish.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.116",
3
+ "version": "0.1.117",
4
4
  "description": "Browser-safe foundation of the Brand Portal framework: Zod schemas, section components, editor shell, hooks, pure libs, collab contract, document engine.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -274,6 +274,11 @@ export default function EditorShell({
274
274
  useEffect(() => {
275
275
  savedShaRef.current = savedSha;
276
276
  }, [savedSha]);
277
+ // Same for main: a no-draft save sends it as `mainVersion`.
278
+ const mainShaRef = useRef<string | null>(null);
279
+ useEffect(() => {
280
+ mainShaRef.current = mainSha;
281
+ }, [mainSha]);
277
282
  const [changedSectionIds, setChangedSectionIds] = useState<Set<string>>(new Set());
278
283
  const [mainIndex, setMainIndex] = useState<SiteIndex | null>(null);
279
284
  // Ref mirrors of the diff state, so save-time callbacks (which run inside a
@@ -652,6 +657,13 @@ export default function EditorShell({
652
657
  : null;
653
658
  if (rebasedManifest) setMediaManifest(rebasedManifest);
654
659
  if (remote.mediaManifest) baseMediaManifestRef.current = remote.mediaManifest;
660
+ // The document sidecar only ever holds committed entries (staged uploads
661
+ // and pending deletions live in the pipeline), so the remote one replaces
662
+ // it outright — no rebase. Skipping it left a peer's documents missing
663
+ // from this tab and, via the retry's cache write, from every reload.
664
+ if (remote.documentManifest) setDocumentManifest(remote.documentManifest);
665
+ // No draft: the fetch read main, which is now what this view is based on.
666
+ if (remote.savedBranchSha === null) setMainSha(remote.sha);
655
667
 
656
668
  setSections(mergedSections);
657
669
  setSiteIndex(mergedIndex);
@@ -663,7 +675,9 @@ export default function EditorShell({
663
675
  // (and onBaselineCommitted reads the diff refs right after it), before
664
676
  // React flushes the setState calls above into their mirror effects.
665
677
  savedShaRef.current = remote.savedBranchSha;
678
+ if (remote.savedBranchSha === null) mainShaRef.current = remote.sha;
666
679
  if (rebasedManifest) mediaManifestRef.current = rebasedManifest;
680
+ if (remote.documentManifest) documentManifestRef.current = remote.documentManifest;
667
681
  sectionsRef.current = mergedSections;
668
682
  siteIndexRef.current = mergedIndex;
669
683
  changedSectionIdsRef.current = new Set(remote.changedSectionIds);
@@ -774,6 +788,11 @@ export default function EditorShell({
774
788
  mediaManifestRef.current = rebasedManifest;
775
789
  baseMediaManifestRef.current = remote.mediaManifest;
776
790
  }
791
+ // Committed-only, so replaced outright — see recoverConflict.
792
+ if (remote.documentManifest) {
793
+ setDocumentManifest(remote.documentManifest);
794
+ documentManifestRef.current = remote.documentManifest;
795
+ }
777
796
  setSections(result.sections);
778
797
  setSiteIndex(result.index);
779
798
  setTagsDegraded(remote.tagsDegraded);
@@ -999,6 +1018,7 @@ export default function EditorShell({
999
1018
  mediaManifest,
1000
1019
  mediaManifestRef,
1001
1020
  documentManifest: documentManifest ?? {},
1021
+ documentManifestRef,
1002
1022
  pendingDocuments: mediaPipeline.pendingDocuments,
1003
1023
  onDocumentsPublished: (ids) => { void mediaPipeline.publishDocuments(ids); },
1004
1024
  pendingDocumentDeletions: mediaPipeline.pendingDocumentDeletions,
@@ -1054,6 +1074,7 @@ export default function EditorShell({
1054
1074
  },
1055
1075
  onPublishComplete: buildStatus.startTracking,
1056
1076
  getBaseVersion: () => savedShaRef.current,
1077
+ getMainVersion: () => mainShaRef.current,
1057
1078
  recoverConflict,
1058
1079
  // C1: re-prime the clean content-cache baseline after every successful plain
1059
1080
  // save (discardSavedChanges just cleared it). The hook hands us the COMMITTED
@@ -93,6 +93,10 @@ interface PublishDeps {
93
93
  // no draft exists yet). Sent with saves so the server can 409 on a concurrent
94
94
  // edit instead of silently overwriting it. Read at save time (latest value).
95
95
  getBaseVersion: () => string | null;
96
+ /** The main commit this editor's content was read at. Sent as `mainVersion`
97
+ * with a no-draft save, so a publish landed by someone else since this tab
98
+ * loaded 409s into recoverConflict instead of committing a stale view. */
99
+ getMainVersion?: () => string | null;
96
100
  /** Injected by EditorShell. On a 409 (concurrent edit) the save calls this to
97
101
  * re-fetch the fresh draft, rebase local unsaved work on top, and refresh the
98
102
  * optimistic-concurrency base (savedShaRef). Returns true when recovery
@@ -125,6 +129,10 @@ interface PublishDeps {
125
129
  /** Committed document manifest, merged with the staged entries this save
126
130
  * commits. Optional so pre-documents callers keep compiling. */
127
131
  documentManifest?: DocumentManifest;
132
+ /** Optional ref mirror of `documentManifest`, read at save time — same
133
+ * contract as mediaManifestRef: the 409 retry sees recoverConflict's
134
+ * refreshed sidecar instead of this render's pre-conflict copy. */
135
+ documentManifestRef?: React.RefObject<DocumentManifest | null>;
128
136
  pendingDocuments?: { id: string; entry: DocumentManifestEntry }[];
129
137
  /** Ids of COMMITTED documents the editor has deleted but not yet saved. They
130
138
  * ride the final request as `deletedDocumentIds`; a staged-but-uncommitted
@@ -166,6 +174,7 @@ export function useEditorPublish({
166
174
  onShasUpdated,
167
175
  onPublishComplete,
168
176
  getBaseVersion,
177
+ getMainVersion,
169
178
  recoverConflict,
170
179
  onBaselineCommitted,
171
180
  onSaveWarnings,
@@ -173,6 +182,7 @@ export function useEditorPublish({
173
182
  savingRef,
174
183
  getEditGeneration,
175
184
  documentManifest = {},
185
+ documentManifestRef,
176
186
  pendingDocuments = [],
177
187
  pendingDocumentDeletions = [],
178
188
  onDocumentsPublished,
@@ -358,6 +368,9 @@ export function useEditorPublish({
358
368
  }
359
369
 
360
370
  let baseVersion = getBaseVersion();
371
+ const mainVersion = getMainVersion?.() ?? null;
372
+ // Only the request that forks the draft carries it; later chunks have a base.
373
+ const pinMain = () => (baseVersion === null && mainVersion !== null ? { mainVersion } : {});
361
374
  const uploadedItems: MediaItem[] = [];
362
375
  const totalUploads = gathered.mediaUploads.length;
363
376
 
@@ -413,6 +426,7 @@ export function useEditorPublish({
413
426
  }
414
427
  const data = await postSave({
415
428
  baseVersion,
429
+ ...pinMain(),
416
430
  media: {
417
431
  uploads: chunk.map(({ id, path, base64 }) => ({
418
432
  // content-ops ignores `item` for blob emission; the folder segment
@@ -441,6 +455,7 @@ export function useEditorPublish({
441
455
  for (const { id, entry } of manifestOnly) committedDocuments[id] = entry;
442
456
  const data = await postSave({
443
457
  baseVersion,
458
+ ...pinMain(),
444
459
  documentManifest: documentManifestToSend(),
445
460
  });
446
461
  baseVersion = data.sha;
@@ -461,6 +476,7 @@ export function useEditorPublish({
461
476
  };
462
477
  const data = await postSave({
463
478
  baseVersion,
479
+ ...pinMain(),
464
480
  media: { uploads: chunk, deletions: [], manifest },
465
481
  });
466
482
  baseVersion = data.sha;
@@ -486,6 +502,7 @@ export function useEditorPublish({
486
502
  sections: dirty.map(({ sectionId, content }) => ({ id: sectionId, content })),
487
503
  siteIndex,
488
504
  baseVersion,
505
+ ...pinMain(),
489
506
  ...(args.deletedSectionIds?.length ? { deletedSectionIds: args.deletedSectionIds } : {}),
490
507
  ...(args.isConfigDirty() ? { siteConfig: args.siteConfig } : {}),
491
508
  ...(gathered.hasMediaChanges ? {
@@ -607,7 +624,7 @@ export function useEditorPublish({
607
624
  // Read through the ref (when the shell supplies one) so the 409 retry
608
625
  // below sees recoverConflict's rebased manifest, not this render's copy.
609
626
  mediaManifest: mediaManifestRef?.current ?? mediaManifest,
610
- documentManifest,
627
+ documentManifest: documentManifestRef?.current ?? documentManifest,
611
628
  pendingDocuments,
612
629
  pendingDocumentDeletions,
613
630
  saveChunkBudget,