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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.0.0-rc.65",
3
+ "version": "2.0.0-rc.67",
4
4
  "description": "Astro integration for TractStack - redeeming the web from boring experiences",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -327,7 +327,7 @@ function processStoryfragmentUpdate(update, config) {
327
327
  `Target pane element for scrolling not found: #pane-${update.gotoPaneId}`
328
328
  );
329
329
  }
330
- }, 150);
330
+ }, 350);
331
331
  }
332
332
  }
333
333
 
@@ -361,6 +361,7 @@ function initializeCurrentView() {
361
361
 
362
362
  function resetViewState() {
363
363
  log('Resetting view state before new page preparation.');
364
+ flushPendingPaneEvents();
364
365
  isPageInitialized = false;
365
366
  paneViewTimes.clear();
366
367
  }
@@ -385,8 +386,13 @@ if (!window.tractstackViewLifecycleListenersAttached) {
385
386
 
386
387
  let beliefValue;
387
388
  if (target.type === 'checkbox') {
388
- // TEMPORARY HARDCODING: Use YES/NO for all toggles.
389
- beliefValue = target.checked ? 'BELIEVES_YES' : 'BELIEVES_NO';
389
+ const onVerb = target.getAttribute('data-verb');
390
+ const offVerb = target.getAttribute('data-off-verb');
391
+ if (onVerb && offVerb) {
392
+ beliefValue = target.checked ? onVerb : offVerb;
393
+ } else {
394
+ beliefValue = target.checked ? 'BELIEVES_YES' : 'BELIEVES_NO';
395
+ }
390
396
  } else {
391
397
  beliefValue = target.value;
392
398
  }
@@ -414,6 +414,7 @@ const EpinetWrapper = ({
414
414
  <EpinetDurationSelector
415
415
  fullContentMap={fullContentMap}
416
416
  isLoading={isLoading || status === 'loading'}
417
+ hourlyNodeActivity={$epinetCustomFilters.hourlyNodeActivity}
417
418
  />
418
419
  </div>
419
420
  </ErrorBoundary>
@@ -27,7 +27,7 @@ const SettingsPanel = ({ config, availableCodeHooks }: SettingsPanelProps) => {
27
27
 
28
28
  return (
29
29
  <div
30
- className="bg-mydarkgrey flex h-full max-w-sm flex-col rounded-xl bg-opacity-20 p-0.5 backdrop-blur-sm"
30
+ className="bg-mydarkgrey min-w-xs flex h-full max-w-sm flex-col rounded-xl bg-opacity-20 p-0.5 backdrop-blur-sm"
31
31
  style={
32
32
  {
33
33
  animation: window.matchMedia(
@@ -931,6 +931,12 @@ export default function SaveModal({
931
931
  >
932
932
  Keep Editing
933
933
  </button>
934
+ <a
935
+ href="/storykeep/content"
936
+ className={`rounded bg-black px-4 py-2 text-white transition-colors hover:bg-white hover:text-black`}
937
+ >
938
+ Dashboard
939
+ </a>
934
940
  </>
935
941
  )}
936
942
  {stage === 'ERROR' && (
@@ -38,10 +38,7 @@ interface WidgetStyles {
38
38
  bgColor: string;
39
39
  bgOpacity: number;
40
40
  }
41
- type StoredDisclosureItem = Omit<
42
- DisclosureItem,
43
- 'id' | 'isDisabled' | 'isCustom'
44
- >;
41
+ type StoredDisclosureItem = Omit<DisclosureItem, 'id' | 'isDisabled'>;
45
42
  interface InteractiveDisclosureWidgetProps {
46
43
  node: FlatNode;
47
44
  onUpdate: (params: string[]) => void;
@@ -91,6 +88,7 @@ const IconSelector = ({
91
88
  <Combobox.Input
92
89
  className="w-full rounded-md border-gray-300 py-1.5 pl-3 pr-10 shadow-sm"
93
90
  placeholder="Search icons..."
91
+ autoFocus
94
92
  />
95
93
  <Combobox.Trigger className="absolute inset-y-0 right-0 flex items-center pr-2">
96
94
  <i className={`bi bi-${value} mr-2 text-lg`}></i>
@@ -141,6 +139,13 @@ const DisclosureItemEditor = ({
141
139
  isFirst: boolean;
142
140
  isLast: boolean;
143
141
  }) => {
142
+ const [isEditingIcon, setIsEditingIcon] = useState(false);
143
+
144
+ const handleIconChange = (newIcon: string) => {
145
+ onUpdate({ icon: newIcon });
146
+ setIsEditingIcon(false);
147
+ };
148
+
144
149
  return (
145
150
  <div
146
151
  className={`space-y-4 rounded-lg border bg-white p-4 shadow-sm transition-opacity ${
@@ -169,9 +174,11 @@ const DisclosureItemEditor = ({
169
174
  </div>
170
175
  <h4 className="font-bold text-gray-800">
171
176
  {item.title}{' '}
172
- <span className="text-xs font-normal text-gray-500">
173
- (Key: {item.beliefValue})
174
- </span>
177
+ {!item.isCustom && (
178
+ <span className="text-xs font-normal text-gray-500">
179
+ (Key: {item.beliefValue})
180
+ </span>
181
+ )}
175
182
  </h4>
176
183
  </div>
177
184
  <button
@@ -189,13 +196,6 @@ const DisclosureItemEditor = ({
189
196
  </button>
190
197
  </div>
191
198
  <fieldset disabled={item.isDisabled} className="space-y-4">
192
- {item.isCustom && (
193
- <SingleParam
194
- label="Key / Value"
195
- value={item.beliefValue}
196
- onChange={(value) => onUpdate({ beliefValue: value })}
197
- />
198
- )}
199
199
  <SingleParam
200
200
  label="Display Title"
201
201
  value={item.title}
@@ -206,10 +206,29 @@ const DisclosureItemEditor = ({
206
206
  value={item.description || ''}
207
207
  onChange={(value) => onUpdate({ description: value })}
208
208
  />
209
- <IconSelector
210
- value={item.icon}
211
- onChange={(value) => onUpdate({ icon: value })}
212
- />
209
+
210
+ {isEditingIcon ? (
211
+ <IconSelector value={item.icon} onChange={handleIconChange} />
212
+ ) : (
213
+ <div>
214
+ <label className="block text-xs font-bold text-gray-600">
215
+ Icon
216
+ </label>
217
+ <div className="mt-1 flex items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-1.5 shadow-sm">
218
+ <div className="flex items-center gap-2">
219
+ <i className={`bi bi-${item.icon} text-lg`}></i>
220
+ <span className="text-sm">{item.icon}</span>
221
+ </div>
222
+ <button
223
+ type="button"
224
+ onClick={() => setIsEditingIcon(true)}
225
+ className="text-sm font-bold text-cyan-600 hover:text-cyan-800"
226
+ >
227
+ Change
228
+ </button>
229
+ </div>
230
+ </div>
231
+ )}
213
232
 
214
233
  {item.isCustom ? (
215
234
  <div className="relative rounded-md border p-3">
@@ -243,8 +262,8 @@ export default function InteractiveDisclosureWidget({
243
262
  const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
244
263
  const [disclosures, setDisclosures] = useState<DisclosureItem[]>([]);
245
264
  const [widgetStyles, setWidgetStyles] = useState<WidgetStyles>({
246
- textColor: '',
247
- bgColor: '',
265
+ textColor: '#000000',
266
+ bgColor: '#ffffff',
248
267
  bgOpacity: 100,
249
268
  });
250
269
  const [isModalOpen, setIsModalOpen] = useState(false);
@@ -268,7 +287,11 @@ export default function InteractiveDisclosureWidget({
268
287
  try {
269
288
  const parsed = JSON.parse(payloadJson);
270
289
  setWidgetStyles(
271
- parsed.styles || { textColor: '', bgColor: '', bgOpacity: 100 }
290
+ parsed.styles || {
291
+ textColor: '#000000',
292
+ bgColor: '#ffffff',
293
+ bgOpacity: 100,
294
+ }
272
295
  );
273
296
  const loadedDisclosures =
274
297
  (parsed.disclosures as StoredDisclosureItem[]) || [];
@@ -290,6 +313,7 @@ export default function InteractiveDisclosureWidget({
290
313
  const isFromScale = scaleKeys.some(
291
314
  (sk) => sk.slug === loadedItem.beliefValue
292
315
  );
316
+
293
317
  return {
294
318
  ...loadedItem,
295
319
  id: generateId(),
@@ -308,7 +332,7 @@ export default function InteractiveDisclosureWidget({
308
332
  beliefValue: slug,
309
333
  title: name,
310
334
  description: '',
311
- icon: 'app',
335
+ icon: 'chat-heart-fill',
312
336
  actionLisp: `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, slug)})`,
313
337
  isCustom: false,
314
338
  isDisabled: true,
@@ -321,7 +345,11 @@ export default function InteractiveDisclosureWidget({
321
345
  }
322
346
  } else {
323
347
  setDisclosures([]);
324
- setWidgetStyles({ textColor: '', bgColor: '', bgOpacity: 100 });
348
+ setWidgetStyles({
349
+ textColor: '#000000',
350
+ bgColor: '#ffffff',
351
+ bgOpacity: 100,
352
+ });
325
353
  }
326
354
  setIsDataLoaded(true);
327
355
  }, [node, beliefs]);
@@ -352,13 +380,18 @@ export default function InteractiveDisclosureWidget({
352
380
  const handleUpdate = () => {
353
381
  const disclosuresToStore: StoredDisclosureItem[] = disclosures
354
382
  .filter((d) => !d.isDisabled)
355
- .map(({ id, isCustom, isDisabled, ...rest }) => rest);
383
+ .map(({ id, isDisabled, ...rest }) => rest);
356
384
  const payload = { styles: widgetStyles, disclosures: disclosuresToStore };
357
385
  onUpdate([selectedBeliefTag, JSON.stringify(payload)]);
358
386
  };
359
387
 
360
388
  const handleBeliefChange = (tag: string) => {
361
389
  setSelectedBeliefTag(tag);
390
+ setWidgetStyles({
391
+ textColor: '#000000',
392
+ bgColor: '#ffffff',
393
+ bgOpacity: 100,
394
+ });
362
395
  const belief = beliefs.find((b) => b.slug === tag);
363
396
  let newDisclosures: DisclosureItem[] = [];
364
397
  if (belief) {
@@ -375,7 +408,7 @@ export default function InteractiveDisclosureWidget({
375
408
  beliefValue: slug,
376
409
  title: name,
377
410
  description: '',
378
- icon: 'app',
411
+ icon: 'chat-heart-fill',
379
412
  actionLisp: `(${actionCommand} ${tag} ${quoteIfNecessary(actionCommand, slug)})`,
380
413
  isCustom: false,
381
414
  isDisabled: false,
@@ -399,10 +432,12 @@ export default function InteractiveDisclosureWidget({
399
432
  const addCustomDisclosure = () => {
400
433
  const newItem: DisclosureItem = {
401
434
  id: generateId(),
402
- beliefValue: `custom-key-${disclosures.length + 1}`,
435
+ beliefValue: `custom-${Date.now()}-${Math.random()
436
+ .toString(36)
437
+ .substring(2, 6)}`,
403
438
  title: 'New Custom Item',
404
439
  description: '',
405
- icon: 'plus-circle',
440
+ icon: 'chat-heart-fill',
406
441
  actionLisp: '',
407
442
  isCustom: true,
408
443
  isDisabled: false,
@@ -418,12 +453,20 @@ export default function InteractiveDisclosureWidget({
418
453
  const updateWidgetStyles = (updates: Partial<WidgetStyles>) =>
419
454
  setWidgetStyles((prev) => ({ ...prev, ...updates }));
420
455
 
421
- const toggleDisclosure = (id: string) =>
422
- setDisclosures(
423
- disclosures.map((d) =>
424
- d.id === id ? { ...d, isDisabled: !d.isDisabled } : d
425
- )
426
- );
456
+ const toggleDisclosure = (id: string) => {
457
+ const itemToToggle = disclosures.find((d) => d.id === id);
458
+ if (!itemToToggle) return;
459
+
460
+ if (itemToToggle.isCustom) {
461
+ setDisclosures(disclosures.filter((d) => d.id !== id));
462
+ } else {
463
+ setDisclosures(
464
+ disclosures.map((d) =>
465
+ d.id === id ? { ...d, isDisabled: !d.isDisabled } : d
466
+ )
467
+ );
468
+ }
469
+ };
427
470
 
428
471
  const handleColorChange = (
429
472
  key: 'textColor' | 'bgColor',
@@ -12,28 +12,27 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
12
12
  const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
13
13
  const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
14
14
  const [currentPrompt, setCurrentPrompt] = useState<string>('');
15
+ const [currentScale, setCurrentScale] = useState<string>('');
15
16
  const [isInitialized, setIsInitialized] = useState(false);
16
17
 
17
- // Get parameter metadata from the widgetMeta constant
18
18
  const widgetInfo = widgetMeta.toggle;
19
19
 
20
20
  const params = node.codeHookParams || [];
21
21
  const beliefTag = String(params[0] || '');
22
22
  const prompt = String(params[1] || '');
23
+ const scale = String(params[2] || '');
23
24
 
24
- // Check if beliefTag is the placeholder value
25
25
  const isPlaceholder = beliefTag === 'BeliefTag';
26
26
 
27
- // Update local state when props change
28
27
  useEffect(() => {
29
28
  if (!isPlaceholder && beliefTag) {
30
29
  setSelectedBeliefTag(beliefTag);
31
30
  }
32
31
  setCurrentPrompt(prompt);
32
+ setCurrentScale(scale);
33
33
  setIsInitialized(true);
34
- }, [beliefTag, prompt, isPlaceholder]);
34
+ }, [beliefTag, prompt, scale, isPlaceholder]);
35
35
 
36
- // Fetch beliefs using new Go backend pattern
37
36
  useEffect(() => {
38
37
  const fetchData = async () => {
39
38
  try {
@@ -41,7 +40,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
41
40
  import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
42
41
  const tenantId = import.meta.env.PUBLIC_TENANTID || 'default';
43
42
 
44
- // Step 1: Get all belief IDs
45
43
  const idsResponse = await fetch(`${goBackend}/api/v1/nodes/beliefs`, {
46
44
  headers: {
47
45
  'X-Tenant-ID': tenantId,
@@ -60,7 +58,6 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
60
58
  return;
61
59
  }
62
60
 
63
- // Step 2: Get belief data by IDs
64
61
  const beliefsResponse = await fetch(
65
62
  `${goBackend}/api/v1/nodes/beliefs`,
66
63
  {
@@ -92,34 +89,30 @@ export default function ToggleWidget({ node, onUpdate }: ToggleWidgetProps) {
92
89
  const handleBeliefChange = (selectedValue: string) => {
93
90
  if (!isInitialized) return;
94
91
  setSelectedBeliefTag(selectedValue);
95
- onUpdate([selectedValue, currentPrompt]);
92
+ const selectedBelief = beliefs.find((b) => b.slug === selectedValue);
93
+ const newScale = selectedBelief ? selectedBelief.scale || '' : '';
94
+ setCurrentScale(newScale);
95
+ onUpdate([selectedValue, currentPrompt, newScale]);
96
96
  };
97
97
 
98
98
  const handlePromptChange = (value: string) => {
99
99
  if (!isInitialized) return;
100
- // Sanitize the input value (remove newlines and pipe characters)
101
100
  const sanitizedValue = value.replace(/[\n\r|]/g, '');
102
101
  setCurrentPrompt(sanitizedValue);
103
-
104
- // Use the actual selected tag (from state) or the original belief tag as fallback
105
102
  const tagToUse = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
106
- onUpdate([tagToUse, sanitizedValue]);
103
+ onUpdate([tagToUse, sanitizedValue, currentScale]);
107
104
  };
108
105
 
109
- // Show beliefs that can be selected for the toggle
110
106
  const filteredBeliefs = beliefs.filter(
111
107
  (b) => b.scale === 'yn' || b.scale === 'tf'
112
108
  );
113
109
 
114
- // Find the selected belief (if any)
115
110
  const selectedBelief = beliefs.find(
116
111
  (b) => b.slug === (selectedBeliefTag || (isPlaceholder ? '' : beliefTag))
117
112
  );
118
113
 
119
- // Determine if we have a real selection - either from state or props
120
114
  const hasRealSelection = !!selectedBelief || (!isPlaceholder && !!beliefTag);
121
115
 
122
- // Calculate the current value to show in the select dropdown
123
116
  const selectValue = selectedBeliefTag || (isPlaceholder ? '' : beliefTag);
124
117
 
125
118
  return (
@@ -171,7 +171,7 @@ for (const [key, value] of Astro.url.searchParams) {
171
171
  <main id="mainContent" class="relative flex-1 overflow-x-auto">
172
172
  <div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
173
173
  <div
174
- class="h-fit min-h-screen pb-24"
174
+ class="h-fit min-h-screen pb-96"
175
175
  style={{
176
176
  backgroundImage:
177
177
  'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',
@@ -159,16 +159,10 @@ for (const [key, value] of Astro.url.searchParams) {
159
159
  <StoryKeepToolMode isContext={true} client:only="react" />
160
160
 
161
161
  <!-- Main Content Area -->
162
- <main
163
- id="mainContent"
164
- class="relative flex-1 overflow-x-auto"
165
- style={{
166
- paddingBottom: 'var(--bottom-right-controls-bottom-offset, 16px)',
167
- }}
168
- >
162
+ <main id="mainContent" class="relative flex-1 overflow-x-auto">
169
163
  <div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
170
164
  <div
171
- class="h-fit min-h-screen"
165
+ class="h-fit min-h-screen pb-96"
172
166
  style={{
173
167
  backgroundImage:
174
168
  'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',