astro-tractstack 2.0.0-rc.8 → 2.0.0
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/LICENSE +8 -97
- package/README.md +7 -5
- package/bin/create-tractstack.js +35 -11
- package/dist/index.js +106 -29
- package/package.json +10 -5
- package/templates/css/frontend.css +1 -1
- package/templates/custom/minimal/CodeHook.astro +13 -12
- package/templates/custom/minimal/CustomRoutes.astro +25 -31
- package/templates/custom/with-examples/CodeHook.astro +22 -11
- package/templates/custom/with-examples/CustomRoutes.astro +4 -8
- package/templates/custom/with-examples/ProductCard.astro +29 -0
- package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
- package/templates/custom/with-examples/ProductGrid.astro +64 -0
- package/templates/custom/with-examples/pages/Collections.astro +58 -98
- package/templates/gitignore +42 -0
- package/templates/prettierignore +5 -0
- package/templates/prettierrc +19 -0
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/htmx.min.js +3519 -0
- package/templates/src/client/view.js +429 -0
- package/templates/src/components/Footer.astro +4 -9
- package/templates/src/components/Header.astro +67 -60
- package/templates/src/components/Menu.tsx +188 -52
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
- package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
- package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
- package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
- package/templates/src/components/codehooks/ListContent.astro +32 -162
- package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
- package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
- package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
- package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
- package/templates/src/components/compositor/Node.tsx +3 -6
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
- package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
- package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
- package/templates/src/components/edit/Header.tsx +10 -4
- package/templates/src/components/edit/PanelSwitch.tsx +11 -7
- package/templates/src/components/edit/SettingsPanel.tsx +29 -18
- package/templates/src/components/edit/ToolBar.tsx +1 -28
- package/templates/src/components/edit/ToolMode.tsx +45 -32
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
- package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
- package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
- package/templates/src/components/edit/state/SaveModal.tsx +316 -169
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/components/fields/ArtpackImage.tsx +4 -1
- package/templates/src/components/fields/BackgroundImage.tsx +1 -1
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
- package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
- package/templates/src/components/fields/ImageUpload.tsx +1 -1
- package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
- package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
- package/templates/src/components/form/ActionBuilderField.tsx +306 -87
- package/templates/src/components/search/SearchModal.tsx +420 -0
- package/templates/src/components/search/SearchResults.tsx +367 -0
- package/templates/src/components/search/SearchWrapper.tsx +46 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
- package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +17 -2
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
- package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
- package/templates/src/components/tenant/RegistrationForm.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/constants.ts +2121 -16
- package/templates/src/hooks/useSearch.ts +228 -0
- package/templates/src/layouts/Layout.astro +213 -104
- package/templates/src/lib/storyData.ts +4 -1
- package/templates/src/pages/[...slug]/edit.astro +14 -14
- package/templates/src/pages/[...slug].astro +82 -21
- package/templates/src/pages/api/orphan-analysis.ts +0 -1
- package/templates/src/pages/api/tailwind.ts +23 -21
- package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
- package/templates/src/pages/context/[...contextSlug].astro +7 -2
- package/templates/src/pages/storykeep/advanced.astro +5 -4
- package/templates/src/pages/storykeep/branding.astro +5 -4
- package/templates/src/pages/storykeep/content.astro +5 -4
- package/templates/src/pages/storykeep/init.astro +40 -1
- package/templates/src/pages/storykeep/login.astro +1 -1
- package/templates/src/pages/storykeep.astro +5 -4
- package/templates/src/stores/nodes.ts +59 -88
- package/templates/src/stores/orphanAnalysis.ts +19 -21
- package/templates/src/stores/storykeep.ts +7 -0
- package/templates/src/types/compositorTypes.ts +6 -0
- package/templates/src/types/tractstack.ts +17 -0
- package/templates/src/utils/actions/lispLexer.ts +2 -2
- package/templates/src/utils/actions/preParse_Action.ts +3 -0
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/templates/src/utils/api/menuHelpers.ts +2 -2
- package/templates/src/utils/api.ts +26 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
- package/templates/src/utils/compositor/allowInsert.ts +5 -3
- package/templates/src/utils/compositor/nodesHelper.ts +4 -0
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
- package/templates/src/utils/compositor/typeGuards.ts +1 -0
- package/templates/src/utils/customHelpers.ts +38 -0
- package/templates/src/utils/helpers.ts +2 -2
- package/templates/src/utils/layout.ts +65 -144
- package/utils/inject-files.ts +95 -18
- 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 -613
- package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
|
@@ -340,6 +340,18 @@ export class NodesContext {
|
|
|
340
340
|
handleClickEventDefault(node, dblClick, this.clickedParentLayer.get());
|
|
341
341
|
break;
|
|
342
342
|
case `text`:
|
|
343
|
+
if (
|
|
344
|
+
node.nodeType === 'TagElement' &&
|
|
345
|
+
'tagName' in node &&
|
|
346
|
+
(node.tagName === 'a' || node.tagName === 'button')
|
|
347
|
+
) {
|
|
348
|
+
this.toolModeValStore.set({ value: 'styles' });
|
|
349
|
+
handleClickEventDefault(
|
|
350
|
+
node,
|
|
351
|
+
dblClick,
|
|
352
|
+
this.clickedParentLayer.get()
|
|
353
|
+
);
|
|
354
|
+
}
|
|
343
355
|
if (dblClick && ![`Markdown`].includes(node.nodeType)) {
|
|
344
356
|
this.toolModeValStore.set({ value: 'styles' });
|
|
345
357
|
handleClickEventDefault(
|
|
@@ -544,7 +556,6 @@ export class NodesContext {
|
|
|
544
556
|
tagNames.length > offset
|
|
545
557
|
? allowInsert(node, node.tagName as Tag, tagName, tagNames[offset + 1])
|
|
546
558
|
: allowInsert(node, node.tagName as Tag, tagName);
|
|
547
|
-
|
|
548
559
|
return { allowInsertBefore, allowInsertAfter };
|
|
549
560
|
}
|
|
550
561
|
|
|
@@ -1063,56 +1074,6 @@ export class NodesContext {
|
|
|
1063
1074
|
return '';
|
|
1064
1075
|
}
|
|
1065
1076
|
|
|
1066
|
-
//addPaneToStoryFragment(
|
|
1067
|
-
// nodeId: string,
|
|
1068
|
-
// pane: PaneNode,
|
|
1069
|
-
// location: 'before' | 'after'
|
|
1070
|
-
///) {
|
|
1071
|
-
// const node = this.allNodes.get().get(nodeId) as BaseNode;
|
|
1072
|
-
// if (
|
|
1073
|
-
// !node ||
|
|
1074
|
-
// (node.nodeType !== 'StoryFragment' && node.nodeType !== 'Pane')
|
|
1075
|
-
// ) {
|
|
1076
|
-
// return;
|
|
1077
|
-
// }
|
|
1078
|
-
|
|
1079
|
-
// pane.id = ulid();
|
|
1080
|
-
// this.addNode(pane);
|
|
1081
|
-
|
|
1082
|
-
// if (node.nodeType === 'Pane') {
|
|
1083
|
-
// const storyFragmentId = this.getClosestNodeTypeFromId(
|
|
1084
|
-
// nodeId,
|
|
1085
|
-
// 'StoryFragment'
|
|
1086
|
-
// );
|
|
1087
|
-
// const storyFragment = this.allNodes
|
|
1088
|
-
// .get()
|
|
1089
|
-
// .get(storyFragmentId) as StoryFragmentNode;
|
|
1090
|
-
// if (storyFragment) {
|
|
1091
|
-
// pane.parentId = storyFragmentId;
|
|
1092
|
-
// const originalPaneIndex = storyFragment.paneIds.indexOf(pane.parentId);
|
|
1093
|
-
// let insertIdx = -1;
|
|
1094
|
-
// if (location === 'before')
|
|
1095
|
-
// insertIdx = Math.max(0, originalPaneIndex - 1);
|
|
1096
|
-
// else
|
|
1097
|
-
// insertIdx = Math.min(
|
|
1098
|
-
// storyFragment.paneIds.length - 1,
|
|
1099
|
-
// originalPaneIndex + 1
|
|
1100
|
-
// );
|
|
1101
|
-
// storyFragment.paneIds.splice(insertIdx, 0, pane.id);
|
|
1102
|
-
// }
|
|
1103
|
-
// } else if (node.nodeType !== 'StoryFragment') {
|
|
1104
|
-
// const storyFragment = node as StoryFragmentNode;
|
|
1105
|
-
// if (storyFragment) {
|
|
1106
|
-
// pane.parentId = node.id;
|
|
1107
|
-
// if (location === 'after') {
|
|
1108
|
-
// storyFragment.paneIds.push(pane.id);
|
|
1109
|
-
// } else {
|
|
1110
|
-
// storyFragment.paneIds.unshift(pane.id);
|
|
1111
|
-
// }
|
|
1112
|
-
// }
|
|
1113
|
-
// }
|
|
1114
|
-
//}
|
|
1115
|
-
|
|
1116
1077
|
addContextTemplatePane(ownerId: string, pane: TemplatePane) {
|
|
1117
1078
|
const ownerNode = this.allNodes.get().get(ownerId);
|
|
1118
1079
|
if (ownerNode?.nodeType === 'Pane') {
|
|
@@ -2178,9 +2139,10 @@ export class NodesContext {
|
|
|
2178
2139
|
getAllBunnyVideoInfo(): { url: string; title: string; videoId: string }[] {
|
|
2179
2140
|
const results: { url: string; title: string; videoId: string }[] = [];
|
|
2180
2141
|
const processedVideoIds = new Set<string>();
|
|
2181
|
-
|
|
2182
|
-
// Find panes with bunny-video code hook
|
|
2183
2142
|
const allNodes = Array.from(this.allNodes.get().values());
|
|
2143
|
+
const BUNNY_EMBED_BASE_URL = 'https://iframe.mediadelivery.net/embed/';
|
|
2144
|
+
|
|
2145
|
+
// Process pane-level bunny videos (which use full URLs)
|
|
2184
2146
|
const paneNodes = allNodes.filter(
|
|
2185
2147
|
(node) =>
|
|
2186
2148
|
node.nodeType === 'Pane' &&
|
|
@@ -2188,7 +2150,6 @@ export class NodesContext {
|
|
|
2188
2150
|
node.codeHookTarget === 'bunny-video'
|
|
2189
2151
|
) as PaneNode[];
|
|
2190
2152
|
|
|
2191
|
-
// Process pane-level bunny videos
|
|
2192
2153
|
for (const paneNode of paneNodes) {
|
|
2193
2154
|
try {
|
|
2194
2155
|
if (
|
|
@@ -2231,7 +2192,7 @@ export class NodesContext {
|
|
|
2231
2192
|
}
|
|
2232
2193
|
}
|
|
2233
2194
|
|
|
2234
|
-
//
|
|
2195
|
+
// Process inline bunny widgets (which use ID/GUID fragments)
|
|
2235
2196
|
const codeNodes = allNodes.filter(
|
|
2236
2197
|
(node) =>
|
|
2237
2198
|
node.nodeType === 'TagElement' &&
|
|
@@ -2243,47 +2204,26 @@ export class NodesContext {
|
|
|
2243
2204
|
node.copy.includes('bunny(')
|
|
2244
2205
|
) as FlatNode[];
|
|
2245
2206
|
|
|
2246
|
-
// Process inline widgets
|
|
2247
2207
|
for (const codeNode of codeNodes) {
|
|
2248
2208
|
if (
|
|
2249
2209
|
Array.isArray(codeNode.codeHookParams) &&
|
|
2250
|
-
codeNode.codeHookParams.length
|
|
2210
|
+
codeNode.codeHookParams.length > 0
|
|
2251
2211
|
) {
|
|
2252
|
-
const
|
|
2253
|
-
const
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
if (url) {
|
|
2263
|
-
let videoId = '';
|
|
2264
|
-
try {
|
|
2265
|
-
const urlObj = new URL(url);
|
|
2266
|
-
if (
|
|
2267
|
-
urlObj.hostname === 'iframe.mediadelivery.net' &&
|
|
2268
|
-
urlObj.pathname.startsWith('/embed/')
|
|
2269
|
-
) {
|
|
2270
|
-
const pathParts = urlObj.pathname.split('/');
|
|
2271
|
-
if (pathParts.length >= 4) {
|
|
2272
|
-
videoId = `${pathParts[2]}/${pathParts[3]}`;
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
} catch (error) {
|
|
2276
|
-
console.error('Error extracting video ID from URL:', error);
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
if (videoId && !processedVideoIds.has(videoId)) {
|
|
2280
|
-
results.push({ url, title, videoId });
|
|
2212
|
+
const videoId = String(codeNode.codeHookParams[0] || '');
|
|
2213
|
+
const title = String(codeNode.codeHookParams[1] || 'Untitled Video');
|
|
2214
|
+
|
|
2215
|
+
if (videoId && /^\d+\/[a-f0-9\-]{36}$/.test(videoId)) {
|
|
2216
|
+
if (!processedVideoIds.has(videoId)) {
|
|
2217
|
+
results.push({
|
|
2218
|
+
url: `${BUNNY_EMBED_BASE_URL}${videoId}`,
|
|
2219
|
+
title,
|
|
2220
|
+
videoId,
|
|
2221
|
+
});
|
|
2281
2222
|
processedVideoIds.add(videoId);
|
|
2282
2223
|
}
|
|
2283
2224
|
}
|
|
2284
2225
|
}
|
|
2285
2226
|
}
|
|
2286
|
-
|
|
2287
2227
|
return results;
|
|
2288
2228
|
}
|
|
2289
2229
|
|
|
@@ -2316,10 +2256,41 @@ export class NodesContext {
|
|
|
2316
2256
|
|
|
2317
2257
|
getDirtyNodesClassData(): { dirtyPaneIds: string[]; classes: string[] } {
|
|
2318
2258
|
const dirtyNodes = this.getDirtyNodes();
|
|
2259
|
+
|
|
2319
2260
|
const dirtyPaneIds = dirtyNodes
|
|
2320
2261
|
.filter((node) => node.nodeType === 'Pane')
|
|
2321
2262
|
.map((node) => node.id);
|
|
2322
|
-
|
|
2263
|
+
|
|
2264
|
+
// Collect all nodes that need class extraction
|
|
2265
|
+
const allNodesToExtract: BaseNode[] = [];
|
|
2266
|
+
|
|
2267
|
+
// Find root dirty nodes (dirty nodes whose parents are NOT dirty)
|
|
2268
|
+
const dirtyNodeIds = new Set(dirtyNodes.map((n) => n.id));
|
|
2269
|
+
const rootDirtyNodes = dirtyNodes.filter(
|
|
2270
|
+
(node) => !node.parentId || !dirtyNodeIds.has(node.parentId)
|
|
2271
|
+
);
|
|
2272
|
+
|
|
2273
|
+
// For each root dirty node, traverse all descendants
|
|
2274
|
+
rootDirtyNodes.forEach((rootNode) => {
|
|
2275
|
+
// Add the root node itself
|
|
2276
|
+
allNodesToExtract.push(rootNode);
|
|
2277
|
+
|
|
2278
|
+
// Traverse all descendants using breadth-first
|
|
2279
|
+
const queue = [...this.getChildNodeIDs(rootNode.id)];
|
|
2280
|
+
while (queue.length > 0) {
|
|
2281
|
+
const currentId = queue.shift();
|
|
2282
|
+
if (!currentId) continue;
|
|
2283
|
+
|
|
2284
|
+
const currentNode = this.allNodes.get().get(currentId);
|
|
2285
|
+
if (currentNode) {
|
|
2286
|
+
allNodesToExtract.push(currentNode);
|
|
2287
|
+
const childrenIds = this.getChildNodeIDs(currentId);
|
|
2288
|
+
queue.push(...childrenIds);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
const classes = extractClassesFromNodes(allNodesToExtract);
|
|
2323
2294
|
|
|
2324
2295
|
return { dirtyPaneIds, classes };
|
|
2325
2296
|
}
|
|
@@ -161,11 +161,10 @@ const pollingState = new Map<
|
|
|
161
161
|
}
|
|
162
162
|
>();
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const MAX_POLLING_INTERVAL = 32000; // 32 seconds max interval
|
|
164
|
+
const MAX_POLLING_ATTEMPTS = 25;
|
|
165
|
+
const MAX_POLLING_DURATION = 10 * 60 * 1000; // 10 minutes
|
|
166
|
+
const BASE_POLLING_INTERVAL = 10000; // 10 seconds
|
|
167
|
+
const MAX_POLLING_INTERVAL = 30000; // 30 seconds
|
|
169
168
|
|
|
170
169
|
const fetchingStates = new Map<string, boolean>();
|
|
171
170
|
|
|
@@ -203,7 +202,7 @@ export async function loadOrphanAnalysis(): Promise<void> {
|
|
|
203
202
|
|
|
204
203
|
updateTenantState(tenantId, {
|
|
205
204
|
data,
|
|
206
|
-
isLoading:
|
|
205
|
+
isLoading: data.status === 'loading', // Only stop loading if complete
|
|
207
206
|
error: null,
|
|
208
207
|
lastFetched: Date.now(),
|
|
209
208
|
});
|
|
@@ -239,7 +238,7 @@ function startPolling(tenantId: string): void {
|
|
|
239
238
|
lastAttemptTime: startTime,
|
|
240
239
|
});
|
|
241
240
|
|
|
242
|
-
// Start the first poll
|
|
241
|
+
// Start the first poll
|
|
243
242
|
scheduleNextPoll(tenantId);
|
|
244
243
|
}
|
|
245
244
|
|
|
@@ -260,21 +259,21 @@ function scheduleNextPoll(tenantId: string): void {
|
|
|
260
259
|
const elapsed = Date.now() - state.startTime;
|
|
261
260
|
if (elapsed >= MAX_POLLING_DURATION) {
|
|
262
261
|
console.warn(
|
|
263
|
-
`Orphan analysis polling stopped: Maximum duration (${
|
|
262
|
+
`Orphan analysis polling stopped: Maximum duration (${
|
|
263
|
+
MAX_POLLING_DURATION / 1000
|
|
264
|
+
}s) exceeded for tenant ${tenantId}`
|
|
264
265
|
);
|
|
265
266
|
handlePollingFailure(tenantId, 'Polling timeout exceeded');
|
|
266
267
|
return;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
);
|
|
277
|
-
}
|
|
270
|
+
// This is more suitable for long-running jobs, as it spaces out requests
|
|
271
|
+
// even when the server responds successfully with a 'loading' status.
|
|
272
|
+
// Polling sequence: 10s → 20s → 30s → 30s...
|
|
273
|
+
const delay = Math.min(
|
|
274
|
+
BASE_POLLING_INTERVAL * Math.pow(2, state.attempts),
|
|
275
|
+
MAX_POLLING_INTERVAL
|
|
276
|
+
);
|
|
278
277
|
|
|
279
278
|
// Schedule the next poll
|
|
280
279
|
const timeoutId = setTimeout(() => executePoll(tenantId), delay);
|
|
@@ -304,6 +303,7 @@ async function executePoll(tenantId: string): Promise<void> {
|
|
|
304
303
|
|
|
305
304
|
// Check if analysis is complete
|
|
306
305
|
if (data.status === 'complete') {
|
|
306
|
+
updateTenantState(tenantId, { isLoading: false });
|
|
307
307
|
stopPolling(tenantId);
|
|
308
308
|
return;
|
|
309
309
|
}
|
|
@@ -329,7 +329,6 @@ async function executePoll(tenantId: string): Promise<void> {
|
|
|
329
329
|
error instanceof Error ? error.message : 'Unknown polling error';
|
|
330
330
|
updateTenantState(tenantId, {
|
|
331
331
|
error: `Polling error (attempt ${state.attempts}/${MAX_POLLING_ATTEMPTS}): ${errorMessage}`,
|
|
332
|
-
lastFetched: Date.now(),
|
|
333
332
|
});
|
|
334
333
|
|
|
335
334
|
// Check if we should stop polling due to consecutive errors
|
|
@@ -344,7 +343,7 @@ async function executePoll(tenantId: string): Promise<void> {
|
|
|
344
343
|
return;
|
|
345
344
|
}
|
|
346
345
|
|
|
347
|
-
// Schedule next poll
|
|
346
|
+
// Schedule next poll (will use exponential backoff due to increased consecutiveErrors)
|
|
348
347
|
scheduleNextPoll(tenantId);
|
|
349
348
|
}
|
|
350
349
|
}
|
|
@@ -357,8 +356,7 @@ function handlePollingFailure(tenantId: string, reason: string): void {
|
|
|
357
356
|
// Update tenant state with final error
|
|
358
357
|
updateTenantState(tenantId, {
|
|
359
358
|
isLoading: false,
|
|
360
|
-
error: `Orphan analysis
|
|
361
|
-
lastFetched: Date.now(),
|
|
359
|
+
error: `Orphan analysis failed: ${reason}. Please try refreshing the page.`,
|
|
362
360
|
});
|
|
363
361
|
|
|
364
362
|
// Clean up polling state
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { atom, map } from 'nanostores';
|
|
2
2
|
import { persistentAtom } from '@nanostores/persistent';
|
|
3
3
|
import { handleSettingsPanelMobile } from '@/utils/layout';
|
|
4
|
+
import { getCtx, ROOT_NODE_NAME } from '@/stores/nodes';
|
|
4
5
|
import type {
|
|
5
6
|
FullContentMapItem,
|
|
6
7
|
Theme,
|
|
@@ -28,6 +29,8 @@ export const preferredThemeStore = atom<Theme>('light');
|
|
|
28
29
|
export const hasAssemblyAIStore = atom<boolean>(false);
|
|
29
30
|
export const codehookMapStore = atom<string[]>([]);
|
|
30
31
|
|
|
32
|
+
export const pendingHomePageSlugStore = atom<string | null>(null);
|
|
33
|
+
|
|
31
34
|
// Tool mode types
|
|
32
35
|
export type ToolModeVal =
|
|
33
36
|
| 'styles'
|
|
@@ -116,6 +119,10 @@ export const setViewportMode = (mode: ViewportKey) => {
|
|
|
116
119
|
} else {
|
|
117
120
|
viewportKeyStore.setKey('value', mode);
|
|
118
121
|
}
|
|
122
|
+
|
|
123
|
+
// Notify root node to trigger coordinated re-render
|
|
124
|
+
const ctx = getCtx();
|
|
125
|
+
ctx.notifyNode(ROOT_NODE_NAME);
|
|
119
126
|
};
|
|
120
127
|
|
|
121
128
|
export const toggleShowAnalytics = () => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { BrandConfig } from './tractstack';
|
|
2
2
|
|
|
3
|
+
export type LispToken = string | number | LispToken[];
|
|
4
|
+
|
|
3
5
|
export type ViewportKey = 'mobile' | 'tablet' | 'desktop' | 'auto';
|
|
4
6
|
export type ViewportAuto = 'mobile' | 'tablet' | 'desktop';
|
|
5
7
|
export type ToolModeVal =
|
|
@@ -21,6 +23,7 @@ export const toolAddModes = [
|
|
|
21
23
|
'yt',
|
|
22
24
|
'bunny',
|
|
23
25
|
'belief',
|
|
26
|
+
'interactiveDisclosure',
|
|
24
27
|
'identify',
|
|
25
28
|
'toggle',
|
|
26
29
|
//"aside",
|
|
@@ -71,6 +74,7 @@ export type SettingsPanelSignal = {
|
|
|
71
74
|
className?: string;
|
|
72
75
|
minimized?: boolean;
|
|
73
76
|
expanded?: boolean;
|
|
77
|
+
editLock?: number;
|
|
74
78
|
};
|
|
75
79
|
|
|
76
80
|
export interface OgImageParams {
|
|
@@ -135,6 +139,7 @@ export type Tag =
|
|
|
135
139
|
| 'signup'
|
|
136
140
|
| 'yt'
|
|
137
141
|
| 'bunny'
|
|
142
|
+
| 'interactiveDisclosure'
|
|
138
143
|
| 'belief'
|
|
139
144
|
| 'identify'
|
|
140
145
|
| 'toggle'
|
|
@@ -156,6 +161,7 @@ export const tagTitles: Record<Tag, string> = {
|
|
|
156
161
|
signup: 'Email Signup Widget',
|
|
157
162
|
yt: 'YouTube Widget',
|
|
158
163
|
bunny: 'Bunny Video Widget',
|
|
164
|
+
interactiveDisclosure: 'Interactive Disclosure',
|
|
159
165
|
belief: 'Belief Select Widget',
|
|
160
166
|
toggle: 'Belief Toggle Widget',
|
|
161
167
|
identify: 'Identify As Widget',
|
|
@@ -443,3 +443,20 @@ export interface BunnyPlayer {
|
|
|
443
443
|
export interface PlayerJS {
|
|
444
444
|
Player: new (elementId: string) => BunnyPlayer;
|
|
445
445
|
}
|
|
446
|
+
|
|
447
|
+
export interface DiscoverySuggestion {
|
|
448
|
+
term: string;
|
|
449
|
+
type: 'EXACT' | 'TOPIC' | 'TEXT' | 'TITLE';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export interface FTSResult {
|
|
453
|
+
ID: string;
|
|
454
|
+
Relevance: number;
|
|
455
|
+
Term: string;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export interface CategorizedResults {
|
|
459
|
+
storyFragmentResults: FTSResult[];
|
|
460
|
+
contextPaneResults: FTSResult[];
|
|
461
|
+
resourceResults: FTSResult[];
|
|
462
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LispToken } from '@/types/compositorTypes';
|
|
2
|
+
|
|
1
3
|
const DOUBLEQUOTE = [`"`];
|
|
2
4
|
const BRACKETLEFT = `(`;
|
|
3
5
|
const BRACKETRIGHT = `)`;
|
|
@@ -5,8 +7,6 @@ const SEMICOLON = `;`;
|
|
|
5
7
|
const NEWLINE = `\n`;
|
|
6
8
|
const WHITESPACE = [` `, `\n`, `\t`];
|
|
7
9
|
|
|
8
|
-
type LispToken = string | number | LispToken[];
|
|
9
|
-
|
|
10
10
|
export function lispLexer(
|
|
11
11
|
payload: string = ``,
|
|
12
12
|
inString: boolean = false
|
|
@@ -4,9 +4,6 @@ import type {
|
|
|
4
4
|
FieldErrors,
|
|
5
5
|
} from '@/types/tractstack';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Convert backend BeliefNode to frontend BeliefNodeState
|
|
9
|
-
*/
|
|
10
7
|
export function convertToLocalState(beliefNode: BeliefNode): BeliefNodeState {
|
|
11
8
|
return {
|
|
12
9
|
id: beliefNode.id,
|
|
@@ -17,9 +14,6 @@ export function convertToLocalState(beliefNode: BeliefNode): BeliefNodeState {
|
|
|
17
14
|
};
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
/**
|
|
21
|
-
* Convert frontend BeliefNodeState to backend BeliefNode format
|
|
22
|
-
*/
|
|
23
17
|
export function convertToBackendFormat(state: BeliefNodeState): BeliefNode {
|
|
24
18
|
return {
|
|
25
19
|
id: state.id,
|
|
@@ -31,47 +25,45 @@ export function convertToBackendFormat(state: BeliefNodeState): BeliefNode {
|
|
|
31
25
|
};
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
/**
|
|
35
|
-
* Validate belief node state
|
|
36
|
-
*/
|
|
37
28
|
export function validateBeliefNode(state: BeliefNodeState): FieldErrors {
|
|
38
29
|
const errors: FieldErrors = {};
|
|
39
30
|
|
|
40
|
-
// Validate title
|
|
41
31
|
if (!state.title?.trim()) {
|
|
42
32
|
errors.title = 'Title is required';
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
// Validate slug
|
|
46
35
|
if (!state.slug?.trim()) {
|
|
47
36
|
errors.slug = 'Slug is required';
|
|
37
|
+
} else {
|
|
38
|
+
const slugRegex = /^[a-zA-Z]+$/;
|
|
39
|
+
if (!slugRegex.test(state.slug)) {
|
|
40
|
+
errors.slug = 'Slug must contain only letters (a-z, A-Z)';
|
|
41
|
+
}
|
|
48
42
|
}
|
|
49
43
|
|
|
50
|
-
// Validate scale
|
|
51
44
|
if (!state.scale?.trim()) {
|
|
52
45
|
errors.scale = 'Scale is required';
|
|
53
46
|
}
|
|
54
47
|
|
|
55
|
-
// Validate custom values if scale is custom
|
|
56
48
|
if (state.scale === 'custom') {
|
|
57
49
|
if (!state.customValues || state.customValues.length === 0) {
|
|
58
50
|
errors.customValues =
|
|
59
51
|
'At least one custom value is required for custom scale';
|
|
60
52
|
} else {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
const valueRegex = /^[a-zA-Z]([a-zA-Z0-9?!]| (?=[a-zA-Z0-9?!]))*$/;
|
|
54
|
+
for (const value of state.customValues) {
|
|
55
|
+
if (value.trim() && !valueRegex.test(value)) {
|
|
56
|
+
errors.customValues =
|
|
57
|
+
'Values must start with a letter, have no double or trailing spaces, and use valid characters.';
|
|
58
|
+
break;
|
|
64
59
|
}
|
|
65
|
-
}
|
|
60
|
+
}
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
|
|
69
64
|
return errors;
|
|
70
65
|
}
|
|
71
66
|
|
|
72
|
-
/**
|
|
73
|
-
* State interceptor for form state management
|
|
74
|
-
*/
|
|
75
67
|
export function beliefStateIntercept(
|
|
76
68
|
state: BeliefNodeState,
|
|
77
69
|
field: keyof BeliefNodeState,
|
|
@@ -88,7 +80,6 @@ export function beliefStateIntercept(
|
|
|
88
80
|
break;
|
|
89
81
|
case 'scale':
|
|
90
82
|
newState.scale = value || '';
|
|
91
|
-
// Clear custom values when scale changes away from custom
|
|
92
83
|
if (value !== 'custom') {
|
|
93
84
|
newState.customValues = [];
|
|
94
85
|
}
|
|
@@ -103,9 +94,6 @@ export function beliefStateIntercept(
|
|
|
103
94
|
return newState;
|
|
104
95
|
}
|
|
105
96
|
|
|
106
|
-
/**
|
|
107
|
-
* Add a new custom value to the state
|
|
108
|
-
*/
|
|
109
97
|
export function addCustomValue(
|
|
110
98
|
state: BeliefNodeState,
|
|
111
99
|
value: string
|
|
@@ -118,9 +106,6 @@ export function addCustomValue(
|
|
|
118
106
|
};
|
|
119
107
|
}
|
|
120
108
|
|
|
121
|
-
/**
|
|
122
|
-
* Remove a custom value from the state
|
|
123
|
-
*/
|
|
124
109
|
export function removeCustomValue(
|
|
125
110
|
state: BeliefNodeState,
|
|
126
111
|
index: number
|
|
@@ -131,9 +116,6 @@ export function removeCustomValue(
|
|
|
131
116
|
};
|
|
132
117
|
}
|
|
133
118
|
|
|
134
|
-
/**
|
|
135
|
-
* Update a specific custom value in the state
|
|
136
|
-
*/
|
|
137
119
|
export function updateCustomValue(
|
|
138
120
|
state: BeliefNodeState,
|
|
139
121
|
index: number,
|
|
@@ -148,9 +130,6 @@ export function updateCustomValue(
|
|
|
148
130
|
};
|
|
149
131
|
}
|
|
150
132
|
|
|
151
|
-
/**
|
|
152
|
-
* Scale options for the belief form
|
|
153
|
-
*/
|
|
154
133
|
export const SCALE_OPTIONS = [
|
|
155
134
|
{ value: 'likert', label: 'Likert Scale (1-5)' },
|
|
156
135
|
{ value: 'agreement', label: 'Agreement (Agree/Disagree)' },
|
|
@@ -160,9 +139,6 @@ export const SCALE_OPTIONS = [
|
|
|
160
139
|
{ value: 'custom', label: 'Custom Values' },
|
|
161
140
|
];
|
|
162
141
|
|
|
163
|
-
/**
|
|
164
|
-
* Get scale preview data for displaying scale options
|
|
165
|
-
*/
|
|
166
142
|
export function getScalePreview(scale: string) {
|
|
167
143
|
const scalePreviewData: {
|
|
168
144
|
[key: string]: Array<{ id: number; name: string; color: string }>;
|
|
@@ -62,9 +62,9 @@ export function validateMenuNode(state: MenuNodeState): FieldErrors {
|
|
|
62
62
|
errors[`menuLinks.${index}.actionLisp`] = 'Action is required';
|
|
63
63
|
} else {
|
|
64
64
|
// Basic ActionLisp validation
|
|
65
|
-
if (!link.actionLisp.startsWith('(
|
|
65
|
+
if (!link.actionLisp.startsWith('(')) {
|
|
66
66
|
errors[`menuLinks.${index}.actionLisp`] =
|
|
67
|
-
'Action must start with "(
|
|
67
|
+
'Action must start with "("';
|
|
68
68
|
}
|
|
69
69
|
if (!link.actionLisp.endsWith('))')) {
|
|
70
70
|
errors[`menuLinks.${index}.actionLisp`] = 'Action must end with "))"';
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DiscoverySuggestion,
|
|
3
|
+
FTSResult,
|
|
4
|
+
CategorizedResults,
|
|
5
|
+
} from '@/types/tractstack';
|
|
6
|
+
|
|
1
7
|
export interface APIResponse<T = any> {
|
|
2
8
|
success: boolean;
|
|
3
9
|
data?: T;
|
|
@@ -73,6 +79,11 @@ export class TractStackAPI {
|
|
|
73
79
|
const defaultHeaders = {
|
|
74
80
|
'Content-Type': 'application/json',
|
|
75
81
|
'X-Tenant-ID': this.tenantId,
|
|
82
|
+
...(typeof window !== 'undefined' &&
|
|
83
|
+
(window as any).TRACTSTACK_CONFIG?.sessionId && {
|
|
84
|
+
'X-TractStack-Session-ID': (window as any).TRACTSTACK_CONFIG
|
|
85
|
+
.sessionId,
|
|
86
|
+
}),
|
|
76
87
|
};
|
|
77
88
|
|
|
78
89
|
try {
|
|
@@ -133,6 +144,21 @@ export class TractStackAPI {
|
|
|
133
144
|
});
|
|
134
145
|
}
|
|
135
146
|
|
|
147
|
+
async discover(
|
|
148
|
+
query: string
|
|
149
|
+
): Promise<APIResponse<{ suggestions: DiscoverySuggestion[] }>> {
|
|
150
|
+
return this.get(`/api/v1/search/discover?q=${encodeURIComponent(query)}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async retrieve(
|
|
154
|
+
term: string,
|
|
155
|
+
isTopic: boolean = false
|
|
156
|
+
): Promise<APIResponse<CategorizedResults>> {
|
|
157
|
+
return this.get(
|
|
158
|
+
`/api/v1/search/retrieve?term=${encodeURIComponent(term)}&topic=${isTopic}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
async getContent(slug: string): Promise<APIResponse> {
|
|
137
163
|
return this.get(`/api/v1/content/${slug}`);
|
|
138
164
|
}
|
|
@@ -116,6 +116,13 @@ export const TemplateBeliefNode = {
|
|
|
116
116
|
codeHookParams: ['BeliefTag', 'likert', 'prompt'],
|
|
117
117
|
} as TemplateNode;
|
|
118
118
|
|
|
119
|
+
export const TemplateDisclosureNode = {
|
|
120
|
+
nodeType: 'TagElement',
|
|
121
|
+
tagName: 'code',
|
|
122
|
+
copy: 'interactiveDisclosure(BeliefTag)',
|
|
123
|
+
codeHookParams: ['BeliefTag'],
|
|
124
|
+
} as TemplateNode;
|
|
125
|
+
|
|
119
126
|
export const TemplateBunnyNode = {
|
|
120
127
|
nodeType: 'TagElement',
|
|
121
128
|
tagName: 'code',
|
|
@@ -18,6 +18,7 @@ const allowInsert = (
|
|
|
18
18
|
switch (tagNameNew) {
|
|
19
19
|
case 'bunny':
|
|
20
20
|
case 'yt':
|
|
21
|
+
case 'interactiveDisclosure':
|
|
21
22
|
case 'belief':
|
|
22
23
|
case 'toggle':
|
|
23
24
|
case 'identify':
|
|
@@ -43,14 +44,14 @@ const allowInsert = (
|
|
|
43
44
|
|
|
44
45
|
default:
|
|
45
46
|
console.log(
|
|
46
|
-
`
|
|
47
|
+
`miss on allowInsert: tagName:${tagName} tagNameNew:${tagNameNew} tagNameAdjacent:${tagNameAdjacent}`
|
|
47
48
|
);
|
|
48
49
|
}
|
|
49
50
|
break;
|
|
50
51
|
|
|
51
52
|
default:
|
|
52
53
|
console.log(
|
|
53
|
-
`
|
|
54
|
+
`miss on allowInsert: tagName:${tagName} tagNameNew:${tagNameNew} tagNameAdjacent:${tagNameAdjacent}`
|
|
54
55
|
);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -61,6 +62,7 @@ const allowInsert = (
|
|
|
61
62
|
case 'bunny':
|
|
62
63
|
case 'yt':
|
|
63
64
|
case 'belief':
|
|
65
|
+
case 'interactiveDisclosure':
|
|
64
66
|
case 'toggle':
|
|
65
67
|
case 'identify':
|
|
66
68
|
case 'signup':
|
|
@@ -83,7 +85,7 @@ const allowInsert = (
|
|
|
83
85
|
return false;
|
|
84
86
|
default:
|
|
85
87
|
console.log(
|
|
86
|
-
`
|
|
88
|
+
`miss on allowInsert: tagName:${tagName} tagNameNew:${tagNameNew} tagNameAdjacent:${tagNameAdjacent}`
|
|
87
89
|
);
|
|
88
90
|
}
|
|
89
91
|
break;
|