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
@@ -43,6 +43,7 @@ export enum PaneAddMode {
43
43
  BREAK = 'BREAK',
44
44
  REUSE = 'REUSE',
45
45
  CODEHOOK = 'CODEHOOK',
46
+ PASTE = 'PASTE',
46
47
  }
47
48
 
48
49
  export enum PaneConfigMode {
@@ -424,10 +425,15 @@ export type TemplateMarkdown = MarkdownPaneFragmentNode & {
424
425
  markdownBody?: string;
425
426
  };
426
427
 
428
+ export type TemplateGridLayout = GridLayoutNode & {
429
+ nodes?: TemplateMarkdown[];
430
+ };
431
+
427
432
  export type TemplatePane = PaneNode & {
428
433
  id?: string;
429
434
  parentId?: string;
430
435
  markdown?: TemplateMarkdown;
436
+ gridLayout?: TemplateGridLayout;
431
437
  bgPane?: VisualBreakNode | ArtpackImageNode | BgImageNode;
432
438
  };
433
439
 
@@ -463,6 +469,19 @@ export type StorageMarkdown = Omit<
463
469
  nodes?: StorageNode[];
464
470
  };
465
471
 
472
+ export type StorageGridLayoutNode = {
473
+ nodeType: string;
474
+ type: string;
475
+ defaultClasses?: Record<string, any>;
476
+ parentClasses?: Record<string, any>[];
477
+ gridColumns: {
478
+ mobile: number;
479
+ tablet: number;
480
+ desktop: number;
481
+ };
482
+ nodes?: StorageMarkdown[];
483
+ };
484
+
466
485
  export type StoragePane = Omit<
467
486
  PaneNode,
468
487
  | 'id'
@@ -476,7 +495,8 @@ export type StoragePane = Omit<
476
495
  | 'codeHookPayload'
477
496
  | 'markdown'
478
497
  > & {
479
- markdown?: StorageMarkdown;
498
+ markdowns?: StorageMarkdown[];
499
+ gridLayout?: StorageGridLayoutNode;
480
500
  bgPane?: StorageBgPane;
481
501
  };
482
502
 
@@ -3,6 +3,7 @@ import type { StoragePane } from './compositorTypes';
3
3
  export type DesignLibraryEntry = {
4
4
  category: string;
5
5
  title: string;
6
+ markdownCount: number;
6
7
  template: StoragePane;
7
8
  };
8
9
 
@@ -1,13 +1,4 @@
1
- import {
2
- //TemplateAsideNode,
3
- TemplateH2Node,
4
- TemplateH3Node,
5
- TemplatePNode,
6
- } from './TemplateNodes';
7
- import { getTemplateSimpleMarkdown } from './TemplateMarkdowns';
8
- import { getColor, tailwindToHex } from './tailwindColors';
9
1
  import type { TemplatePane } from '@/types/compositorTypes';
10
- import type { Theme } from '@/types/tractstack';
11
2
 
12
3
  export const getTemplateVisualBreakPane = (variant: string) => {
13
4
  // colour will be set on insert based on adjacent nodes
@@ -31,70 +22,3 @@ export const getTemplateVisualBreakPane = (variant: string) => {
31
22
  },
32
23
  } as TemplatePane;
33
24
  };
