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
@@ -0,0 +1,205 @@
1
+ import { useState, useMemo, useEffect } from 'react';
2
+ import type { BrandConfig } from '@/types/tractstack';
3
+ import { savePaneToLibrary } from '@/utils/compositor/designLibraryHelper';
4
+ import StringInput from '@/components/form/StringInput';
5
+ import { CheckIcon } from '@heroicons/react/20/solid';
6
+
7
+ interface SaveToLibraryModalProps {
8
+ paneId: string;
9
+ config: BrandConfig;
10
+ tenantId: string;
11
+ onClose: () => void;
12
+ }
13
+
14
+ type CopyMode = 'retain' | 'lorem' | 'blank';
15
+ type SaveState = 'idle' | 'saving' | 'saved';
16
+
17
+ const copyOptions: { id: CopyMode; title: string; description: string }[] = [
18
+ {
19
+ id: 'retain',
20
+ title: 'Retain Copy',
21
+ description: 'Save the design with all current text and content.',
22
+ },
23
+ {
24
+ id: 'lorem',
25
+ title: 'Lorem Ipsum',
26
+ description:
27
+ 'Save the design structure, replacing text with placeholders and removing overrides.',
28
+ },
29
+ {
30
+ id: 'blank',
31
+ title: 'Blank',
32
+ description: 'Save the design structure with no content nodes.',
33
+ },
34
+ ];
35
+
36
+ const OTHER_CATEGORY = 'other';
37
+
38
+ export function SaveToLibraryModal({
39
+ paneId,
40
+ config,
41
+ tenantId,
42
+ onClose,
43
+ }: SaveToLibraryModalProps) {
44
+ const [title, setTitle] = useState('');
45
+ const [selectedCategory, setSelectedCategory] = useState(OTHER_CATEGORY);
46
+ const [customCategory, setCustomCategory] = useState('');
47
+ const [copyMode, setCopyMode] = useState<CopyMode>('retain');
48
+ const [saveState, setSaveState] = useState<SaveState>('idle');
49
+ const [error, setError] = useState('');
50
+
51
+ const categories = useMemo(() => {
52
+ const cats =
53
+ config.DESIGN_LIBRARY?.map((item) => item.category).filter(
54
+ (v, i, a) => a.indexOf(v) === i
55
+ ) || [];
56
+ return [...cats, OTHER_CATEGORY];
57
+ }, [config.DESIGN_LIBRARY]);
58
+
59
+ useEffect(() => {
60
+ if (saveState === 'saved') {
61
+ const timer = setTimeout(() => {
62
+ onClose();
63
+ }, 2000);
64
+ return () => clearTimeout(timer);
65
+ }
66
+ }, [saveState, onClose]);
67
+
68
+ const handleSave = async () => {
69
+ const finalCategory =
70
+ selectedCategory === OTHER_CATEGORY ? customCategory : selectedCategory;
71
+ if (!title || !finalCategory) {
72
+ setError('Title and category are required.');
73
+ return;
74
+ }
75
+ setError('');
76
+ setSaveState('saving');
77
+
78
+ const formData = {
79
+ title: title,
80
+ category: finalCategory,
81
+ copyMode: copyMode,
82
+ };
83
+
84
+ const success = await savePaneToLibrary(paneId, tenantId, config, formData);
85
+
86
+ if (success) {
87
+ setSaveState('saved');
88
+ } else {
89
+ setSaveState('idle');
90
+ setError('Failed to save template. Please try again.');
91
+ }
92
+ };
93
+
94
+ return (
95
+ <div
96
+ className="z-105 fixed inset-0 flex items-center justify-center bg-black/50"
97
+ onClick={saveState === 'idle' ? onClose : undefined}
98
+ >
99
+ <div
100
+ className="w-full max-w-lg rounded-lg bg-white p-6 shadow-xl"
101
+ onClick={(e) => e.stopPropagation()}
102
+ >
103
+ <div className="flex items-center justify-between">
104
+ <h2 className="text-lg font-medium text-gray-900">
105
+ Save Pane to Library
106
+ </h2>
107
+ </div>
108
+
109
+ <div className="mt-4 space-y-4">
110
+ <StringInput label="Title" value={title} onChange={setTitle} />
111
+
112
+ <div>
113
+ <label
114
+ htmlFor="category-select"
115
+ className="block text-sm font-medium text-gray-700"
116
+ >
117
+ Category
118
+ </label>
119
+ <select
120
+ id="category-select"
121
+ value={selectedCategory}
122
+ onChange={(e) => setSelectedCategory(e.target.value)}
123
+ className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-500 focus:outline-none focus:ring-cyan-500 sm:text-sm"
124
+ >
125
+ {categories.map((cat) => (
126
+ <option key={cat} value={cat}>
127
+ {cat === OTHER_CATEGORY ? 'New Category...' : cat}
128
+ </option>
129
+ ))}
130
+ </select>
131
+ {selectedCategory === OTHER_CATEGORY && (
132
+ <StringInput
133
+ label="New Category Name"
134
+ value={customCategory}
135
+ onChange={setCustomCategory}
136
+ className="mt-2"
137
+ />
138
+ )}
139
+ </div>
140
+
141
+ <div>
142
+ <label className="block text-sm font-medium text-gray-700">
143
+ Content Mode
144
+ </label>
145
+ <fieldset className="mt-2">
146
+ <legend className="sr-only">Copy Mode</legend>
147
+ <div className="space-y-2">
148
+ {copyOptions.map((option) => (
149
+ <div key={option.id} className="flex items-center">
150
+ <input
151
+ id={option.id}
152
+ name="copy-mode"
153
+ type="radio"
154
+ value={option.id}
155
+ checked={copyMode === option.id}
156
+ onChange={() => setCopyMode(option.id)}
157
+ className="h-4 w-4 border-gray-300 text-cyan-600 focus:ring-cyan-500"
158
+ />
159
+ <label
160
+ htmlFor={option.id}
161
+ className="ml-3 block text-sm font-medium text-gray-700"
162
+ >
163
+ {option.title}
164
+ <p className="text-xs text-gray-500">
165
+ {option.description}
166
+ </p>
167
+ </label>
168
+ </div>
169
+ ))}
170
+ </div>
171
+ </fieldset>
172
+ </div>
173
+
174
+ {error && <p className="text-sm text-red-600">{error}</p>}
175
+ </div>
176
+
177
+ <div className="mt-6 flex justify-end space-x-3">
178
+ <button
179
+ type="button"
180
+ disabled={saveState !== 'idle'}
181
+ className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-50"
182
+ onClick={onClose}
183
+ >
184
+ Cancel
185
+ </button>
186
+ <button
187
+ type="button"
188
+ disabled={saveState !== 'idle'}
189
+ className="flex min-w-36 items-center justify-center rounded-md border border-transparent bg-cyan-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-cyan-700 disabled:cursor-not-allowed disabled:opacity-50"
190
+ onClick={handleSave}
191
+ >
192
+ {saveState === 'idle' && 'Save to Library'}
193
+ {saveState === 'saving' && 'Saving...'}
194
+ {saveState === 'saved' && (
195
+ <>
196
+ <CheckIcon className="mr-2 h-5 w-5" />
197
+ Saved
198
+ </>
199
+ )}
200
+ </button>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ );
205
+ }
@@ -45,6 +45,8 @@
45
45
  },
