astro-tractstack 2.2.1 → 2.2.3

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 (68) hide show
  1. package/package.json +1 -1
  2. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +0 -1
  3. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +0 -1
  4. package/templates/src/components/codehooks/ListContentSetup.tsx +0 -1
  5. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -1
  6. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -1
  7. package/templates/src/components/compositor/Compositor.tsx +0 -1
  8. package/templates/src/components/compositor/Node.tsx +157 -133
  9. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +2 -4
  10. package/templates/src/components/edit/Header.tsx +2 -6
  11. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +1 -1
  12. package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +0 -1
  13. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +1 -0
  14. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +8 -12
  15. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +9 -6
  16. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +7 -69
  17. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +2 -2
  18. package/templates/src/components/edit/pane/PanePanel_impression.tsx +0 -4
  19. package/templates/src/components/edit/pane/PanePanel_path.tsx +0 -1
  20. package/templates/src/components/edit/pane/PanePanel_title.tsx +1 -2
  21. package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -4
  22. package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +0 -3
  23. package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +2 -2
  24. package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +173 -80
  25. package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +0 -5
  26. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +2 -1
  27. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +1 -4
  28. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +0 -1
  29. package/templates/src/components/edit/panels/StyleElementPanel.tsx +1 -1
  30. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +1 -4
  31. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +3 -3
  32. package/templates/src/components/edit/panels/StyleImagePanel.tsx +3 -3
  33. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +1 -4
  34. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +3 -4
  35. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +1 -4
  36. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +3 -3
  37. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +1 -1
  38. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +1 -1
  39. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +1 -1
  40. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +1 -1
  41. package/templates/src/components/edit/panels/StyleParentPanel.tsx +0 -7
  42. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +0 -2
  43. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +0 -2
  44. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +0 -2
  45. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +0 -3
  46. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +1 -4
  47. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +3 -4
  48. package/templates/src/components/edit/panels/StyleWordCarouselPanel.tsx +0 -2
  49. package/templates/src/components/edit/state/StylesMemory.tsx +3 -9
  50. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +0 -1
  51. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +0 -2
  52. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +0 -2
  53. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +0 -1
  54. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +0 -1
  55. package/templates/src/components/fields/ArtpackImage.tsx +0 -7
  56. package/templates/src/components/fields/BackgroundImage.tsx +0 -14
  57. package/templates/src/components/fields/BackgroundImageWrapper.tsx +0 -5
  58. package/templates/src/components/fields/ImageUpload.tsx +0 -3
  59. package/templates/src/pages/[...slug]/edit.astro +0 -1
  60. package/templates/src/pages/sandbox.astro +0 -1
  61. package/templates/src/stores/nodes.ts +278 -312
  62. package/templates/src/stores/nodesHistory.ts +59 -24
  63. package/templates/src/utils/api/setupHelpers.ts +1 -1
  64. package/templates/src/utils/compositor/aiPaneParser.ts +57 -0
  65. package/templates/src/utils/compositor/designLibraryHelper.ts +1 -3
  66. package/templates/src/utils/compositor/htmlAst.ts +109 -2
  67. package/templates/src/utils/compositor/nodesHelper.ts +1 -9
  68. package/templates/src/utils/compositor/savePipeline.ts +1 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "Astro integration for TractStack - the free web press by At Risk Media",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -149,7 +149,6 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
149
149
  bgColor,
150
150
  }),
151
151
  },
152
- isChanged: true,
153
152
  };
154
153
 
155
154
  if (bgColor) {
@@ -90,7 +90,6 @@ const FeaturedArticleSetup = ({
90
90
  }),
91
91
  },
92
92
  bgColour: bgColor || undefined,
93
- isChanged: true,
94
93
  };
95
94
 
96
95
  // If bgColor is empty, remove the property
@@ -109,7 +109,6 @@ const ListContentSetup = ({ params, nodeId }: ListContentSetupProps) => {
109
109
  }),
110
110
  },
111
111
  bgColour: bgColor || undefined,
112
- isChanged: true,
113
112
  };
114
113
 
115
114
  // If bgColor is empty, remove the property
@@ -56,7 +56,6 @@ export const ProductCardSetup = (props: ProductCardSetupProps) => {
56
56
  target: paneNode.codeHookPayload?.target,
57
57
  options: JSON.stringify(newPayload),
58
58
  },
