@aphexcms/cms-core 7.0.0 → 8.1.0

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 (127) hide show
  1. package/dist/api/documents.d.ts +19 -0
  2. package/dist/api/documents.d.ts.map +1 -1
  3. package/dist/api/documents.js +21 -0
  4. package/dist/api/schemas/documents.d.ts +3 -0
  5. package/dist/api/schemas/documents.d.ts.map +1 -1
  6. package/dist/api/schemas/documents.js +12 -0
  7. package/dist/cli/generate-types.d.ts.map +1 -1
  8. package/dist/cli/generate-types.js +133 -19
  9. package/dist/cli/generate-types.js.map +1 -1
  10. package/dist/components/AdminApp.svelte +106 -52
  11. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  12. package/dist/components/admin/DocumentEditor.svelte +65 -2
  13. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  14. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte +8 -6
  15. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts.map +1 -1
  16. package/dist/components/admin/fields/ArrayField.svelte +149 -2
  17. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
  18. package/dist/components/admin/fields/ReferenceField.svelte +198 -125
  19. package/dist/components/admin/fields/ReferenceField.svelte.d.ts +9 -2
  20. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  21. package/dist/components/layout/Sidebar.svelte +1 -1
  22. package/dist/components/layout/sidebar/AppSidebar.svelte +2 -1
  23. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
  24. package/dist/components/layout/sidebar/NavMain.svelte +9 -2
  25. package/dist/components/layout/sidebar/NavMain.svelte.d.ts +2 -0
  26. package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -1
  27. package/dist/db/interfaces/index.d.ts +3 -1
  28. package/dist/db/interfaces/index.d.ts.map +1 -1
  29. package/dist/db/interfaces/reference.d.ts +43 -0
  30. package/dist/db/interfaces/reference.d.ts.map +1 -0
  31. package/dist/db/interfaces/reference.js +4 -0
  32. package/dist/db/utils/reference-resolver.d.ts +4 -6
  33. package/dist/db/utils/reference-resolver.d.ts.map +1 -1
  34. package/dist/db/utils/reference-resolver.js +33 -31
  35. package/dist/document-refresh.svelte.d.ts +3 -0
  36. package/dist/document-refresh.svelte.d.ts.map +1 -0
  37. package/dist/document-refresh.svelte.js +21 -0
  38. package/dist/graphql/resolvers.d.ts.map +1 -1
  39. package/dist/graphql/resolvers.js +61 -1
  40. package/dist/graphql/schema.d.ts.map +1 -1
  41. package/dist/graphql/schema.js +10 -0
  42. package/dist/lib/api/documents.d.ts +19 -0
  43. package/dist/lib/api/documents.d.ts.map +1 -1
  44. package/dist/lib/api/documents.js +21 -0
  45. package/dist/lib/api/documents.js.map +1 -1
  46. package/dist/lib/api/schemas/documents.d.ts +3 -0
  47. package/dist/lib/api/schemas/documents.d.ts.map +1 -1
  48. package/dist/lib/api/schemas/documents.js +12 -0
  49. package/dist/lib/api/schemas/documents.js.map +1 -1
  50. package/dist/lib/db/interfaces/index.d.ts +3 -1
  51. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  52. package/dist/lib/db/interfaces/reference.d.ts +43 -0
  53. package/dist/lib/db/interfaces/reference.d.ts.map +1 -0
  54. package/dist/lib/db/interfaces/reference.js +5 -0
  55. package/dist/lib/db/interfaces/reference.js.map +1 -0
  56. package/dist/lib/db/utils/reference-resolver.d.ts +4 -6
  57. package/dist/lib/db/utils/reference-resolver.d.ts.map +1 -1
  58. package/dist/lib/db/utils/reference-resolver.js +33 -31
  59. package/dist/lib/db/utils/reference-resolver.js.map +1 -1
  60. package/dist/lib/document-refresh.svelte.d.ts +3 -0
  61. package/dist/lib/document-refresh.svelte.d.ts.map +1 -0
  62. package/dist/lib/document-refresh.svelte.js +22 -0
  63. package/dist/lib/document-refresh.svelte.js.map +1 -0
  64. package/dist/lib/graphql/resolvers.d.ts.map +1 -1
  65. package/dist/lib/graphql/resolvers.js +61 -1
  66. package/dist/lib/graphql/resolvers.js.map +1 -1
  67. package/dist/lib/graphql/schema.d.ts.map +1 -1
  68. package/dist/lib/graphql/schema.js +10 -0
  69. package/dist/lib/graphql/schema.js.map +1 -1
  70. package/dist/lib/local-api/collection-api.d.ts +10 -1
  71. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  72. package/dist/lib/local-api/collection-api.js +39 -1
  73. package/dist/lib/local-api/collection-api.js.map +1 -1
  74. package/dist/lib/local-api/index.d.ts +29 -0
  75. package/dist/lib/local-api/index.d.ts.map +1 -1
  76. package/dist/lib/local-api/index.js +66 -2
  77. package/dist/lib/local-api/index.js.map +1 -1
  78. package/dist/lib/schema-utils/validator.js +21 -11
  79. package/dist/lib/schema-utils/validator.js.map +1 -1
  80. package/dist/lib/server/api/index.d.ts.map +1 -1
  81. package/dist/lib/server/api/index.js +3 -1
  82. package/dist/lib/server/api/index.js.map +1 -1
  83. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  84. package/dist/lib/server/api/routes/documents-by-id.js +23 -0
  85. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  86. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  87. package/dist/lib/server/api/routes/documents.js +29 -1
  88. package/dist/lib/server/api/routes/documents.js.map +1 -1
  89. package/dist/lib/services/references-service.d.ts +35 -0
  90. package/dist/lib/services/references-service.d.ts.map +1 -0
  91. package/dist/lib/services/references-service.js +60 -0
  92. package/dist/lib/services/references-service.js.map +1 -0
  93. package/dist/lib/types/schemas.d.ts +8 -0
  94. package/dist/lib/types/schemas.d.ts.map +1 -1
  95. package/dist/lib/utils/preview.d.ts +2 -3
  96. package/dist/lib/utils/preview.d.ts.map +1 -1
  97. package/dist/lib/utils/preview.js +35 -8
  98. package/dist/lib/utils/preview.js.map +1 -1
  99. package/dist/lib/utils/reference-walk.d.ts +8 -0
  100. package/dist/lib/utils/reference-walk.d.ts.map +1 -0
  101. package/dist/lib/utils/reference-walk.js +43 -0
  102. package/dist/lib/utils/reference-walk.js.map +1 -0
  103. package/dist/local-api/collection-api.d.ts +10 -1
  104. package/dist/local-api/collection-api.d.ts.map +1 -1
  105. package/dist/local-api/collection-api.js +39 -1
  106. package/dist/local-api/index.d.ts +29 -0
  107. package/dist/local-api/index.d.ts.map +1 -1
  108. package/dist/local-api/index.js +66 -2
  109. package/dist/schema-utils/validator.js +21 -11
  110. package/dist/server/api/index.d.ts.map +1 -1
  111. package/dist/server/api/index.js +3 -1
  112. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  113. package/dist/server/api/routes/documents-by-id.js +23 -0
  114. package/dist/server/api/routes/documents.d.ts.map +1 -1
  115. package/dist/server/api/routes/documents.js +29 -1
  116. package/dist/services/references-service.d.ts +35 -0
  117. package/dist/services/references-service.d.ts.map +1 -0
  118. package/dist/services/references-service.js +59 -0
  119. package/dist/types/schemas.d.ts +8 -0
  120. package/dist/types/schemas.d.ts.map +1 -1
  121. package/dist/utils/preview.d.ts +2 -3
  122. package/dist/utils/preview.d.ts.map +1 -1
  123. package/dist/utils/preview.js +35 -8
  124. package/dist/utils/reference-walk.d.ts +8 -0
  125. package/dist/utils/reference-walk.d.ts.map +1 -0
  126. package/dist/utils/reference-walk.js +42 -0
  127. package/package.json +3 -3
