astro-tractstack 2.0.18 → 2.0.20

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 (58) hide show
  1. package/dist/index.js +6 -32
  2. package/package.json +1 -1
  3. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +1 -4
  4. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +0 -4
  5. package/templates/src/components/codehooks/ListContentSetup.tsx +1 -8
  6. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -2
  7. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -2
  8. package/templates/src/components/compositor/Compositor.tsx +3 -6
  9. package/templates/src/components/compositor/Node.tsx +13 -32
  10. package/templates/src/components/compositor/NodeWithGuid.tsx +49 -5
  11. package/templates/src/components/compositor/nodes/Pane.tsx +4 -21
  12. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +27 -7
  13. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +3 -1
  14. package/templates/src/components/compositor/preview/OgImagePreview.tsx +0 -5
  15. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +5 -6
  16. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +1 -0
  17. package/templates/src/components/edit/PanelSwitch.tsx +3 -24
  18. package/templates/src/components/edit/SettingsPanel.tsx +0 -1
  19. package/templates/src/components/edit/ToolMode.tsx +6 -14
  20. package/templates/src/components/edit/pane/AddPanePanel.tsx +45 -25
  21. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +277 -70
  22. package/templates/src/components/edit/pane/AddPanePanel_paste.tsx +111 -0
  23. package/templates/src/components/edit/pane/RestylePaneModal.tsx +7 -14
  24. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +0 -5
  25. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -11
  26. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +1 -3
  27. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +0 -6
  28. package/templates/src/components/edit/panels/StyleImagePanel.tsx +0 -1
  29. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +0 -3
  30. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +0 -4
  31. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +8 -5
  32. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +1 -2
  33. package/templates/src/components/edit/panels/StyleParentPanel.tsx +1 -3
  34. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +2 -5
  35. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +2 -8
  36. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +0 -4
  37. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +27 -16
  38. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +9 -26
  39. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +7 -16
  40. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +5 -6
  41. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +0 -5
  42. package/templates/src/components/fields/BackgroundImageWrapper.tsx +1 -7
  43. package/templates/src/components/fields/ColorPickerCombo.tsx +8 -12
  44. package/templates/src/components/fields/ViewportComboBox.tsx +4 -6
  45. package/templates/src/constants/prompts.json +22 -1
  46. package/templates/src/stores/nodes.ts +297 -222
  47. package/templates/src/stores/storykeep.ts +3 -3
  48. package/templates/src/types/compositorTypes.ts +21 -1
  49. package/templates/src/types/tractstack.ts +1 -0
  50. package/templates/src/utils/compositor/TemplatePanes.ts +0 -76
  51. package/templates/src/utils/compositor/aiPaneParser.ts +265 -83
  52. package/templates/src/utils/compositor/designLibraryHelper.ts +252 -26
  53. package/templates/src/utils/helpers.ts +5 -4
  54. package/utils/inject-files.ts +6 -32
  55. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +0 -154
  56. package/templates/src/components/edit/pane/PageGen_preview.tsx +0 -511
  57. package/templates/src/utils/compositor/processMarkdown.ts +0 -445
  58. package/templates/src/utils/compositor/templateMarkdownStyles.ts +0 -1273