46
46
  "aiPaneCopyPrompt": {
47
47
  "system": "You are an expert **web designer and copywriter**. Your task is to generate a single, visually compelling block of HTML content. You must ensure the content is well-written, engaging, **beautifully spaced**, and **highly readable**.",
48
- "user_template": "Here is the design 'shell' and 'theme' (bgColour, parentClasses, and defaultClasses) you must write your HTML for. Your HTML will be placed *inside* this shell. Use the `defaultClasses` as your base theme for styling:\n{{SHELL_JSON}}\n\nNow, generate the HTML content based on these inputs:\n\nContent Prompt: \"{{COPY_INPUT}}\"\nDesign Style: **strictly for visual reference** when choosing element styles **DO NOT** include any words or concepts from the `Design Style` input in the written copy text itself.\"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You are responsible for the **inner layout and visual rhythm**. You MUST add appropriate vertical margins (e.g., `mt-4`, `mt-6`, `mt-8`) directly to any HTML block elements that *deviate* from the default spacing. **Elements must not touch.**\n2. You **MUST NOT** use `<h1>` tags. You must use `<h2>`, `<h3>`, and `<p>` tags for all text content.\n3. For responsive styles, you *must* only use `md:` and `xl:` prefixes.\n4. To make headlines pop, you MUST wrap key words in `<span>` tags with creative classes (e.g., gradient text, different colors).\n5. **All text**, even short links or phrases (like 'Learn more →'), **must** be wrapped in a block element like `<p>` or `<button>`.\n6. You MUST include at least one `<button>` tag for the primary call-to-action.\n7. Verify that **all text elements**, including text within `<span>` tags, `<button>` elements, and any elements using override classes, maintain **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) against the `bgColour` provided in the `SHELL_JSON`. **Prioritize readability above all else**.\n8. Respond *only* with the raw HTML.\n\nEXAMPLE of a good, well-spaced response:\n<h2 class=\"text-4xl font-bold tracking-tight text-white md:text-6xl\"><span class=\"bg-gradient-to-r from-purple-500 to-indigo-400 bg-clip-text text-transparent\">Own the Art.</span> Possess the Reality.</h2>\n<p class=\"mt-6 text-lg leading-8 text-gray-300 md:text-xl\">Every Sneaky Productions NFT is your key. This is where digital rarity meets tangible legacy.</p>\n<button class=\"mt-8 rounded-md bg-indigo-600 px-5 py-3 text-base font-semibold text-white shadow-sm hover:bg-indigo-500\">Secure Your Drop</button>\n<p class=\"mt-4 text-sm text-gray-400\">Learn more <span>→</span></p>"
48
+ "user_template": "Here is the design 'shell' and 'theme' (bgColour, parentClasses, and defaultClasses) you must write your HTML for. Your HTML will be placed *inside* this shell. Use the `defaultClasses` as your base theme for styling:\n{{SHELL_JSON}}\n\nNow, generate the HTML content based on these inputs:\n\nContent Prompt: \"{{COPY_INPUT}}\"\nDesign Style: **strictly for visual reference** when choosing element styles **DO NOT** include any words or concepts from the `Design Style` input in the written copy text itself.\"{{DESIGN_INPUT}}\"\nLayout Type: \"{{LAYOUT_TYPE}}\"\n\nCRITICAL RULES:\n1. You are responsible for the **inner layout and visual rhythm**. You MUST add appropriate vertical margins (e.g., `mt-4`, `mt-6`, `mt-8`) directly to any HTML block elements that *deviate* from the default spacing. **Elements must not touch.**\n2. You **MUST NOT** use `<h1>` tags. You must use `<h2>`, `<h3>`, and `<p>` tags for all text content.\n3. For responsive styles, you *must* only use `md:` and `xl:` prefixes.\n4. To make headlines pop, you MUST wrap key words in `<span>` tags with creative classes (e.g., gradient text, different colors).\n5. **All text**, even short links or phrases (like 'Learn more →'), **must** be wrapped in a block element like `<p>` or `<button>`.\n6. You MUST include at least one `<button>` tag for the primary call-to-action.\n7. Verify that **all text elements**, including text within `<span>` tags, `<button>` elements, and any elements using override classes, maintain **high contrast** (meeting at least WCAG AA standards - 4.5:1 for normal text, 3:1 for large text) against the `bgColour` provided in the `SHELL_JSON`. **Prioritize readability above all else**.\n8. Respond *only* with the raw HTML.\n\nEXAMPLE of a good, well-spaced response:\n<h2 class=\"text-4xl font-bold tracking-tight text-white md:text-6xl\"><span class=\"bg-gradient-to-r from-purple-500 to-indigo-400 bg-clip-text text-transparent\">Own the Art.</span> Possess the Reality.</h2>\n<p class=\"mt-6 text-lg leading-8 text-gray-300 md:text-xl\">Every Sneaky Productions NFT is your key. This is where digital rarity meets tangible legacy.</p>\n<button class=\"mt-8 rounded-md bg-indigo-600 px-5 py-3 text-base font-semibold text-white shadow-sm hover:bg-indigo-500\">Secure Your Drop</button>\n<p class=\"mt-4 text-sm text-gray-400\">Learn more <span>→</span></p>",
49
+ "heroDefault": "A compelling hero section for a website about [topic]. It should have a strong, attention-grabbing headline, a brief paragraph explaining the core value proposition, and a clear call-to-action.",
50
+ "contentDefault": "A content section that follows a hero. It should elaborate on a key feature or benefit related to [topic]. Include a sub-headline and a descriptive paragraph."
49
51
  }
