astro-tractstack 2.0.13 → 2.0.15

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 (28) hide show
  1. package/dist/index.js +40 -0
  2. package/package.json +1 -1
  3. package/templates/src/client/view.js +5 -0
  4. package/templates/src/components/compositor/Compositor.tsx +3 -2
  5. package/templates/src/components/compositor/Node.tsx +25 -8
  6. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +105 -0
  7. package/templates/src/components/edit/ToolMode.tsx +7 -0
  8. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +459 -561
  9. package/templates/src/components/edit/pane/AiPaneGenerator.tsx +19 -82
  10. package/templates/src/components/edit/pane/RestylePaneModal.tsx +573 -0
  11. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
  12. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
  13. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -0
  14. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +205 -0
  15. package/templates/src/constants/prompts.json +3 -1
  16. package/templates/src/stores/selection.ts +4 -0
  17. package/templates/src/types/compositorTypes.ts +51 -1
  18. package/templates/src/types/tractstack.ts +36 -31
  19. package/templates/src/utils/aai/getTitleSlug.ts +1 -1
  20. package/templates/src/utils/api/brandConfig.ts +8 -2
  21. package/templates/src/utils/api/brandHelpers.ts +4 -0
  22. package/templates/src/utils/compositor/aiPaneParser.ts +32 -84
  23. package/templates/src/utils/compositor/designLibraryHelper.ts +416 -0
  24. package/templates/src/utils/compositor/processMarkdown.ts +1 -1
  25. package/utils/inject-files.ts +40 -0
  26. package/templates/src/components/edit/pane/PageGen.tsx +0 -485
  27. package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
  28. package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