@@ -38,6 +38,7 @@ import type {
38
38
  PaneNode,
39
39
  StoryFragmentNode,
40
40
  Tag,
41
+ TemplateGridLayout,
41
42
  TemplateMarkdown,
42
43
  TemplateNode,
43
44
  TemplatePane,
@@ -102,7 +103,6 @@ export class NodesContext {
102
103
  toolAddModeStore = map<{ value: ToolAddMode }>({
103
104
  value: 'p',
104
105
  });
105
- showGuids = atom<boolean>(false);
106
106
 
107
107
  /**
108
108
  * Sets an edit lock on a specific node to prevent re-renders during editing
@@ -1445,124 +1445,126 @@ export class NodesContext {
1445
1445
  }
1446
1446
  break;
1447
1447
 
1448
- case 'TagElement':
1449
- {
1450
- const getButtonClasses = (node: FlatNode) => {
1451
- return {
1452
- mobile: strippedStyles(node.buttonPayload?.buttonClasses || {}),
1453
- tablet: {},
1454
- desktop: {},
1455
- };
1448
+ case 'TagElement': {
1449
+ const getButtonClasses = (node: FlatNode) => {
1450
+ return {
1451
+ mobile: strippedStyles(node.buttonPayload?.buttonClasses || {}),
1452
+ tablet: {},
1453
+ desktop: {},
1456
1454
  };
1455
+ };
1457
1456
 
1458
- const getHoverClasses = (node: FlatNode) => {
1459
- return {
1460
- mobile: strippedStyles(
1461
- node.buttonPayload?.buttonHoverClasses || {}
1462
- ),
1463
- tablet: {},
1464
- desktop: {},
1465
- };
1457
+ const getHoverClasses = (node: FlatNode) => {
1458
+ return {
1459
+ mobile: strippedStyles(
1460
+ node.buttonPayload?.buttonHoverClasses || {}
1461
+ ),
1462
+ tablet: {},
1463
+ desktop: {},
1466
1464
  };
1465
+ };
1467
1466
 
1468
- if (hasButtonPayload(node)) {
1469
- const [classesPayload] = processClassesForViewports(
1470
- getButtonClasses(node),
1471
- {},
1472
- 1
1473
- );
1474
- const [classesHoverPayload] = processClassesForViewports(
1475
- getHoverClasses(node),
1476
- {},
1477
- 1
1478
- );
1479
- return `${classesPayload?.length ? classesPayload[0] : ``} ${
1480
- classesHoverPayload?.length
1481
- ? addHoverPrefix(classesHoverPayload[0])
1482
- : ``
1483
- }`;
1484
- }
1467
+ if (hasButtonPayload(node)) {
1468
+ const [classesPayload] = processClassesForViewports(
1469
+ getButtonClasses(node),
1470
+ {},
1471
+ 1
1472
+ );
1473
+ const [classesHoverPayload] = processClassesForViewports(
1474
+ getHoverClasses(node),
1475
+ {},
1476
+ 1
1477
+ );
1478
+ return `${classesPayload?.length ? classesPayload[0] : ``} ${
1479
+ classesHoverPayload?.length
1480
+ ? addHoverPrefix(classesHoverPayload[0])
1481
+ : ``
1482
+ }`;
1483
+ }
1485
1484
 
1486
- if ('tagName' in node && node.tagName === 'span') {
1487
- const spanNode = node as FlatNode;
1488
- const [all, mobile, tablet, desktop] = processClassesForViewports(
1489
- { mobile: {}, tablet: {}, desktop: {} },
1490
- spanNode.overrideClasses || {},
1491
- 1
1492
- );
1493
- const outlineClass =
1494
- this.toolModeValStore.get().value === 'styles'
1495
- ? ' outline outline-1 outline-dotted outline-gray-400/60'
1496
- : '';
1485
+ if ('tagName' in node && node.tagName === 'span') {
1486
+ const spanNode = node as FlatNode;
1487
+ const [all, mobile, tablet, desktop] = processClassesForViewports(
1488
+ { mobile: {}, tablet: {}, desktop: {} },
1489
+ spanNode.overrideClasses || {},
1490
+ 1
1491
+ );
1492
+ const outlineClass =
1493
+ this.toolModeValStore.get().value === 'styles'
1494
+ ? ' outline outline-1 outline-dotted outline-gray-400/60'
1495
+ : '';
1497
1496
 
1498
- const getClassString = (classes: string[]): string =>
1499
- classes && classes.length > 0 ? classes[0] : '';
1497
+ const getClassString = (classes: string[]): string =>
1498
+ classes && classes.length > 0 ? classes[0] : '';
1500
1499
 
1501
- if (isPreview) return getClassString(desktop) + outlineClass;
1502
- switch (viewport) {
1503
- case 'desktop':
1504
- return getClassString(desktop) + outlineClass;
1505
- case 'tablet':
1506
- return getClassString(tablet) + outlineClass;
1507
- case 'mobile':
1508
- return getClassString(mobile) + outlineClass;
1509
- default:
1510
- return getClassString(all) + outlineClass;
1511
- }
1500
+ if (isPreview) return getClassString(desktop) + outlineClass;
1501
+ switch (viewport) {
1502
+ case 'desktop':
1503
+ return getClassString(desktop) + outlineClass;
1504
+ case 'tablet':
1505
+ return getClassString(tablet) + outlineClass;
1506
+ case 'mobile':
1507
+ return getClassString(mobile) + outlineClass;
1508
+ default:
1509
+ return getClassString(all) + outlineClass;
1512
1510
  }
1511
+ }
1513
1512
 
1514
- // Begin Default Class Lookup Logic
1515
- const markdownParentId = this.getClosestNodeTypeFromId(
1516
- nodeId,
1517
- 'Markdown'
1518
- );
1519
- if (!markdownParentId) break;
1513
+ // Begin Default Class Lookup Logic
1514
+ const markdownParentId = this.getClosestNodeTypeFromId(
1515
+ nodeId,
1516
+ 'Markdown'
1517
+ );
1518
+ if (!markdownParentId) break;
1520
1519
 
1521
- const markdownParentNode = this.allNodes
1522
- .get()
1523
- .get(markdownParentId) as MarkdownPaneFragmentNode;
1524
- if (!markdownParentNode) break;
1525
-
1526
- const tagNameStr = (node as FlatNode).tagName as string;
1527
-
1528
- // By default, assume the markdown node is the source of styles.
1529
- let styleSourceNode: MarkdownPaneFragmentNode | GridLayoutNode =
1530
- markdownParentNode;
1531
- let styles = styleSourceNode.defaultClasses?.[tagNameStr];
1532
-
1533
- // If the markdown node has no styles for this tag, check for a GridLayout grandparent.
1534
- // This handles the case where the MarkdownNode is a column.
1535
- if (!styles || Object.keys(styles.mobile).length === 0) {
1536
- const grandparent = markdownParentNode.parentId
1537
- ? this.allNodes.get().get(markdownParentNode.parentId)
1538
- : null;
1539
-
1540
- if (grandparent && isGridLayoutNode(grandparent)) {
1541
- styleSourceNode = grandparent;
1542
- styles = styleSourceNode.defaultClasses?.[tagNameStr];
1543
- }
1520
+ const markdownParentNode = this.allNodes
1521
+ .get()
1522
+ .get(markdownParentId) as MarkdownPaneFragmentNode;
1523
+ if (!markdownParentNode) break;
1524
+
1525
+ const tagNameStr = (node as FlatNode).tagName as string;
1526
+
1527
+ // By default, assume the markdown node is the source of styles.
1528
+ let styleSourceNode: MarkdownPaneFragmentNode | GridLayoutNode =
1529
+ markdownParentNode;
1530
+ let styles = styleSourceNode.defaultClasses?.[tagNameStr];
1531
+
1532
+ // If the markdown node has no styles for this tag, check for a GridLayout grandparent.
1533
+ // This handles the case where the MarkdownNode is a column.
1534
+ if (!styles || Object.keys(styles.mobile).length === 0) {
1535
+ const grandparent = markdownParentNode.parentId
1536
+ ? this.allNodes.get().get(markdownParentNode.parentId)
1537
+ : null;
1538
+
1539
+ if (grandparent && isGridLayoutNode(grandparent)) {
1540
+ styleSourceNode = grandparent;
1541
+ styles = styleSourceNode.defaultClasses?.[tagNameStr];
1544
1542
  }
1543
+ }
1545
1544
 
1546
- if (styles && styles.mobile) {
1547
- const [all, mobile, tablet, desktop] = processClassesForViewports(
1548
- styles,
1549
- (node as FlatNode)?.overrideClasses || {},
1550
- 1
1551
- );
1552
- if (isPreview) return desktop[0];
1553
- switch (viewport) {
1554
- case 'desktop':
1555
- return desktop[0];
1556
- case 'tablet':
1557
- return tablet[0];
1558
- case 'mobile':
1559
- return mobile[0];
1560
- default:
1561
- return all[0];
1562
- }
1563
- }
1545
+ const baseStyles =
1546
+ styles && styles.mobile
1547
+ ? styles
1548
+ : { mobile: {}, tablet: {}, desktop: {} };
1549
+
1550
+ const [all, mobile, tablet, desktop] = processClassesForViewports(
1551
+ baseStyles,
1552
+ (node as FlatNode)?.overrideClasses || {},
1553
+ 1
1554
+ );
1555
+
1556
+ if (isPreview) return desktop[0];
1557
+ switch (viewport) {
1558
+ case 'desktop':
1559
+ return desktop[0];
1560
+ case 'tablet':
1561
+ return tablet[0];
1562
+ case 'mobile':
1563
+ return mobile[0];
1564
+ default:
1565
+ return all[0];
1564
1566
  }
1565
- break;
1567
+ }
1566
1568
 
1567
1569
  case 'StoryFragment': {
1568
1570
  const storyFragment = node as StoryFragmentNode;
@@ -1775,39 +1777,20 @@ export class NodesContext {
1775
1777
  duplicatedPane.isChanged = true;
1776
1778
 
1777
1779
  // Track all nodes that need to be added
1778
- let allNodes: BaseNode[] = [];
1780
+ // Call the new helper to process markdown, gridLayout, and bgPane
1781
+ const allNodes: BaseNode[] = this._processPaneTemplate(
1782
+ duplicatedPane,
1783
+ ownerId
1784
+ );
1779
1785
 
1780
- // must generate nodes from markdown
1786
+ if (duplicatedPane.bgPane) {
1787
+ delete duplicatedPane.bgPane;
1788
+ }
1781
1789
  if (duplicatedPane.markdown) {
1782
- duplicatedPane.markdown = cloneDeep(pane.markdown) as TemplateMarkdown;
1783
- duplicatedPane.markdown.id = pane?.markdown?.id || ulid();
1784
- duplicatedPane.markdown.markdownId = pane?.markdown?.markdownId || ulid();
1785
- duplicatedPane.markdown.parentId = ownerId;
1786
-
1787
- let markdownNodes: TemplateNode[] = [];
1788
- if (duplicatedPane.markdown.markdownBody) {
1789
- const markdownGen = new MarkdownGenerator(this);
1790
- markdownNodes = markdownGen.markdownToFlatNodes(
1791
- duplicatedPane.markdown.markdownBody,
1792
- duplicatedPane.markdown.id
1793
- ) as TemplateNode[];
1794
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1795
- }
1796
-
1797
- // Markdown already as nodes
1798
- else if (
1799
- typeof duplicatedPane.markdown !== `undefined` &&
1800
- typeof duplicatedPane.markdown.id === `string`
1801
- ) {
1802
- duplicatedPane?.markdown.nodes?.forEach((node) => {
1803
- const childrenNodes = this.setupTemplateNodeRecursively(
1804
- node,
1805
- duplicatedPane?.markdown?.id || ''
1806
- );
1807
- markdownNodes.push(...childrenNodes);
1808
- });
1809
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1810
- }
1790
+ delete duplicatedPane.markdown;
1791
+ }
1792
+ if (duplicatedPane.gridLayout) {
1793
+ delete duplicatedPane.gridLayout;
1811
1794
  }
1812
1795
 
1813
1796
  this.addNode(duplicatedPane as PaneNode);
@@ -1852,87 +1835,21 @@ export class NodesContext {
1852
1835
  }
1853
1836
  }
1854
1837
 
1855
- let allNodes: BaseNode[] = [];
1856
-
1857
- if (duplicatedPane.markdown) {
1858
- duplicatedPane.markdown = cloneDeep(pane.markdown) as TemplateMarkdown;
1859
- duplicatedPane.markdown.id = pane?.markdown?.id || ulid();
1860
- duplicatedPane.markdown.markdownId = pane?.markdown?.markdownId || ulid();
1861
- duplicatedPane.markdown.parentId = duplicatedPaneId;
1862
-
1863
- let markdownNodes: TemplateNode[] = [];
1864
- if (duplicatedPane.markdown.markdownBody) {
1865
- const markdownGen = new MarkdownGenerator(this);
1866
- markdownNodes = markdownGen.markdownToFlatNodes(
1867
- duplicatedPane.markdown.markdownBody,
1868
- duplicatedPane.markdown.id
1869
- ) as TemplateNode[];
1870
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1871
- } else if (
1872
- typeof duplicatedPane.markdown !== `undefined` &&
1873
- typeof duplicatedPane.markdown.id === `string`
1874
- ) {
1875
- // Create a map to track the original node ID to its duplicated node ID
1876
- const oldToNewIdMap = new Map<string, string>();
1877
- // First pass: Clone nodes and generate new IDs
1878
- const nodesClone =
1879
- duplicatedPane?.markdown?.nodes?.map((originalNode) => {
1880
- const newNode = cloneDeep(originalNode);
1881
- newNode.id = ulid();
1882
- oldToNewIdMap.set(originalNode.id, newNode.id);
1883
- return newNode;
1884
- }) || [];
1885
- // Second pass: Update parent IDs using the mapping
1886
- nodesClone.forEach((node) => {
1887
- // Special case for direct children of markdown
1888
- if (node.parentId === pane?.markdown?.id) {
1889
- node.parentId = duplicatedPane?.markdown?.id || '';
1890
- } else {
1891
- // For all other nodes, use the mapping to find the new parent ID
1892
- const newParentId = oldToNewIdMap.get(node.parentId || '');
1893
- if (newParentId) {
1894
- node.parentId = newParentId;
1895
- }
1896
- }
1897
- markdownNodes.push(node);
1898
- });
1899
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1900
- }
1901
- }
1838
+ // Call the new helper to process markdown, gridLayout, and bgPane
1839
+ const allNodes: BaseNode[] = this._processPaneTemplate(
1840
+ duplicatedPane,
1841
+ duplicatedPaneId
1842
+ );
1902
1843
 
1903
1844
  if (duplicatedPane.bgPane) {
1904
- const bgPaneId = ulid();
1905
-
1906
- if (duplicatedPane.bgPane.type === 'visual-break') {
1907
- const visualBreakPane = duplicatedPane.bgPane as VisualBreakNode;
1908
- const bgPaneNode: VisualBreakNode = {
1909
- id: bgPaneId,
1910
- nodeType: 'BgPane',
1911
- parentId: duplicatedPaneId,
1912
- type: 'visual-break',
1913
- breakDesktop: visualBreakPane.breakDesktop,
1914
- breakTablet: visualBreakPane.breakTablet,
1915
- breakMobile: visualBreakPane.breakMobile,
1916
- };
1917
- allNodes.push(bgPaneNode);
1918
- } else if (duplicatedPane.bgPane.type === 'artpack-image') {
1919
- const artpackBgPane = duplicatedPane.bgPane as ArtpackImageNode;
1920
- const bgPaneNode: ArtpackImageNode = {
1921
- id: bgPaneId,
1922
- nodeType: 'BgPane',
1923
- parentId: duplicatedPaneId,
1924
- type: 'artpack-image',
1925
- collection: artpackBgPane.collection,
1926
- image: artpackBgPane.image,
1927
- src: artpackBgPane.src,
1928
- srcSet: artpackBgPane.srcSet,
1929
- alt: artpackBgPane.alt || `Artpack image`,
1930
- objectFit: artpackBgPane.objectFit || 'cover',
1931
- };
1932
- allNodes.push(bgPaneNode);
1933
- }
1934
1845
  delete duplicatedPane.bgPane;
1935
1846
  }
1847
+ if (duplicatedPane.markdown) {
1848
+ delete duplicatedPane.markdown;
1849
+ }
1850
+ if (duplicatedPane.gridLayout) {
1851
+ delete duplicatedPane.gridLayout;
1852
+ }
1936
1853
 
1937
1854
  const storyFragmentNode = ownerNode as StoryFragmentNode;
1938
1855
  let specificIdx = -1;
@@ -1973,10 +1890,14 @@ export class NodesContext {
1973
1890
  this.addNodes(allNodes);
1974
1891
  this.notifyNode(ownerId);
1975
1892
 
1893
+ // Combine the pane and all its child nodes for the history patch
1894
+ const nodesToHistory = [duplicatedPane as BaseNode, ...allNodes];
1895
+
1976
1896
  this.history.addPatch({
1977
1897
  op: PatchOp.ADD,
1978
1898
  undo: (ctx) => {
1979
- ctx.deleteNodes(allNodes);
1899
+ // Delete all nodes created (pane + children)
1900
+ ctx.deleteNodes(nodesToHistory);
1980
1901
 
1981
1902
  if (
1982
1903
  storyFragmentNode &&
@@ -1988,8 +1909,6 @@ export class NodesContext {
1988
1909
  );
1989
1910
  storyFragmentNode.isChanged = storyFragmentWasChanged;
1990
1911
  }
1991
-
1992
- ctx.deleteNodes([duplicatedPane]);
1993
1912
  },
1994
1913
  redo: (ctx) => {
1995
1914
  if (storyFragmentNode?.nodeType === 'StoryFragment') {
@@ -2005,13 +1924,13 @@ export class NodesContext {
2005
1924
  storyFragmentNode.isChanged = true;
2006
1925
  }
2007
1926
 
2008
- ctx.addNodes([duplicatedPane]);
1927
+ // Add all nodes back (pane + children)
1928
+ ctx.addNodes(nodesToHistory);
2009
1929
  ctx.linkChildToParent(
2010
1930
  duplicatedPane.id,
2011
1931
  duplicatedPane.parentId,
2012
1932
  specificIdx
2013
1933
  );
2014
- ctx.addNodes(allNodes);
2015
1934
  },
2016
1935
  });
2017
1936
 
@@ -2098,7 +2017,7 @@ export class NodesContext {
2098
2017
 
2099
2018
  let autoCreatedMarkdownNode: MarkdownPaneFragmentNode | null = null;
2100
2019
 
2101
- console.log(`--- [TRAP - TEMPLATE BEFORE] ---`, cloneDeep(node));
2020
+ //console.log(`--- [TRAP - TEMPLATE BEFORE] ---`, cloneDeep(node));
2102
2021
  // 3. HANDLE EMPTY PANE BY AUTO-CREATING A MARKDOWN NODE
2103
2022
  if (targetNode.nodeType === 'Pane') {
2104
2023
  // Create a minimal markdown node to act as the container
@@ -2176,7 +2095,7 @@ export class NodesContext {
2176
2095
  );
2177
2096
  }
2178
2097
 
2179
- console.log(`--- [TRAP - FLATTENED AFTER] ---`, cloneDeep(flattenedNodes));
2098
+ //console.log(`--- [TRAP - FLATTENED AFTER] ---`, cloneDeep(flattenedNodes));
2180
2099
 
2181
2100
  // 5. PERFORM REMAINING STATE MUTATIONS
2182
2101
  if (originalPaneNode) {
@@ -2275,7 +2194,9 @@ export class NodesContext {
2275
2194
 
2276
2195
  node.id = ulid();
2277
2196
  node.parentId = parentId;
2278
- result.push(node);
2197
+ const thisNode = cloneDeep(node);
2198
+ delete thisNode.nodes;
2199
+ result.push(thisNode);
2279
2200
  if ('nodes' in node && node.nodes) {
2280
2201
  for (let i = 0; i < node.nodes.length; ++i) {
2281
2202
  result = result.concat(
@@ -3224,6 +3145,160 @@ export class NodesContext {
3224
3145
 
3225
3146
  return deletedNodes;
3226
3147
  }
3148
+
3149
+ /**
3150
+ * Processes a TemplatePane's content (markdown, grid, or bgPane) and
3151
+ * returns a flat list of all nodes to be added to the store.
3152
+ * This is a de-duplicated helper used by addTemplatePane and addContextTemplatePane.
3153
+ * @param paneTemplate - The TemplatePane object to process.
3154
+ * @param newPaneId - The ID of the parent Pane node.
3155
+ * @returns An array of BaseNode objects to be added to allNodes.
3156
+ */
3157
+ private _processPaneTemplate(
3158
+ paneTemplate: TemplatePane,
3159
+ newPaneId: string
3160
+ ): BaseNode[] {
3161
+ let allNodes: BaseNode[] = [];
3162
+
3163
+ // 1. Process Markdown Content
3164
+ if (paneTemplate.markdown) {
3165
+ const duplicatedMarkdown = cloneDeep(
3166
+ paneTemplate.markdown
3167
+ ) as TemplateMarkdown;
3168
+ duplicatedMarkdown.id = paneTemplate.markdown.id || ulid();
3169
+ duplicatedMarkdown.markdownId =
3170
+ paneTemplate.markdown.markdownId || ulid();
3171
+ duplicatedMarkdown.parentId = newPaneId;
3172
+
3173
+ let markdownNodes: TemplateNode[] = [];
3174
+ if (duplicatedMarkdown.markdownBody) {
3175
+ const markdownGen = new MarkdownGenerator(this);
3176
+ markdownNodes = markdownGen.markdownToFlatNodes(
3177
+ duplicatedMarkdown.markdownBody,
3178
+ duplicatedMarkdown.id
3179
+ ) as TemplateNode[];
3180
+ allNodes = [...allNodes, duplicatedMarkdown, ...markdownNodes];
3181
+ } else if (
3182
+ typeof duplicatedMarkdown !== `undefined` &&
3183
+ typeof duplicatedMarkdown.id === `string`
3184
+ ) {
3185
+ // Create a map to track the original node ID to its duplicated node ID
3186
+ const oldToNewIdMap = new Map<string, string>();
3187
+ // First pass: Clone nodes and generate new IDs
3188
+ const nodesClone =
3189
+ duplicatedMarkdown.nodes?.map((originalNode) => {
3190
+ const newNode = cloneDeep(originalNode);
3191
+ newNode.id = ulid();
3192
+ oldToNewIdMap.set(originalNode.id, newNode.id);
3193
+ return newNode;
3194
+ }) || [];
3195
+ // Second pass: Update parent IDs using the mapping
3196
+ nodesClone.forEach((node) => {
3197
+ // Special case for direct children of markdown
3198
+ if (node.parentId === paneTemplate.markdown?.id) {
3199
+ node.parentId = duplicatedMarkdown.id;
3200
+ } else {
3201
+ // For all other nodes, use the mapping to find the new parent ID
3202
+ const newParentId = oldToNewIdMap.get(node.parentId || '');
3203
+ if (newParentId) {
3204
+ node.parentId = newParentId;
3205
+ }
3206
+ }
3207
+ markdownNodes.push(node);
3208
+ });
3209
+ allNodes = [...allNodes, duplicatedMarkdown, ...markdownNodes];
3210
+ }
3211
+
3212
+ // 2. Process GridLayout Content
3213
+ } else if (paneTemplate.gridLayout) {
3214
+ const duplicatedGrid = cloneDeep(
3215
+ paneTemplate.gridLayout
3216
+ ) as TemplateGridLayout;
3217
+ duplicatedGrid.id = paneTemplate.gridLayout.id || ulid();
3218
+ duplicatedGrid.parentId = newPaneId;
3219
+ allNodes.push(duplicatedGrid as GridLayoutNode);
3220
+
3221
+ // Map for all nodes within the grid
3222
+ const oldToNewIdMap = new Map<string, string>();
3223
+
3224
+ // First pass: Collect all column nodes and their descendant nodes
3225
+ const allOriginalNodes: TemplateNode[] = [];
3226
+ const columnNodes: TemplateMarkdown[] = [];
3227
+
3228
+ duplicatedGrid.nodes?.forEach((originalColumn) => {
3229
+ const newColumn = cloneDeep(originalColumn);
3230
+ newColumn.id = ulid();
3231
+ newColumn.markdownId = ulid();
3232
+ oldToNewIdMap.set(originalColumn.id, newColumn.id);
3233
+ columnNodes.push(newColumn);
3234
+
3235
+ originalColumn.nodes?.forEach((colNode) => {
3236
+ allOriginalNodes.push(colNode);
3237
+ });
3238
+ });
3239
+
3240
+ // Second pass: Clone all descendant nodes
3241
+ const allClonedDescendants = allOriginalNodes.map((originalNode) => {
3242
+ const newNode = cloneDeep(originalNode);
3243
+ newNode.id = ulid();
3244
+ oldToNewIdMap.set(originalNode.id, newNode.id);
3245
+ return newNode;
3246
+ });
3247
+
3248
+ // Third pass: Re-map parent IDs for columns
3249
+ columnNodes.forEach((col) => {
3250
+ col.parentId = duplicatedGrid.id;
3251
+ allNodes.push(col as MarkdownPaneFragmentNode);
3252
+ });
3253
+
3254
+ // Fourth pass: Re-map parent IDs for all descendants
3255
+ allClonedDescendants.forEach((node) => {
3256
+ const newParentId = oldToNewIdMap.get(node.parentId || '');
3257
+ if (newParentId) {
3258
+ node.parentId = newParentId;
3259
+ }
3260
+ allNodes.push(node);
3261
+ });
3262
+ }
3263
+
3264
+ // 3. Process Background Pane
3265
+ if (paneTemplate.bgPane) {
3266
+ const bgPaneId = ulid();
3267
+
3268
+ if (paneTemplate.bgPane.type === 'visual-break') {
3269
+ const visualBreakPane = paneTemplate.bgPane as VisualBreakNode;
3270
+ const bgPaneNode: VisualBreakNode = {
3271
+ id: bgPaneId,
3272
+ nodeType: 'BgPane',
3273
+ parentId: newPaneId,
3274
+ type: 'visual-break',
3275
+ breakDesktop: visualBreakPane.breakDesktop,
3276
+ breakTablet: visualBreakPane.breakTablet,
3277
+ breakMobile: visualBreakPane.breakMobile,
3278
+ };
3279
+ allNodes.push(bgPaneNode);
3280
+ } else if (paneTemplate.bgPane.type === 'artpack-image') {
3281
+ const artpackBgPane = paneTemplate.bgPane as ArtpackImageNode;
3282
+ const bgPaneNode: ArtpackImageNode = {
3283
+ id: bgPaneId,
3284
+ nodeType: 'BgPane',
3285
+ parentId: newPaneId,
3286
+ type: 'artpack-image',
3287
+ collection: artpackBgPane.collection,
3288
+ image: artpackBgPane.image,
3289
+ src: artpackBgPane.src,
3290
+ srcSet: artpackBgPane.srcSet,
3291
+ alt: artpackBgPane.alt || `Artpack image`,
3292
+ objectFit: artpackBgPane.objectFit || 'cover',
3293
+ };
3294
+ allNodes.push(bgPaneNode);
3295
+ }
3296
+ // This helper only processes nodes, it doesn't modify the paneTemplate.
3297
+ // The deletion of `duplicatedPane.bgPane` will remain in `addTemplatePane`.
3298
+ }
3299
+
3300
+ return allNodes;
3301
+ }
3227
3302
  }
3228
3303
 
3229
3304
  export const globalCtx: NodesContext = new NodesContext();
@@ -6,6 +6,7 @@ import type {
6
6
  FullContentMapItem,
7
7
  Theme,
8
8
  ArtpacksStore,
9
+ BrandConfig,
9
10
  } from '@/types/tractstack';
10
11
  import type { SettingsPanelSignal, ViewportKey } from '@/types/compositorTypes';
11
12
  import type {
@@ -21,9 +22,6 @@ export const fullContentMapStore = atom<FullContentMapItem[]>([]);
21
22
  export const hasArtpacksStore = map<ArtpacksStore>({});
22
23
  export const urlParamsStore = atom<Record<string, string | boolean>>({});
23
24
  export const canonicalURLStore = atom<string>('');
24
- export const brandColourStore = atom<string>(
25
- '10120d,fcfcfc,f58333,c8df8c,293f58,a7b1b7,393d34,e3e3e3'
26
- );
27
25
  export const preferredThemeStore = atom<Theme>('light');
28
26
 
29
27
  export const hasAssemblyAIStore = atom<boolean>(false);
@@ -156,6 +154,8 @@ export const resetStoryKeepState = () => {
156
154
  canRedoStore.set(false);
157
155
  };
158
156
 
157
+ export const brandConfigStore = atom<BrandConfig | null>(null);
158
+
159
159
  export const settingsPanelStore = atom<SettingsPanelSignal | null>(null);
160
160
  export const stylePanelTargetMemoryStore = atom<Map<string, number>>(
161
161
  new Map<string, number>()