50
52
  }
@@ -19,6 +19,8 @@ export interface SelectionStoreState extends SelectionRange {
19
19
  isActive: boolean;
20
20
  selectionBox: SelectionBox | null;
21
21
  pendingAction: 'style' | 'link' | null;
22
+ isRestyleModalOpen: boolean;
23
+ paneToRestyleId: string | null;
22
24
  }
23
25
 
24
26
  const DEFAULT_SELECTION_STATE: SelectionStoreState = {
@@ -32,6 +34,8 @@ const DEFAULT_SELECTION_STATE: SelectionStoreState = {
32
34
  endCharOffset: 0,
33
35
  selectionBox: null,
34
36
  pendingAction: null,
37
+ isRestyleModalOpen: false,
38
+ paneToRestyleId: null,
35
39
  };
36
40
 
37
41
  export const selectionStore = map<SelectionStoreState>(DEFAULT_SELECTION_STATE);
@@ -11,6 +11,7 @@ export type ToolModeVal =
11
11
  | 'eraser'
12
12
  | 'move'
13
13
  | 'layout'
14
+ | 'designLibrary'
14
15
  | 'debug';
15
16
 
16
17
  export const toolAddModes = [
@@ -404,7 +405,56 @@ export type TemplatePane = PaneNode & {
404
405
  id?: string;
405
406
  parentId?: string;
406
407
  markdown?: TemplateMarkdown;
407
- bgPane?: VisualBreakNode | ArtpackImageNode;
408
+ bgPane?: VisualBreakNode | ArtpackImageNode | BgImageNode;
409
+ };
410
+
411
+ export type StorageArtpackImageNode = Omit<
412
+ ArtpackImageNode,
413
+ 'id' | 'parentId' | 'isChanged'
414
+ >;
415
+ export type StorageBgImageNode = Omit<
416
+ BgImageNode,
417
+ 'id' | 'parentId' | 'isChanged' | 'base64Data'
418
+ >;
419
+ export type StorageVisualBreakNode = Omit<
420
+ VisualBreakNode,
421
+ 'id' | 'parentId' | 'isChanged'
422
+ >;
423
+
424
+ export type StorageBgPane =
425
+ | StorageArtpackImageNode
426
+ | StorageBgImageNode
427
+ | StorageVisualBreakNode;
428
+
429
+ export type StorageNode = Omit<
430
+ FlatNode,
431
+ 'id' | 'parentId' | 'isChanged' | 'base64Data' | 'isPlaceholder'
432
+ > & {
433
+ nodes?: StorageNode[];
434
+ };
435
+
436
+ export type StorageMarkdown = Omit<
437
+ MarkdownPaneFragmentNode,
438
+ 'id' | 'parentId' | 'isChanged' | 'markdownId' | 'parentCss' | 'nodes'
439
+ > & {
440
+ nodes?: StorageNode[];
441
+ };
442
+
443
+ export type StoragePane = Omit<
444
+ PaneNode,
445
+ | 'id'
446
+ | 'parentId'
447
+ | 'isChanged'
448
+ | 'created'
449
+ | 'changed'
450
+ | 'heldBeliefs'
451
+ | 'withheldBeliefs'
452
+ | 'codeHookTarget'
453
+ | 'codeHookPayload'
454
+ | 'markdown'
455
+ > & {
456
+ markdown?: StorageMarkdown;
457
+ bgPane?: StorageBgPane;
408
458
  };
409
459
 
410
460
  export interface LinkNode extends FlatNode {
@@ -1,16 +1,19 @@
1
- // Base component props that all TractStack components should support
1
+ import type { StoragePane } from './compositorTypes';
2
+
3
+ export type DesignLibraryEntry = {
4
+ category: string;
5
+ title: string;
6
+ template: StoragePane;
7
+ };
8
+
9
+ export type DesignLibraryConfig = DesignLibraryEntry[];
10
+
2
11
  export interface BaseComponentProps {
3
- /** Additional CSS classes */
4
12
  class?: string;
5
-
6
- /** Inline styles */
7
13
  style?: React.CSSProperties | string;
8
-
9
- /** Component ID */
10
14
  id?: string;
11
15
  }
12
16
 
13
- // HTMX-specific attributes for components
14
17
  export interface HTMXAttributes {
15
18
  'hx-get'?: string;
16
19
  'hx-post'?: string;
@@ -148,39 +151,40 @@ export interface FullContentMapItem {
148
151
  }
149
152
 
150
153
  export interface BrandConfig {
151
- // Core site configuration
152
- SITE_INIT: boolean;
153
- WORDMARK_MODE: string;
154
- OPEN_DEMO: boolean;
155
- STYLES_VER: number;
156
- HOME_SLUG: string;
157
- TRACTSTACK_HOME_SLUG: string;
158
- THEME: string; // e.g., "light-bold"
159
- BRAND_COLOURS: string; // e.g., "10120d,fcfcfc,f58333,c8df8c,293f58,a7b1b7,393d34,e3e3e3"
160
- SOCIALS: string; // e.g., "github|https://github.com/user,twitter|https://twitter.com/user"
161
- LOGO: string;
162
- WORDMARK: string;
163
- FAVICON: string;
164
- SITE_URL: string;
165
- SLOGAN: string;
166
- FOOTER: string;
167
- OG: string;
168
- OGLOGO: string;
169
- OGTITLE: string;
170
- OGAUTHOR: string;
171
- OGDESC: string;
154
+ TENANT_ID: string;
155
+ SITE_INIT?: boolean;
156
+ WORDMARK_MODE?: string;
157
+ OPEN_DEMO?: boolean;
158
+ STYLES_VER?: number;
159
+ HOME_SLUG?: string;
160
+ TRACTSTACK_HOME_SLUG?: string;
161
+ THEME?: string; // e.g., "light-bold"
162
+ BRAND_COLOURS?: string; // e.g., "10120d,fcfcfc,f58333,c8df8c,293f58,a7b1b7,393d34,e3e3e3"
163
+ SOCIALS?: string; // e.g., "github|https://github.com/user,twitter|https://twitter.com/user"
164
+ LOGO?: string;
165
+ WORDMARK?: string;
166
+ FAVICON?: string;
167
+ SITE_URL?: string;
168
+ SLOGAN?: string;
169
+ FOOTER?: string;
170
+ OG?: string;
171
+ OGLOGO?: string;
172
+ OGTITLE?: string;
173
+ OGAUTHOR?: string;
174
+ OGDESC?: string;
172
175
  LOGO_BASE64?: string;
173
176
  WORDMARK_BASE64?: string;
174
177
  OG_BASE64?: string;
175
178
  OGLOGO_BASE64?: string;
176
179
  FAVICON_BASE64?: string;
177
- GTAG: string;
180
+ GTAG?: string;
178
181
  KNOWN_RESOURCES?: KnownResourcesConfig;
179
- HAS_AAI: boolean;
182
+ DESIGN_LIBRARY?: DesignLibraryConfig;
183
+ HAS_AAI?: boolean;
180
184
  }
181
185
 
182
186
  export interface BrandConfigState {
183
- // Core site configuration
187
+ tenantId: string;
184
188
  siteInit: boolean;
185
189
  wordmarkMode: string;
186
190
  openDemo: boolean;
@@ -208,6 +212,7 @@ export interface BrandConfigState {
208
212
  faviconBase64?: string;
209
213
  gtag: string;
210
214
  knownResources: KnownResourcesConfig;
215
+ designLibrary?: DesignLibraryConfig;
211
216
  hasAAI: boolean;
212
217
  }
213
218
 
@@ -38,7 +38,7 @@ Example response format:
38
38
  "slug": "short-descriptive-title"
39
39
  }`,
40
40
  input_text: markdownContent,
41
- final_model: 'anthropic/claude-3-5-sonnet',
41
+ final_model: '',
42
42
  }),
43
43
  });
44
44
 
@@ -7,9 +7,11 @@ export async function saveBrandConfig(
7
7
  brandConfig: BrandConfig
8
8
  ): Promise<BrandConfig> {
9
9
  const api = new TractStackAPI(tenantId);
10
+ const config: any = brandConfig;
11
+ delete config.TENANT_ID;
10
12
  try {
11
13
  const response = await api.put('/api/v1/config/brand', {
12
- ...brandConfig,
14
+ ...config,
13
15
  SITE_INIT: true,
14
16
  });
15
17
  if (!response.success) {
@@ -39,6 +41,7 @@ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
39
41
  ) {
40
42
  // Return empty/default config when backend is down
41
43
  return {
44
+ TENANT_ID: tenantId,
42
45
  SITE_INIT: false,
43
46
  WORDMARK_MODE: '',
44
47
  BRAND_COLOURS: '',
@@ -61,16 +64,18 @@ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
61
64
  GTAG: '',
62
65
  STYLES_VER: 1,
63
66
  KNOWN_RESOURCES: {},
67
+ DESIGN_LIBRARY: [],
64
68
  HAS_AAI: false,
65
69
  } as BrandConfig;
66
70
  }
67
71
  throw new Error(response.error || 'Failed to get brand configuration');
68
72
  }
69
- return response.data;
73
+ return { ...response.data, TENANT_ID: tenantId };
70
74
  } catch (error) {
71
75
  // If it's a network error (backend down), return default config
72
76
  if (error instanceof TypeError && error.message.includes('fetch failed')) {
73
77
  return {
78
+ TENANT_ID: tenantId,
74
79
  SITE_INIT: false,
75
80
  WORDMARK_MODE: '',
76
81
  BRAND_COLOURS: '',
@@ -93,6 +98,7 @@ export async function getBrandConfig(tenantId: string): Promise<BrandConfig> {
93
98
  GTAG: '',
94
99
  STYLES_VER: 1,
95
100
  KNOWN_RESOURCES: {},
101
+ DESIGN_LIBRARY: [],
96
102
  HAS_AAI: false,
97
103
  } as BrandConfig;
98
104
  }
@@ -11,6 +11,7 @@ export function convertToLocalState(
11
11
  brandConfig: BrandConfig
12
12
  ): BrandConfigState {
13
13
  return {
14
+ tenantId: brandConfig.TENANT_ID || `default`,
14
15
  siteInit: brandConfig.SITE_INIT ?? false,
15
16
  wordmarkMode: brandConfig.WORDMARK_MODE ?? '',
16
17
  brandColours: brandConfig.BRAND_COLOURS
@@ -37,6 +38,7 @@ export function convertToLocalState(
37
38
  gtag: brandConfig.GTAG ?? '',
38
39
  stylesVer: brandConfig.STYLES_VER ?? 1,
39
40
  knownResources: brandConfig.KNOWN_RESOURCES ?? {},
41
+ designLibrary: brandConfig.DESIGN_LIBRARY ?? undefined,
40
42
  hasAAI: brandConfig.HAS_AAI ?? false,
41
43
  };
42
44
  }
@@ -49,6 +51,7 @@ export function convertToBackendFormat(
49
51
  localState: BrandConfigState
50
52
  ): BrandConfig {
51
53
  return {
54
+ TENANT_ID: localState.tenantId,
52
55
  SITE_INIT: localState.siteInit,
53
56
  WORDMARK_MODE: localState.wordmarkMode,
54
57
  BRAND_COLOURS: localState.brandColours.join(','),
@@ -66,6 +69,7 @@ export function convertToBackendFormat(
66
69
  OGDESC: localState.ogdesc,
67
70
  GTAG: localState.gtag,
68
71
  KNOWN_RESOURCES: localState.knownResources,
72
+ DESIGN_LIBRARY: localState.designLibrary,
69
73
  HAS_AAI: localState.hasAAI,
70
74
 
71
75
  // ALWAYS send asset paths (current state)