@@ -78,7 +78,6 @@ function buildKeyNormalizationLookup(): Map<string, string> {
78
78
 
79
79
  const keyMap = new Map<string, string>();
80
80
  for (const key in tailwindClasses) {
81
- // Store lowercase key -> correctly cased key
82
81
  keyMap.set(key.toLowerCase(), key);
83
82
  }
84
83
  KEY_NORMALIZATION_LOOKUP = keyMap;
@@ -97,7 +96,6 @@ function normalizeKeys(
97
96
  if (Object.prototype.hasOwnProperty.call(styleObj, key)) {
98
97
  const lowerKey = key.toLowerCase();
99
98
  const correctKey = keyMap.get(lowerKey);
100
- // Use the correctly cased key if found, otherwise keep original (handles potential non-Tailwind keys)
101
99
  normalized[correctKey || key] = styleObj[key];
102
100
  }
103
101
  }
@@ -258,22 +256,16 @@ function walkDom(
258
256
  ) {
259
257
  if (domNode.nodeType === Node.TEXT_NODE) {
260
258
  const copy = domNode.textContent || '';
261
- // Preserve leading/trailing spaces unless the *entire* content is just whitespace.
262
- // Trim internal excessive whitespace as a basic sanitation step.
263
259
  const trimmedCopy = copy.replace(/\s+/g, ' ').trim();
264
260
 
265
261
  if (trimmedCopy.length > 0) {
266
- // Use the original copy to preserve meaningful spaces, but cleaned up.
267
262
  let finalCopy = copy.replace(/\s+/g, ' ');
268
- // Preserve single leading space if original had one AND previous sibling exists
269
263
  if (copy.startsWith(' ') && domNode.previousSibling) {
270
264
  finalCopy = ' ' + finalCopy.trimStart();
271
265
  }
272
- // Preserve single trailing space if original had one AND next sibling exists
273
266
  if (copy.endsWith(' ') && domNode.nextSibling) {
274
267
  finalCopy = finalCopy.trimEnd() + ' ';
275
268
  }
276
- // Special case: if it was ONLY space, respect if it was intended between elements
277
269
  if (
278
270
  trimmedCopy.length === 0 &&
279
271
  copy.length > 0 &&
@@ -283,15 +275,13 @@ function walkDom(
283
275
  finalCopy = ' ';
284
276
  }
285
277
 
286
- // Only create node if there's actual content or a meaningful space
287
278
  if (finalCopy.trim().length > 0 || finalCopy === ' ') {
288
279
  const textNode: TemplateNode = {
289
280
  id: ulid(),
290
281
  nodeType: 'TagElement',
291
282
  parentId: parentId,
292
283
  tagName: 'text',
293
- copy: finalCopy, // Use the carefully preserved copy
294
- overrideClasses: {},
284
+ copy: finalCopy,
295
285
  };
296
286
  parsedNodes.push({
297
287
  flatNode: textNode,
@@ -326,7 +316,6 @@ function walkDom(
326
316
  nodeType: 'TagElement',
327
317
  parentId: parentId,
328
318
  tagName: 'p',
329
- overrideClasses: {},
330
319
  };
331
320
  parsedNodes.push({
332
321
  flatNode: pNode,
@@ -341,8 +330,7 @@ function walkDom(
341
330
  id: ulid(),
342
331
  nodeType: 'TagElement',
343
332
  parentId: finalParentId,
344
- tagName: 'a',
345
- overrideClasses: {},
333
+ tagName: 'a', // Buttons are converted to anchor tags for our system
346
334
  href: '#',
347
335
  buttonPayload: {
348
336
  ...buttonPayload,
@@ -368,7 +356,6 @@ function walkDom(
368
356
  nodeType: 'TagElement',
369
357
  parentId: parentId,
370
358
  tagName: tagName,
371
- overrideClasses: {},
372
359
  };
373
360
 
374
361
  if (tagName === 'span') {
@@ -493,14 +480,41 @@ function parseDefaultClassesFromShell(
493
480
  return sanitizedDefaults;
494
481
  }
495
482
 
483
+ /**
484
+ * Parses a raw HTML string from the AI into a structured array of TemplateNodes.
485
+ * @param copyHtml The raw HTML string.
486
+ * @param markdownId The parent ID for the top-level nodes.
487
+ * @returns An array of TemplateNodes representing the structured content.
488
+ */
489
+ export function parseAiCopyHtml(
490
+ copyHtml: string,
491
+ markdownId: string
492
+ ): TemplateNode[] {
493
+ const parser = new DOMParser();
494
+ const doc = parser.parseFromString(copyHtml, 'text/html');
495
+
496
+ const allParsedNodes: ParsedNode[] = [];
497
+ walkDom(doc.body, markdownId, allParsedNodes, markdownId);
498
+
499
+ // When parsing copy in isolation, all classes are treated as potential overrides.
500
+ // The consumer is responsible for merging these with a set of defaults if needed.
501
+ return allParsedNodes.map((pNode) => {
502
+ if (
503
+ Object.keys(pNode.responsiveClasses).length > 0 &&
504
+ pNode.flatNode.tagName !== 'span'
505
+ ) {
506
+ pNode.flatNode.overrideClasses = pNode.responsiveClasses;
507
+ }
508
+ return pNode.flatNode;
509
+ });
510
+ }
511
+
496
512
  export const parseAiPane = (
497
513
  shellJson: string,
498
514
  copyHtml: string,
499
515
  layout: string
500
516
  ): TemplatePane => {
501
517
  const shell: ShellJson = JSON.parse(shellJson);
502
- const parser = new DOMParser();
503
- const doc = parser.parseFromString(copyHtml, 'text/html');
504
518
 
505
519
  const paneId = ulid();
506
520
  const markdownId = ulid();
@@ -527,73 +541,7 @@ export const parseAiPane = (
527
541
  defaultClasses: shellDefaults,
528
542
  };
529
543
 
530
- const allParsedNodes: ParsedNode[] = [];
531
- walkDom(doc.body, markdownId, allParsedNodes, markdownId);
532
-
533
- const templateNodes: TemplateNode[] = [];
534
- const nodesByTag = new Map<string, ParsedNode[]>();
535
-
536
- allParsedNodes.forEach((parsedNode) => {
537
- templateNodes.push(parsedNode.flatNode);
538
- const tagName = parsedNode.flatNode.tagName;
539
-
540
- if (
541
- tagName &&
542
- tagName !== 'span' &&
543
- tagName !== 'text' &&
544
- tagName !== 'em' &&
545
- tagName !== 'strong' &&
546
- tagName !== 'a'
547
- ) {
548
- if (!nodesByTag.has(tagName)) {
549
- nodesByTag.set(tagName, []);
550
- }
551
- nodesByTag.get(tagName)!.push(parsedNode);
552
- }
553
- });
554
-
555
- nodesByTag.forEach((nodes, tagName) => {
556
- const commonResponsiveFromCopy = findMostCommonClasses(nodes);
557
- const requiredCommonFromCopy = ensureRequiredViewports(
558
- commonResponsiveFromCopy
559
- );
560
-
561
- const existingShellDefault = markdownNode.defaultClasses![tagName];
562
- const mergedDefault = ensureRequiredViewports(
563
- mergeResponsive(existingShellDefault, commonResponsiveFromCopy)
564
- );
565
-
566
- markdownNode.defaultClasses![tagName] = mergedDefault;
567
-
568
- nodes.forEach((parsedNode) => {
569
- const requiredNodeResponsive = ensureRequiredViewports(
570
- parsedNode.responsiveClasses
571
- );
572
-
573
- if (!isDeepEqual(requiredNodeResponsive, requiredCommonFromCopy)) {
574
- if (!parsedNode.flatNode.overrideClasses) {
575
- parsedNode.flatNode.overrideClasses = {};
576
- }
577
- parsedNode.flatNode.overrideClasses = parsedNode.responsiveClasses;
578
- }
579
- });
580
- });
581
-
582
- if (layout.includes('Image')) {
583
- const imgNode: TemplateNode = {
584
- id: ulid(),
585
- nodeType: 'TagElement',
586
- parentId: markdownId,
587
- tagName: 'img',
588
- src: '/static.jpg',
589
- overrideClasses: {},
590
- };
591
- if (layout === 'Text + Image Left') {
592
- templateNodes.unshift(imgNode);
593
- } else {
594
- templateNodes.push(imgNode);
595
- }
596
- }
544
+ const templateNodes = parseAiCopyHtml(copyHtml, markdownId);
597
545
 
598
546
  const templatePane: TemplatePane = {
599
547
  id: paneId,
@@ -0,0 +1,416 @@
1
+ import { ulid } from 'ulid';
2
+ import { getCtx, type NodesContext } from '@/stores/nodes';
3
+ import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
4
+ import {
5
+ type PaneNode,
6
+ type FlatNode,
7
+ type MarkdownPaneFragmentNode,
8
+ type StoragePane,
9
+ type StorageNode,
10
+ type StorageMarkdown,
11
+ type StorageBgPane,
12
+ type ArtpackImageNode,
13
+ type BgImageNode,
14
+ type VisualBreakNode,
15
+ type TemplatePane,
16
+ type TemplateNode,
17
+ } from '@/types/compositorTypes';
18
+ import type {
19
+ BrandConfig,
20
+ BrandConfigState,
21
+ DesignLibraryConfig,
22
+ DesignLibraryEntry,
23
+ } from '@/types/tractstack';
24
+ import { saveBrandConfig } from '@/utils/api/brandConfig';
25
+ import {
26
+ convertToLocalState,
27
+ convertToBackendFormat,
28
+ } from '@/utils/api/brandHelpers';
29
+
30
+ type CopyMode = 'retain' | 'lorem' | 'blank';
31
+
32
+ export type ExtractedCopy = StorageNode[];
33
+
34
+ const LOREM_SHORT = 'Lorem ipsum dolor sit amet.';
35
+ const LOREM_LONG =
36
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
37
+
38
+ function convertLiveNodeToStorageNode(
39
+ node: FlatNode,
40
+ ctx: NodesContext,
41
+ copyMode: CopyMode
42
+ ): StorageNode | null {
43
+ if (copyMode === 'lorem') {
44
+ if (!node.tagName || !['h2', 'h3', 'h4', 'p'].includes(node.tagName)) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ const storageNode: StorageNode = {
50
+ nodeType: node.nodeType,
51
+ tagName: node.tagName,
52
+ tagNameCustom: node.tagNameCustom,
53
+ overrideClasses: copyMode === 'retain' ? node.overrideClasses : undefined,
54
+ href: copyMode === 'retain' ? node.href : undefined,
55
+ src: copyMode === 'retain' ? node.src : undefined,
56
+ alt: copyMode === 'retain' ? node.alt : undefined,
57
+ fileId: copyMode === 'retain' ? node.fileId : undefined,
58
+ buttonPayload: copyMode === 'retain' ? node.buttonPayload : undefined,
59
+ codeHookParams: copyMode === 'retain' ? node.codeHookParams : undefined,
60
+ elementCss: copyMode === 'retain' ? node.elementCss : undefined,
61
+ };
62
+
63
+ const childIds = ctx.getChildNodeIDs(node.id);
64
+
65
+ if (childIds.length > 0) {
66
+ const childNodes = childIds
67
+ .map((id) => {
68
+ const childNode = ctx.allNodes.get().get(id) as FlatNode;
69
+ if (!childNode) return null;
70
+
71
+ if (childNode.tagName === 'text' && childNode.copy) {
72
+ if (copyMode === 'lorem') {
73
+ const isHeadingParent =
74
+ node.tagName && node.tagName.startsWith('h');
75
+ return {
76
+ nodeType: 'TagElement',
77
+ tagName: 'text',
78
+ copy: isHeadingParent ? LOREM_SHORT : LOREM_LONG,
79
+ };
80
+ }
81
+ if (copyMode === 'retain') {
82
+ return {
83
+ nodeType: 'TagElement',
84
+ tagName: 'text',
85
+ copy: childNode.copy,
86
+ };
87
+ }
88
+ }
89
+
90
+ return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
91
+ })
92
+ .filter((n): n is StorageNode => n !== null);
93
+
94
+ if (childNodes.length > 0) {
95
+ storageNode.nodes = childNodes;
96
+ }
97
+ }
98
+
99
+ return storageNode;
100
+ }
101
+
102
+ export async function savePaneToLibrary(
103
+ paneId: string,
104
+ tenantId: string,
105
+ config: BrandConfig,
106
+ formData: {
107
+ title: string;
108
+ category: string;
109
+ copyMode: CopyMode;
110
+ }
111
+ ): Promise<boolean> {
112
+ const ctx = getCtx();
113
+ const { title, category, copyMode } = formData;
114
+ const paneNode = ctx.allNodes.get().get(paneId) as PaneNode;
115
+
116
+ if (!paneNode) {
117
+ console.error('savePaneToLibrary: PaneNode not found.');
118
+ return false;
119
+ }
120
+
121
+ const childNodes = ctx
122
+ .getChildNodeIDs(paneId)
123
+ .map((id) => ctx.allNodes.get().get(id));
124
+
125
+ const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
126
+ | MarkdownPaneFragmentNode
127
+ | undefined;
128
+
129
+ const bgPaneNode = childNodes.find((n) => n?.nodeType === 'BgPane') as
130
+ | ArtpackImageNode
131
+ | BgImageNode
132
+ | VisualBreakNode
133
+ | undefined;
134
+
135
+ const newStorageMarkdown: StorageMarkdown | undefined = markdownNode
136
+ ? {
137
+ nodeType: 'Markdown',
138
+ type: 'markdown',
139
+ defaultClasses: markdownNode.defaultClasses || {},
140
+ parentClasses: markdownNode.parentClasses || [],
141
+ nodes:
142
+ copyMode !== 'blank'
143
+ ? ctx
144
+ .getChildNodeIDs(markdownNode.id)
145
+ .map((childId) => {
146
+ const childNode = ctx.allNodes.get().get(childId) as FlatNode;
147
+ return convertLiveNodeToStorageNode(childNode, ctx, copyMode);
148
+ })
149
+ .filter((n): n is StorageNode => n !== null)
150
+ : [],
151
+ }
152
+ : undefined;
153
+
154
+ const newStorageBgPane: StorageBgPane | undefined = bgPaneNode
155
+ ? { ...bgPaneNode }
156
+ : undefined;
157
+
158
+ if (newStorageBgPane) {
159
+ delete (newStorageBgPane as any).id;
160
+ delete (newStorageBgPane as any).parentId;
161
+ }
162
+
163
+ const newStoragePane: StoragePane = {
164
+ nodeType: 'Pane',
165
+ title: title,
166
+ slug: '',
167
+ bgColour: paneNode.bgColour,
168
+ isDecorative: paneNode.isDecorative,
169
+ heightOffsetDesktop: paneNode.heightOffsetDesktop,
170
+ heightOffsetMobile: paneNode.heightOffsetMobile,
171
+ heightOffsetTablet: paneNode.heightOffsetTablet,
172
+ heightRatioDesktop: paneNode.heightRatioDesktop,
173
+ heightRatioMobile: paneNode.heightRatioMobile,
174
+ heightRatioTablet: paneNode.heightRatioTablet,
175
+ markdown: newStorageMarkdown,
176
+ bgPane: newStorageBgPane,
177
+ };
178
+
179
+ const newLibraryEntry: DesignLibraryEntry = {
180
+ category: category,
181
+ title: title,
182
+ template: newStoragePane,
183
+ };
184
+
185
+ const currentState: BrandConfigState = convertToLocalState(config);
186
+ const currentLibrary =
187
+ (currentState.designLibrary as DesignLibraryConfig) || [];
188
+
189
+ const existingEntryIndex = currentLibrary.findIndex(
190
+ (entry) => entry.title === title && entry.category === category
191
+ );
192
+
193
+ let newLibrary: DesignLibraryConfig;
194
+ if (existingEntryIndex !== -1) {
195
+ newLibrary = [...currentLibrary];
196
+ newLibrary[existingEntryIndex] = newLibraryEntry;
197
+ } else {
198
+ newLibrary = [...currentLibrary, newLibraryEntry];
199
+ }
200
+
201
+ const updatedState: BrandConfigState = {
202
+ ...currentState,
203
+ designLibrary: newLibrary,
204
+ };
205
+
206
+ const backendDTO: BrandConfig = convertToBackendFormat(updatedState);
207
+
208
+ try {
209
+ await saveBrandConfig(tenantId, backendDTO);
210
+ return true;
211
+ } catch (error) {
212
+ console.error('Failed to save design library:', error);
213
+ return false;
214
+ }
215
+ }
216
+
217
+ export function extractPaneCopy(paneNode: PaneNode): ExtractedCopy {
218
+ const ctx = getCtx();
219
+ const childNodes = ctx
220
+ .getChildNodeIDs(paneNode.id)
221
+ .map((id) => ctx.allNodes.get().get(id));
222
+
223
+ const markdownNode = childNodes.find((n) => n?.nodeType === 'Markdown') as
224
+ | MarkdownPaneFragmentNode
225
+ | undefined;
226
+
227
+ if (!markdownNode) {
228
+ return [];
229
+ }
230
+
231
+ return (
232
+ ctx
233
+ .getChildNodeIDs(markdownNode.id)
234
+ .map((childId) => {
235
+ const childNode = ctx.allNodes.get().get(childId) as FlatNode;
236
+ return convertLiveNodeToStorageNode(childNode, ctx, 'retain');
237
+ })
238
+ .filter((n): n is StorageNode => n !== null) || []
239
+ );
240
+ }
241
+
242
+ export function mergeCopyIntoTemplate(
243
+ template: StoragePane,
244
+ copy: ExtractedCopy
245
+ ): StoragePane {
246
+ const newTemplate = { ...template };
247
+ if (newTemplate.markdown) {
248
+ newTemplate.markdown.nodes = copy;
249
+ } else if (copy.length > 0) {
250
+ newTemplate.markdown = {
251
+ nodeType: 'Markdown',
252
+ type: 'markdown',
253
+ defaultClasses: {},
254
+ parentClasses: [],
255
+ nodes: copy,
256
+ };
257
+ }
258
+ return newTemplate;
259
+ }
260
+
261
+ function processStorageNode(
262
+ node: StorageNode,
263
+ parentId: string
264
+ ): TemplateNode[] {
265
+ const newId = ulid();
266
+ const { nodes, ...rest } = node;
267
+ const liveNode: TemplateNode = {
268
+ ...rest,
269
+ id: newId,
270
+ parentId: parentId,
271
+ };
272
+
273
+ const flatList: TemplateNode[] = [liveNode];
274
+
275
+ if (nodes) {
276
+ for (const child of nodes) {
277
+ const processedChildren = processStorageNode(child, newId);
278
+ flatList.push(...processedChildren);
279
+ }
280
+ }
281
+
282
+ return flatList;
283
+ }
284
+
285
+ export function convertStorageToLiveTemplate(
286
+ storagePane: StoragePane
287
+ ): TemplatePane {
288
+ const paneId = ulid();
289
+ const markdownId = ulid();
290
+ const flatNodeList: TemplateNode[] = [];
291
+
292
+ if (storagePane.markdown && storagePane.markdown.nodes) {
293
+ for (const storageNode of storagePane.markdown.nodes) {
294
+ const processedNodes = processStorageNode(storageNode, markdownId);
295
+ flatNodeList.push(...processedNodes);
296
+ }
297
+ }
298
+
299
+ let liveBgPane: ArtpackImageNode | BgImageNode | VisualBreakNode | undefined =
300
+ undefined;
301
+ if (storagePane.bgPane) {
302
+ const bgPaneId = ulid();
303
+ liveBgPane = {
304
+ ...storagePane.bgPane,
305
+ id: bgPaneId,
306
+ parentId: paneId,
307
+ };
308
+ }
309
+
310
+ const liveTemplatePane: TemplatePane = {
311
+ ...storagePane,
312
+ id: paneId,
313
+ parentId: '',
314
+ markdown: {
315
+ ...(storagePane.markdown || {
316
+ nodeType: 'Markdown',
317
+ type: 'markdown',
318
+ defaultClasses: {},
319
+ parentClasses: [],
320
+ }),
321
+ id: markdownId,
322
+ markdownId: markdownId,
323
+ parentId: paneId,
324
+ nodes: flatNodeList,
325
+ },
326
+ bgPane: liveBgPane,
327
+ };
328
+
329
+ return liveTemplatePane;
330
+ }
331
+
332
+ // Helper to convert a style object { "px": "4", "fontBOLD": "bold" } to "px-4 font-bold"
333
+ function classObjectToString(
334
+ classObj: Record<string, string> | undefined
335
+ ): string {
336
+ if (!classObj) return '';
337
+
338
+ return Object.entries(classObj)
339
+ .map(([key, value]) => {
340
+ const definition = tailwindClasses[key];
341
+ if (!definition) return ''; // Ignore keys not in our definitions
342
+
343
+ if (definition.useKeyAsClass) {
344
+ return value; // e.g., for 'fontBOLD', value is 'font-bold'
345
+ }
346
+
347
+ // Handle negative values
348
+ if (typeof value === 'string' && value.startsWith('-')) {
349
+ return `-${definition.prefix}${value.substring(1)}`;
350
+ }
351
+
352
+ return `${definition.prefix}${value}`;
353
+ })
354
+ .filter(Boolean)
355
+ .join(' ');
356
+ }
357
+
358
+ /**
359
+ * Translates a TemplatePane from the design library into an AI-compatible JSON shell
360
+ * for the hybrid AI copy generation path.
361
+ * @param template The TemplatePane object selected by the user.
362
+ * @returns A JSON string compatible with the AI's second-stage prompt.
363
+ */
364
+ export function convertTemplateToAIShell(template: TemplatePane): string {
365
+ const shell: any = {
366
+ bgColour: template.bgColour || '#ffffff',
367
+ parentClasses: [],
368
+ defaultClasses: {},
369
+ };
370
+
371
+ // 1. Process parentClasses (layout)
372
+ if (template.markdown?.parentClasses) {
373
+ shell.parentClasses = template.markdown.parentClasses.map((layer) => {
374
+ const newLayer: { mobile?: string; tablet?: string; desktop?: string } =
375
+ {};
376
+ if (layer.mobile && Object.keys(layer.mobile).length > 0) {
377
+ newLayer.mobile = classObjectToString(layer.mobile);
378
+ }
379
+ if (layer.tablet && Object.keys(layer.tablet).length > 0) {
380
+ newLayer.tablet = classObjectToString(layer.tablet);
381
+ }
382
+ if (layer.desktop && Object.keys(layer.desktop).length > 0) {
383
+ newLayer.desktop = classObjectToString(layer.desktop);
384
+ }
385
+ return newLayer;
386
+ });
387
+ }
388
+
389
+ // 2. Process defaultClasses (typography, etc.)
390
+ if (template.markdown?.defaultClasses) {
391
+ for (const tag in template.markdown.defaultClasses) {
392
+ const styles = template.markdown.defaultClasses[tag];
393
+ const newTagStyles: {
394
+ mobile?: string;
395
+ tablet?: string;
396
+ desktop?: string;
397
+ } = {};
398
+
399
+ if (styles.mobile && Object.keys(styles.mobile).length > 0) {
400
+ newTagStyles.mobile = classObjectToString(styles.mobile);
401
+ }
402
+ if (styles.tablet && Object.keys(styles.tablet).length > 0) {
403
+ newTagStyles.tablet = classObjectToString(styles.tablet);
404
+ }
405
+ if (styles.desktop && Object.keys(styles.desktop).length > 0) {
406
+ newTagStyles.desktop = classObjectToString(styles.desktop);
407
+ }
408
+
409
+ if (Object.keys(newTagStyles).length > 0) {
410
+ shell.defaultClasses[tag] = newTagStyles;
411
+ }
412
+ }
413
+ }
414
+
415
+ return JSON.stringify(shell, null, 2);
416
+ }
@@ -63,7 +63,7 @@ Example response format:
63
63
  "slug": "short-descriptive-title"
64
64
  }`,
65
65
  input_text: markdownContent,
66
- final_model: 'anthropic/claude-3-5-sonnet',
66
+ final_model: '',
67
67
  temperature: 0.3,
68
68
  max_tokens: 200,
69
69
  }),
@@ -127,6 +127,12 @@ export async function injectTemplateFiles(
127
127
  ),
128
128
  dest: 'src/components/compositor/nodes/Pane_eraser.tsx',
129
129
  },
130
+ {
131
+ src: resolve(
132
+ '../templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx'
133
+ ),
134
+ dest: 'src/components/compositor/nodes/Pane_DesignLibrary.tsx',
135
+ },
130
136
  {
131
137
  src: resolve(
132
138
  '../templates/src/components/compositor/nodes/Pane_layout.tsx'
@@ -429,6 +435,12 @@ export async function injectTemplateFiles(
429
435
  ),
430
436
  dest: 'src/components/edit/pane/AddPanePanel_codehook.tsx',
431
437
  },
438
+ {
439
+ src: resolve(
440
+ '../templates/src/components/edit/pane/RestylePaneModal.tsx'
441
+ ),
442
+ dest: 'src/components/edit/pane/RestylePaneModal.tsx',
443
+ },
432
444
  {
433
445
  src: resolve('../templates/src/components/edit/pane/AiPaneGenerator.tsx'),
434
446
  dest: 'src/components/edit/pane/AiPaneGenerator.tsx',
@@ -437,6 +449,24 @@ export async function injectTemplateFiles(
437
449
  src: resolve('../templates/src/components/edit/pane/AiPanePreview.tsx'),
438
450
  dest: 'src/components/edit/pane/AiPanePreview.tsx',
439
451
  },
452
+ {
453
+ src: resolve(
454
+ '../templates/src/components/edit/pane/steps/CopyInputStep.tsx'
455
+ ),
456
+ dest: 'src/components/edit/pane/steps/CopyInputStep.tsx',
457
+ },
458
+ {
459
+ src: resolve(
460
+ '../templates/src/components/edit/pane/steps/DesignLibraryStep.tsx'
461
+ ),
462
+ dest: 'src/components/edit/pane/steps/DesignLibraryStep.tsx',
463
+ },
464
+ {
465
+ src: resolve(
466
+ '../templates/src/components/edit/pane/steps/AiDesignStep.tsx'
467
+ ),
468
+ dest: 'src/components/edit/pane/steps/AiDesignStep.tsx',
469
+ },
440
470
  {
441
471
  src: resolve(
442
472
  '../templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx'
@@ -629,6 +659,10 @@ export async function injectTemplateFiles(
629
659
  src: resolve('../templates/src/utils/compositor/typeGuards.ts'),
630
660
  dest: 'src/utils/compositor/typeGuards.ts',
631
661
  },
662
+ {
663
+ src: resolve('../templates/src/utils/compositor/designLibraryHelper.ts'),
664
+ dest: 'src/utils/compositor/designLibraryHelper.ts',
665
+ },
632
666
  {
633
667
  src: resolve('../templates/src/utils/compositor/domHelpers.ts'),
634
668
  dest: 'src/utils/compositor/domHelpers.ts',
@@ -1372,6 +1406,12 @@ export async function injectTemplateFiles(
1372
1406
  src: resolve('../templates/src/components/edit/state/SaveModal.tsx'),
1373
1407
  dest: 'src/components/edit/state/SaveModal.tsx',
1374
1408
  },
1409
+ {
1410
+ src: resolve(
1411
+ '../templates/src/components/edit/state/SaveToLibraryModal.tsx'
1412
+ ),
1413
+ dest: 'src/components/edit/state/SaveToLibraryModal.tsx',
1414
+ },
1375
1415
  {
1376
1416
  src: resolve('../templates/src/components/edit/state/StylesMemory.tsx'),
1377
1417
  dest: 'src/components/edit/state/StylesMemory.tsx',