@sampleapp.ai/sdk 1.0.42 → 1.0.43

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
@@ -15,8 +15,16 @@ const SandboxHome = (props) => React.createElement(ShadowDomWrapper, null, React
15
15
  export { ChatButton, ModalSearchAndChat, ChatBar, TailwindExample, SandboxHome };
16
16
  // Export Sandbox component and types
17
17
  export { Sandbox } from "./components/sandbox";
18
+ // Export sandbox API functions
19
+ export { fetchSandboxConfig, fetchSandboxConfigWithContent, buildConfigFromContent } from "./components/sandbox/api";
20
+ // Export tree selector utilities and types
21
+ export { useTreeSelector } from "./hooks/use-tree-selector";
22
+ export { getNodeAtPath, getNodesAlongPath, pathToByType, getDefaultPath, getSelectableSections, updateSelectionPath, createSelection, isPathComplete, getNodeLabel } from "./lib/tree-utils";
23
+ export { findMatchingContent, hasContentForSelection, getUniqueFieldValues, filterContent } from "./lib/content-matcher";
24
+ // Export API client
25
+ export { createApiClient, extractContainerIdFromUrl } from "./lib/api-client";
18
26
  // Export Guardian components (now nested under sandbox)
19
- export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, } from "./components/sandbox/guardian";
27
+ export { GuardianPlayground, GuardianComponent, GuardianProvider, VmProvider, buildGuardianConfig, Header } from "./components/sandbox/guardian";
20
28
  // Export themes
21
29
  export { themes, getTheme, DEFAULT_THEME } from "./themes";
22
30
  // SDK class for loadScript programmatic usage
@@ -33,7 +41,7 @@ class SampleAppSDK {
33
41
  onClose: () => {
34
42
  root.unmount();
35
43
  modalContainer.remove();
36
- },
44
+ }
37
45
  });
38
46
  root.render(component);
39
47
  return {
@@ -46,7 +54,7 @@ class SampleAppSDK {
46
54
  },
47
55
  hide: () => {
48
56
  modalContainer.style.display = "none";
49
- },
57
+ }
50
58
  };
51
59
  };
52
60
  this.ChatButton = (settings, container) => {
@@ -58,7 +66,7 @@ class SampleAppSDK {
58
66
  unmount: () => {
59
67
  root.unmount();
60
68
  targetContainer.remove();
61
- },
69
+ }
62
70
  };
63
71
  };
64
72
  }
@@ -74,6 +74,74 @@ class ApiClient {
74
74
  method: "GET",
75
75
  }, { skip, limit });
76
76
  },
77
+ /**
78
+ * Get all sandbox contents for a specific use case
79
+ * Used for upfront loading to enable local filtering
80
+ * @param useCaseId - The ID of the use case
81
+ * @param skip - Number of records to skip (default: 0)
82
+ * @param limit - Maximum number of records to return (default: 100)
83
+ * @returns Array of sandbox content data
84
+ */
85
+ getByUseCaseId: async (useCaseId, skip = 0, limit = 100) => {
86
+ return this.request(["sandbox-content", "use-case", useCaseId.toString()], {
87
+ method: "GET",
88
+ }, { skip, limit });
89
+ },
90
+ };
91
+ /**
92
+ * Sandbox use case endpoints
93
+ */
94
+ this.sandboxUseCase = {
95
+ /**
96
+ * Get a sandbox use case by UID
97
+ * @param uid - The UID of the use case
98
+ * @returns The use case data including tech_stack_config
99
+ */
100
+ getByUid: async (uid) => {
101
+ return this.request(["sandbox-use-case", uid]);
102
+ },
103
+ /**
104
+ * Get a sandbox use case by ID
105
+ * @param id - The numeric ID of the use case
106
+ * @returns The use case data including tech_stack_config
107
+ */
108
+ getById: async (id) => {
109
+ return this.request(["sandbox-use-case", "id", id.toString()]);
110
+ },
111
+ /**
112
+ * Get all sandbox use cases for a specific playground
113
+ * @param playgroundUid - The UID of the playground
114
+ * @param skip - Number of records to skip (default: 0)
115
+ * @param limit - Maximum number of records to return (default: 100)
116
+ * @returns Array of use case data
117
+ */
118
+ getByPlayground: async (playgroundUid, skip = 0, limit = 100) => {
119
+ return this.request(["sandbox-use-case", "playground", playgroundUid], {
120
+ method: "GET",
121
+ }, { skip, limit });
122
+ },
123
+ /**
124
+ * Get all template use cases (public access)
125
+ * @param skip - Number of records to skip (default: 0)
126
+ * @param limit - Maximum number of records to return (default: 100)
127
+ * @returns Array of template use case data
128
+ */
129
+ getTemplates: async (skip = 0, limit = 100) => {
130
+ return this.request(["sandbox-use-case", "templates", "list"], {
131
+ method: "GET",
132
+ }, { skip, limit });
133
+ },
134
+ /**
135
+ * Get all public use cases
136
+ * @param skip - Number of records to skip (default: 0)
137
+ * @param limit - Maximum number of records to return (default: 100)
138
+ * @returns Array of public use case data
139
+ */
140
+ getPublic: async (skip = 0, limit = 100) => {
141
+ return this.request(["sandbox-use-case", "public", "list"], {
142
+ method: "GET",
143
+ }, { skip, limit });
144
+ },
77
145
  };
