astro-tractstack 2.1.3 → 2.2.1

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 (128) hide show
  1. package/README.md +54 -266
  2. package/bin/create-tractstack.js +9 -6
  3. package/dist/index.js +109 -71
  4. package/package.json +4 -2
  5. package/templates/css/custom.css +5 -0
  6. package/templates/icons/code.svg +18 -0
  7. package/templates/icons/li.svg +4 -0
  8. package/templates/icons/link.svg +22 -0
  9. package/templates/icons/p.svg +3 -0
  10. package/templates/src/client/app.js +80 -1
  11. package/templates/src/components/Footer.astro +1 -1
  12. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
  13. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
  14. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
  15. package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
  16. package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
  17. package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
  18. package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
  19. package/templates/src/components/compositor/Compositor.tsx +25 -9
  20. package/templates/src/components/compositor/Node.tsx +168 -496
  21. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
  22. package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
  23. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
  24. package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
  25. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
  26. package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
  27. package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
  28. package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
  29. package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
  30. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
  31. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
  32. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
  33. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
  34. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
  35. package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
  36. package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
  37. package/templates/src/components/edit/Header.tsx +68 -9
  38. package/templates/src/components/edit/PanelSwitch.tsx +42 -4
  39. package/templates/src/components/edit/SettingsPanel.tsx +2 -3
  40. package/templates/src/components/edit/ToolMode.tsx +1 -31
  41. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
  42. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
  43. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
  44. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
  45. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
  46. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
  47. package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
  48. package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
  49. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
  50. package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
  51. package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
  52. package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
  53. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
  54. package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
  55. package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
  56. package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
  57. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
  58. package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
  59. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
  60. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
  61. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
  62. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
  63. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
  64. package/templates/src/components/edit/state/SaveModal.tsx +19 -787
  65. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
  66. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
  67. package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
  68. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
  69. package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
  70. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
  71. package/templates/src/components/fields/ArtpackImage.tsx +11 -3
  72. package/templates/src/components/fields/BackgroundImage.tsx +8 -0
  73. package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
  74. package/templates/src/components/fields/ImageUpload.tsx +6 -0
  75. package/templates/src/components/form/ActionBuilderField.tsx +15 -5
  76. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
  77. package/templates/src/components/form/ColorPicker.tsx +1 -1
  78. package/templates/src/components/form/EnumSelect.tsx +1 -1
  79. package/templates/src/components/form/NumberInput.tsx +1 -1
  80. package/templates/src/components/form/StringArrayInput.tsx +1 -1
  81. package/templates/src/components/form/StringInput.tsx +1 -1
  82. package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
  83. package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
  84. package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
  85. package/templates/src/components/profile/ProfileCreate.tsx +1 -1
  86. package/templates/src/components/profile/ProfileEdit.tsx +1 -1
  87. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
  88. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
  89. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
  90. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
  91. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
  92. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
  93. package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
  94. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
  95. package/templates/src/constants/prompts.json +18 -10
  96. package/templates/src/constants.ts +3 -0
  97. package/templates/src/hooks/usePaneFragments.ts +60 -0
  98. package/templates/src/lib/session.ts +71 -16
  99. package/templates/src/pages/[...slug].astro +4 -46
  100. package/templates/src/pages/api/css.ts +149 -0
  101. package/templates/src/pages/maint.astro +1 -1
  102. package/templates/src/pages/storykeep/login.astro +2 -2
  103. package/templates/src/stores/nodes.ts +162 -49
  104. package/templates/src/stores/orphanAnalysis.ts +6 -30
  105. package/templates/src/stores/previews.ts +7 -0
  106. package/templates/src/stores/storykeep.ts +0 -8
  107. package/templates/src/types/compositorTypes.ts +53 -10
  108. package/templates/src/utils/compositor/aiGeneration.ts +93 -0
  109. package/templates/src/utils/compositor/allowInsert.ts +2 -0
  110. package/templates/src/utils/compositor/htmlAst.ts +704 -0
  111. package/templates/src/utils/compositor/nodesHelper.ts +281 -102
  112. package/templates/src/utils/compositor/savePipeline.ts +893 -0
  113. package/templates/src/utils/etl/index.ts +3 -0
  114. package/templates/src/utils/etl/transformer.ts +10 -0
  115. package/templates/src/utils/helpers.ts +101 -0
  116. package/utils/inject-files.ts +100 -62
  117. package/templates/icons/text.svg +0 -6
  118. package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
  119. package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
  120. package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
  121. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
  122. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
  123. package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
  124. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
  125. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
  126. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
  127. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
  128. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