34
-
35
- export const getTemplateSimplePane = (
36
- theme: Theme,
37
- brand: string,
38
- useOdd: boolean = false
39
- ) => {
40
- return {
41
- nodeType: 'Pane',
42
- title: '',
43
- slug: '',
44
- bgColour: tailwindToHex(
45
- getColor(
46
- {
47
- light: !useOdd ? 'brand-2' : 'white',
48
- 'light-bw': !useOdd ? 'white' : 'brand-2',
49
- 'light-bold': !useOdd ? 'brand-2' : 'white',
50
- dark: !useOdd ? 'black' : 'brand-1',
51
- 'dark-bw': !useOdd ? 'black' : 'brand-1',
52
- 'dark-bold': !useOdd ? 'brand-1' : 'black',
53
- },
54
- theme
55
- ),
56
- brand
57
- ),
58
- markdown: {
59
- ...getTemplateSimpleMarkdown(theme),
60
- nodes: [
61
- { ...TemplateH2Node, copy: 'H2 node in simple pane' },
62
- { ...TemplatePNode, copy: 'P node in simple pane' },
63
- { ...TemplateH3Node, copy: 'H3 node in simple pane' },
64
- //{ ...TemplateAsideNode, copy: "Aside node in simple pane" },
65
- ],
66
- },
67
- } as TemplatePane;
68
- };
69
-
70
- export const getTemplateMarkdownPane = (
71
- theme: Theme,
72
- variant: string,
73
- brand: string,
74
- useOdd: boolean = false
75
- ) => {
76
- console.log(`variant: ${variant}`);
77
- return {
78
- nodeType: 'Pane',
79
- title: '',
80
- slug: '',
81
- bgColour: tailwindToHex(
82
- getColor(
83
- {
84
- light: !useOdd ? 'brand-2' : 'white',
85
- 'light-bw': !useOdd ? 'white' : 'brand-2',
86
- 'light-bold': !useOdd ? 'brand-2' : 'white',
87
- dark: !useOdd ? 'black' : 'brand-1',
88
- 'dark-bw': !useOdd ? 'black' : 'brand-1',
89
- 'dark-bold': !useOdd ? 'brand-1' : 'black',
90
- },
91
- theme
92
- ),
93
- brand
94
- ),
95
- markdown: {
96
- ...getTemplateSimpleMarkdown(theme),
97
- markdownBody: `## add a catchy title here\n\nyour story continues... and continues... and continues... and continues... and continues... and continues... with nice layout and typography.\n\n[Try it now!](try) &nbsp; [Learn more](learn)\n`,
98
- },
99
- } as TemplatePane;
100
- };
@@ -7,14 +7,23 @@ import type {
7
7
  DefaultClasses,
8
8
  ResponsiveClasses,
9
9
  ButtonPayload,
10
+ GridLayoutNode,
10
11
  } from '@/types/compositorTypes';
11
- import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
12
12
  import { isDeepEqual } from '@/utils/helpers';
13
+ import { tailwindClasses } from '@/utils/compositor/tailwindClasses';
13
14
 
14
15
  type LLMShellLayer = {
15
- mobile?: Record<string, string>;
16
- tablet?: Record<string, string>;
17
- desktop?: Record<string, string>;
16
+ mobile?: string;
17
+ tablet?: string;
18
+ desktop?: string;
19
+ };
20
+
21
+ type LLMColumnLayer = {
22
+ gridClasses: {
23
+ mobile?: string;
24
+ tablet?: string;
25
+ desktop?: string;
26
+ };
18
27
  };
19
28
 