78
146
  /**
79
147
  * SDK endpoints
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Utility functions for matching sandbox content to tree selections
3
+ * Enables local filtering without API calls after initial load
4
+ */
5
+ /**
6
+ * Find sandbox content that matches the current tree selection
7
+ * Matches against flat fields: product, product_version, architecture, frontend, backend, framework
8
+ *
9
+ * @param allContent - All sandbox content entries for the use case
10
+ * @param selection - Current tree selection
11
+ * @returns Matching content or null if not found
12
+ */
13
+ export function findMatchingContent(allContent, selection) {
14
+ const { byType } = selection;
15
+ const match = allContent.find((content) => {
16
+ // Match each selected dimension against content fields
17
+ // Only check fields that are present in the selection
18
+ if (byType.product && content.product !== byType.product)
19
+ return false;
20
+ if (byType.version && content.product_version !== byType.version)
21
+ return false;
22
+ if (byType.architecture && content.architecture !== byType.architecture)
23
+ return false;
24
+ if (byType.frontend && content.frontend !== byType.frontend)
25
+ return false;
26
+ if (byType.backend && content.backend !== byType.backend)
27
+ return false;
28
+ if (byType.framework && content.framework !== byType.framework)
29
+ return false;
30
+ return true;
31
+ });
32
+ return match || null;
33
+ }
34
+ /**
35
+ * Check if content exists for a potential selection
36
+ * Used to disable/gray out options that have no content
37
+ *
38
+ * @param allContent - All sandbox content entries for the use case
39
+ * @param partialSelection - Partial selection to check (only specified fields are matched)
40
+ * @returns True if at least one content entry matches
41
+ */
42
+ export function hasContentForSelection(allContent, partialSelection) {
43
+ return allContent.some((content) => {
44
+ if (partialSelection.product &&
45
+ content.product !== partialSelection.product)
46
+ return false;
47
+ if (partialSelection.version &&
48
+ content.product_version !== partialSelection.version)
49
+ return false;
50
+ if (partialSelection.architecture &&
51
+ content.architecture !== partialSelection.architecture)
52
+ return false;
53
+ if (partialSelection.frontend &&
54
+ content.frontend !== partialSelection.frontend)
55
+ return false;
56
+ if (partialSelection.backend &&
57
+ content.backend !== partialSelection.backend)
58
+ return false;
59
+ if (partialSelection.framework &&
60
+ content.framework !== partialSelection.framework)
61
+ return false;
62
+ return true;
63
+ });
64
+ }
65
+ /**
66
+ * Get all unique values for a specific field from content entries
67
+ * Useful for building filter options
68
+ *
69
+ * @param allContent - All sandbox content entries
70
+ * @param field - The field to extract unique values from
71
+ * @returns Array of unique non-null values
72
+ */
73
+ export function getUniqueFieldValues(allContent, field) {
74
+ const values = new Set();
75
+ for (const content of allContent) {
76
+ const value = content[field];
77
+ if (value) {
78
+ values.add(value);
79
+ }
80
+ }
81
+ return Array.from(values);
82
+ }
83
+ /**
84
+ * Filter content entries by multiple criteria
85
+ *
86
+ * @param allContent - All sandbox content entries
87
+ * @param filters - Object with field names and values to filter by
88
+ * @returns Filtered array of content entries
89
+ */
90
+ export function filterContent(allContent, filters) {
91
+ return allContent.filter((content) => {
92
+ for (const [key, value] of Object.entries(filters)) {
93
+ if (value && content[key] !== value) {
94
+ return false;
95
+ }
96
+ }
97
+ return true;
98
+ });
99
+ }
@@ -9,13 +9,25 @@ export const ShadowDomWrapper = ({ children, }) => {
9
9
  const containerRef = useRef(null);
10
10
  const shadowRootRef = useRef(null);
11
11
  const mountPointRef = useRef(null);
12
+ const rootRef = useRef(null);
13
+ // Initialize shadow DOM once on mount
12
14
  useEffect(() => {
13
15
  if (!containerRef.current)
14
16
  return;
15
- // Create shadow DOM
16
- const shadow = containerRef.current.attachShadow({ mode: "open" });
17
- shadowRootRef.current = shadow;
18
- // Create mount point
17
+ let shadow;
18
+ // Check if shadow root already exists (e.g., from hot reload)
19
+ if (containerRef.current.shadowRoot) {
20
+ shadow = containerRef.current.shadowRoot;
21
+ shadowRootRef.current = shadow;
22
+ // Clear existing content to prevent stale roots
23
+ shadow.innerHTML = '';
24
+ }
25
+ else {
26
+ // Create shadow DOM
27
+ shadow = containerRef.current.attachShadow({ mode: "open" });
28
+ shadowRootRef.current = shadow;
29
+ }
30
+ // Always create fresh mount point
19
31
  const mountPoint = document.createElement("div");
20
32
  mountPoint.id = "sdk-mount-point";
21
33
  // Don't reset all styles - only reset what's necessary to prevent host page interference
@@ -28,15 +40,24 @@ export const ShadowDomWrapper = ({ children, }) => {
28
40
  styleElement.textContent = TAILWIND_CSS;
29
41
  shadow.appendChild(styleElement);
30
42
  shadow.appendChild(mountPoint);
31
- // Render children into shadow DOM mount point
32
- const root = createRoot(mountPoint);
33
- root.render(children);
43
+ // Create React root
44
+ rootRef.current = createRoot(mountPoint);
34
45
  return () => {
35
- root.unmount();
36
- if (containerRef.current && shadowRootRef.current) {
37
- containerRef.current.innerHTML = "";
46
+ // Schedule unmount for after current render completes
47
+ if (rootRef.current) {
48
+ const root = rootRef.current;
49
+ rootRef.current = null;
50
+ setTimeout(() => {
51
+ root.unmount();
52
+ }, 0);
38
53
  }
39
54
  };
55
+ }, []); // Only run once on mount
56
+ // Update rendered content when children change
57
+ useEffect(() => {
58
+ if (rootRef.current) {
59
+ rootRef.current.render(children);
60
+ }
40
61
  }, [children]);
41
62
  return React.createElement("div", { ref: containerRef, style: { display: "contents" } });
42
63
  };
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Utility functions for navigating and manipulating the tech stack tree
3
+ */
4
+ import { NODE_TYPE_LABELS } from "./types/tree-config";
5
+ /**
6
+ * Get the display label for a node (falls back to key if no label)
7
+ */
8
+ export function getNodeLabel(node) {
9
+ return node.label || node.key;
10
+ }
11
+ /**
12
+ * Navigate the tree to find a node at a specific path
13
+ * @param root - The root node to start from
14
+ * @param path - Array of keys to follow
15
+ * @returns The node at the path, or null if not found
16
+ */
17
+ export function getNodeAtPath(root, path) {
18
+ let current = root;
19
+ for (const key of path) {
20
+ if (!(current === null || current === void 0 ? void 0 : current.children))
21
+ return null;
22
+ const child = current.children.find((c) => c.key === key);
23
+ if (!child)
24
+ return null;
25
+ current = child;
26
+ }
27
+ return current;
28
+ }
29
+ /**
30
+ * Get all nodes along a path (including the root)
31
+ * @param root - The root node
32
+ * @param path - Array of keys to follow
33
+ * @returns Array of nodes from root to the final node
34
+ */
35
+ export function getNodesAlongPath(root, path) {
36
+ const nodes = [root];
37
+ let current = root;
38
+ for (const key of path) {
39
+ if (!current.children)
40
+ break;
41
+ const child = current.children.find((c) => c.key === key);
42
+ if (!child)
43
+ break;
44
+ nodes.push(child);
45
+ current = child;
46
+ }
47
+ return nodes;
48
+ }
49
+ /**
50
+ * Convert a path to a byType map
51
+ * @param root - The root node
52
+ * @param path - Array of keys
53
+ * @returns Map of nodeType to selected key
54
+ */
55
+ export function pathToByType(root, path) {
56
+ const byType = {};
57
+ const nodes = getNodesAlongPath(root, path);
58
+ // Skip the root node (index 0), start from first actual selection
59
+ for (let i = 1; i < nodes.length; i++) {
60
+ const node = nodes[i];
61
+ if (node.nodeType && node.nodeType !== "root") {
62
+ byType[node.nodeType] = node.key;
63
+ }
64
+ }
65
+ return byType;
66
+ }
67
+ /**
68
+ * Get the default path through the tree (following isDefault flags or first child)
69
+ * @param root - The root node
70
+ * @returns Array of keys representing the default selection path
71
+ */
72
+ export function getDefaultPath(root) {
73
+ const path = [];
74
+ let current = root;
75
+ while (current.children && current.children.length > 0) {
76
+ // Find default child or use first child
77
+ const defaultChild = current.children.find((c) => c.isDefault) || current.children[0];
78
+ path.push(defaultChild.key);
79
+ current = defaultChild;
80
+ }
81
+ return path;
82
+ }
83
+ /**
84
+ * Get selectable sections based on current selection path
85
+ * This builds the button groups shown in the header
86
+ * @param config - The tech stack configuration
87
+ * @param currentPath - Current selection path
88
+ * @returns Array of sections with their options
89
+ */
90
+ export function getSelectableSections(config, currentPath) {
91
+ const sections = [];
92
+ const nodes = getNodesAlongPath(config.root, currentPath);
93
+ // For each level in the path, create a section showing siblings
94
+ for (let i = 0; i < nodes.length; i++) {
95
+ const node = nodes[i];
96
+ // Skip root node
97
+ if (node.nodeType === "root")
98
+ continue;
99
+ // Get parent to find siblings
100
+ const parent = i === 0 ? config.root : nodes[i - 1];
101
+ if (!parent.children)
102
+ continue;
103
+ const options = parent.children.map((sibling) => ({
104
+ key: sibling.key,
105
+ label: getNodeLabel(sibling),
106
+ isSelected: sibling.key === node.key
107
+ }));
108
+ sections.push({
109
+ nodeType: node.nodeType,
110
+ label: NODE_TYPE_LABELS[node.nodeType] || `${node.nodeType}:`,
111
+ options
112
+ });
113
+ }
114
+ // Add the next level if current node has children
115
+ const currentNode = getNodeAtPath(config.root, currentPath);
116
+ if ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.children) && currentNode.children.length > 0) {
117
+ const firstChild = currentNode.children[0];
118
+ const options = currentNode.children.map((child) => ({
119
+ key: child.key,
120
+ label: getNodeLabel(child),
121
+ isSelected: false // None selected yet at this level
122
+ }));
123
+ sections.push({
124
+ nodeType: firstChild.nodeType,
125
+ label: NODE_TYPE_LABELS[firstChild.nodeType] || `${firstChild.nodeType}:`,
126
+ options
127
+ });
128
+ }
129
+ return sections;
130
+ }
131
+ /**
132
+ * Update the selection path when a user selects an option
133
+ * @param config - The tech stack configuration
134
+ * @param currentPath - Current selection path
135
+ * @param nodeType - The nodeType of the selection being changed
136
+ * @param selectedKey - The key of the newly selected option
137
+ * @returns New selection path
138
+ */
139
+ export function updateSelectionPath(config, currentPath, nodeType, selectedKey) {
140
+ var _a;
141
+ const nodes = getNodesAlongPath(config.root, currentPath);
142
+ // Find which level this nodeType is at
143
+ let levelIndex = -1;
144
+ for (let i = 1; i < nodes.length; i++) {
145
+ if (nodes[i].nodeType === nodeType) {
146
+ levelIndex = i - 1; // Index in path (nodes includes root, path doesn't)
147
+ break;
148
+ }
149
+ }
150
+ // If not found in current path, it might be the next level
151
+ if (levelIndex === -1) {
152
+ const currentNode = getNodeAtPath(config.root, currentPath);
153
+ if ((currentNode === null || currentNode === void 0 ? void 0 : currentNode.children) &&
154
+ ((_a = currentNode.children[0]) === null || _a === void 0 ? void 0 : _a.nodeType) === nodeType) {
155
+ // Append to current path
156
+ return [...currentPath, selectedKey];
157
+ }
158
+ // Unknown level, return current path
159
+ return currentPath;
160
+ }
161
+ // Truncate path at this level and set new selection
162
+ const newPath = currentPath.slice(0, levelIndex);
163
+ newPath.push(selectedKey);
164
+ // Follow defaults for remaining levels
165
+ const newNode = getNodeAtPath(config.root, newPath);
166
+ if (newNode) {
167
+ let current = newNode;
168
+ while (current.children && current.children.length > 0) {
169
+ const defaultChild = current.children.find((c) => c.isDefault) || current.children[0];
170
+ newPath.push(defaultChild.key);
171
+ current = defaultChild;
172
+ }
173
+ }
174
+ return newPath;
175
+ }
176
+ /**
177
+ * Create a TreeSelection object from a path
178
+ */
179
+ export function createSelection(config, path) {
180
+ return {
181
+ path,
182
+ byType: pathToByType(config.root, path)
183
+ };
184
+ }
185
+ /**
186
+ * Check if a path is complete (reaches a leaf node)
187
+ */
188
+ export function isPathComplete(config, path) {
189
+ const node = getNodeAtPath(config.root, path);
190
+ return node !== null && (!node.children || node.children.length === 0);
191
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Tree configuration types for dynamic tech stack selection
3
+ * Matches the tech_stack_config structure from SandboxUseCase
4
+ */
5
+ /**
6
+ * Human-readable labels for node types
7
+ */
8
+ export const NODE_TYPE_LABELS = {
9
+ root: "",
10
+ product: "Product:",
11
+ version: "Version:",
12
+ architecture: "Architecture:",
13
+ frontend: "Frontend:",
14
+ backend: "Backend:",
15
+ framework: "Framework:",
16
+ integration: "Integration Path:",
17
+ platform: "Platform:",
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sampleapp.ai/sdk",
3
- "version": "1.0.42",
3
+ "version": "1.0.43",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for your components",
6
6
  "main": "./dist/index.umd.js",
@@ -33,7 +33,7 @@
33
33
  "preview": "vite preview",
34
34
  "type-check": "tsc --noEmit",
35
35
  "test:api-client": "tsx test-api-client.ts",
36
- "prepublishOnly": "npm run build:prod"
36
+ "prepublishOnly": "rm -rf dist && npm run build:prod"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^24.3.1",