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.
- package/dist/index.js +4 -8
- package/package.json +2 -2
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/view.js +423 -0
- package/templates/src/components/Menu.tsx +22 -26
- package/templates/src/components/edit/pane/PanePanel_path.tsx +0 -2
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +18 -9
- package/templates/src/components/form/ActionBuilderField.tsx +20 -3
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
- package/templates/src/layouts/Layout.astro +24 -65
- package/templates/src/pages/[...slug].astro +1 -1
- package/templates/src/pages/context/[...contextSlug].astro +1 -1
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/utils/inject-files.ts +4 -8
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -191
- package/templates/src/client/sse.js +0 -683
|
@@ -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 ${
|
|
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 ${
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
106
|
-
|
|
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
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
{
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
|
311
|
-
|
|
312
|
-
|
|
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">
|