astro-tractstack 2.0.0-rc.64 → 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.
@@ -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 ? (
@@ -1317,16 +1317,12 @@ export async function injectTemplateFiles(
1317
1317
  dest: 'public/client/htmx.min.js',
1318
1318
  },
1319
1319
  {
1320
- src: resolve('../templates/src/client/sse.js'),
1321
- dest: 'public/client/sse.js',
1320
+ src: resolve('../templates/src/client/view.js'),
1321
+ dest: 'public/client/view.js',
1322
1322
  },
1323
1323
  {
1324
- src: resolve('../templates/src/client/belief-events.js'),
1325
- dest: 'public/client/belief-events.js',
1326
- },
1327
- {
1328
- src: resolve('../templates/src/client/analytics-events.js'),
1329
- dest: 'public/client/analytics-events.js',
1324
+ src: resolve('../templates/src/client/app.js'),
1325
+ dest: 'public/client/app.js',
1330
1326
  },
1331
1327
 
1332
1328
  // StoryKeep Editor (add new section)
@@ -1,207 +0,0 @@
1
- const THRESHOLD_GLOSSED = 7000; // 7 seconds in ms
2
- const THRESHOLD_READ = 42000; // 42 seconds in ms
3
- const VERBOSE = false;
4
-
5
- const paneViewTimes = new Map();
6
- let hasTrackedEntered =
7
- localStorage.getItem('tractstack_entered_tracked') === 'true';
8
- let currentStoryfragmentId = null;
9
- let isPageInitialized = false;
10
- let globalObserver = null;
11
-
12
- function waitForSessionReady() {
13
- return new Promise((resolve) => {
14
- if (window.TRACTSTACK_CONFIG?.session?.isReady) {
15
- resolve();
16
- } else {
17
- window.addEventListener('tractstack:session-ready', () => resolve(), {
18
- once: true,
19
- });
20
- }
21
- });
22
- }
23
-
24
- export async function initAnalyticsTracking(storyfragmentId) {
25
- if (isPageInitialized) return;
26
- isPageInitialized = true;
27
-
28
- if (VERBOSE)
29
- console.log('📊 ANALYTICS: Initializing tracking for page view.');
30
- currentStoryfragmentId = storyfragmentId || null;
31
- initPaneVisibilityTracking();
32
- await waitForSessionReady();
33
- trackEnteredEvent();
34
- trackPageViewedEvent();
35
- }
36
-
37
- function trackEnteredEvent() {
38
- if (!hasTrackedEntered && currentStoryfragmentId) {
39
- sendAnalyticsEvent({
40
- contentId: currentStoryfragmentId,
41
- contentType: 'StoryFragment',
42
- eventVerb: 'ENTERED',
43
- });
44
- hasTrackedEntered = true;
45
- localStorage.setItem('tractstack_entered_tracked', 'true');
46
- }
47
- }
48
-
49
- function trackPageViewedEvent() {
50
- if (currentStoryfragmentId) {
51
- // This event is now PURELY for analytics. It no longer triggers
52
- // a synchronization workaround on the backend.
53
- sendAnalyticsEvent({
54
- contentId: currentStoryfragmentId,
55
- contentType: 'StoryFragment',
56
- eventVerb: 'PAGEVIEWED',
57
- });
58
- }
59
- }
60
-
61
- function initPaneVisibilityTracking() {
62
- if (globalObserver) {
63
- globalObserver.disconnect();
64
- }
65
- globalObserver = new IntersectionObserver(
66
- (entries) => {
67
- entries.forEach((entry) => {
68
- const paneId = getPaneIdFromElement(entry.target);
69
- if (!paneId) return;
70
- if (entry.isIntersecting) {
71
- if (!paneViewTimes.has(paneId)) {
72
- paneViewTimes.set(paneId, Date.now());
73
- }
74
- } else {
75
- const startTime = paneViewTimes.get(paneId);
76
- if (startTime) {
77
- const duration = Date.now() - startTime;
78
- paneViewTimes.delete(paneId);
79
- let eventVerb = null;
80
- if (duration >= THRESHOLD_READ) eventVerb = 'READ';
81
- else if (duration >= THRESHOLD_GLOSSED) eventVerb = 'GLOSSED';
82
- if (eventVerb) {
83
- sendAnalyticsEvent({
84
- contentId: paneId,
85
- contentType: 'Pane',
86
- eventVerb,
87
- duration,
88
- });
89
- }
90
- }
91
- }
92
- });
93
- },
94
- { threshold: 0.1, rootMargin: '0px' }
95
- );
96
- observeAllPanes();
97
- }
98
-
99
- function observeAllPanes() {
100
- if (!globalObserver) return;
101
- const panes = document.querySelectorAll('[data-pane-id]');
102
- panes.forEach((pane) => globalObserver.observe(pane));
103
- }
104
-
105
- function setupLifecycleListeners() {
106
- if (window.ANALYTICS_INITIALIZED) return;
107
- window.ANALYTICS_INITIALIZED = true;
108
-
109
- if (VERBOSE)
110
- console.log(
111
- '📊 ANALYTICS: Setting up lifecycle listeners for the first time.'
112
- );
113
-
114
- window.addEventListener('beforeunload', () => {
115
- flushPendingPaneEvents();
116
- });
117
-
118
- document.addEventListener('astro:page-load', () => {
119
- isPageInitialized = false;
120
- flushPendingPaneEvents();
121
- if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
122
- initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
123
- }
124
- });
125
-
126
- if (document.readyState === 'complete') {
127
- if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
128
- initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
129
- }
130
- } else {
131
- document.addEventListener(
132
- 'DOMContentLoaded',
133
- () => {
134
- if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
135
- initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
136
- }
137
- },
138
- { once: true }
139
- );
140
- }
141
- }
142
-
143
- function flushPendingPaneEvents() {
144
- if (paneViewTimes.size === 0) return;
145
- const flushTime = Date.now();
146
- paneViewTimes.forEach((startTime, paneId) => {
147
- const duration = flushTime - startTime;
148
- let eventVerb = null;
149
- if (duration >= THRESHOLD_READ) eventVerb = 'READ';
150
- else if (duration >= THRESHOLD_GLOSSED) eventVerb = 'GLOSSED';
151
- if (eventVerb) {
152
- sendAnalyticsEvent({
153
- contentId: paneId,
154
- contentType: 'Pane',
155
- eventVerb,
156
- duration,
157
- });
158
- }
159
- });
160
- paneViewTimes.clear();
161
- }
162
-
163
- async function sendAnalyticsEvent(event) {
164
- try {
165
- const config = window.TRACTSTACK_CONFIG;
166
- if (!config || !config.sessionId) return; // Use server-provided session ID
167
-
168
- const sessionId = config.sessionId;
169
- const formData = {
170
- beliefId: event.contentId,
171
- beliefType: event.contentType,
172
- beliefValue: event.eventVerb,
173
- paneId: '',
174
- };
175
-
176
- if (event.duration !== undefined) {
177
- formData.duration = event.duration.toString();
178
- }
179
-
180
- await fetch(`${config.backendUrl}/api/v1/state`, {
181
- method: 'POST',
182
- headers: {
183
- 'Content-Type': 'application/x-www-form-urlencoded',
184
- 'X-Tenant-ID': config.tenantId,
185
- 'X-TractStack-Session-ID': sessionId,
186
- 'X-StoryFragment-ID': config.storyfragmentId,
187
- },
188
- body: new URLSearchParams(formData),
189
- });
190
- } catch (error) {
191
- console.error('❌ API ERROR: Analytics event failed', error, event);
192
- }
193
- }
194
-
195
- function getPaneIdFromElement(element) {
196
- return element.getAttribute('data-pane-id');
197
- }
198
-
199
- window.initAnalyticsTracking = initAnalyticsTracking;
200
-
201
- setupLifecycleListeners();
202
-
203
- if (VERBOSE) {
204
- console.log(
205
- '📊 ANALYTICS: Analytics events module loaded and is persistent.'
206
- );
207
- }
@@ -1,210 +0,0 @@
1
- const VERBOSE = false;
2
-
3
- function configureHtmx() {
4
- if (!window.htmx || window.HTMX_LISTENER_ATTACHED) {
5
- return;
6
- }
7
- window.HTMX_LISTENER_ATTACHED = true;
8
-
9
- if (!window.HTMX_CONFIGURED) {
10
- window.htmx.config.selfRequestsOnly = false;
11
- window.HTMX_CONFIGURED = true;
12
- }
13
-
14
- window.htmx.on(document.body, 'htmx:configRequest', function (evt) {
15
- const config = window.TRACTSTACK_CONFIG;
16
- if (!config || !config.sessionId) return;
17
-
18
- if (evt.detail.path && evt.detail.path.startsWith('/api/v1/')) {
19
- evt.detail.path = config.backendUrl + evt.detail.path;
20
- }
21
-
22
- const sessionId = config.sessionId;
23
- evt.detail.headers['X-Tenant-ID'] = config.tenantId;
24
- evt.detail.headers['X-StoryFragment-ID'] = config.storyfragmentId;
25
- if (sessionId) {
26
- evt.detail.headers['X-TractStack-Session-ID'] = sessionId;
27
- }
28
- });
29
-
30
- window.htmx.on(document.body, 'htmx:beforeRequest', async function (evt) {
31
- const params = evt.detail.requestConfig.parameters;
32
- if (params && params.beliefVerb === 'IDENTIFY_AS') {
33
- evt.preventDefault();
34
-
35
- const originalPayload = params;
36
- const unsetPayload = {
37
- unsetBeliefIds: originalPayload.beliefId,
38
- paneId: originalPayload.paneId || '',
39
- };
40
-
41
- try {
42
- await sendBeliefUpdate(unsetPayload);
43
- await sendBeliefUpdate(originalPayload);
44
- } catch (error) {
45
- if (VERBOSE)
46
- console.error('🔴 BELIEF: Two-step identifyAs update failed', error);
47
- }
48
- }
49
- });
50
- }
51
-
52
- const pageBeliefs = {};
53
- let activeStoryfragmentId = null;
54
-
55
- function waitForSessionReady() {
56
- return new Promise((resolve) => {
57
- if (window.TRACTSTACK_CONFIG?.session?.isReady) {
58
- resolve();
59
- } else {
60
- window.addEventListener('tractstack:session-ready', () => resolve(), {
61
- once: true,
62
- });
63
- }
64
- });
65
- }
66
-
67
- function initializeBeliefs() {
68
- if (window.BELIEF_INITIALIZED) {
69
- return;
70
- }
71
- window.BELIEF_INITIALIZED = true;
72
-
73
- if (VERBOSE)
74
- console.log(
75
- '🔧 BELIEF: First-time initialization of belief handlers and HTMX config.'
76
- );
77
-
78
- configureHtmx();
79
-
80
- document.addEventListener('change', function (event) {
81
- const target = event.target;
82
- if (
83
- target.matches &&
84
- (target.matches('select[data-belief-id]') ||
85
- target.matches('input[type="checkbox"][data-belief-id]'))
86
- ) {
87
- handleBeliefChange(target);
88
- }
89
- });
90
- }
91
-
92
- async function handleBeliefChange(element) {
93
- const beliefId = element.getAttribute('data-belief-id');
94
- const beliefType = element.getAttribute('data-belief-type');
95
- const paneId = element.getAttribute('data-pane-id');
96
-
97
- if (!beliefId || !beliefType) {
98
- if (VERBOSE)
99
- console.error('🔴 BELIEF: Missing required attributes on', element.id);
100
- return;
101
- }
102
-
103
- let beliefValue;
104
- if (element.type === 'checkbox') {
105
- beliefValue = element.checked ? 'BELIEVES_YES' : 'BELIEVES_NO';
106
- } else {
107
- beliefValue = element.value;
108
- }
109
-
110
- if (VERBOSE)
111
- console.log('🔄 BELIEF: Widget changed', {
112
- beliefId,
113
- beliefType,
114
- beliefValue,
115
- paneId,
116
- });
117
-
118
- trackBeliefState(beliefId, beliefValue);
119
-
120
- await sendBeliefUpdate({
121
- beliefId,
122
- beliefType,
123
- beliefValue,
124
- paneId: paneId || '',
125
- });
126
- }
127
-
128
- async function sendBeliefUpdate(data) {
129
- await waitForSessionReady();
130
-
131
- try {
132
- const config = window.TRACTSTACK_CONFIG;
133
- if (!config || !config.sessionId) return;
134
-
135
- if (VERBOSE)
136
- console.log('🚨 FRONTEND DEBUG: Sending belief update with headers:', {
137
- 'X-StoryFragment-ID': config.storyfragmentId,
138
- beliefData: data,
139
- currentSSEContext: 'check sse.ts logs',
140
- });
141
-
142
- const sessionId = config.sessionId;
143
- const headers = {
144
- 'Content-Type': 'application/x-www-form-urlencoded',
145
- 'X-Tenant-ID': config.tenantId,
146
- 'X-StoryFragment-ID': config.storyfragmentId,
147
- 'X-TractStack-Session-ID': sessionId,
148
- };
149
-
150
- if (VERBOSE)
151
- console.log('📡 BELIEF: Session ready, sending update.', { data });
152
-
153
- const response = await fetch(`${config.backendUrl}/api/v1/state`, {
154
- method: 'POST',
155
- headers: headers,
156
- body: new URLSearchParams(data),
157
- });
158
-
159
- if (!response.ok) {
160
- throw new Error(`HTTP error! status: ${response.status}`);
161
- }
162
- } catch (error) {
163
- const errorMessage =
164
- error instanceof Error ? error.message : 'Unknown error';
165
- if (VERBOSE)
166
- console.error('🔴 BELIEF: Update failed', {
167
- error: errorMessage,
168
- beliefId: data.beliefId,
169
- });
170
- }
171
- }
172
-
173
- function trackBeliefState(beliefId, beliefValue) {
174
- if (!activeStoryfragmentId) return;
175
-
176
- if (!pageBeliefs[activeStoryfragmentId]) {
177
- pageBeliefs[activeStoryfragmentId] = {};
178
- }
179
- pageBeliefs[activeStoryfragmentId][beliefId] = beliefValue;
180
- if (VERBOSE)
181
- console.log(`📝 BELIEF: Tracked state for page ${activeStoryfragmentId}`, {
182
- ...pageBeliefs[activeStoryfragmentId],
183
- });
184
- }
185
-
186
- function setActiveStoryFragment() {
187
- if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
188
- activeStoryfragmentId = window.TRACTSTACK_CONFIG.storyfragmentId;
189
- if (VERBOSE)
190
- console.log(
191
- `📖 BELIEF: Active story fragment set to ${activeStoryfragmentId}`
192
- );
193
-
194
- if (!pageBeliefs[activeStoryfragmentId]) {
195
- pageBeliefs[activeStoryfragmentId] = {};
196
- }
197
- }
198
- }
199
-
200
- initializeBeliefs();
201
-
202
- document.addEventListener('astro:page-load', () => {
203
- configureHtmx();
204
- setActiveStoryFragment();
205
- });
206
-
207
- document.addEventListener('DOMContentLoaded', setActiveStoryFragment);
208
-
209
- if (VERBOSE)
210
- console.log('🔧 BELIEF: Belief events module loaded and is persistent.');