@@ -88,6 +88,7 @@ const PanelVisibilityWrapper = ({
88
88
  data-panel-type={panelType}
89
89
  data-node-id={nodeId}
90
90
  data-active={isActive}
91
+ className="pt-6"
91
92
  >
92
93
  {children}
93
94
  </div>
@@ -135,7 +135,7 @@ export const SignUp = ({
135
135
  </Select.Control>
136
136
  <Portal>
137
137
  <Select.Positioner>
138
- <Select.Content className="sm:text-sm z-50 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none">
138
+ <Select.Content className="z-50 max-h-56 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none md:text-sm">
139
139
  {personaCollection.items.map((option) => (
140
140
  <Select.Item
141
141
  key={option.id}
@@ -10,8 +10,10 @@ export const YouTubeWrapper = ({
10
10
  style={{
11
11
  position: 'relative',
12
12
  width: '100%',
13
+ maxWidth: '100%',
13
14
  height: '0',
14
15
  paddingBottom: '56.25%',
16
+ margin: '0 auto',
15
17
  }}
16
18
  >
17
19
  <iframe
@@ -0,0 +1,262 @@
1
+ import {
2
+ useEffect,
3
+ useState,
4
+ useRef,
5
+ type FocusEvent,
6
+ type KeyboardEvent,
7
+ } from 'react';
8
+ import { useStore } from '@nanostores/react';
9
+ import { renderedPreviews, updatePreview } from '@/stores/previews';
10
+ import { settingsPanelStore, viewportKeyStore } from '@/stores/storykeep';
11
+ import { getCtx } from '@/stores/nodes';
12
+ import type { CreativePanePayload } from '@/types/compositorTypes';
13
+
14
+ export interface CreativePaneProps {
15
+ nodeId: string;
16
+ htmlAst: CreativePanePayload;
17
+ isProtected?: boolean;
18
+ }
19
+
20
+ const viewportMap = {
21
+ mobile: 'xs',
22
+ tablet: 'md',
23
+ desktop: 'xl',
24
+ } as const;
25
+
26
+ export const CreativePane = ({
27
+ nodeId,
28
+ htmlAst,
29
+ isProtected = false,
30
+ }: CreativePaneProps) => {
31
+ const previews = useStore(renderedPreviews);
32
+ const { value: viewportKey } = useStore(viewportKeyStore);
33
+ const [loading, setLoading] = useState(false);
34
+ const [error, setError] = useState<string | null>(null);
35
+ const contentRef = useRef<HTMLDivElement>(null);
36
+
37
+ const activeViewport = viewportMap[viewportKey];
38
+ const htmlContent = previews[nodeId];
39
+
40
+ useEffect(() => {
41
+ const controller = new AbortController();
42
+ const signal = controller.signal;
43
+ const fetchPreview = async () => {
44
+ if (!htmlAst?.tree) return;
45
+ setLoading(true);
46
+ setError(null);
47
+ try {
48
+ const tenantId =
49
+ (window as any).TRACTSTACK_CONFIG?.tenantId ||
50
+ import.meta.env.PUBLIC_TENANTID ||
51
+ 'default';
52
+
53
+ const goBackend =
54
+ import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
55
+
56
+ const response = await fetch(
57
+ `${goBackend}/api/v1/fragments/ast-preview`,
58
+ {
59
+ method: 'POST',
60
+ headers: {
61
+ 'Content-Type': 'application/json',
62
+ 'X-Tenant-ID': tenantId,
63
+ },
64
+ body: JSON.stringify({
65
+ id: nodeId,
66
+ title: 'Editor Preview',
67
+ tree: htmlAst.tree,
68
+ }),
69
+ signal,
70
+ }
71
+ );
72
+
73
+ if (!response.ok) {
74
+ const text = await response.text();
75
+ throw new Error(
76
+ text || `Preview generation failed: ${response.status}`
77
+ );
78
+ }
79
+
80
+ const html = await response.text();
81
+
82
+ if (!signal.aborted) {
83
+ updatePreview(nodeId, html);
84
+ }
85
+ } catch (err: any) {
86
+ if (err.name === 'AbortError') return;
87
+
88
+ console.error(`CreativePane fetch failed for ${nodeId}:`, err);
89
+ setError(err instanceof Error ? err.message : 'Unknown error');
90
+ } finally {
91
+ if (!signal.aborted) {
92
+ setLoading(false);
93
+ }
94
+ }
95
+ };
96
+
97
+ fetchPreview();
98
+
99
+ return () => {
100
+ controller.abort();
101
+ };
102
+ }, [htmlAst?.css, htmlAst?.tree, nodeId]);
103
+
104
+ useEffect(() => {
105
+ const ctx = getCtx();
106
+ const unsubscribe = ctx.toolModeValStore.subscribe((state) => {
107
+ const container = contentRef.current;
108
+ if (!container) return;
109
+
110
+ const editables = container.querySelectorAll('[data-ast-id]');
111
+
112
+ if (state.value === 'text') {
113
+ editables.forEach((el) => {
114
+ const htmlEl = el as HTMLElement;
115
+ if (htmlEl.isContentEditable) return;
116
+ htmlEl.style.outline = '2px dotted #06b6d4';
117
+ htmlEl.style.outlineOffset = '2px';
118
+
119
+ const astId = htmlEl.getAttribute('data-ast-id');
120
+ if (!astId) return;
121
+
122
+ const existingIcon = container.querySelector(
123
+ `[data-proxy-for="${astId}"]`
124
+ );
125
+ if (existingIcon) return;
126
+
127
+ const icon = document.createElement('div');
128
+ icon.setAttribute('data-proxy-for', astId);
129
+ icon.style.position = 'absolute';
130
+ icon.style.zIndex = '1003';
131
+ icon.style.width = '24px';
132
+ icon.style.height = '24px';
133
+ icon.style.backgroundColor = '#06b6d4';
134
+ icon.style.borderRadius = '9999px';
135
+ icon.style.display = 'flex';
136
+ icon.style.alignItems = 'center';
137
+ icon.style.justifyContent = 'center';
138
+ icon.style.color = 'white';
139
+ icon.style.fontSize = '12px';
140
+ icon.style.boxShadow = '0 10px 15px -3px rgb(0 0 0 / 0.1)';
141
+ icon.style.cursor = 'pointer';
142
+ icon.innerHTML = '✎';
143
+
144
+ const rect = htmlEl.getBoundingClientRect();
145
+ const containerRect = container.getBoundingClientRect();
146
+ icon.style.top = `${rect.top - containerRect.top - 12}px`;
147
+ icon.style.left = `${rect.left - containerRect.left - 12}px`;
148
+
149
+ icon.onmouseenter = () => {
150
+ htmlEl.style.outline = '3px solid #06b6d4';
151
+ };
152
+ icon.onmouseleave = () => {
153
+ htmlEl.style.outline = '2px dotted #06b6d4';
154
+ };
155
+ icon.onclick = () => {
156
+ const meta = htmlAst.editableElements?.[astId];
157
+ if (meta) {
158
+ let action = '';
159
+ if (meta.isCssBackground) {
160
+ action = 'style-creative-bg';
161
+ } else if (meta.tagName === 'img') {
162
+ action = 'style-creative-img';
163
+ } else if (meta.tagName === 'a') {
164
+ action = 'style-creative-link';
165
+ } else if (meta.tagName === 'button') {
166
+ action = 'style-creative-btn';
167
+ }
168
+
169
+ if (action) {
170
+ settingsPanelStore.set({
171
+ action,
172
+ nodeId,
173
+ childId: astId,
174
+ expanded: true,
175
+ });
176
+ }
177
+ }
178
+ };
179
+
180
+ container.appendChild(icon);
181
+ });
182
+ } else {
183
+ editables.forEach((el) => {
184
+ (el as HTMLElement).style.outline = '';
185
+ (el as HTMLElement).style.outlineOffset = '';
186
+ (el as HTMLElement).style.cursor = '';
187
+ });
188
+ const icons = container.querySelectorAll('[data-proxy-for]');
189
+ icons.forEach((icon) => icon.remove());
190
+ }
191
+ });
192
+
193
+ return () => unsubscribe();
194
+ }, [htmlContent]);
195
+
196
+ const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
197
+ const ctx = getCtx();
198
+ const mode = ctx.toolModeValStore.get().value;
199
+ if (mode !== 'text' || isProtected) return;
200
+
201
+ if (e.key === 'Enter') {
202
+ const target = e.target as HTMLElement;
203
+ if (target.isContentEditable) {
204
+ e.preventDefault();
205
+ target.blur();
206
+ }
207
+ }
208
+ };
209
+
210
+ const handleBlur = (e: FocusEvent<HTMLDivElement>) => {
211
+ const ctx = getCtx();
212
+ const mode = ctx.toolModeValStore.get().value;
213
+ if (mode !== 'text' || isProtected) return;
214
+
215
+ const target = e.target as HTMLElement;
216
+ const astId = target.getAttribute('data-ast-id');
217
+
218
+ if (astId && target.isContentEditable) {
219
+ const content = target.innerHTML;
220
+ ctx.updateCreativePane(nodeId, astId, content);
221
+ }
222
+ };
223
+
224
+ if (error) {
225
+ return (
226
+ <div className="flex h-full w-full items-center justify-center border border-dashed border-red-300 bg-red-50 p-4 text-sm text-red-500">
227
+ Preview Error: {error}
228
+ </div>
229
+ );
230
+ }
231
+
232
+ if (!htmlContent && loading) {
233
+ return (
234
+ <div
235
+ className="flex h-full w-full items-center justify-center bg-gray-50"
236
+ style={{ minHeight: '100px' }}
237
+ >
238
+ <div className="h-6 w-6 animate-spin rounded-full border-b-2 border-cyan-600"></div>
239
+ </div>
240
+ );
241
+ }
242
+
243
+ if (!htmlContent) return null;
244
+ const activeCss = htmlAst.viewportCss?.[activeViewport] || htmlAst.css;
245
+
246
+ return (
247
+ <>
248
+ <style dangerouslySetInnerHTML={{ __html: activeCss }} />
249
+ <div
250
+ ref={contentRef}
251
+ onKeyDown={handleKeyDown}
252
+ onBlur={handleBlur}
253
+ className="creative-pane-wrapper relative h-full w-full"
254
+ >
255
+ {isProtected && (
256
+ <div className="absolute inset-0 z-50 cursor-crosshair bg-transparent" />
257
+ )}
258
+ <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
259
+ </div>
260
+ </>
261
+ );
262
+ };
@@ -108,7 +108,7 @@ export const GhostInsertBlock = memo((props: GhostInsertBlockProps) => {
108
108
  );
109
109
 
110
110
  const ElementButtons = () => (
111
- <div className="sm:grid-cols-3 grid grid-cols-2 gap-2 p-2">
111
+ <div className="grid grid-cols-2 gap-2 p-2 md:grid-cols-3">
112
112
  {$toolAddModes
113
113
  .filter((mode) => !['p', 'h2', 'h3', 'h4'].includes(mode))
114
114
  .filter((mode) => allowedModes.includes(mode as ToolAddMode))
@@ -169,11 +169,9 @@ export const GhostInsertBlock = memo((props: GhostInsertBlockProps) => {
169
169
  ) : (
170
170
  <button
171
171
  onClick={(e) => {
172
- if (toolModeVal !== `eraser`) {
173
- e.stopPropagation();
174
- settingsPanelStore.set(null);
175
- setShowInsertOptions(true);
176
- }
172
+ e.stopPropagation();
173
+ settingsPanelStore.set(null);
174
+ setShowInsertOptions(true);
177
175
  }}
178
176
  className="group w-full rounded-lg border-2 border-dashed border-cyan-500 bg-cyan-50 p-6 transition-colors hover:bg-cyan-100 dark:border-cyan-600 dark:bg-cyan-900 dark:hover:bg-cyan-800"
179
177
  >
@@ -3,6 +3,7 @@ import { useStore } from '@nanostores/react';
3
3
  import { getCtx } from '@/stores/nodes';
4
4
  import { RenderChildren } from './RenderChildren';
5
5
  import { isGridLayoutNode } from '@/utils/compositor/typeGuards';
6
+ import { PaneOverlay } from '@/components/compositor/tools/PaneOverlay';
6
7
  import type { NodeProps } from '@/types/nodeProps';
7
8
  import type { ParentClassesPayload } from '@/types/compositorTypes';
8
9
  import { viewportKeyStore, settingsPanelStore } from '@/stores/storykeep';
@@ -108,17 +109,18 @@ export const GridLayout = (props: NodeProps) => {
108
109
  getCtx(props).setClickedNodeId(props.nodeId, true);
109
110
  e.stopPropagation();
110
111
  }}
111
- className={getCtx(props).getNodeClasses(
112
+ className={`${getCtx(props).getNodeClasses(
112
113
  props.nodeId,
113
114
  currentViewport,
114
115
  i - 1
115
- )}
116
+ )} group relative`}
116
117
  style={
117
118
  i === parentClassesLength
118
119
  ? { position: 'relative', zIndex: 10 }
119
120
  : undefined
120
121
  }
121
122
  >
123
+ <PaneOverlay {...props} layer={i} />
122
124
  {nodesToRender}
123
125
  </div>
124
126
  );
@@ -3,7 +3,9 @@ import { getCtx } from '@/stores/nodes';
3
3
  import { viewportKeyStore } from '@/stores/storykeep';
4
4
  import { RenderChildren } from './RenderChildren';
5
5
  import { GhostInsertBlock } from './GhostInsertBlock';
6
+ import { PaneOverlay } from '@/components/compositor/tools/PaneOverlay';
6
7
  import { processClassesForViewports } from '@/utils/compositor/reduceNodesClassNames';
8
+ import { isGridLayoutNode } from '@/utils/compositor/typeGuards';
7
9
  import type { NodeProps } from '@/types/nodeProps';
8
10
  import type {
9
11
  MarkdownPaneFragmentNode,
@@ -35,6 +37,11 @@ export const Markdown = (props: NodeProps) => {
35
37
 
36
38
  if (!node) return null;
37
39
 
40
+ // Context Check: Are we inside a Grid?
41
+ const allNodes = ctx.allNodes.get();
42
+ const parentNode = node.parentId ? allNodes.get(node.parentId) : null;
43
+ const isGridChild = parentNode ? isGridLayoutNode(parentNode) : false;
44
+
38
45
  let isHidden = false;
39
46
  switch (currentViewport) {
40
47
  case 'mobile':
@@ -65,7 +72,6 @@ export const Markdown = (props: NodeProps) => {
65
72
  );
66
73
  }
67
74
 
68
- const allNodes = ctx.allNodes.get();
69
75
  const parentPaneId = ctx.getClosestNodeTypeFromId(id, 'Pane');
70
76
  const bgNode = parentPaneId
71
77
  ? (() => {
@@ -185,7 +191,11 @@ export const Markdown = (props: NodeProps) => {
185
191
  </>
186
192
  );
187
193
 
194
+ // Conditional Layer Rendering:
195
+ // If we are NOT in a grid (standard Pane mode), we render the parentClasses loop.
196
+ // If we ARE in a grid, we skip this (the grid handles the outer layers, we handle the column style).
188
197
  if (
198
+ !isGridChild &&
189
199
  'parentClasses' in node &&
190
200
  (node.parentClasses as ParentClassesPayload)?.length > 0
191
201
  ) {
@@ -210,13 +220,14 @@ export const Markdown = (props: NodeProps) => {
210
220
  ctx.setClickedNodeId(props.nodeId, true);
211
221
  e.stopPropagation();
212
222
  }}
213
- className={ctx.getNodeClasses(id, currentViewport, i - 1)}
223
+ className={`${ctx.getNodeClasses(id, currentViewport, i - 1)} group relative`}
214
224
  style={
215
225
  i === parentClassesLength
216
226
  ? { position: 'relative', zIndex: 10 }
217
227
  : undefined
218
228
  }
219
229
  >
230
+ <PaneOverlay {...props} layer={i} />
220
231
  {nodesToRender}
221
232
  </div>
222
233
  );
@@ -224,7 +235,11 @@ export const Markdown = (props: NodeProps) => {
224
235
  }
225
236
 
226
237
  return (
227
- <div className={gridClassName} style={{ position: 'relative', zIndex: 10 }}>
238
+ <div
239
+ className={`${gridClassName} group relative`}
240
+ style={{ position: 'relative', zIndex: 10 }}
241
+ >
242
+ <PaneOverlay {...props} isColumn={isGridChild} />
228
243
  {nodesToRender}
229
244
  </div>
230
245
  );
@@ -7,7 +7,12 @@ import ListContentSetup from '@/components/codehooks/ListContentSetup';
7
7
  import BunnyVideoSetup from '@/components/codehooks/BunnyVideoSetup';
8
8
  import { ProductCardSetup } from '@/components/codehooks/ProductCardSetup';
9
9
  import { ProductGridSetup } from '@/components/codehooks/ProductGridSetup';
10
- import type { BgImageNode, ArtpackImageNode } from '@/types/compositorTypes';
10
+ import { PaneOverlay } from '@/components/compositor/tools/PaneOverlay';
11
+ import type {
12
+ PaneNode,
13
+ BgImageNode,
14
+ ArtpackImageNode,
15
+ } from '@/types/compositorTypes';
11
16
  import type { NodeProps } from '@/types/nodeProps';
12
17
 
13
18
  const TARGETS = [
@@ -95,7 +100,7 @@ const Pane = memo(
95
100
  return () => unsubscribeViewport();
96
101
  }, []);
97
102
 
98
- const wrapperClasses = `grid ${getCtx(props).getNodeClasses(props.nodeId, currentViewport)}`;
103
+ const wrapperClasses = `grid pt-6 ${getCtx(props).getNodeClasses(props.nodeId, currentViewport)}`;
99
104
 
100
105
  const contentClasses = 'relative w-full h-auto justify-self-start';
101
106
  const contentStyles: CSSProperties = {
@@ -119,6 +124,8 @@ const Pane = memo(
119
124
 
120
125
  // Get background node if it exists
121
126
  const allNodes = getCtx(props).allNodes.get();
127
+ const getPaneId = () => `pane-${props.nodeId}`;
128
+
122
129
  const bgNode = children
123
130
  .map((id) => allNodes.get(id))
124
131
  .find(
@@ -143,8 +150,6 @@ const Pane = memo(
143
150
  ? 'flex-row-reverse'
144
151
  : 'flex-row';
145
152
 
146
- const getPaneId = () => `pane-${props.nodeId}`;
147
-
148
153
  const handleNotification = () => {
149
154
  const newChildren = [...getCtx(props).getChildNodeIDs(props.nodeId)];
150
155
  setChildren(newChildren); // Fresh copy
@@ -160,7 +165,8 @@ const Pane = memo(
160
165
  }, [props.nodeId]);
161
166
 
162
167
  return (
163
- <div id={getPaneId()} className="pane">
168
+ <div id={getPaneId()} className="pane group relative">
169
+ <PaneOverlay {...props} hasBackground={!!bgNode} />
164
170
  <div
165
171
  id={getCtx(props).getNodeSlug(props.nodeId)}
166
172
  className={useFlexLayout ? '' : wrapperClasses}
@@ -1,6 +1,6 @@
1
1
  // templates/src/components/compositor/nodes/RenderChildren.tsx
2
2
 
3
- import Node from '../Node';
3
+ import { Node } from '../Node';
4
4
  import type { NodeProps } from '@/types/nodeProps';
5
5
  import type { CompositorProps } from '../Compositor';
6
6
 
@@ -195,7 +195,7 @@ export const NodeAnchorComponent = (props: NodeProps, tagName: string) => {
195
195
  // Create appropriate element based on tagName
196
196
  let baseClasses = ctx.getNodeClasses(nodeId, viewportKeyStore.get().value);
197
197
 
198
- if (toolMode === 'styles' && settingsPanel?.nodeId != nodeId) {
198
+ if (toolMode === 'text' && settingsPanel?.nodeId != nodeId) {
199
199
  baseClasses += ' outline outline-1 outline-dotted outline-black';
200
200
  } else if (settingsPanel?.nodeId === nodeId) {
201
201
  baseClasses +=
@@ -213,9 +213,9 @@ export const NodeAnchorComponent = (props: NodeProps, tagName: string) => {
213
213
  onClick={handleClick}
214
214
  onDoubleClick={handleDoubleClick}
215
215
  data-editable-link="true"
216
- // Preserve display mode
216
+ data-node-id={nodeId}
217
+ data-tag="a"
217
218
  style={{
218
- display: 'inline',
219
219
  cursor: isEditMode ? 'text' : 'pointer',
220
220
  }}
221
221
  >
@@ -237,9 +237,9 @@ export const NodeAnchorComponent = (props: NodeProps, tagName: string) => {
237
237
  onClick={handleClick}
238
238
  onDoubleClick={handleDoubleClick}
239
239
  data-editable-button="true"
240
- // Preserve display mode
240
+ data-node-id={nodeId}
241
+ data-tag="button"
241
242
  style={{
242
- display: 'inline',
243
243
  cursor: isEditMode ? 'text' : 'crosshair',
244
244
  }}
245
245
  >