20
29
  type LLMDefaultClasses = {
@@ -29,6 +38,7 @@ type ShellJson = {
29
38
  bgColour: string;
30
39
  parentClasses: LLMShellLayer[];
31
40
  defaultClasses: LLMDefaultClasses;
41
+ columns?: LLMColumnLayer[];
32
42
  };
33
43
 
34
44
  type ParsedNode = {
@@ -36,12 +46,6 @@ type ParsedNode = {
36
46
  responsiveClasses: ResponsiveClasses;
37
47
  };
38
48
 
39
- type ParentClassLayer = {
40
- mobile: Record<string, string>;
41
- tablet: Record<string, string>;
42
- desktop: Record<string, string>;
43
- };
44
-
45
49
  type DefaultClassValue = {
46
50
  mobile: Record<string, string>;
47
51
  tablet: Record<string, string>;
@@ -54,7 +58,6 @@ type ClassLookupValue = {
54
58
  viewport: 'mobile' | 'tablet' | 'desktop';
55
59
  };
56
60
 
57
- let KEY_NORMALIZATION_LOOKUP: Map<string, string> | null = null;
58
61
  let RESPONSIVE_CLASS_LOOKUP: Map<string, ClassLookupValue> | null = null;
59
62
  let BUTTON_CLASS_LOOKUP: Map<string, { key: string; value: string }> | null =
60
63
  null;
@@ -74,37 +77,6 @@ const ALLOWED_TAGS = new Set([
74
77
  'a',
75
78
  ]);
76
79
 
77
- function buildKeyNormalizationLookup(): Map<string, string> {
78
- if (KEY_NORMALIZATION_LOOKUP) {
79
- return KEY_NORMALIZATION_LOOKUP;
80
- }
81
-
82
- const keyMap = new Map<string, string>();
83
- for (const key in tailwindClasses) {
84
- keyMap.set(key.toLowerCase(), key);
85
- }
86
- KEY_NORMALIZATION_LOOKUP = keyMap;
87
- return keyMap;
88
- }
89
-
90
- function normalizeKeys(
91
- styleObj: Record<string, string> | undefined
92
- ): Record<string, string> {
93
- if (!styleObj) return {};
94
-
95
- const keyMap = buildKeyNormalizationLookup();
96
- const normalized: Record<string, string> = {};
97
-
98
- for (const key in styleObj) {
99
- if (Object.prototype.hasOwnProperty.call(styleObj, key)) {
100
- const lowerKey = key.toLowerCase();
101
- const correctKey = keyMap.get(lowerKey);
102
- normalized[correctKey || key] = styleObj[key];
103
- }
104
- }
105
- return normalized;
106
- }
107
-
108
80
  function buildResponsiveClassLookup(): Map<string, ClassLookupValue> {
109
81
  if (RESPONSIVE_CLASS_LOOKUP) {
110
82
  return RESPONSIVE_CLASS_LOOKUP;
@@ -406,6 +378,121 @@ function findMostCommonClasses(nodes: ParsedNode[]): ResponsiveClasses {
406
378
  return classMap.get(mostCommonKey) || {};
407
379
  }
408
380
 
381
+ /**
382
+ * Calculates the difference between a node's full style and the default theme for its tag.
383
+ * @returns An object with only the override styles, or undefined if there are no overrides.
384
+ */
385
+ function calculateOverrides(
386
+ fullStyle: ResponsiveClasses,
387
+ defaultStyle: DefaultClassValue
388
+ ): ResponsiveClasses | undefined {
389
+ const overrides: ResponsiveClasses = {};
390
+ const viewports: Array<keyof ResponsiveClasses> = [
391
+ 'mobile',
392
+ 'tablet',
393
+ 'desktop',
394
+ ];
395
+
396
+ for (const viewport of viewports) {
397
+ const fullVPStyle = fullStyle[viewport];
398
+ const defaultVPStyle = defaultStyle[viewport];
399
+
400
+ if (!fullVPStyle) {
401
+ continue;
402
+ }
403
+
404
+ for (const key in fullVPStyle) {
405
+ if (
406
+ Object.prototype.hasOwnProperty.call(fullVPStyle, key) &&
407
+ fullVPStyle[key] !== defaultVPStyle?.[key]
408
+ ) {
409
+ if (!overrides[viewport]) {
410
+ overrides[viewport] = {};
411
+ }
412
+ overrides[viewport]![key] = fullVPStyle[key];
413
+ }
414
+ }
415
+ }
416
+
417
+ if (Object.keys(overrides).length > 0 && !isDeepEqual(overrides, {})) {
418
+ return overrides;
419
+ }
420
+
421
+ return undefined;
422
+ }
423
+
424
+ /**
425
+ * Reconciles classes for a set of final TemplateNodes against a base theme.
426
+ * It discovers the true theme from the content, merges it into the base theme,
427
+ * and then mutates the nodes to only contain true override classes.
428
+ * @param nodes The array of TemplateNode objects to mutate.
429
+ * @param baseDefaults The base theme object to mutate.
430
+ */
431
+ function reconcileClasses(
432
+ nodes: TemplateNode[],
433
+ baseDefaults: DefaultClasses
434
+ ): void {
435
+ const nodesByTag: Record<string, TemplateNode[]> = {};
436
+ const tempParsedNodes: ParsedNode[] = [];
437
+
438
+ nodes.forEach((node) => {
439
+ // Create a temporary ParsedNode structure for analysis
440
+ const tempParsedNode: ParsedNode = {
441
+ flatNode: node,
442
+ responsiveClasses: node.overrideClasses || {},
443
+ };
444
+ tempParsedNodes.push(tempParsedNode);
445
+
446
+ const tagName = node.tagName;
447
+ if (!['p', 'h2', 'h3', 'h4', 'h5'].includes(tagName)) {
448
+ return;
449
+ }
450
+ if (!nodesByTag[tagName]) {
451
+ nodesByTag[tagName] = [];
452
+ }
453
+ nodesByTag[tagName].push(node);
454
+ });
455
+
456
+ const finalDefaults = JSON.parse(JSON.stringify(baseDefaults));
457
+
458
+ for (const tagName in nodesByTag) {
459
+ const nodesForTag = nodesByTag[tagName];
460
+ const tempParsedForTag = nodesForTag.map((n) => ({
461
+ flatNode: n,
462
+ responsiveClasses: n.overrideClasses || {},
463
+ }));
464
+
465
+ const commonStyleForTag = findMostCommonClasses(tempParsedForTag);
466
+ const mergedDefault = mergeResponsive(
467
+ finalDefaults[tagName],
468
+ commonStyleForTag
469
+ );
470
+ finalDefaults[tagName] = ensureRequiredViewports(mergedDefault);
471
+ }
472
+
473
+ nodes.forEach((node) => {
474
+ const tagName = node.tagName;
475
+ const defaultStyleForTag = finalDefaults[tagName];
476
+ const fullStyle = node.overrideClasses || {};
477
+
478
+ if (defaultStyleForTag) {
479
+ const overrides = calculateOverrides(fullStyle, defaultStyleForTag);
480
+ node.overrideClasses = overrides;
481
+ } else if (Object.keys(fullStyle).length > 0 && node.tagName !== 'span') {
482
+ if (!isDeepEqual(fullStyle, {})) {
483
+ node.overrideClasses = fullStyle;
484
+ } else {
485
+ node.overrideClasses = undefined;
486
+ }
487
+ } else if (node.tagName !== 'span') {
488
+ node.overrideClasses = undefined;
489
+ }
490
+ });
491
+
492
+ Object.keys(baseDefaults).forEach((key) => delete baseDefaults[key]);
493
+ Object.assign(baseDefaults, finalDefaults);
494
+ }
495
+
409
496
  function ensureRequiredViewports(
410
497
  responsive: ResponsiveClasses | undefined
411
498
  ): DefaultClassValue {
@@ -502,8 +589,6 @@ export function parseAiCopyHtml(
502
589
  const allParsedNodes: ParsedNode[] = [];
503
590
  walkDom(doc.body, markdownId, allParsedNodes, markdownId);
504
591
 
505
- // When parsing copy in isolation, all classes are treated as potential overrides.
506
- // The consumer is responsible for merging these with a set of defaults if needed.
507
592
  return allParsedNodes.map((pNode) => {
508
593
  if (
509
594
  Object.keys(pNode.responsiveClasses).length > 0 &&
@@ -515,53 +600,150 @@ export function parseAiCopyHtml(
515
600
  });
516
601
  }
517
602
 
603
+ function transformClassesFromShellLayer(
604
+ layer: LLMShellLayer | LLMColumnLayer['gridClasses']
605
+ ): DefaultClassValue {
606
+ const mobileClasses = sanitizeResponsiveClasses(layer.mobile);
607
+
608
+ const tabletString = layer.tablet
609
+ ? layer.tablet
610
+ .split(/\s+/)
611
+ .filter(Boolean)
612
+ .map((c) => `md:${c}`)
613
+ .join(' ')
614
+ : undefined;
615
+ const tabletClasses = sanitizeResponsiveClasses(tabletString);
616
+
617
+ const desktopString = layer.desktop
618
+ ? layer.desktop
619
+ .split(/\s+/)
620
+ .filter(Boolean)
621
+ .map((c) => `xl:${c}`)
622
+ .join(' ')
623
+ : undefined;
624
+ const desktopClasses = sanitizeResponsiveClasses(desktopString);
625
+
626
+ let merged = mergeResponsive(mobileClasses, tabletClasses);
627
+ merged = mergeResponsive(merged, desktopClasses);
628
+
629
+ return ensureRequiredViewports(merged);
630
+ }
631
+
632
+ function transformParentClassesFromShell(
633
+ llmParentClasses: LLMShellLayer[]
634
+ ): ParentClassesPayload {
635
+ return llmParentClasses.map((layer) => transformClassesFromShellLayer(layer));
636
+ }
637
+
518
638
  export const parseAiPane = (
519
639
  shellJson: string,
520
- copyHtml: string,
640
+ copyHtml: string | string[],
521
641
  layout: string
522
- ): TemplatePane => {
523
- const shell: ShellJson = JSON.parse(shellJson);
642
+ ): Omit<TemplatePane, 'nodes'> & {
643
+ nodes?: (TemplateMarkdown | GridLayoutNode)[];
644
+ } => {
645
+ //console.log('--- ENTERING parseAiPane ---', { shellJson, copyHtml, layout });
524
646
 
647
+ const shell: ShellJson = JSON.parse(shellJson);
525
648
  const paneId = ulid();
526
- const markdownId = ulid();
527
-
528
- const transformedParentClasses: ParentClassesPayload = (
529
- shell.parentClasses || []
530
- ).map(
531
- (layer): ParentClassLayer => ({
532
- mobile: normalizeKeys(layer.mobile),
533
- tablet: normalizeKeys(layer.tablet),
534
- desktop: normalizeKeys(layer.desktop),
535
- })
536
- );
537
649
 
538
- const shellDefaults = parseDefaultClassesFromShell(shell.defaultClasses);
650
+ // --- GRID LAYOUT PATH ---
651
+ if (shell.columns && Array.isArray(copyHtml)) {
652
+ const gridLayoutId = ulid();
653
+ const shellDefaults = parseDefaultClassesFromShell(shell.defaultClasses);
654
+ const transformedParentClasses = transformParentClassesFromShell(
655
+ shell.parentClasses || []
656
+ );
539
657
 
540
- const markdownNode: TemplateMarkdown = {
541
- id: markdownId,
542
- nodeType: 'Markdown',
543
- parentId: paneId,
544
- type: 'markdown',
545
- markdownId: ulid(),
546
- parentClasses: transformedParentClasses,
547
- defaultClasses: shellDefaults,
548
- };
658
+ const childMarkdownNodes = shell.columns.map((column, index) => {
659
+ const markdownId = ulid();
660
+ const gridClasses = transformClassesFromShellLayer(column.gridClasses);
661
+ const templateNodes = parseAiCopyHtml(copyHtml[index] || '', markdownId);
662
+
663
+ const markdownNode: TemplateMarkdown = {
664
+ id: markdownId,
665
+ nodeType: 'Markdown',
666
+ parentId: gridLayoutId,
667
+ type: 'markdown',
668
+ markdownId: ulid(),
669
+ gridClasses,
670
+ nodes: templateNodes,
671
+ };
672
+ return markdownNode;
673
+ });
674
+
675
+ const gridLayoutNode: GridLayoutNode & { nodes: TemplateMarkdown[] } = {
676
+ id: gridLayoutId,
677
+ nodeType: 'GridLayoutNode',
678
+ parentId: paneId,
679
+ type: 'grid-layout',
680
+ parentClasses: transformedParentClasses,
681
+ defaultClasses: shellDefaults,
682
+ gridColumns: {
683
+ mobile: 1,
684
+ tablet: 2,
685
+ desktop: 2,
686
+ },
687
+ nodes: childMarkdownNodes,
688
+ };
689
+
690
+ const templatePane = {
691
+ id: paneId,
692
+ nodeType: 'Pane' as const,
693
+ parentId: '',
694
+ title: 'AI Pane',
695
+ slug: `ai-${paneId.slice(-4)}`,
696
+ bgColour: shell.bgColour,
697
+ isDecorative: false,
698
+ gridLayout: gridLayoutNode,
699
+ };
700
+ /*
701
+ console.log({
702
+ shellDefaults: shellDefaults,
703
+ transformedParentClasses: transformedParentClasses,
704
+ childMarkdownNodes: childMarkdownNodes,
705
+ gridLayoutNode: gridLayoutNode,
706
+ templatePane: templatePane,
707
+ });
708
+ */
709
+ return templatePane;
710
+ }
549
711
 
550
- const templateNodes = parseAiCopyHtml(copyHtml, markdownId);
551
-
552
- const templatePane: TemplatePane = {
553
- id: paneId,
554
- nodeType: 'Pane',
555
- parentId: '',
556
- title: 'AI Pane',
557
- slug: `ai-${paneId.slice(-4)}`,
558
- bgColour: shell.bgColour,
559
- isDecorative: false,
560
- markdown: {
561
- ...markdownNode,
712
+ // --- SINGLE-COLUMN LAYOUT PATH ---
713
+ if (typeof copyHtml === 'string') {
714
+ const markdownId = ulid();
715
+ const shellDefaults = parseDefaultClassesFromShell(shell.defaultClasses);
716
+ const transformedParentClasses = transformParentClassesFromShell(
717
+ shell.parentClasses || []
718
+ );
719
+ const templateNodes = parseAiCopyHtml(copyHtml, markdownId);
720
+
721
+ reconcileClasses(templateNodes, shellDefaults);
722
+
723
+ const markdownNode: TemplateMarkdown = {
724
+ id: markdownId,
725
+ nodeType: 'Markdown',
726
+ parentId: paneId,
727
+ type: 'markdown',
728
+ markdownId: ulid(),
729
+ parentClasses: transformedParentClasses,
730
+ defaultClasses: shellDefaults,
562
731
  nodes: templateNodes,
563
- },
564
- };
732
+ };
733
+
734
+ const templatePane = {
735
+ id: paneId,
736
+ nodeType: 'Pane' as const,
737
+ parentId: '',
738
+ title: 'AI Pane',
739
+ slug: `ai-${paneId.slice(-4)}`,
740
+ bgColour: shell.bgColour,
741
+ isDecorative: false,
742
+ markdown: markdownNode,
743
+ };
744
+ return templatePane;
745
+ }
565
746
 
566
- return templatePane;
747
+ // Fallback for invalid input
748
+ throw new Error('Invalid input for parseAiPane');
567
749
  };