59
- isChanged: true,
60
59
  };
61
60
  ctx.modifyNodes([updatedPaneNode]);
62
61
  };
@@ -117,7 +117,6 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
117
117
  target: paneNode.codeHookPayload?.target,
118
118
  options: JSON.stringify(constructPayload()),
119
119
  },
120
- isChanged: true,
121
120
  };
122
121
  ctx.modifyNodes([updatedPaneNode]);
123
122
  }, 500);
@@ -463,7 +463,6 @@ export const Compositor = (props: CompositorProps) => {
463
463
  {
464
464
  ...node,
465
465
  wordCarouselPayload: { words, speed: 2 },
466
- isChanged: true,
467
466
  } as FlatNode,
468
467
  ]);
469
468
  settingsPanelStore.set({
@@ -8,7 +8,6 @@ import {
8
8
  isTopLevelBlockNode,
9
9
  parseCodeHook,
10
10
  } from '@/utils/compositor/nodesHelper';
11
- import { isPaneNode, isGridLayoutNode } from '@/utils/compositor/typeGuards';
12
11
  import { PaneAddMode } from '@/types/compositorTypes';
13
12
  import { NodeOverlay } from './tools/NodeOverlay';
14
13
  import PanelVisibilityWrapper from '@/components/compositor/PanelVisibilityWrapper';
@@ -59,70 +58,32 @@ export const Node = memo((props: NodeProps) => {
59
58
  const ctx = getCtx(props);
60
59
  const node = ctx.allNodes.get().get(props.nodeId);
61
60
  const viewport = useStore(viewportKeyStore).value;
62
- const isPreview = ctx.rootNodeId.get() === `tmp`;
61
+ const isPreview = ctx.rootNodeId.get() === `tmp` || ctx.isTemplate.get();
63
62
  const hasPanes = useStore(ctx.hasPanes);
64
63
 
65
64
  if (!node) return null;
66
65
 
67
66
  let element: ReactNode = null;
68
67
 
69
- // 1. Root Types (StoryFragment & Pane) - handled with specific early returns or complex wrapping
70
- if (node.nodeType === 'StoryFragment') {
71
- const sf = node as StoryFragmentNode;
72
- if (!isPreview) ctx.hasTitle.set(!(!sf.slug || !sf.title));
68
+ switch (node.nodeType) {
69
+ case 'StoryFragment': {
70
+ const sf = node as StoryFragmentNode;
71
+ if (!isPreview) ctx.hasTitle.set(!(!sf.slug || !sf.title));
73
72
 
74
- if (!(sf.slug && sf.title)) {
75
- return (
76
- <div
77
- className="fixed inset-0 overflow-y-auto bg-black bg-opacity-75"
78
- style={{ zIndex: 9005 }}
79
- >
80
- <div className="flex min-h-screen items-center justify-center p-1.5">
81
- <div
82
- className="fixed inset-0 bg-black opacity-65"
83
- onClick={() => (window.location.href = '/storykeep')}
84
- />
85
- <div className="relative w-full max-w-4xl rounded-lg bg-white shadow-xl">
86
- <div className="p-6">
87
- <StoryFragmentTitlePanel nodeId={props.nodeId} />
88
- </div>
89
- </div>
90
- </div>
91
- </div>
92
- );
93
- }
94
-
95
- if (!hasPanes && !isPreview) {
96
- return <EmptyPageHandler {...props} />;
97
- }
98
-
99
- return (
100
- <>
101
- <StoryFragmentConfigPanel
102
- nodeId={props.nodeId}
103
- isSandboxMode={props.isSandboxMode || false}
104
- />
105
- <StoryFragment {...props} />
106
- </>
107
- );
108
- } else if (isPaneNode(node)) {
109
- const paneNode = node as PaneNode;
110
- const isHtmlAstPane = !!paneNode.htmlAst;
111
- const paneNodes = ctx.getChildNodeIDs(node.id);
112
-
113
- if (paneNode.isContextPane) {
114
- if (!isPreview) ctx.hasTitle.set(!(!paneNode.slug || !paneNode.title));
115
-
116
- if (!isPreview && !(paneNode.slug && paneNode.title)) {
73
+ if (!(sf.slug && sf.title)) {
117
74
  return (
118
75
  <div
119
76
  className="fixed inset-0 overflow-y-auto bg-black bg-opacity-75"
120
77
  style={{ zIndex: 9005 }}
121
78
  >
122
79
  <div className="flex min-h-screen items-center justify-center p-1.5">
80
+ <div
81
+ className="fixed inset-0 bg-black opacity-65"
82
+ onClick={() => (window.location.href = '/storykeep')}
83
+ />
123
84
  <div className="relative w-full max-w-4xl rounded-lg bg-white shadow-xl">
124
85
  <div className="p-6">
125
- <ContextPaneTitlePanel nodeId={props.nodeId} />
86
+ <StoryFragmentTitlePanel nodeId={props.nodeId} />
126
87
  </div>
127
88
  </div>
128
89
  </div>
@@ -130,96 +91,159 @@ export const Node = memo((props: NodeProps) => {
130
91
  );
131
92
  }
132
93
 
133
- if (!isPreview && !paneNodes.length) {
134
- // Context Pane Empty State
135
- return (
94
+ if (!hasPanes && !isPreview) {
95
+ return <EmptyPageHandler {...props} />;
96
+ }
97
+
98
+ return (
99
+ <>
100
+ <StoryFragmentConfigPanel
101
+ nodeId={props.nodeId}
102
+ isSandboxMode={props.isSandboxMode || false}
103
+ />
104
+ <StoryFragment {...props} />
105
+ </>
106
+ );
107
+ }
108
+ case 'Pane':
109
+ {
110
+ const paneNode = node as PaneNode;
111
+ const storyfragmentNodeId = ctx.getClosestNodeTypeFromId(
112
+ node.id,
113
+ 'StoryFragment'
114
+ );
115
+ const storyfragmentNode = ctx.allNodes
116
+ .get()
117
+ .get(storyfragmentNodeId) as StoryFragmentNode;
118
+ const first = paneNode.id === storyfragmentNode.paneIds?.[0];
119
+ const isHtmlAstPane = !!paneNode.htmlAst;
120
+ const paneNodes = ctx.getChildNodeIDs(node.id);
121
+
122
+ if (paneNode.isContextPane) {
123
+ if (!isPreview)
124
+ ctx.hasTitle.set(!(!paneNode.slug || !paneNode.title));
125
+
126
+ if (!isPreview && !(paneNode.slug && paneNode.title)) {
127
+ return (
128
+ <div
129
+ className="fixed inset-0 overflow-y-auto bg-black bg-opacity-75"
130
+ style={{ zIndex: 9005 }}
131
+ >
132
+ <div className="flex min-h-screen items-center justify-center p-1.5">
133
+ <div className="relative w-full max-w-4xl rounded-lg bg-white shadow-xl">
134
+ <div className="p-6">
135
+ <ContextPaneTitlePanel nodeId={props.nodeId} />
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ );
141
+ }
142
+
143
+ if (!isPreview && !paneNodes.length) {
144
+ return (
145
+ <>
146
+ <ContextPanePanel nodeId={node.id} />
147
+ <PanelVisibilityWrapper
148
+ nodeId={node.id}
149
+ panelType="add"
150
+ ctx={ctx}
151
+ >
152
+ <Pane {...props} />
153
+ </PanelVisibilityWrapper>
154
+ <AddPanePanel
155
+ nodeId={node.id}
156
+ first={true}
157
+ ctx={ctx}
158
+ isContextPane={true}
159
+ />
160
+ </>
161
+ );
162
+ }
163
+ }
164
+
165
+ const content = isHtmlAstPane ? (
166
+ <CreativePane nodeId={props.nodeId} htmlAst={paneNode.htmlAst!} />
167
+ ) : (
168
+ <Pane {...props} />
169
+ );
170
+
171
+ element = (
136
172
  <>
137
- <ContextPanePanel nodeId={node.id} />
138
- <PanelVisibilityWrapper nodeId={node.id} panelType="add" ctx={ctx}>
139
- <Pane {...props} />
140
- </PanelVisibilityWrapper>
141
- <AddPanePanel
142
- nodeId={node.id}
143
- first={true}
144
- ctx={ctx}
145
- isContextPane={true}
146
- />
173
+ {first && (
174
+ <AddPanePanel nodeId={props.nodeId} first={true} ctx={ctx} />
175
+ )}
176
+ <div className="py-0.5">
177
+ <ConfigPanePanel
178
+ nodeId={props.nodeId}
179
+ isHtmlAstPane={isHtmlAstPane}
180
+ isSandboxMode={props.isSandboxMode || false}
181
+ />
182
+ <PanelVisibilityWrapper
183
+ nodeId={props.nodeId}
184
+ panelType="settings"
185
+ ctx={ctx}
186
+ >
187
+ {content}
188
+ </PanelVisibilityWrapper>
189
+ </div>
190
+ <AddPanePanel nodeId={props.nodeId} first={false} ctx={ctx} />
147
191
  </>
148
192
  );
149
193
  }
150
- }
194
+ break;
151
195
 
152
- // Resolve Content
153
- const content = isHtmlAstPane ? (
154
- <CreativePane nodeId={props.nodeId} htmlAst={paneNode.htmlAst!} />
155
- ) : (
156
- <Pane {...props} />
157
- );
196
+ case 'BgPane':
197
+ element = <BgPaneWrapper {...props} />;
198
+ break;
158
199
 
159
- element = (
160
- <>
161
- <div className="py-0.5">
162
- <ConfigPanePanel
163
- nodeId={props.nodeId}
164
- isHtmlAstPane={isHtmlAstPane}
165
- />
166
- <PanelVisibilityWrapper
167
- nodeId={props.nodeId}
168
- panelType="settings"
169
- ctx={ctx}
170
- >
171
- {content}
172
- </PanelVisibilityWrapper>
173
- </div>
174
- <AddPanePanel nodeId={props.nodeId} first={false} ctx={ctx} />
175
- </>
176
- );
177
- }
178
- // 2. Content Types
179
- else if (node.nodeType === 'BgPane') {
180
- element = <BgPaneWrapper {...props} />;
181
- } else if (node.nodeType === 'Markdown') {
182
- element = <Markdown {...props} />;
183
- } else if (isGridLayoutNode(node)) {
184
- element = <GridLayout {...props} />;
185
- } else if (node.nodeType === 'TagElement') {
186
- const flatNode = node as FlatNode;
187
-
188
- switch (flatNode.tagName) {
189
- case 'text':
190
- element = <NodeText {...props} />;
191
- break;
192
- case 'img':
193
- element = <NodeImg {...props} />;
194
- break;
195
- case 'a':
196
- element = <NodeA {...props} />;
197
- break;
198
- case 'button':
199
- element = <NodeButton {...props} />;
200
- break;
201
- case 'code': {
202
- const hookData = parseCodeHook(node);
203
- element = hookData ? (
204
- <Widget
205
- {...props}
206
- hook={hookData.hook}
207
- value1={hookData.value1}
208
- value2={hookData.value2}
209
- value3={hookData.value3}
210
- />
211
- ) : null;
212
- break;
200
+ case 'Markdown':
201
+ element = <Markdown {...props} />;
202
+ break;
203
+
204
+ case 'GridLayoutNode':
205
+ element = <GridLayout {...props} />;
206
+ break;
207
+
208
+ case 'TagElement': {
209
+ const flatNode = node as FlatNode;
210
+
211
+ switch (flatNode.tagName) {
212
+ case 'text':
213
+ element = <NodeText {...props} />;
214
+ break;
215
+ case 'img':
216
+ element = <NodeImg {...props} />;
217
+ break;
218
+ case 'a':
219
+ element = <NodeA {...props} />;
220
+ break;
221
+ case 'button':
222
+ element = <NodeButton {...props} />;
223
+ break;
224
+ case 'code': {
225
+ const hookData = parseCodeHook(node);
226
+ element = hookData ? (
227
+ <Widget
228
+ {...props}
229
+ hook={hookData.hook}
230
+ value1={hookData.value1}
231
+ value2={hookData.value2}
232
+ value3={hookData.value3}
233
+ />
234
+ ) : null;
235
+ break;
236
+ }
237
+ default:
238
+ element = (
239
+ <NodeBasicTag
240
+ {...props}
241
+ tagName={flatNode.tagName as keyof JSX.IntrinsicElements}
242
+ isSelectableText={true}
243
+ />
244
+ );
245
+ break;
213
246
  }
214
- default:
215
- element = (
216
- <NodeBasicTag
217
- {...props}
218
- tagName={flatNode.tagName as keyof JSX.IntrinsicElements}
219
- isSelectableText={true}
220
- />
221
- );
222
- break;
223
247
  }
224
248
  }
225
249
 
@@ -497,7 +497,6 @@ export const NodeBasicTag = (props: NodeTagProps) => {
497
497
  const updatedNode = {
498
498
  ...cloneDeep(node),
499
499
  isPlaceholder: false,
500
- isChanged: true,
501
500
  };
502
501
  ctx.modifyNodes([updatedNode]);
503
502
  }
@@ -507,7 +506,7 @@ export const NodeBasicTag = (props: NodeTagProps) => {
507
506
  const paneNode = cloneDeep(
508
507
  ctx.allNodes.get().get(paneNodeId)
509
508
  ) as PaneNode;
510
- ctx.modifyNodes([{ ...paneNode, isChanged: true }]);
509
+ ctx.modifyNodes([paneNode]);
511
510
  }
512
511
  }
513
512
  } catch (error) {
@@ -573,7 +572,6 @@ export const NodeBasicTag = (props: NodeTagProps) => {
573
572
  const updatedNode = {
574
573
  ...cloneDeep(node),
575
574
  isPlaceholder: false,
576
- isChanged: true,
577
575
  };
578
576
  ctx.modifyNodes([updatedNode]);
579
577
  }
@@ -591,7 +589,7 @@ export const NodeBasicTag = (props: NodeTagProps) => {
591
589
  const paneNode = cloneDeep(
592
590
  ctx.allNodes.get().get(paneNodeId)
593
591
  ) as PaneNode;
594
- ctx.modifyNodes([{ ...paneNode, isChanged: true }]);
592
+ ctx.modifyNodes([paneNode]);
595
593
  }
596
594
 
597
595
  ctx.clearEditLock();
@@ -34,7 +34,6 @@ const StoryKeepHeader = ({
34
34
  const viewport = useStore(viewportModeStore);
35
35
  const pendingHomePageSlug = useStore(pendingHomePageSlugStore);
36
36
  const ctx = getCtx();
37
- const showSaveBypass = useStore(ctx.showSaveBypass);
38
37
  const hasTitle = useStore(ctx.hasTitle);
39
38
  const hasPanes = useStore(ctx.hasPanes);
40
39
  const [canUndo, setCanUndo] = useState(false);
@@ -124,7 +123,7 @@ const StoryKeepHeader = ({
124
123
  ];
125
124
 
126
125
  // Show save button if there are undo changes OR pending home page change
127
- const shouldShowSave = canUndo || pendingHomePageSlug || showSaveBypass;
126
+ const shouldShowSave = canUndo || pendingHomePageSlug;
128
127
 
129
128
  if (!hasTitle && !hasPanes) return null;
130
129
 
@@ -203,12 +202,9 @@ const StoryKeepHeader = ({
203
202
  console.log('Total Nodes in Store:', allNodesArray.length);
204
203
  console.log('Full Nodes Map:', allNodesMap);
205
204
 
206
- // Specifically audit Creative Panes for large htmlAst payloads
207
- // Using type guards/property checks to handle the BaseNode vs PaneNode distinction
208
205
  const creativePanes = allNodesArray.filter(
209
206
  (n) => n.nodeType === 'Pane' && 'htmlAst' in n
210
207
  );
211
-
212
208
  if (creativePanes.length > 0) {
213
209
  console.group('Creative Panes Detail (htmlAst Audit)');
214
210
  creativePanes.forEach((pane) => {
@@ -229,7 +225,7 @@ const StoryKeepHeader = ({
229
225
  }}
230
226
  className="rounded-md bg-myblue px-3.5 py-1.5 font-action font-bold text-white hover:bg-myorange"
231
227
  >
232
- Inspect
228
+ Nodes Dump
233
229
  </button>
234
230
  )}
235
231
 
@@ -57,7 +57,7 @@ const PaneSlugPanel = ({ nodeId, setMode }: PaneSlugPanelProps) => {
57
57
  if (slug.length >= 3) {
58
58
  // Only update if meets minimum length
59
59
  const ctx = getCtx();
60
- const updatedNode = { ...cloneDeep(paneNode), slug, isChanged: true };
60
+ const updatedNode = { ...cloneDeep(paneNode), slug };
61
61
  ctx.modifyNodes([updatedNode]);
62
62
  }
63
63
  };
@@ -57,7 +57,6 @@ const ContextPaneTitlePanel = ({
57
57
  ...paneNode,
58
58
  title,
59
59
  ...(newSlug ? { slug: newSlug } : {}),
60
- isChanged: true,
61
60
  });
62
61
  ctx.modifyNodes([updatedNode]);
63
62
  }
@@ -114,6 +114,7 @@ const AddPaneBreakPanel = ({
114
114
  useEffect(() => {
115
115
  const newPreviews = filteredVariants.map((variant, index: number) => {
116
116
  const ctx = new NodesContext();
117
+ ctx.isTemplate.set(true);
117
118
  ctx.addNode(createEmptyStorykeep('tmp'));
118
119
  const template = templateCategory.getTemplatesByVariant(variant)[0];
119
120
  ctx.addTemplatePane('tmp', template);
@@ -134,22 +134,18 @@ const AddPaneNewPanel = ({
134
134
 
135
135
  if (isContextPane) {
136
136
  insertTemplate.isContextPane = true;
137
- await ctx.applyAtomicUpdate(async (tmpCtx) => {
138
- tmpCtx.addContextTemplatePane(ownerId, insertTemplate);
139
- });
137
+ ctx.addContextTemplatePane(ownerId, insertTemplate);
140
138
  } else {
141
- await ctx.applyAtomicUpdate(async (tmpCtx) => {
142
- tmpCtx.addTemplatePane(
143
- ownerId,
144
- insertTemplate,
145
- nodeId,
146
- first ? 'before' : 'after'
147
- );
148
- });
139
+ ctx.addTemplatePane(
140
+ ownerId,
141
+ insertTemplate,
142
+ nodeId,
143
+ first ? 'before' : 'after'
144
+ );
149
145
  const storyFragment = cloneDeep(
150
146
  ctx.allNodes.get().get(ownerId)
151
147
  ) as StoryFragmentNode;
152
- ctx.modifyNodes([{ ...storyFragment, isChanged: true }]);
148
+ ctx.modifyNodes([{ ...storyFragment }]);
153
149
  }
154
150
  ctx.notifyNode(`root`);
155
151
  setParentMode(PaneAddMode.DEFAULT, false);
@@ -91,11 +91,15 @@ const AddPaneReUsePanel = ({
91
91
  pane.slug.toLowerCase().includes(query.toLowerCase())
92
92
  );
93
93
 
94
- const newPreviews = filteredPanes.map((pane, index) => ({
95
- ctx: new NodesContext(),
96
- pane,
97
- index,
98
- }));
94
+ const newPreviews = filteredPanes.map((pane, index) => {
95
+ const previewCtx = new NodesContext();
96
+ previewCtx.isTemplate.set(true);
97
+ return {
98
+ ctx: previewCtx,
99
+ pane,
100
+ index,
101
+ };
102
+ });
99
103
 
100
104
  setPreviews(newPreviews);
101
105
  setCurrentPage(0);
@@ -194,7 +198,6 @@ const AddPaneReUsePanel = ({
194
198
  );
195
199
  }
196
200
  }
197
- storyFragmentNode.isChanged = true;
198
201
 
199
202
  ctx.addNode(templateData.paneNode);
200
203
  ctx.linkChildToParent(
@@ -7,75 +7,13 @@ import SparklesIcon from '@heroicons/react/24/outline/SparklesIcon';
7
7
  import { getCtx } from '@/stores/nodes';
8
8
  import { selectionStore } from '@/stores/selection';
9
9
  import { renderedPreviews } from '@/stores/previews';
10
- import { sandboxTokenStore } from '@/stores/storykeep';
11
10
  import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
12
11
  import { AiRefineDesignStep } from './steps/AiRefineDesignStep';
13
12
  import prompts from '@/constants/prompts.json';
14
- import { TractStackAPI } from '@/utils/api';
15
13
  import { parseAiPane } from '@/utils/compositor/aiPaneParser';
14
+ import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
16
15
  import type { PaneNode, TemplatePane } from '@/types/compositorTypes';
17
16
 
18
- const callAskLemurAPI = async (
19
- prompt: string,
20
- context: string,
21
- expectJson: boolean,
22
- isSandboxMode: boolean
23
- ): Promise<string> => {
24
- const tenantId =
25
- (window as any).TRACTSTACK_CONFIG?.tenantId ||
26
- import.meta.env.PUBLIC_TENANTID ||
27
- 'default';
28
- const api = new TractStackAPI(tenantId);
29
-
30
- const requestBody = {
31
- prompt,
32
- input_text: context,
33
- final_model: '',
34
- temperature: 0.5,
35
- max_tokens: 10000,
36
- };
37
-
38
- let resultData: any;
39
-
40
- if (isSandboxMode) {
41
- const token = sandboxTokenStore.get();
42
- const response = await fetch(`/api/sandbox`, {
43
- method: 'POST',
44
- headers: {
45
- 'Content-Type': 'application/json',
46
- 'X-Tenant-ID': tenantId,
47
- 'X-Sandbox-Token': token || '',
48
- },
49
- credentials: 'include',
50
- body: JSON.stringify({ action: 'askLemur', payload: requestBody }),
51
- });
52
-
53
- if (!response.ok) {
54
- const errorText = await response.text();
55
- throw new Error(`Sandbox API failed: ${response.status} ${errorText}`);
56
- }
57
-
58
- const json = await response.json();
59
- if (!json.success) {
60
- throw new Error(json.error || 'Sandbox generation failed');
61
- }
62
- resultData = json.data;
63
- } else {
64
- const response = await api.post('/api/v1/aai/askLemur', requestBody);
65
- if (!response.success || !response.data?.response) {
66
- throw new Error(response.error || 'AI generation failed');
67
- }
68
- resultData = response.data;
69
- }
70
-
71
- let raw = resultData.response;
72
- if (typeof raw === 'string') {
73
- if (raw.startsWith('```json')) raw = raw.slice(7, -3).trim();
74
- }
75
- if (expectJson && typeof raw === 'object') return JSON.stringify(raw);
76
- return raw;
77
- };
78
-
79
17
  interface AiRestylePaneModalProps {
80
18
  isSandboxMode?: boolean;
81
19
  }
@@ -149,12 +87,12 @@ export const AiRestylePaneModal = ({
149
87
  .replace('{{COPY_INPUT}}', 'A generic content section')
150
88
  .replace('{{LAYOUT_TYPE}}', 'Text Only');
151
89
 
152
- const resultStr = await callAskLemurAPI(
153
- formattedPrompt,
154
- shellPromptDetails.system || '',
155
- true,
156
- isSandboxMode
157
- );
90
+ const resultStr = await callAskLemurAPI({
91
+ prompt: formattedPrompt,
92
+ context: shellPromptDetails.system || '',
93
+ expectJson: true,
94
+ isSandboxMode,
95
+ });
158
96
 
159
97
  let dummyCopy: string | string[] = '';
160
98
  if (isGrid) {
@@ -252,7 +252,7 @@ const ConfigPanePanel = ({
252
252
  </div>
253
253
 
254
254
  {/* Design Library Tools (Right Aligned) */}
255
- <div className="ml-auto flex items-center gap-2 border-l border-gray-300 pl-2">
255
+ <div className="ml-auto flex items-center gap-2 border-l border-gray-300 px-2">
256
256
  {!isHtmlAstPane && !isSandboxMode && (
257
257
  <button
258
258
  title="Save Pane to Design Library"
@@ -280,7 +280,7 @@ const ConfigPanePanel = ({
280
280
  <ArrowPathRoundedSquareIcon className="h-4 w-4 text-white" />
281
281
  </button>
282
282
  )}
283
- {import.meta.env.DEV && (
283
+ {import.meta.env.DEV && !isHtmlAstPane && (
284
284
  <button
285
285
  title="Copy Pane Design to Clipboard"
286
286
  onClick={handleCopyToClipboard}