astro-tractstack 2.0.0-rc.48 → 2.0.0-rc.49

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 CHANGED
@@ -2062,7 +2062,7 @@ async function w(t, e, c) {
2062
2062
  try {
2063
2063
  const p = i(s.dest);
2064
2064
  r(p) || x(p, { recursive: !0 });
2065
- const o = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest === ".gitignore");
2065
+ const o = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest.startsWith("public/client/") || s.dest === ".gitignore");
2066
2066
  if (!r(s.dest) || o)
2067
2067
  if (r(s.src))
2068
2068
  k(s.src, s.dest), e.info(`Updated ${s.dest}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.0-rc.48",
3
+ "version": "2.0.0-rc.49",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -25,7 +25,7 @@ function log(...args) {
25
25
  }
26
26
 
27
27
  function logCritical(...args) {
28
- if (VERBOSE) console.error('🚨 SSE CRITICAL:', ...args);
28
+ if (VERBOSE) console.warn('🚨 SSE CRITICAL:', ...args);
29
29
  }
30
30
 
31
31
  function logStoryfragmentChange(action, oldId, newId, source) {
@@ -151,57 +151,6 @@ async function performSSEHandshake(sessionId) {
151
151
  localStorage.setItem('tractstack_consent', result.consent);
152
152
  log('💾 Set consent:', result.consent);
153
153
 
154
- if (
155
- result.restored &&
156
- result.affectedPanes &&
157
- result.affectedPanes.length > 0
158
- ) {
159
- log('🔄 State restoration needed. Affected panes:', result.affectedPanes);
160
-
161
- if (window.htmx && isHtmxReady) {
162
- log('✅ HTMX ready, triggering immediate pane refreshes');
163
- result.affectedPanes.forEach((paneId) => {
164
- const element = document.querySelector(`[data-pane-id="${paneId}"]`);
165
- if (element) {
166
- log(`🔄 Restoring pane: ${paneId}`);
167
- window.htmx.trigger(element, 'refresh');
168
- } else {
169
- log(`âš ī¸ Restoration pane element not found: ${paneId}`);
170
- }
171
- });
172
- } else {
173
- log(
174
- 'âš ī¸ HTMX not ready during restoration, setting up delayed refresh'
175
- );
176
- document.addEventListener(
177
- 'astro:page-load',
178
- () => {
179
- if (window.htmx && result.affectedPanes) {
180
- log(
181
- '🔄 HTMX now ready after page load, triggering delayed pane refreshes'
182
- );
183
- result.affectedPanes.forEach((paneId) => {
184
- const element = document.querySelector(
185
- `[data-pane-id="${paneId}"]`
186
- );
187
- if (element) {
188
- log(`🔄 Delayed restoration for pane: ${paneId}`);
189
- window.htmx.trigger(element, 'refresh');
190
- } else {
191
- log(
192
- `âš ī¸ Delayed restoration pane element not found: ${paneId}`
193
- );
194
- }
195
- });
196
- }
197
- },
198
- { once: true }
199
- );
200
- }
201
- } else {
202
- log('â„šī¸ No state restoration needed');
203
- }
204
-
205
154
  if (window.TRACTSTACK_CONFIG) {
206
155
  window.TRACTSTACK_CONFIG.session = { isReady: true };
207
156
  log('✅ Marked session as ready in config');
@@ -297,6 +246,7 @@ function initializeSSE(sessionId) {
297
246
  for (const update of data.updates) {
298
247
  logCritical(`🔍 CHECKING UPDATE:`, {
299
248
  updateStoryfragment: update.storyfragmentId,
249
+ payload: update,
300
250
  currentStoryfragment: currentStoryfragmentId,
301
251
  configStoryfragment: window.TRACTSTACK_CONFIG?.storyfragmentId,
302
252
  willProcess: update.storyfragmentId === currentStoryfragmentId,
@@ -380,6 +330,7 @@ function processStoryfragmentUpdate(update) {
380
330
  storyfragmentId: update.storyfragmentId,
381
331
  affectedPanes: update.affectedPanes,
382
332
  gotoPaneId: update.gotoPaneId,
333
+ codeHookVisibility: update.CodeHookVisibility,
383
334
  currentContext: currentStoryfragmentId,
384
335
  });
385
336
 
@@ -395,10 +346,111 @@ function processStoryfragmentUpdate(update) {
395
346
  return;
396
347
  }
397
348
 
349
+ // Split pane IDs: code hooks vs regular panes
350
+ const codeHookPaneIds = [];
351
+ const regularPaneIds = [];
352
+
353
+ uniquePaneIds.forEach((paneId) => {
354
+ if (
355
+ update.CodeHookVisibility &&
356
+ update.CodeHookVisibility.hasOwnProperty(paneId)
357
+ ) {
358
+ codeHookPaneIds.push(paneId);
359
+ } else {
360
+ regularPaneIds.push(paneId);
361
+ }
362
+ });
363
+
364
+ log('🎭 Code hook pane IDs:', codeHookPaneIds);
365
+ log('📄 Regular pane IDs:', regularPaneIds);
366
+
367
+ // Process code hook visibility changes (CSS toggling)
368
+ if (codeHookPaneIds.length > 0) {
369
+ log('🎭 Processing code hook visibility changes');
370
+ codeHookPaneIds.forEach((paneId) => {
371
+ const element = document.querySelector(`#pane-${paneId}`);
372
+ if (element) {
373
+ const visibilityValue = update.CodeHookVisibility[paneId];
374
+ log(
375
+ `🎭 Code hook ${paneId} visibility value:`,
376
+ visibilityValue,
377
+ typeof visibilityValue
378
+ );
379
+
380
+ // Handle pane visibility
381
+ if (visibilityValue === false) {
382
+ element.style.display = 'none';
383
+ log(`🎭 Code hook pane ${paneId} HIDDEN`);
384
+ } else if (visibilityValue === true || Array.isArray(visibilityValue)) {
385
+ element.style.display = 'block';
386
+ log(`🎭 Code hook pane ${paneId} VISIBLE`);
387
+ }
388
+
389
+ // Handle unset button visibility
390
+ const unsetDiv = document.querySelector(`#pane-${paneId}-unset`);
391
+ if (unsetDiv) {
392
+ if (Array.isArray(visibilityValue)) {
393
+ // Show unset button - pane is visible and has beliefs to unset
394
+ const hxValsObject = {
395
+ unsetBeliefIds: visibilityValue.join(','),
396
+ paneId: paneId,
397
+ };
398
+ const hxValsJson = JSON.stringify(hxValsObject);
399
+
400
+ unsetDiv.innerHTML = `
401
+ <button
402
+ type="button"
403
+ class="text-mydarkgrey absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 hover:bg-black hover:text-white"
404
+ title="Go Back"
405
+ hx-post="/api/v1/state"
406
+ hx-trigger="click"
407
+ hx-swap="none"
408
+ hx-vals='${hxValsJson}'
409
+ hx-preserve="true"
410
+ >
411
+ <svg
412
+ class="h-6 w-6"
413
+ fill="none"
414
+ viewBox="0 0 24 24"
415
+ stroke-width="1.5"
416
+ stroke="currentColor"
417
+ >
418
+ <path
419
+ stroke-linecap="round"
420
+ stroke-linejoin="round"
421
+ d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
422
+ />
423
+ </svg>
424
+ </button>
425
+ `;
426
+
427
+ // Tell HTMX to process the new button so hx-post works
428
+ if (window.htmx) {
429
+ window.htmx.process(unsetDiv);
430
+ }
431
+
432
+ log(
433
+ `🔄 Added unset button for pane ${paneId} with beliefs: ${visibilityValue.join(',')}`
434
+ );
435
+ } else {
436
+ // Clear unset button - pane is hidden or visible without unset needs
437
+ unsetDiv.innerHTML = '';
438
+ log(`đŸšĢ Cleared unset button for pane ${paneId}`);
439
+ }
440
+ } else {
441
+ log(`âš ī¸ Unset div not found: pane-${paneId}-unset`);
442
+ }
443
+ } else {
444
+ log(`âš ī¸ Code hook element not found: pane-${paneId}`);
445
+ }
446
+ });
447
+ }
448
+
449
+ // Process regular panes (HTMX refresh)
398
450
  let refreshedCount = 0;