@@ -276,11 +276,13 @@
276
276
  const COLLAPSED_WIDTH = 60; // Width of collapsed panels
277
277
  const TYPES_EXPANDED = 350;
278
278
  const DOCS_EXPANDED = 350;
279
-
280
279
  let layoutConfig = $derived.by(() => {
281
280
  const start = performance.now();
281
+ // Version panel is fixed-width (280px), not a full editor — deduct it
282
+ // from available space instead of counting it as an editor slot.
282
283
  const totalEditors =
283
- (currentView === 'editor' ? 1 : 0) + editorStack.length + (showVersionPanel ? 1 : 0);
284
+ (currentView === 'editor' ? 1 : 0) +
285
+ (editorStack.length > 0 ? 1 : 0);
284
286
 
285
287
  if (totalEditors === 0) {
286
288
  return {
@@ -367,19 +369,20 @@
367
369
  let remainingWidth = windowWidth - typesWidth - docsWidth;
368
370
  let maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
369
371
 
370
- // If we can't fit all editors, start collapsing panels
372
+ // If we can't fit all editors, start collapsing panels.
373
+ // Types sidebar collapses first — the docs list is more useful when
374
+ // actively editing (shows siblings in the same collection).
371
375
  if (maxExpandedEditors < totalEditors) {
372
- // Try collapsing docs first
373
- docsWidth = selectedDocumentType ? COLLAPSED_WIDTH : 0;
374
- docsExpanded = false;
376
+ typesWidth = COLLAPSED_WIDTH;
377
+ typesExpanded = false;
375
378
  remainingWidth = windowWidth - typesWidth - docsWidth;
376
379
  maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
377
380
  }
378
381
 
379
- // If still not enough space, collapse types too
382
+ // If still not enough space, collapse docs list too
380
383
  if (maxExpandedEditors < totalEditors) {
381
- typesWidth = COLLAPSED_WIDTH;
382
- typesExpanded = false;
384
+ docsWidth = selectedDocumentType ? COLLAPSED_WIDTH : 0;
385
+ docsExpanded = false;
383
386
  remainingWidth = windowWidth - typesWidth - docsWidth;
384
387
  maxExpandedEditors = Math.floor(remainingWidth / MIN_EDITOR_WIDTH);
385
388
  }
@@ -488,6 +491,12 @@
488
491
 
489
492
  if (currentView !== 'editor') return { visible: false, expanded: false };
490
493
 
494
+ // In focus mode, only the active editor shows — if active is a stacked
495
+ // editor, the primary hides.
496
+ if (focusModeOn && activeEditorIndex !== 0) {
497
+ return { visible: true, expanded: false };
498
+ }
499
+
491
500
  const primaryIndex = 0;
492
501
  const isExpanded = layoutConfig.expandedIndices.includes(primaryIndex);
493
502
 
@@ -536,6 +545,7 @@
536
545
  const action = url.searchParams.get('action');
537
546
  const docId = url.searchParams.get('docId');
538
547
  const stackParam = url.searchParams.get('stack');
548
+ const historyParam = url.searchParams.get('history');
539
549
 
540
550
  cmsLogger.debug('[URL Effect]', 'Params:', {
541
551
  docType,
@@ -602,6 +612,18 @@
602
612
  }
603
613
  }
604
614
 
615
+ // Restore version history panel from URL
616
+ if (historyParam === '1' && !showVersionPanel) {
617
+ showVersionPanel = true;
618
+ versionPanelDocId = stackParam
619
+ ? editorStack[editorStack.length - 1]?.documentId ?? docId
620
+ : docId;
621
+ } else if (!historyParam && showVersionPanel) {
622
+ showVersionPanel = false;
623
+ versionPanelDocId = null;
624
+ versionPreviewData = null;
625
+ }
626
+
605
627
  if (docType) {
606
628
  if (selectedDocumentType !== docType) {
607
629
  selectedDocumentType = docType;
@@ -678,6 +700,7 @@
678
700
  params.delete('docId');
679
701
  params.delete('action');
680
702
  params.delete('stack');
703
+ params.delete('history');
681
704
  await goto(`/admin?${params.toString()}`, { replaceState: false });
682
705
  mobileView = 'documents';
683
706
  }
@@ -688,6 +711,7 @@
688
711
  params.set('action', 'create');
689
712
  params.delete('docId');
690
713
  params.delete('stack');
714
+ params.delete('history');
691
715
  await goto(`/admin?${params.toString()}`, { replaceState: false });
692
716
  mobileView = 'editor';
693
717
  }
@@ -717,7 +741,7 @@
717
741
  if (fromDocId && fromDocType) {
718
742
  // Navigate back to the document we came from
719
743
  await navigateToEditDocument(fromDocId, fromDocType, false);
720
- } else if (selectedDocumentType) {
744
+ } else if (selectedDocumentType && !currentTypeIsSingleton) {
721
745
  // Navigate back to document list
722
746
  const params = new SvelteURLSearchParams(page.url.searchParams);
723
747
  params.set('docType', selectedDocumentType);
@@ -725,6 +749,7 @@
725
749
  params.delete('action');
726
750
  params.delete('stack');
727
751
  params.delete('focus');
752
+ params.delete('history');
728
753
  await goto(`/admin?${params.toString()}`, { replaceState: false });
729
754
  mobileView = 'documents';
730
755
  } else {
@@ -735,33 +760,43 @@
735
760
  params.delete('action');
736
761
  params.delete('stack');
737
762
  params.delete('focus');
763
+ params.delete('history');
738
764
  await goto(`/admin?${params.toString()}`, { replaceState: false });
739
765
  mobileView = 'types';
740
766
  }
741
767
  }
742
768
 
743
- // Handle opening reference in new editor panel
744
769
  function handleOpenVersionHistory(docId: string) {
745
770
  showVersionPanel = true;
746
771
  versionPanelDocId = docId;
772
+ const params = new SvelteURLSearchParams(page.url.searchParams);
773
+ params.set('history', '1');
774
+ goto(`/admin?${params.toString()}`, { replaceState: true });
747
775
  }
748
776
 
749
777
  function handleCloseVersionPanel() {
750
778
  showVersionPanel = false;
751
779
  versionPanelDocId = null;
752
780
  versionPreviewData = null;
781
+ const params = new SvelteURLSearchParams(page.url.searchParams);
782
+ params.delete('history');
783
+ goto(`/admin?${params.toString()}`, { replaceState: true });
753
784
  }
754
785
 
755
786
  // Close version panel when navigating away
756
787
  let prevDocType = $state(selectedDocumentType);
757
788
  let prevDocId = $state(editingDocumentId);
789
+ let initialNavDone = $state(false);
758
790
  $effect(() => {
759
791
  if (selectedDocumentType !== prevDocType || editingDocumentId !== prevDocId) {
760
792
  prevDocType = selectedDocumentType;
761
793
  prevDocId = editingDocumentId;
762
- if (showVersionPanel) {
763
- handleCloseVersionPanel();
794
+ if (initialNavDone && showVersionPanel) {
795
+ showVersionPanel = false;
796
+ versionPanelDocId = null;
797
+ versionPreviewData = null;
764
798
  }
799
+ initialNavDone = true;
765
800
  }
766
801
  });
767
802
 
@@ -785,38 +820,40 @@
785
820
  return;
786
821
  }
787
822
 
788
- // On desktop, add to editor stack unless the document is already open somewhere
823
+ // On desktop push onto the reference history stack. The UI only ever
824
+ // shows one stacked panel (the last entry). The back button pops the
825
+ // stack instead of closing, so you can walk back through the chain.
789
826
  if (editingDocumentId === documentId) {
790
- // Already open as the primary editor — just focus it
791
827
  activeEditorIndex = 0;
792
828
  return;
793
829
  }
794
- const existingIndex = editorStack.findIndex((item) => item.documentId === documentId);
795
- if (existingIndex !== -1) {
796
- // Already open in the stack — focus that editor (stack index 0 = activeEditorIndex 1)
797
- activeEditorIndex = existingIndex + 1;
798
- return;
799
- }
800
830
 
801
- const newStack = [...editorStack, { documentId, documentType, isCreating: false }];
831
+ const newEntry = { documentId, documentType, isCreating: false };
802
832
 
803
- // Build stack param string: type1:id1,type2:id2,...
804
- const stackParam = newStack.map((item) => `${item.documentType}:${item.documentId}`).join(',');
833
+ // If clicking a ref from the primary editor (activeEditorIndex === 0)
834
+ // and a stack already exists, the user is picking a different ref from
835
+ // the same array — replace the whole stack with the new pick.
836
+ // If clicking from within the stacked panel, push deeper.
837
+ const newStack =
838
+ activeEditorIndex === 0 && editorStack.length > 0
839
+ ? [newEntry]
840
+ : [...editorStack, newEntry];
805
841
 
806
- // Update URL with new stack
842
+ // URL tracks the full chain for refresh support
843
+ const stackParam = newStack.map((item) => `${item.documentType}:${item.documentId}`).join(',');
807
844
  const params = new SvelteURLSearchParams(page.url.searchParams);
808
845
  params.set('stack', stackParam);
809
846
  await goto(`/admin?${params.toString()}`, { replaceState: false });
810
847
 
811
- // Set the new editor as active
812
- activeEditorIndex = newStack.length; // 0 = primary, so stack.length is the new editor
848
+ // The stacked panel is always index 1 (only one panel rendered)
849
+ activeEditorIndex = 1;
813
850
  }
814
851
 
815
- // Close editor from stack
816
- async function handleCloseStackedEditor(index: number) {
817
- const newStack = editorStack.slice(0, index);
852
+ // Back button on the stacked panel — pop one level. If the stack
853
+ // becomes empty, the panel closes entirely.
854
+ async function handleStackedEditorBack() {
855
+ const newStack = editorStack.slice(0, -1);
818
856
 
819
- // Update URL
820
857
  const params = new SvelteURLSearchParams(page.url.searchParams);
821
858
  if (newStack.length > 0) {
822
859
  const stackParam = newStack
@@ -828,8 +865,16 @@
828
865
  }
829
866
  await goto(`/admin?${params.toString()}`, { replaceState: false });
830
867
 
831
- // Reset active editor to the last one
832
- activeEditorIndex = Math.min(activeEditorIndex, newStack.length);
868
+ activeEditorIndex = newStack.length > 0 ? 1 : 0;
869
+ }
870
+
871
+ // Hard close — removes the stacked panel entirely (used by delete action)
872
+ async function handleCloseStackedEditor(_index: number) {
873
+ const params = new SvelteURLSearchParams(page.url.searchParams);
874
+ params.delete('stack');
875
+ params.delete('history');
876
+ await goto(`/admin?${params.toString()}`, { replaceState: false });
877
+ activeEditorIndex = 0;
833
878
  }
834
879
 
835
880
  // Set active editor when clicking on a strip
@@ -976,6 +1021,7 @@
976
1021
  params.delete('docId');
977
1022
  params.delete('action');
978
1023
  params.delete('stack');
1024
+ params.delete('history');
979
1025
  await goto(`/admin?${params.toString()}`, { replaceState: false });
980
1026
  }}
981
1027
  class="text-muted-foreground hover:text-foreground flex items-center gap-2 text-sm"
@@ -1592,7 +1638,7 @@
1592
1638
  {isReadOnly}
1593
1639
  />
1594
1640
  </div>
1595
- {#if !primaryEditorState.expanded}
1641
+ {#if !primaryEditorState.expanded && !focusModeOn}
1596
1642
  <!-- Collapsed Primary Editor Strip -->
1597
1643
  <button
1598
1644
  onclick={() => setActiveEditor(0)}
@@ -1613,25 +1659,33 @@
1613
1659
  {/if}
1614
1660
  {/if}
1615
1661
 
1616
- <!-- Stacked Reference Editors -->
1617
- {#each editorStack as stackedEditor, index (index)}
1618
- {@const editorIndex = index + 1}
1619
- {@const isExpanded = layoutConfig.expandedIndices.includes(editorIndex)}
1662
+ <!-- Stacked Reference Panel — only the last entry in the stack
1663
+ is rendered. Back button pops the stack (walks back through
1664
+ the ref chain); closing entirely on the last pop. -->
1665
+ {#if editorStack.length > 0}
1666
+ {@const currentRef = editorStack[editorStack.length - 1]!}
1667
+ {@const isExpanded = focusModeOn
1668
+ ? activeEditorIndex === 1
1669
+ : layoutConfig.expandedIndices.includes(1)}
1620
1670
 
1621
1671
  {#if isExpanded}
1622
1672
  <div
1623
1673
  class="border-rule h-full flex-1 overflow-x-hidden overflow-y-auto border-l transition-all duration-200"
1624
- style="min-width: 0;"
1674
+ style="min-width: 0;{focusModeOn || showVersionPanel ? '' : ' max-width: 50%;'}"
1625
1675
  >
1626
1676
  <DocumentEditor
1627
1677
  {schemas}
1628
- documentType={stackedEditor.documentType}
1629
- documentId={stackedEditor.documentId}
1630
- isCreating={stackedEditor.isCreating}
1631
- onBack={() => handleCloseStackedEditor(index)}
1678
+ documentType={currentRef.documentType}
1679
+ documentId={currentRef.documentId}
1680
+ isCreating={currentRef.isCreating}
1681
+ onBack={handleStackedEditorBack}
1632
1682
  onOpenReference={handleOpenReference}
1633
1683
  onOpenVersionHistory={handleOpenVersionHistory}
1634
- externalVersionPreview={versionPanelDocId === stackedEditor.documentId
1684
+ onToggleFocus={() => {
1685
+ activeEditorIndex = 1;
1686
+ toggleFocusMode();
1687
+ }}
1688
+ externalVersionPreview={versionPanelDocId === currentRef.documentId
1635
1689
  ? versionPreviewData
1636
1690
  : null}
1637
1691
  onSaved={async () => {}}
@@ -1655,35 +1709,35 @@
1655
1709
  }
1656
1710
  }}
1657
1711
  onDeleted={async () => {
1658
- handleCloseStackedEditor(index);
1712
+ handleCloseStackedEditor(0);
1659
1713
  }}
1660
1714
  {isReadOnly}
1661
1715
  />
1662
1716
  </div>
1663
- {:else}
1717
+ {:else if !focusModeOn}
1664
1718
  <!-- Collapsed Stacked Editor Strip -->
1665
1719
  <button
1666
- onclick={() => setActiveEditor(editorIndex)}
1720
+ onclick={() => setActiveEditor(1)}
1667
1721
  class="border-rule hover:bg-muted/50 flex h-full w-[60px] cursor-pointer flex-col border-l transition-colors"
1668
- title="Click to expand {stackedEditor.documentType}"
1722
+ title="Click to expand {currentRef.documentType}"
1669
1723
  >
1670
1724
  <div class="flex h-full flex-1 items-start justify-center p-2 pt-8 text-left">
1671
1725
  <div
1672
1726
  class="text-foreground text-sm font-medium whitespace-nowrap [writing-mode:vertical-rl]"
1673
1727
  >
1674
- {stackedEditor.documentType.charAt(0).toUpperCase() +
1675
- stackedEditor.documentType.slice(1)}
1728
+ {currentRef.documentType.charAt(0).toUpperCase() +
1729
+ currentRef.documentType.slice(1)}
1676
1730
  </div>
1677
1731
  </div>
1678
1732
  </button>
1679
1733
  {/if}
1680
- {/each}
1734
+ {/if}
1681
1735
  {/if}
1682
1736
 
1683
1737
  <!-- Version History Panel -->
1684
1738
  {#if showVersionPanel && versionPanelDocId}
1685
1739
  <div
1686
- class="border-rule h-full w-[280px] min-w-[250px] overflow-y-auto border-l transition-all duration-200"
1740
+ class="border-rule h-full w-[280px] shrink-0 overflow-y-auto border-l transition-all duration-200"
1687
1741
  >
1688
1742
  <DocumentVersionPanel
1689
1743
  bind:this={versionPanelRef}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AA4BnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAA;KAAE,CAAC;IACxD,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAChD;AA0/CF,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AA4BnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAA;KAAE,CAAC;IACxD,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAChD;AA6iDF,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -13,6 +13,8 @@
13
13
  import { setSaveStateContext } from '../../save-state-context.svelte';
14
14
  import { usePermissions } from '../../permissions-context.svelte';
15
15
  import { getDefaultValueForFieldType } from '../../utils/field-defaults';
16
+ import { notifyDocumentChanged } from '../../document-refresh.svelte';
17
+ import { collectReferenceIds } from '../../utils/reference-walk';
16
18
  import { cmsLogger } from '../../utils/logger';
17
19
  import { toast } from 'svelte-sonner';
18
20
  import { confirmDialog } from './confirm-dialog/confirm-dialog.svelte';
@@ -645,6 +647,7 @@
645
647
  if (response?.success) {
646
648
  lastSaved = new Date();
647
649
  hasUnsavedChanges = false;
650
+ if (response.data?.id) notifyDocumentChanged(response.data.id);
648
651
  if (isAutoSave) {
649
652
  // Trigger validation on all fields after auto-save
650
653
  validateAllFields(); // Update validation status
@@ -716,6 +719,47 @@
716
719
  return;
717
720
  }
718
721
 
722
+ // Referential integrity guard: every referenced document must itself be
723
+ // published. Without this, publishing a doc that points at drafts would
724
+ // expose dangling references in the published perspective. One batched
725
+ // HTTP call regardless of ref count.
726
+ const refIds = collectReferenceIds(documentData, schema, schemas);
727
+ if (refIds.length > 0) {
728
+ let fetched: any[] = [];
729
+ try {
730
+ const res = await documents.getMany(refIds);
731
+ if (res.success && res.data) fetched = res.data;
732
+ } catch {
733
+ // fall through — missing fetched IDs become "blockers" below
734
+ }
735
+ const fetchedById = new Map(fetched.map((d) => [d.id, d]));
736
+ const checks = refIds.map((id) => ({ id, doc: fetchedById.get(id) ?? null }));
737
+ const blockers = checks.filter(
738
+ (c) => !c.doc || (c.doc as any)._meta?.status !== 'published'
739
+ );
740
+ if (blockers.length > 0) {
741
+ const titles = blockers
742
+ .slice(0, 3)
743
+ .map((b) => {
744
+ if (!b.doc) return `Missing (${b.id.slice(0, 8)})`;
745
+ const d = b.doc as any;
746
+ const label = d.title ?? d.name ?? d.heading ?? d.label ?? b.id;
747
+ const type = d._meta?.type;
748
+ return type ? `"${label}" (${type})` : `"${label}"`;
749
+ })
750
+ .join(', ');
751
+ const remainder = blockers.length > 3 ? ` and ${blockers.length - 3} more` : '';
752
+ saveError = `Cannot publish — unpublished references: ${titles}${remainder}`;
753
+ toast.error(
754
+ `${blockers.length} referenced document${blockers.length === 1 ? '' : 's'} ${blockers.length === 1 ? 'is' : 'are'} not published`,
755
+ {
756
+ description: 'Publish the referenced documents first, then try again.'
757
+ }
758
+ );
759
+ return;
760
+ }
761
+ }
762
+
719
763
  saving = true;
720
764
  saveError = null;
721
765
 
@@ -733,6 +777,7 @@
733
777
  fullDocument = response.data;
734
778
  lastSaved = new Date();
735
779
  publishSuccess = new Date();
780
+ notifyDocumentChanged(documentId);
736
781
  cmsLogger.debug('[Document Editor]', '✅ Document published successfully');
737
782
  cmsLogger.debug('[Document Editor]', '📄 New published hash:', response.data.publishedHash);
738
783
 
@@ -790,10 +835,27 @@
790
835
  async function unpublishDocument() {
791
836
  if (!documentId || saving) return;
792
837
 
838
+ // Surface published back-references so the user knows what will end up
839
+ // with dangling refs in the published perspective. Best-effort —
840
+ // network errors fall through to the standard confirm.
841
+ let backRefDescription =
842
+ 'It will be removed from published queries, but the data is preserved and you can re-publish anytime.';
843
+ try {
844
+ const backRefRes = await documents.getBackReferences(documentId);
845
+ if (backRefRes.success && backRefRes.data) {
846
+ const publishedBackRefs = backRefRes.data.filter((r) => r.status === 'published');
847
+ if (publishedBackRefs.length > 0) {
848
+ const count = publishedBackRefs.length;
849
+ backRefDescription = `${count} published document${count === 1 ? '' : 's'} reference${count === 1 ? 's' : ''} this one — their references will be left dangling in the published perspective until you re-publish them. Continue?`;
850
+ }
851
+ }
852
+ } catch {
853
+ // Silently fall through to the generic confirm; ref index may be unpopulated.
854
+ }
855
+
793
856
  const confirmUnpublish = await confirmDialog({
794
857
  title: 'Unpublish this document?',
795
- description:
796
- 'It will be removed from published queries, but the data is preserved and you can re-publish anytime.',
858
+ description: backRefDescription,
797
859
  confirmText: 'Unpublish',
798
860
  variant: 'destructive'
799
861
  });
@@ -809,6 +871,7 @@
809
871
  ...fullDocument,
810
872
  _meta: { ...fullDocument?._meta, status: 'unpublished' }
811
873
  };
874
+ notifyDocumentChanged(documentId);
812
875
  toast.success('Document unpublished — you can re-publish anytime');
813
876
  if (onUnpublished && documentId) {
814
877
  onUnpublished(documentId);
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/DocumentEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAaxD,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,sBAAsB,CAAC,EAAE;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1B,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,IAAI,CAAC;IACT,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAm+CF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"DocumentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/DocumentEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAexD,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,oBAAoB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,sBAAsB,CAAC,EAAE;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1B,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,IAAI,CAAC;IACT,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAkiDF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import * as AlertDialog from '@aphexcms/ui/shadcn/alert-dialog';
3
- import { Button } from '@aphexcms/ui/shadcn/button';
3
+ import { buttonVariants } from '@aphexcms/ui/shadcn/button';
4
4
  import { confirmDialogState, resolveConfirmDialog } from './confirm-dialog.svelte';
5
5
 
6
6
  function handleOpenChange(open: boolean) {
@@ -21,15 +21,17 @@
21
21
  {/if}
22
22
  </AlertDialog.Header>
23
23
  <AlertDialog.Footer>
24
- <Button variant="outline" onclick={() => resolveConfirmDialog(false)}>
24
+ <AlertDialog.Cancel onclick={() => resolveConfirmDialog(false)}>
25
25
  {confirmDialogState.cancelText}
26
- </Button>
27
- <Button
28
- variant={confirmDialogState.variant === 'destructive' ? 'destructive' : 'default'}
26
+ </AlertDialog.Cancel>
27
+ <AlertDialog.Action
28
+ class={confirmDialogState.variant === 'destructive'
29
+ ? buttonVariants({ variant: 'destructive' })
30
+ : undefined}
29
31
  onclick={() => resolveConfirmDialog(true)}
30
32
  >
31
33
  {confirmDialogState.confirmText}
32
- </Button>
34
+ </AlertDialog.Action>
33
35
  </AlertDialog.Footer>
34
36
  </AlertDialog.Content>
35
37
  </AlertDialog.Root>
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialogHost.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/admin/confirm-dialog/ConfirmDialogHost.svelte.ts"],"names":[],"mappings":"AA0CA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,iBAAiB;;kBAA+E,CAAC;AACrF,KAAK,iBAAiB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ConfirmDialogHost.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/admin/confirm-dialog/ConfirmDialogHost.svelte.ts"],"names":[],"mappings":"AA4CA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,iBAAiB;;kBAA+E,CAAC;AACrF,KAAK,iBAAiB,GAAG,YAAY,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,eAAe,iBAAiB,CAAC"}