astro-tractstack 2.0.0-rc.63 → 2.0.0-rc.65

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.
@@ -50,6 +50,13 @@ interface InteractiveDisclosureWidgetProps {
50
50
 
51
51
  const generateId = (): string => Math.random().toString(36).substring(2, 9);
52
52
 
53
+ const quoteIfNecessary = (command: string, value: string): string => {
54
+ if (command === 'identifyAs' && value.includes(' ')) {
55
+ return `"${value}"`;
56
+ }
57
+ return value;
58
+ };
59
+
53
60
  const IconSelector = ({
54
61
  value,
55
62
  onChange,
@@ -136,7 +143,9 @@ const DisclosureItemEditor = ({
136
143
  }) => {
137
144
  return (
138
145
  <div
139
- className={`space-y-4 rounded-lg border bg-white p-4 shadow-sm transition-opacity ${item.isDisabled ? 'border-gray-100 opacity-40' : 'border-gray-200'}`}
146
+ className={`space-y-4 rounded-lg border bg-white p-4 shadow-sm transition-opacity ${
147
+ item.isDisabled ? 'border-gray-100 opacity-40' : 'border-gray-200'
148
+ }`}
140
149
  >
141
150
  <div className="flex items-center justify-between">
142
151
  <div className="flex items-center gap-2">
@@ -168,7 +177,9 @@ const DisclosureItemEditor = ({
168
177
  <button
169
178
  type="button"
170
179
  onClick={onToggle}
171
- className={`rounded p-1 hover:bg-gray-100 ${item.isDisabled ? 'text-blue-600' : 'text-red-600'}`}
180
+ className={`rounded p-1 hover:bg-gray-100 ${
181
+ item.isDisabled ? 'text-blue-600' : 'text-red-600'
182
+ }`}
172
183
  >
173
184
  {item.isDisabled ? (
174
185
  <ArrowUturnLeftIcon className="h-4 w-4" />
@@ -274,7 +285,6 @@ export default function InteractiveDisclosureWidget({
274
285
 
275
286
  const actionCommand =
276
287
  currentBelief.scale === 'custom' ? 'identifyAs' : 'declare';
277
-
278
288
  const finalDisclosures: DisclosureItem[] = loadedDisclosures.map(
279
289
  (loadedItem) => {
280
290
  const isFromScale = scaleKeys.some(
@@ -285,13 +295,12 @@ export default function InteractiveDisclosureWidget({
285
295
  id: generateId(),
286
296
  isCustom: !isFromScale,
287
297
  actionLisp: isFromScale
288
- ? `(${actionCommand} ${beliefTag} ${loadedItem.beliefValue})`
298
+ ? `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, loadedItem.beliefValue)})`
289
299
  : loadedItem.actionLisp,
290
300
  isDisabled: false,
291
301
  };
292
302
  }
293
303
  );
294
-
295
304
  scaleKeys.forEach(({ slug, name }) => {
296
305
  if (!finalDisclosures.some((d) => d.beliefValue === slug)) {
297
306
  finalDisclosures.push({
@@ -300,13 +309,12 @@ export default function InteractiveDisclosureWidget({
300
309
  title: name,
301
310
  description: '',
302
311
  icon: 'app',
303
- actionLisp: `(${actionCommand} ${beliefTag} ${slug})`,
312
+ actionLisp: `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, slug)})`,
304
313
  isCustom: false,
305
314
  isDisabled: true,
306
315
  });
307
316
  }
308
317
  });
309
-
310
318
  setDisclosures(finalDisclosures);
311
319
  } catch (e) {
312
320
  console.error('Error parsing disclosure payload:', e);
@@ -345,7 +353,6 @@ export default function InteractiveDisclosureWidget({
345
353
  const disclosuresToStore: StoredDisclosureItem[] = disclosures
346
354
  .filter((d) => !d.isDisabled)
347
355
  .map(({ id, isCustom, isDisabled, ...rest }) => rest);
348
-
349
356
  const payload = { styles: widgetStyles, disclosures: disclosuresToStore };
350
357
  onUpdate([selectedBeliefTag, JSON.stringify(payload)]);
351
358
  };
@@ -369,7 +376,7 @@ export default function InteractiveDisclosureWidget({
369
376
  title: name,
370
377
  description: '',
371
378
  icon: 'app',
372
- actionLisp: `(${actionCommand} ${tag} ${slug})`,
379
+ actionLisp: `(${actionCommand} ${tag} ${quoteIfNecessary(actionCommand, slug)})`,
373
380
  isCustom: false,
374
381
  isDisabled: false,
375
382
  }));
@@ -407,8 +414,10 @@ export default function InteractiveDisclosureWidget({
407
414
  setDisclosures(
408
415
  disclosures.map((d) => (d.id === id ? { ...d, ...updates } : d))
409
416
  );
417
+
410
418
  const updateWidgetStyles = (updates: Partial<WidgetStyles>) =>
411
419
  setWidgetStyles((prev) => ({ ...prev, ...updates }));
420
+
412
421
  const toggleDisclosure = (id: string) =>
413
422
  setDisclosures(
414
423
  disclosures.map((d) =>
@@ -80,10 +80,27 @@ export default function ActionBuilderField({
80
80
 
81
81
  const handleParamChange = (newParams: string) => {
82
82
  setParams(newParams);
83
- if (newParams && newParams.trim() !== '' && newParams.trim() !== '()') {
84
- onChange(`(${command} ${newParams})`);
85
- } else {
83
+ const trimmedParams = newParams.trim();
84
+
85
+ if (!trimmedParams || trimmedParams === '()') {
86
86
  onChange('');
87
+ return;
88
+ }
89
+
90
+ if (command === 'identifyAs') {
91
+ const firstSpaceIndex = trimmedParams.indexOf(' ');
92
+ if (firstSpaceIndex === -1) {
93
+ // Handle case with only beliefId and no value
94
+ onChange(`(${command} ${trimmedParams})`);
95
+ } else {
96
+ const beliefId = trimmedParams.substring(0, firstSpaceIndex);
97
+ const value = trimmedParams.substring(firstSpaceIndex + 1);
98
+ const finalValue = value.includes(' ') ? `"${value}"` : value;
99
+ onChange(`(${command} ${beliefId} ${finalValue})`);
100
+ }
101
+ } else {
102
+ // Original behavior for all other commands
103
+ onChange(`(${command} ${trimmedParams})`);
87
104
  }
88
105
  };
89
106
 
@@ -37,16 +37,14 @@ export default function BeliefForm({
37
37
  onClose,
38
38
  }: BeliefFormProps) {
39
39
  const [customValue, setCustomValue] = useState('');
40
+ const [customValueError, setCustomValueError] = useState<string | null>(null);
40
41
 
41
- // Subscribe to orphan analysis store
42
42
  const orphanState = useStore(orphanAnalysisStore);
43
43
 
44
- // Load orphan analysis on component mount
45
44
  useEffect(() => {
46
45
  loadOrphanAnalysis();
47
46
  }, []);
48
47
 
49
- // Get usage information for this belief
50
48
  const getBeliefUsage = (): string[] => {
51
49
  if (!belief?.id || !orphanState.data || !orphanState.data.beliefs) {
52
50
  return [];
@@ -54,7 +52,6 @@ export default function BeliefForm({
54
52
  return orphanState.data.beliefs[belief.id] || [];
55
53
  };
56
54
 
57
- // Check if belief is in use
58
55
  const isBeliefInUse = (): boolean => {
59
56
  if (isCreate || !belief?.id) return false;
60
57
  return getBeliefUsage().length > 0;
@@ -63,7 +60,6 @@ export default function BeliefForm({
63
60
  const beliefInUse = isBeliefInUse();
64
61
  const usageCount = getBeliefUsage().length;
65
62
 
66
- // Initialize form state
67
63
  const initialState: BeliefNodeState = belief
68
64
  ? convertToLocalState(belief)
69
65
  : {
@@ -85,7 +81,6 @@ export default function BeliefForm({
85
81
  data
86
82
  );
87
83
 
88
- // Call success callback after save (original pattern)
89
84
  setTimeout(() => {
90
85
  onClose?.(true);
91
86
  }, 1000);
@@ -102,23 +97,30 @@ export default function BeliefForm({
102
97
  },
103
98
  });
104
99
 
105
- const handleAddCustomValue = () => {
106
- if (!customValue.trim()) return;
100
+ const handleCustomValueChange = (value: string) => {
101
+ setCustomValue(value);
102
+ const valueRegex = /^[a-zA-Z]([a-zA-Z0-9?!]| (?=[a-zA-Z0-9?!]))*$/;
103
+ if (value && !valueRegex.test(value)) {
104
+ setCustomValueError(
105
+ 'Must start with a letter. No double or trailing spaces.'
106
+ );
107
+ } else {
108
+ setCustomValueError(null);
109
+ }
110
+ };
107
111
 
112
+ const handleAddCustomValue = () => {
113
+ if (!customValue.trim() || customValueError) return;
108
114
  const newState = addCustomValue(formState.state, customValue);
109
115
  formState.updateField('customValues', newState.customValues);
110
116
  setCustomValue('');
111
117
  };
112
118
 
113
119
  const handleRemoveCustomValue = (index: number) => {
114
- // Check if this is a newly added value (not saved yet)
115
120
  const currentValue = formState.state.customValues[index];
116
121
  const originalValues = formState.originalState.customValues || [];
117
122
  const isNewValue = !originalValues.includes(currentValue);
118
123
 
119
- // Allow removal if:
120
- // 1. Belief is not in use, OR
121
- // 2. This is a new value that hasn't been saved yet
122
124
  if (!beliefInUse || isNewValue) {
123
125
  const newState = removeCustomValue(formState.state, index);
124
126
  formState.updateField('customValues', newState.customValues);
@@ -188,7 +190,6 @@ export default function BeliefForm({
188
190
 
189
191
  return (
190
192
  <div className="space-y-8">
191
- {/* Header */}
192
193
  <div className="border-b border-gray-200 pb-4">
193
194
  <h2 className="text-2xl font-bold text-gray-900">
194
195
  {isCreate ? 'Create Belief' : 'Edit Belief'}
@@ -200,10 +201,8 @@ export default function BeliefForm({
200
201
  </p>
201
202
  </div>
202
203
 
203
- {/* Usage Warning */}
204
204
  {renderUsageWarning()}
205
205
 
206
- {/* Info Box */}
207
206
  <div className="rounded-md bg-blue-50 p-4">
208
207
  <div className="text-sm text-blue-700">
209
208
  <p className="font-bold">What are Beliefs?</p>
@@ -225,7 +224,6 @@ export default function BeliefForm({
225
224
  </div>
226
225
  </div>
227
226
 
228
- {/* Basic Fields */}
229
227
  <div className="grid grid-cols-1 gap-6 md:grid-cols-2">
230
228
  <StringInput
231
229
  value={formState.state.title}
@@ -254,7 +252,6 @@ export default function BeliefForm({
254
252
  </div>
255
253
  </div>
256
254
 
257
- {/* Scale Selection */}
258
255
  <div className="space-y-4">
259
256
  <div className="relative">
260
257
  <EnumSelect
@@ -276,7 +273,6 @@ export default function BeliefForm({
276
273
  {renderScalePreview()}
277
274
  </div>
278
275
 
279
- {/* Custom Values Section */}
280
276
  {formState.state.scale === 'custom' && (
281
277
  <div className="space-y-4">
282
278
  <div>
@@ -286,31 +282,41 @@ export default function BeliefForm({
286
282
  </p>
287
283
  </div>
288
284
 
289
- {/* Add Custom Value */}
290
285
  <div className="flex gap-2">
291
286
  <div className="flex-1">
292
- <div className="flex-1">
293
- <input
294
- type="text"
295
- value={customValue}
296
- onChange={(e) => setCustomValue(e.target.value)}
297
- onKeyDown={handleKeyDown}
298
- placeholder="Enter custom value"
299
- className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-cyan-600 sm:text-sm sm:leading-6"
300
- />
301
- </div>
287
+ <input
288
+ type="text"
289
+ value={customValue}
290
+ onChange={(e) => handleCustomValueChange(e.target.value)}
291
+ onKeyDown={handleKeyDown}
292
+ placeholder="Enter custom value"
293
+ className={`block w-full rounded-md border-0 px-3 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 ${
294
+ customValueError
295
+ ? 'ring-red-500 focus:ring-red-600'
296
+ : 'ring-gray-300 focus:ring-cyan-600'
297
+ }`}
298
+ />
302
299
  </div>
303
300
  <button
304
301
  type="button"
305
302
  onClick={handleAddCustomValue}
306
- disabled={!customValue.trim()}
303
+ disabled={!customValue.trim() || !!customValueError}
307
304
  className="inline-flex items-center rounded-md bg-cyan-600 px-3 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-600 disabled:cursor-not-allowed disabled:opacity-50"
308
305
  >
309
306
  <PlusIcon className="h-4 w-4" />
310
307
  </button>
311
308
  </div>
312
309
 
313
- {/* Custom Values List */}
310
+ {customValueError && (
311
+ <p className="mt-1 text-sm text-red-600">{customValueError}</p>
312
+ )}
313
+
314
+ {formState.errors.customValues && (
315
+ <p className="text-sm text-red-600">
316
+ {formState.errors.customValues}
317
+ </p>
318
+ )}
319
+
314
320
  {formState.state.customValues.length > 0 && (
315
321
  <div className="space-y-2">
316
322
  {formState.state.customValues.map((value, index) => {
@@ -355,7 +361,6 @@ export default function BeliefForm({
355
361
  </div>
356
362
  )}
357
363
 
358
- {/* Save/Cancel Bar */}
359
364
  <UnsavedChangesBar
360
365
  formState={formState}
361
366
  message="You have unsaved belief changes"
@@ -363,7 +368,6 @@ export default function BeliefForm({
363
368
  cancelLabel="Discard Changes"
364
369
  />
365
370
 
366
- {/* Cancel Navigation Button */}
367
371
  <div className="flex justify-start">
368
372
  <button
369
373
  type="button"
@@ -45,41 +45,28 @@ const {
45
45
  impressions = [],
46
46
  } = Astro.props;
47
47
 
48
- // Get site status from the store
49
48
  const isInitialized = !freshInstallStore.get().needsSetup;
50
-
51
- // ensure we have brand config!
52
49
  const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
53
50
  const tenantId =
54
51
  Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
55
52
  const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
56
-
57
- // Conditionally set asset paths based on initialization status
58
53
  const cssBasePath = isInitialized ? '/media/css' : '/styles';
59
54
  const fontBasePath = isInitialized ? '/media/fonts' : '/fonts';
60
55
  const mainStylesUrl = (() => {
61
- const baseUrl = isStoryKeep // || isDev
56
+ const baseUrl = isStoryKeep
62
57
  ? `${cssBasePath}/storykeep.css`
63
58
  : `${cssBasePath}/frontend.css`;
64
-
65
- // Only add version for frontend.css (the dynamic one)
66
59
  if (!isStoryKeep && brandConfig?.STYLES_VER) {
67
60
  return `${baseUrl}?v=${brandConfig.STYLES_VER}`;
68
61
  }
69
-
70
62
  return baseUrl;
71
63
  })();
72
64
 
73
- // Social media and SEO setup
74
65
  const defaultFavIcon = brandConfig.FAVICON || `/brand/favicon.ico`;
75
66
  const defaultSocialImageURL = ogImage || brandConfig.OG || `/brand/og.png`;
76
67
  const defaultSocialLogoURL = brandConfig.OGLOGO || `/brand/oglogo.png`;
77
68
  const defaultSocialTitle =
78
- typeof title === `string` && title
79
- ? title
80
- : typeof brandConfig.OGTITLE === `string`
81
- ? brandConfig.OGTITLE
82
- : `TractStack dynamic website`;
69
+ title || brandConfig.OGTITLE || `TractStack dynamic website`;
83
70
  const defaultSocialAuthor = brandConfig.OGAUTHOR || `TractStack`;
84
71
  const defaultSocialDesc =
85
72
  description ||
@@ -93,7 +80,7 @@ const socialLogoFullURL = brandConfig?.SITE_URL
93
80
  : `${defaultSocialLogoURL}`;
94
81
  const gtagId = brandConfig?.GTAG || false;
95
82
  const gtagUrl =
96
- typeof gtagId === `string` && gtagId.length > 1
83
+ gtagId && typeof gtagId === `string` && gtagId.length > 1
97
84
  ? `https://www.googletagmanager.com/gtag/js?id=${gtagId}`
98
85
  : null;
99
86
  const fullCanonicalUrl = brandConfig?.SITE_URL
@@ -207,7 +194,7 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
207
194
  )
208
195
  }
209
196
 
210
- <script src="/client/htmx.min.js" is:inline is:persist></script>
197
+ <script src="/client/htmx.min.js" is:inline></script>
211
198
 
212
199
  <script
213
200
  define:vars={{
@@ -219,43 +206,32 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
219
206
  }}
220
207
  is:inline
221
208
  >
222
- function createTractstackConfig() {
223
- window.TRACTSTACK_CONFIG = {
224
- configured: true,
225
- backendUrl: goBackend,
226
- tenantId: tenantId,
227
- fontBasePath: fontBasePath,
228
- storyfragmentId: storyfragmentId,
229
- sessionId: sessionId,
230
- };
231
- }
209
+ // Capture the initial, server-rendered values from define:vars into top-level constants.
210
+ // This makes the scope explicit and resolves the linter error.
211
+ const initialStoryfragmentId = storyfragmentId;
212
+ const initialSessionId = sessionId;
232
213
 
233
214
  function updateTractstackConfig() {
234
- // Get current values from meta tags
235
215
  const storyfragmentMeta = document.querySelector(
236
216
  'meta[name="storyfragment-id"]'
237
217
  );
238
218
  const sessionMeta = document.querySelector('meta[name="session-id"]');
239
219
 
240
- const currentStoryfragmentId = storyfragmentMeta
241
- ? storyfragmentMeta.content
242
- : null;
243
- const currentSessionId = sessionMeta ? sessionMeta.content : sessionId;
244
- if (window.TRACTSTACK_CONFIG && currentStoryfragmentId) {
245
- window.TRACTSTACK_CONFIG.storyfragmentId = currentStoryfragmentId;
246
- window.TRACTSTACK_CONFIG.sessionId = currentSessionId;
247
- } else {
248
- createTractstackConfig();
249
- }
250
- }
251
-
252
- // Check if config exists.
253
- // If not, create it. This handles the very first load.
254
- if (!window.TRACTSTACK_CONFIG) {
255
- createTractstackConfig();
220
+ window.TRACTSTACK_CONFIG = {
221
+ configured: true,
222
+ backendUrl: goBackend,
223
+ tenantId: tenantId,
224
+ fontBasePath: fontBasePath,
225
+ // Use the meta tag if it exists (for subsequent client-side loads),
226
+ // otherwise fall back to the initial server-rendered value.
227
+ storyfragmentId: storyfragmentMeta
228
+ ? storyfragmentMeta.content
229
+ : initialStoryfragmentId,
230
+ sessionId: sessionMeta ? sessionMeta.content : initialSessionId,
231
+ };
256
232
  }
257
233
 
258
- // Update on subsequent loads, or set for the first time if script runs after initial setup
234
+ updateTractstackConfig();
259
235
  document.addEventListener('astro:page-load', updateTractstackConfig);
260
236
  </script>
261
237
  </head>
@@ -307,14 +283,11 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
307
283
  }
308
284
  </div>
309
285
 
310
- <script type="module" is:inline is:persist is:raw>
311
- import '/client/sse.js';
312
- import '/client/belief-events.js';
313
- import '/client/analytics-events.js';
314
- </script>
286
+ <script type="module" src="/client/app.js" is:inline is:persist></script>
287
+
288
+ <script type="module" src="/client/view.js" is:inline></script>
315
289
 
316
290
  <script is:inline is:persist>
317
- // Navigation loading animation with blur backdrop
318
291
  let navProgressInterval = null;
319
292
  let navSafetyTimeout = null;
320
293
 
@@ -329,24 +302,19 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
329
302
  loadingIndicator &&
330
303
  loadingBackdrop
331
304
  ) {
332
- // Clear any existing safety timeout
333
305
  if (navSafetyTimeout !== null) {
334
306
  clearTimeout(navSafetyTimeout);
335
307
  navSafetyTimeout = null;
336
308
  }
337
309
 
338
- // Show and animate backdrop
339
310
  loadingBackdrop.style.display = 'block';
340
- // Force reflow to ensure display:block is applied before opacity transition
341
311
  void loadingBackdrop.offsetHeight;
342
312
  loadingBackdrop.style.opacity = '1';
343
313
 
344
- // Fade out content slightly
345
314
  if (content) {
346
315
  content.style.opacity = '0.7';
347
316
  }
348
317
 
349
- // Animate progress bar
350
318
  loadingIndicator.style.transform = 'scaleX(0)';
351
319
  loadingIndicator.style.display = 'block';
352
320
 
@@ -362,7 +330,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
362
330
  loadingIndicator.style.transform = `scaleX(${progress / 100})`;
363
331
  }, 20);
364
332
 
365
- // AUTOMATIC SHUTOFF: Always stop after 10 seconds
366
333
  navSafetyTimeout = setTimeout(() => {
367
334
  stopNavLoadingAnimation();
368
335
  }, 10000);
@@ -374,7 +341,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
374
341
  const loadingBackdrop = document.getElementById('loading-backdrop');
375
342
  const content = document.getElementById('content');
376
343
 
377
- // Clear safety timeout
378
344
  if (navSafetyTimeout !== null) {
379
345
  clearTimeout(navSafetyTimeout);
380
346
  navSafetyTimeout = null;
@@ -386,21 +352,17 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
386
352
  loadingIndicator &&
387
353
  loadingBackdrop
388
354
  ) {
389
- // Clear interval
390
355
  if (navProgressInterval !== null) {
391
356
  clearInterval(navProgressInterval);
392
357
  navProgressInterval = null;
393
358
  }
394
359
 
395
- // Complete progress bar
396
360
  loadingIndicator.style.transform = 'scaleX(1)';
397
361
 
398
- // Restore content opacity
399
362
  if (content) {
400
363
  content.style.opacity = '1';
401
364
  }
402
365
 
403
- // Hide backdrop
404
366
  loadingBackdrop.style.opacity = '0';
405
367
 
406
368
  setTimeout(() => {
@@ -411,7 +373,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
411
373
  }
412
374
  }
413
375
 
414
- // Set up navigation loading
415
376
  function setupNavigationLoading() {
416
377
  document.addEventListener('astro:before-preparation', () => {
417
378
  startNavLoadingAnimation();
@@ -426,14 +387,12 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
426
387
  });
427
388
  }
428
389
 
429
- // Initialize
430
390
  if (document.readyState === 'loading') {
431
391
  document.addEventListener('DOMContentLoaded', setupNavigationLoading);
432
392
  } else {
433
393
  setupNavigationLoading();
434
394
  }
435
395
 
436
- // Re-setup after navigation
437
396
  document.addEventListener('astro:page-load', setupNavigationLoading);
438
397
  </script>
439
398
  </body>
@@ -148,7 +148,7 @@ paneIds.forEach((paneId: string) => {
148
148
  }
149
149
  hx-get={`/api/v1/fragments/panes/${paneId}`}
150
150
  hx-trigger="refresh"
151
- hx-swap="innerHTML"
151
+ hx-swap="innerHTML scroll:none"
152
152
  >
153
153
  {codeHookTargets[paneId] ? (
154
154
  <div class="relative overflow-hidden">
@@ -120,7 +120,7 @@ if (!brandConfig.SITE_INIT) {
120
120
  class="context-pane-container"
121
121
  hx-get={`/api/v1/fragments/panes/${paneId}`}
122
122
  hx-trigger="refresh"
123
- hx-swap="innerHTML"
123
+ hx-swap="innerHTML scroll:none"
124
124
  >
125
125
  {
126
126
  codeHookTarget ? (