399
451
  let errorCount = 0;
400
452
 
401
- uniquePaneIds.forEach((paneId) => {
453
+ regularPaneIds.forEach((paneId) => {
402
454
  const element = document.querySelector(`[data-pane-id="${paneId}"]`);
403
455
 
404
456
  if (element && window.htmx) {
@@ -431,7 +483,7 @@ function processStoryfragmentUpdate(update) {
431
483
  if (update.gotoPaneId) {
432
484
  const targetElement = document.getElementById(`pane-${update.gotoPaneId}`);
433
485
  if (targetElement) {
434
- log(`📍 Scrolling to target pane: ${update.gotoPaneId}`);
486
+ log(`🔍 Scrolling to target pane: ${update.gotoPaneId}`);
435
487
  try {
436
488
  targetElement.scrollIntoView({ behavior: 'smooth' });
437
489
  log('✅ Scroll completed successfully');
@@ -21,13 +21,11 @@ interface SearchModalProps {
21
21
  contentMap: FullContentMapItem[];
22
22
  }
23
23
 
24
- // --- CHANGE START ---
25
24
  // 1. Define a new type for the selected suggestions to include their type
26
25
  interface SelectedSuggestion {
27
26
  term: string;
28
27
  type: string;
29
28
  }
30
- // --- CHANGE END ---
31
29
 
32
30
  export default function SearchModal({
33
31
  isOpen,
@@ -35,12 +33,10 @@ export default function SearchModal({
35
33
  contentMap,
36
34
  }: SearchModalProps) {
37
35
  const [query, setQuery] = useState('');
38
- // --- CHANGE START ---
39
36
  // 2. Update state to use the new type instead of just string[]
40
37
  const [selectedSuggestions, setSelectedSuggestions] = useState<
41
38
  SelectedSuggestion[]
42
39
  >([]);
43
- // --- CHANGE END ---
44
40
  const inputRef = useRef<HTMLInputElement>(null);
45
41
  const {
46
42
  suggestions,
@@ -64,10 +60,8 @@ export default function SearchModal({
64
60
  useEffect(() => {
65
61
  if (!isOpen) {
66
62
  setQuery('');
67
- // --- CHANGE START ---
68
63
  // 3. Update cleanup logic to use the new state
69
64
  setSelectedSuggestions([]);
70
- // --- CHANGE END ---
71
65
  clearAll();
72
66
  }
73
67
  }, [isOpen, clearAll]);
@@ -84,10 +78,8 @@ export default function SearchModal({
84
78
 
85
79
  const handleClose = () => {
86
80
  setQuery('');
87
- // --- CHANGE START ---
88
81
  // 4. Update cleanup logic to use the new state
89
82
  setSelectedSuggestions([]);
90
- // --- CHANGE END ---
91
83
  clearAll();
92
84
  onClose();
93
85
  };
@@ -120,7 +112,6 @@ export default function SearchModal({
120
112
  };
121
113
 
122
114
  const handleSuggestionSelect = (suggestion: DiscoverySuggestion) => {
123
- // --- CHANGE START ---
124
115
  // 5. Update how suggestions are added to the state
125
116
  // Check for duplicates before adding
126
117
  if (!selectedSuggestions.some((s) => s.term === suggestion.term)) {
@@ -129,32 +120,27 @@ export default function SearchModal({
129
120
  { term: suggestion.term, type: suggestion.type },
130
121
  ]);
131
122
  }
132
- // --- CHANGE END ---
133
123
 
134
124
  setQuery('');
135
125
  selectSuggestion(suggestion);
136
126
  };
137
127
 
138
128
  const handleExactMatch = (term: string) => {
139
- // --- CHANGE START ---
140
129
  // 6. Update exact match handling to add a default type
141
- // From the legend, "Exact Match" uses the 'COLLECTION' style
130
+ // From the legend, "Exact Match" uses the 'EXACT' style
142
131
  if (!selectedSuggestions.some((s) => s.term === term)) {
143
- setSelectedSuggestions((prev) => [...prev, { term, type: 'COLLECTION' }]);
132
+ setSelectedSuggestions((prev) => [...prev, { term, type: 'EXACT' }]);
144
133
  }
145
- // --- CHANGE END ---
146
134
 
147
135
  setQuery('');
148
136
  selectExactMatch(term);
149
137
  };
150
138
 
151
139
  const removeTerm = (indexToRemove: number) => {
152
- // --- CHANGE START ---
153
140
  // 7. Update remove logic to use the new state
154
141
  setSelectedSuggestions((prev) =>
155
142
  prev.filter((_, index) => index !== indexToRemove)
156
143
  );
157
- // --- CHANGE END ---
158
144
  clearAll();
159
145
  if (inputRef.current) {
160
146
  inputRef.current.focus();
@@ -165,30 +151,28 @@ export default function SearchModal({
165
151
  switch (type) {
166
152
  case 'TOPIC':
167
153
  return 'bg-purple-100 text-purple-800 border-purple-200';
168
- case 'COLLECTION':
154
+ case 'EXACT':
169
155
  return 'bg-orange-100 text-orange-800 border-orange-200';
170
- case 'CONTENT':
156
+ case 'TEXT':
171
157
  return 'bg-green-100 text-green-800 border-green-200';
172
158
  default:
173
159
  return 'bg-gray-100 text-gray-800 border-gray-200';
174
160
  }
175
161
  };
176
162
 
177
- // --- CHANGE START ---
178
163
  // 8. (Optional but recommended) Create a helper for the 'X' button color
179
164
  const getCloseButtonColor = (type: string) => {
180
165
  switch (type) {
181
166
  case 'TOPIC':
182
167
  return 'text-purple-600 hover:text-purple-800';
183
- case 'COLLECTION':
168
+ case 'EXACT':
184
169
  return 'text-orange-600 hover:text-orange-800';
185
- case 'CONTENT':
170
+ case 'TEXT':
186
171
  return 'text-green-600 hover:text-green-800';
187
172
  default:
188
173
  return 'text-gray-600 hover:text-gray-800';
189
174
  }
190
175
  };
191
- // --- CHANGE END ---
192
176
 
193
177
  const bestCompletion =
194
178
  suggestions.length > 0 && query.length >= 3 ? suggestions[0].term : '';
@@ -224,7 +208,6 @@ export default function SearchModal({
224
208
  style={{ height: '80vh' }}
225
209
  >
226
210
  <div className="relative w-full border-b border-gray-200 p-4">
227
- {/* --- CHANGE START --- */}
228
211
  {/* 9. Update the rendering of selected term pills */}
229
212
  {selectedSuggestions.length > 0 && (
230
213
  <div className="mb-3 flex flex-wrap gap-2">
@@ -251,7 +234,6 @@ export default function SearchModal({
251
234
  ))}
252
235
  </div>
253
236
  )}
254
- {/* --- CHANGE END --- */}
255
237
 
256
238
  {!showResults && (
257
239
  <div className="relative w-full px-6 py-2">
@@ -289,10 +271,8 @@ export default function SearchModal({
289
271
  className="w-full overflow-y-auto"
290
272
  style={{ height: 'calc(80vh - 80px)' }}
291
273
  >
292
- {/* --- CHANGE START --- */}
293
274
  {/* 10. Final cleanup logic update */}
294
275
  {!query.trim() && selectedSuggestions.length === 0 && (
295
- // --- CHANGE END ---
296
276
  <div className="w-full p-8 text-center text-gray-500">
297
277
  <MagnifyingGlassIcon className="mx-auto mb-4 h-16 w-16 text-gray-300" />
298
278
  <p className="text-lg">Search across all content</p>
@@ -356,7 +336,7 @@ export default function SearchModal({
356
336
  <span className="font-bold">Legend:</span>
357
337
  <span
358
338
  className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
359
- 'COLLECTION'
339
+ 'EXACT'
360
340
  )}`}
361
341
  >
362
342
  Exact Match
@@ -370,7 +350,7 @@ export default function SearchModal({
370
350
  </span>
371
351
  <span
372
352
  className={`inline-flex items-center rounded-lg border px-3 py-1.5 text-sm font-bold ${getTypeColor(
373
- 'CONTENT'
353
+ 'TEXT'
374
354
  )}`}
375
355
  >
376
356
  Text Match
@@ -7,6 +7,7 @@ export interface StoryData {
7
7
  slug: string;
8
8
  paneIds: string[];
9
9
  codeHookTargets: Record<string, string>;
10
+ codeHookVisibility: Record<string, boolean | string[]>;
10
11
  resourcesPayload: Record<string, ResourceNode[]>;
11
12
  impressions: ImpressionNode[];
12
13
  fragments: Record<string, string>;
@@ -131,12 +131,19 @@ const ogImage =
131
131
  id={`pane-${paneId}`}
132
132
  data-pane-id={paneId}
133
133
  class="pane-fragment-container"
134
+ style={
135
+ !codeHookTargets[paneId]
136
+ ? undefined
137
+ : !storyData.codeHookVisibility?.[paneId]
138
+ ? 'display:none;'
139
+ : 'display:block;'
140
+ }
134
141
  hx-get={`/api/v1/fragments/panes/${paneId}`}
135
142
  hx-trigger="refresh"
136
143
  hx-swap="innerHTML"
137
144
  >
138
145
  {codeHookTargets[paneId] ? (
139
- <div class="overflow-hidden">
146
+ <div class="relative overflow-hidden">
140
147
  <CodeHook
141
148
  target={codeHookTargets[paneId]}
142
149
  options={(() => {
@@ -149,6 +156,38 @@ const ogImage =
149
156
  fullContentMap={fullContentMap}
150
157
  resourcesPayload={resourcesPayload}
151
158
  />
159
+ <div id={`pane-${paneId}-unset`}>
160
+ {Array.isArray(storyData.codeHookVisibility?.[paneId]) && (
161
+ <button
162
+ type="button"
163
+ class="text-mydarkgrey absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 hover:bg-black hover:text-white"
164
+ title="Go Back"
165
+ hx-post="/api/v1/state"
166
+ hx-trigger="click"
167
+ hx-swap="none"
168
+ hx-vals={JSON.stringify({
169
+ unsetBeliefIds:
170
+ storyData.codeHookVisibility[paneId].join(','),
171
+ paneId: paneId,
172
+ })}
173
+ hx-preserve="true"
174
+ >
175
+ <svg
176
+ class="h-6 w-6"
177
+ fill="none"
178
+ viewBox="0 0 24 24"
179
+ stroke-width="1.5"
180
+ stroke="currentColor"
181
+ >
182
+ <path
183
+ stroke-linecap="round"
184
+ stroke-linejoin="round"
185
+ d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
186
+ />
187
+ </svg>
188
+ </button>
189
+ )}
190
+ </div>
152
191
  </div>
153
192
  ) : (
154
193
  <Fragment set:html={fragmentsData[paneId] || ''} />
@@ -75,7 +75,7 @@ const mainStylesUrl = isDev
75
75
  </h2>
76
76
  </div>
77
77
 
78
- <div class="mx-auto">
78
+ <div class="mx-auto max-w-sm">
79
79
  <div class="rounded-lg bg-white px-6 py-12 shadow-inner">
80
80
  <!-- Error message -->
81
81
  <div
@@ -2119,6 +2119,7 @@ export async function injectTemplateFiles(
2119
2119
  file.dest.startsWith('src/components/codehooks/') ||
2120
2120
  file.dest.startsWith('src/components/widgets/') ||
2121
2121
  file.dest.startsWith('src/') ||
2122
+ file.dest.startsWith('public/client/') ||
2122
2123
  file.dest === '.gitignore');
2123
2124
 
2124
2125
  if (!existsSync(file.dest) || shouldOverwrite) {