@sampleapp.ai/sdk 1.0.42 → 1.0.44

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.
Files changed (40) hide show
  1. package/dist/components/sandbox/Sandbox.js +96 -29
  2. package/dist/components/sandbox/api.js +73 -1
  3. package/dist/components/sandbox/guardian/code-focus-section.js +4 -2
  4. package/dist/components/sandbox/guardian/context/theme-context.js +47 -0
  5. package/dist/components/sandbox/guardian/demo/guardian-demo.js +4 -4
  6. package/dist/components/sandbox/guardian/demo/left-view.js +13 -7
  7. package/dist/components/sandbox/guardian/guardian-component.js +39 -8
  8. package/dist/components/sandbox/guardian/guardian-playground.js +14 -15
  9. package/dist/components/sandbox/guardian/guardian-style-wrapper.js +8 -6
  10. package/dist/components/sandbox/guardian/header.js +37 -41
  11. package/dist/components/sandbox/guardian/hooks/use-frame-messages.js +24 -8
  12. package/dist/components/sandbox/guardian/hooks/use-frame-params.js +94 -36
  13. package/dist/components/sandbox/guardian/hooks/use-sandbox-url-loader.js +16 -4
  14. package/dist/components/sandbox/guardian/index.js +2 -0
  15. package/dist/components/sandbox/guardian/right-view/pill-file-selector.js +70 -20
  16. package/dist/components/sandbox/guardian/right-view/preview-control-bar.js +5 -2
  17. package/dist/components/sandbox/guardian/right-view/right-top-down-view/network-requests-view.js +117 -0
  18. package/dist/components/sandbox/guardian/right-view/right-top-down-view.js +38 -140
  19. package/dist/components/sandbox/guardian/right-view/right-view.js +3 -3
  20. package/dist/components/sandbox/guardian/right-view/simplified-editor.js +9 -2
  21. package/dist/components/sandbox/guardian/ui/markdown/code-group/code-block.js +7 -33
  22. package/dist/components/sandbox/guardian/ui/markdown.js +23 -14
  23. package/dist/components/sandbox/guardian/utils.js +4 -16
  24. package/dist/components/sandbox/sandbox-home/SandboxCard.js +7 -5
  25. package/dist/components/sandbox/sandbox-home/SandboxHome.js +32 -13
  26. package/dist/components/sandbox/sandbox-home/SearchBar.js +5 -3
  27. package/dist/hooks/use-tree-selector.js +98 -0
  28. package/dist/index.d.ts +631 -21
  29. package/dist/index.es.js +22806 -22028
  30. package/dist/index.js +13 -4
  31. package/dist/index.standalone.umd.js +8 -8
  32. package/dist/lib/api-client.js +68 -0
  33. package/dist/lib/content-matcher.js +99 -0
  34. package/dist/lib/generated-css.js +1 -1
  35. package/dist/lib/shadow-dom-wrapper.js +31 -10
  36. package/dist/lib/tree-utils.js +193 -0
  37. package/dist/lib/types/tree-config.js +36 -0
  38. package/dist/sdk.css +1 -1
  39. package/dist/tailwind.css +2 -2
  40. package/package.json +2 -2
@@ -1,11 +1,86 @@
1
1
  "use client";
2
- import React, { useEffect, useState } from "react";
3
- import { fetchSandboxConfig } from "./api";
2
+ import React, { useEffect, useState, useCallback, useRef } from "react";
3
+ import { fetchSandboxConfigWithContent, buildConfigFromContent } from "./api";
4
4
  import GuardianPlayground from "./guardian/guardian-playground";
5
5
  import { GuardianProvider } from "./guardian/context/guardian-context";
6
6
  import { VmProvider } from "./guardian/context/vm-context";
7
- import { buildGuardianConfig } from "./guardian/utils";
8
7
  import { Skeleton } from "../ui/skeleton";
8
+ import { useTreeSelector } from "../../hooks/use-tree-selector";
9
+ import { SELECTABLE_NODE_TYPES } from "../../lib/types/tree-config";
10
+ import { useFrameParams } from "./guardian/hooks/use-frame-params";
11
+ /**
12
+ * Inner component that uses the tree selector hook
13
+ * Separated to ensure hooks are called after data is loaded
14
+ */
15
+ function SandboxInner({ configWithContent, apiKey, env, themeColor, theme, config }) {
16
+ var _a;
17
+ const [currentConfig, setCurrentConfig] = useState(configWithContent.legacyConfig);
18
+ // Get frame params for syncing external selection changes
19
+ const frameParams = useFrameParams();
20
+ const isFrame = (config === null || config === void 0 ? void 0 : config.isFrame) || frameParams.isFrame;
21
+ // Track previous nodeTypes to detect changes
22
+ const prevNodeTypesRef = useRef({});
23
+ // Handle selection changes - update the config when user selects different options
24
+ const handleSelectionChange = useCallback((selection, content) => {
25
+ if (content) {
26
+ const newConfig = buildConfigFromContent(content);
27
+ // Apply theme color override if provided
28
+ if (themeColor) {
29
+ newConfig.themeColor = themeColor;
30
+ newConfig.useCases = newConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
31
+ }
32
+ setCurrentConfig(newConfig);
33
+ }
34
+ }, [themeColor]);
35
+ // Use the tree selector hook for managing selection state
36
+ const { sections, selectOption, currentContent, selection } = useTreeSelector({
37
+ config: configWithContent.techStackConfig,
38
+ allContent: configWithContent.allContent,
39
+ initialPath: configWithContent.initialSelectionPath,
40
+ onSelectionChange: handleSelectionChange
41
+ });
42
+ // Sync frame params with tree selector when in frame mode
43
+ // This effect listens for changes in URL params (via postMessage or direct URL changes)
44
+ // and updates the tree selector state accordingly
45
+ useEffect(() => {
46
+ if (!isFrame) {
47
+ return;
48
+ }
49
+ const prevNodeTypes = prevNodeTypesRef.current;
50
+ const currentNodeTypes = frameParams.nodeTypes;
51
+ // Check each node type for changes and call selectOption if changed
52
+ for (const nodeType of SELECTABLE_NODE_TYPES) {
53
+ const prevValue = prevNodeTypes[nodeType];
54
+ const currentValue = currentNodeTypes[nodeType];
55
+ // If the value changed and there's a new value, update the selection
56
+ if (currentValue && currentValue !== prevValue) {
57
+ // Only select if this nodeType exists in the current sections
58
+ const sectionExists = sections.some((s) => s.nodeType === nodeType);
59
+ if (sectionExists) {
60
+ // Check if the option exists in the section
61
+ const section = sections.find((s) => s.nodeType === nodeType);
62
+ const optionExists = section === null || section === void 0 ? void 0 : section.options.some((o) => o.key === currentValue);
63
+ if (optionExists) {
64
+ selectOption(nodeType, currentValue);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ // Update the ref for next comparison
70
+ prevNodeTypesRef.current = Object.assign({}, currentNodeTypes);
71
+ }, [isFrame, frameParams.nodeTypes, sections, selectOption]);
72
+ // Get the first use case and framework from current config
73
+ const firstUseCase = currentConfig.useCases[0];
74
+ const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
75
+ if (!firstUseCase || !firstFramework) {
76
+ return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
77
+ React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
78
+ }
79
+ return (React.createElement(GuardianProvider, null,
80
+ React.createElement(VmProvider, null,
81
+ React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
82
+ React.createElement(GuardianPlayground, { sandboxConfig: currentConfig, sections: sections, onSelect: selectOption, useCase: firstUseCase.id, framework: firstFramework, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: currentConfig.chatUid, theme: theme, hasNetworkView: config === null || config === void 0 ? void 0 : config.hasNetworkView, stationaryPublishedUrl: config === null || config === void 0 ? void 0 : config.stationaryPublishedUrl })))));
83
+ }
9
84
  /**
10
85
  * Sandbox component - simplified API for embedding sandboxes
11
86
  *
@@ -33,14 +108,22 @@ import { Skeleton } from "../ui/skeleton";
33
108
  * />
34
109
  * ```
35
110
  */
36
- export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", }) {
37
- var _a;
111
+ /**
112
+ *
113
+ * Configurations for the config param
114
+ * {
115
+ * isFrame: boolean;
116
+ * }
117
+ *
118
+ *
119
+ */
120
+ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "dark", config }) {
38
121
  // Validate apiKey immediately
39
122
  if (!apiKey || apiKey.trim() === "") {
40
123
  return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
41
124
  React.createElement("div", { className: "text-red-500" }, "Error: apiKey is required. Please provide a valid API key.")));
42
125
  }
43
- const [config, setConfig] = useState(null);
126
+ const [configWithContent, setConfigWithContent] = useState(null);
44
127
  const [loading, setLoading] = useState(true);
45
128
  const [error, setError] = useState(null);
46
129
  useEffect(() => {
@@ -49,14 +132,15 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "d
49
132
  try {
50
133
  setLoading(true);
51
134
  setError(null);
52
- const fetchedConfig = await fetchSandboxConfig(apiKey, sandboxId);
135
+ const fetchedConfig = await fetchSandboxConfigWithContent(apiKey, sandboxId);
53
136
  if (!cancelled) {
54
137
  // Override theme color if provided
55
138
  if (themeColor) {
56
- fetchedConfig.themeColor = themeColor;
57
- fetchedConfig.useCases = fetchedConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
139
+ fetchedConfig.legacyConfig.themeColor = themeColor;
140
+ fetchedConfig.legacyConfig.useCases =
141
+ fetchedConfig.legacyConfig.useCases.map((uc) => (Object.assign(Object.assign({}, uc), { themeColor: themeColor, frameworks: uc.frameworks.map((fw) => (Object.assign(Object.assign({}, fw), { themeColor: themeColor }))) })));
58
142
  }
59
- setConfig(fetchedConfig);
143
+ setConfigWithContent(fetchedConfig);
60
144
  setLoading(false);
61
145
  }
62
146
  }
@@ -82,25 +166,8 @@ export default function Sandbox({ apiKey, sandboxId, env, themeColor, theme = "d
82
166
  "Error: ",
83
167
  error)));
84
168
  }
85
- if (!config) {
169
+ if (!configWithContent) {
86
170
  return null;
87
171
  }
88
- // Build the nested config from use cases
89
- const nestedConfig = buildGuardianConfig(config.useCases, {
90
- playgroundUid: sandboxId,
91
- playgroundLogo: config.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, config.name)),
92
- });
93
- // Get the first use case and framework as defaults
94
- const firstUseCase = config.useCases[0];
95
- const firstFramework = (_a = firstUseCase === null || firstUseCase === void 0 ? void 0 : firstUseCase.frameworks[0]) === null || _a === void 0 ? void 0 : _a.key;
96
- if (!firstUseCase || !firstFramework) {
97
- return (React.createElement("div", { className: "flex items-center justify-center h-screen bg-white dark:bg-black" },
98
- React.createElement("div", { className: "text-red-500" }, "No use cases or frameworks found")));
99
- }
100
- // TODO: Pass env variables to the container runtime
101
- // This will be implemented when integrating with the container technology
102
- return (React.createElement(GuardianProvider, null,
103
- React.createElement(VmProvider, null,
104
- React.createElement("div", { className: "h-screen w-screen bg-white dark:bg-black" },
105
- React.createElement(GuardianPlayground, { nestedConfig: nestedConfig, useCase: firstUseCase.id, framework: firstFramework, isFrame: false, apiKey: apiKey, env: env, chatUid: config.chatUid, theme: theme })))));
172
+ return (React.createElement(SandboxInner, { configWithContent: configWithContent, apiKey: apiKey, env: env, themeColor: themeColor, theme: theme, config: config }));
106
173
  }
@@ -15,10 +15,82 @@ function createDownloadLink(downloadEndpoint) {
15
15
  document.body.removeChild(link);
16
16
  }
17
17
  /**
18
- * Fetches sandbox configuration from the API
18
+ * Fetches sandbox configuration with all content for upfront loading
19
+ * This enables instant switching between tech stack options without API calls
20
+ *
21
+ * @param apiKey - API key from SandboxProps
22
+ * @param sandboxId - Sandbox content UID (maps to sandbox_content_uid)
23
+ * @returns Extended sandbox configuration with all content
24
+ */
25
+ export async function fetchSandboxConfigWithContent(apiKey, sandboxId) {
26
+ const client = createApiClient({ apiKey });
27
+ // 1. Fetch the initially requested sandbox content
28
+ const initialContent = await client.sandboxContent.getPublic(sandboxId);
29
+ // 2. Fetch the use case to get tech_stack_config
30
+ const useCase = await client.sandboxUseCase.getById(initialContent.use_case_id);
31
+ // 3. Fetch ALL sandbox content for this use case (for local filtering)
32
+ const allContent = await client.sandboxContent.getByUseCaseId(initialContent.use_case_id);
33
+ // 4. Build legacy config for backward compatibility
34
+ const legacyConfig = buildLegacyConfig(initialContent);
35
+ return {
36
+ useCase,
37
+ techStackConfig: useCase.tech_stack_config,
38
+ allContent,
39
+ initialContent,
40
+ initialSelectionPath: initialContent.selection_path || [],
41
+ legacyConfig,
42
+ };
43
+ }
44
+ /**
45
+ * Build legacy SandboxConfig from SandboxContent for backward compatibility
46
+ */
47
+ function buildLegacyConfig(sandboxContent) {
48
+ const frameworkKey = sandboxContent.framework || Framework.NEXTJS;
49
+ const generatedCode = sandboxContent.generated_code || {};
50
+ const completeCodeZipFile = "";
51
+ return {
52
+ id: sandboxContent.uid,
53
+ name: sandboxContent.uid,
54
+ description: sandboxContent.markdown || "",
55
+ themeColor: "#3b82f6",
56
+ hasPreview: sandboxContent.has_preview,
57
+ chatUid: sandboxContent.chat_uid || "",
58
+ useCases: [
59
+ {
60
+ id: sandboxContent.uid,
61
+ name: sandboxContent.uid,
62
+ description: sandboxContent.markdown || "",
63
+ themeColor: "#3b82f6",
64
+ hasPreview: sandboxContent.has_preview,
65
+ frameworks: [
66
+ {
67
+ key: frameworkKey,
68
+ browserUrl: sandboxContent.published_url || "",
69
+ sandboxUid: sandboxContent.uid,
70
+ codeZipFile: Object.keys(generatedCode).length > 0 ? generatedCode : "",
71
+ completeCodeZipFile,
72
+ useVm: sandboxContent.is_vm_enabled || false,
73
+ CustomConsole: sandboxContent.markdown || "",
74
+ GuideView: "Default Guide",
75
+ gitUrl: sandboxContent.github_url || undefined,
76
+ },
77
+ ],
78
+ },
79
+ ],
80
+ };
81
+ }
82
+ /**
83
+ * Build SandboxConfig from any SandboxContent (for switching between selections)
84
+ */
85
+ export function buildConfigFromContent(content) {
86
+ return buildLegacyConfig(content);
87
+ }
88
+ /**
89
+ * Fetches sandbox configuration from the API (legacy method)
19
90
  * @param apiKey - API key from SandboxProps
20
91
  * @param sandboxId - Sandbox content UID (maps to sandbox_content_uid)
21
92
  * @returns Sandbox configuration
93
+ * @deprecated Use fetchSandboxConfigWithContent for tree-based selection
22
94
  */
23
95
  export async function fetchSandboxConfig(apiKey, sandboxId) {
24
96
  const client = createApiClient({
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
3
3
  import { useGuardianContext } from "./context/guardian-context";
4
+ import { useSdkTheme } from "./context/theme-context";
4
5
  // Helper function to find a node in the file tree by path
5
6
  function getTargetNode(fileTree, targetPath) {
6
7
  const parts = targetPath.split("/").filter(Boolean);
@@ -163,6 +164,7 @@ let sectionIdCounter = 0;
163
164
  export default function CodeFocusSection({ filePath, lineRange, title, description, children, themeColor, }) {
164
165
  const sectionRef = useRef(null);
165
166
  const sectionIdRef = useRef(`code-focus-${++sectionIdCounter}`);
167
+ const { isDark } = useSdkTheme();
166
168
  const { generatedCode, fileTree, setActiveFilePath, setActiveFileName, setActiveLineNumber, setActiveLineRange, updateCode, setLanguage, filesEdited, activeFilePath, activeLineRange, } = useGuardianContext();
167
169
  // Memoize the focus code function to prevent unnecessary recreations
168
170
  const focusCode = useCallback(() => {
@@ -306,7 +308,7 @@ export default function CodeFocusSection({ filePath, lineRange, title, descripti
306
308
  marginLeft: "-2rem",
307
309
  } })),
308
310
  React.createElement("div", { className: "flex items-center gap-2" },
309
- React.createElement("h3", { className: "text-md font-semibold text-gray-900 dark:text-gray-100" }, title)),
310
- description && (React.createElement("p", { className: "text-sm text-gray-400 mt-3" }, description)),
311
+ React.createElement("h3", { className: `text-md font-semibold ${isDark ? "text-gray-100" : "text-gray-900"}` }, title)),
312
+ description && (React.createElement("p", { className: `text-sm mt-3 ${isDark ? "text-gray-400" : "text-gray-600"}` }, description)),
311
313
  children && React.createElement("div", { className: "mt-3" }, children)));
312
314
  }
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import React, { createContext, useContext, useState, useEffect, } from "react";
3
+ const ThemeContext = createContext(undefined);
4
+ export function SdkThemeProvider({ children, theme = "dark" }) {
5
+ const [resolvedTheme, setResolvedTheme] = useState(theme === "system" ? "dark" : theme);
6
+ // Handle system theme preference
7
+ useEffect(() => {
8
+ if (theme === "system") {
9
+ // Check if we're in browser environment
10
+ if (typeof window === "undefined")
11
+ return;
12
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
13
+ setResolvedTheme(mediaQuery.matches ? "dark" : "light");
14
+ const handler = (e) => {
15
+ setResolvedTheme(e.matches ? "dark" : "light");
16
+ };
17
+ mediaQuery.addEventListener("change", handler);
18
+ return () => mediaQuery.removeEventListener("change", handler);
19
+ }
20
+ else {
21
+ setResolvedTheme(theme);
22
+ }
23
+ }, [theme]);
24
+ const value = {
25
+ themeMode: theme,
26
+ resolvedTheme,
27
+ isDark: resolvedTheme === "dark",
28
+ };
29
+ return (React.createElement(ThemeContext.Provider, { value: value }, children));
30
+ }
31
+ export function useSdkTheme() {
32
+ const context = useContext(ThemeContext);
33
+ if (!context) {
34
+ throw new Error("useSdkTheme must be used within a SdkThemeProvider");
35
+ }
36
+ return context;
37
+ }
38
+ /**
39
+ * Hook that returns theme-aware class names
40
+ * @param lightClass - Class to use in light mode
41
+ * @param darkClass - Class to use in dark mode
42
+ * @returns The appropriate class based on current theme
43
+ */
44
+ export function useThemeClass(lightClass, darkClass) {
45
+ const { isDark } = useSdkTheme();
46
+ return isDark ? darkClass : lightClass;
47
+ }
@@ -4,7 +4,7 @@ import ConsoleWithApp from "../ui/console-with-app";
4
4
  import { useGuardianContext } from "../context/guardian-context";
5
5
  import ConsoleWithGuide from "./left-view";
6
6
  import RightView from "../right-view/right-view";
7
- export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, }) {
7
+ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, playgroundUid, useVm, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview, isFrame = false, isGuardian = false, gitUrl, isBrowserUrlReady = false, sandboxId, apiKey, sandboxError, hasNetworkView = false, }) {
8
8
  const { isBrowserMaximized } = useGuardianContext();
9
9
  const { setCurrentView } = useGuardianContext();
10
10
  const [reloadCounter, setReloadCounter] = useState(0);
@@ -24,12 +24,12 @@ export default function GuardianDemo({ CustomConsole, GuideView, browserUrl, pla
24
24
  }, [setCurrentView]);
25
25
  // If isFrame, just return RightView directly
26
26
  if (isFrame) {
27
- return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }));
27
+ return (React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }));
28
28
  }
29
29
  // If browser is maximized, render RightView at full size without the console
30
30
  if (isBrowserMaximized && isGuardian) {
31
31
  return (React.createElement("div", { className: "w-full h-full rounded-2xl overflow-hidden" },
32
- React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError })));
32
+ React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: true, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView })));
33
33
  }
34
- return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError }) }));
34
+ return (React.createElement(ConsoleWithApp, { containerClassName: "", console: React.createElement(ConsoleWithGuide, { CustomConsole: CustomConsole, GuideView: GuideView, playgroundUid: playgroundUid, onReloadPreview: handleReloadPreview, onStageChange: handleStageChange, codeZipFile: codeZipFile, themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }), app: React.createElement(RightView, { reloadCounter: reloadCounter, overlayStage: overlayStage, browserUrl: browserUrl, useVm: useVm, codeZipFile: codeZipFile, hasPreview: hasPreview, variant: variant, themeColor: themeColor, completeCodeZipFile: completeCodeZipFile, isGuardian: isGuardian, isBrowserMaximized: isBrowserMaximized, isBrowserUrlReady: isBrowserUrlReady, isFrame: isFrame, sandboxId: sandboxId, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }) }));
35
35
  }
@@ -5,10 +5,12 @@ import React, { useMemo, useState } from "react";
5
5
  // import DefaultGuideView from "../default-guide-view";
6
6
  import { Markdown } from "../ui/markdown";
7
7
  import sampleappLogo from "../../../../assets/sampleapp-logo.png";
8
+ import { useSdkTheme } from "../context/theme-context";
8
9
  function Logo({ href, width }) {
10
+ const { isDark } = useSdkTheme();
9
11
  return (React.createElement("div", { className: "flex justify-center md:justify-start" },
10
12
  React.createElement("a", { href: href ? href : "https://sampleapp.ai", className: "flex", target: "_blank", rel: "noopener noreferrer" },
11
- React.createElement("div", { className: "invert dark:invert-0" },
13
+ React.createElement("div", { className: isDark ? "" : "invert" },
12
14
  React.createElement("img", { src: sampleappLogo, alt: "SampleApp Logo", width: width || 160, height: 40 })))));
13
15
  }
14
16
  export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundUid, onReloadPreview, onStageChange, codeZipFile, themeColor, browserUrl, gitUrl, sandboxId, apiKey, }) {
@@ -20,7 +22,7 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
20
22
  if (typeof CustomConsole === "string") {
21
23
  return (React.createElement("div", { className: "h-full flex flex-col" },
22
24
  React.createElement("div", { className: "flex-1 px-8 " },
23
- React.createElement(Markdown, { className: "text-zinc-900 dark:text-gray-100", themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
25
+ React.createElement(Markdown, { themeColor: themeColor, browserUrl: browserUrl, gitUrl: gitUrl, sandboxId: sandboxId, apiKey: apiKey }, CustomConsole))));
24
26
  }
25
27
  const CustomConsoleComponent = CustomConsole;
26
28
  return (React.createElement(CustomConsoleComponent, { onReloadPreview: onReloadPreview !== null && onReloadPreview !== void 0 ? onReloadPreview : (() => { }), onStageChange: onStageChange !== null && onStageChange !== void 0 ? onStageChange : (() => { }), themeColor: themeColor }));
@@ -56,9 +58,13 @@ export default function ConsoleWithGuide({ CustomConsole, GuideView, playgroundU
56
58
  return (React.createElement("div", { className: "flex-1 min-h-0 relative flex flex-col" },
57
59
  React.createElement("div", { className: "h-[calc(100vh-9.5rem)] min-h-0 overflow-hidden flex-1 flex flex-col" },
58
60
  React.createElement("div", { className: "flex-1 min-h-0 overflow-y-auto" }, isConsoleView && renderCustomConsole()),
59
- React.createElement("div", { className: "relative" },
60
- React.createElement("div", { className: "flex flex-row w-full items-center text-xs justify-center opacity-30 mt-1 pb-2 bg-background" },
61
- React.createElement("span", { className: "text-zinc-600 dark:text-gray-300" }, "Powered by"),
62
- React.createElement("span", { className: "hover:bg-accent rounded-sm p-1 mx-1" },
63
- React.createElement(Logo, { width: 100, href: "https://sampleapp.ai" })))))));
61
+ React.createElement(PoweredByFooter, null))));
62
+ }
63
+ function PoweredByFooter() {
64
+ const { isDark } = useSdkTheme();
65
+ return (React.createElement("div", { className: "relative" },
66
+ React.createElement("div", { className: "flex flex-row w-full items-center text-xs justify-center opacity-30 mt-1 pb-2 bg-background" },
67
+ React.createElement("span", { className: isDark ? "text-gray-300" : "text-zinc-600" }, "Powered by"),
68
+ React.createElement("span", { className: "hover:bg-accent rounded-sm p-1 mx-1" },
69
+ React.createElement(Logo, { width: 100, href: "https://sampleapp.ai" })))));
64
70
  }
@@ -8,15 +8,17 @@ import { useSandboxUrlLoader } from "./hooks/use-sandbox-url-loader";
8
8
  import { useFrameMessages } from "./hooks/use-frame-messages";
9
9
  import { useVmContext } from "./context/vm-context";
10
10
  import { cn } from "../../../lib/utils";
11
- export default function GuardianComponent({ demoOptions, frameworkOptions, firstFrameworkByUseCase, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = false, // Hardcoded to true by default, not exposed in Sandbox.tsx
12
- gitUrl, }) {
11
+ export default function GuardianComponent({ sections, onSelect, currentFramework, currentUseCase, CustomConsole, GuideView, playgroundLogo, playgroundUid, browserUrl, useVm, sandboxUid, codeZipFile, completeCodeZipFile, variant, themeColor, hasPreview = true, isFrame = false, apiKey, env, chatUid, hideHeader = false, // Hardcoded to true by default, not exposed in Sandbox.tsx
12
+ gitUrl, hasNetworkView = false, stationaryPublishedUrl }) {
13
13
  const { previewUrl, setPreviewUrl } = useGuardianContext();
14
14
  const { vmResolution } = useVmContext();
15
15
  const [sandboxError, setSandboxError] = React.useState(null);
16
16
  // Build startSandboxConfig for use-sandbox-url-loader
17
17
  // apiKey is always included (guaranteed by parent validation)
18
18
  // Include chatUid and env when browserUrl is missing (needed for startSandbox API call)
19
- const startSandboxConfig = Object.assign({ apiKey }, (!browserUrl && chatUid ? { env, chatUid } : {}));
19
+ // Include hasPreview to skip env/chatUid validation when no preview is needed
20
+ const startSandboxConfig = Object.assign({ apiKey,
21
+ hasPreview }, (!browserUrl && chatUid ? { env, chatUid } : {}));
20
22
  // Determine if browserUrl is ready to use immediately (from published_url)
21
23
  // If browserUrl exists, it's ready (startSandboxConfig may still exist with just apiKey for VM calls)
22
24
  const isBrowserUrlReady = !!browserUrl;
@@ -26,9 +28,20 @@ gitUrl, }) {
26
28
  useFrameMessages();
27
29
  // Track which main sandbox we've loaded to prevent duplicates
28
30
  const loadedSandboxRef = useRef("");
31
+ // Track initial URLs per use case for stationaryPublishedUrl feature
32
+ const initialUrlsByUseCaseRef = useRef(new Map());
29
33
  useEffect(() => {
30
34
  // Create a key for this sandbox load
31
35
  const sandboxKey = `${sandboxUid}-${browserUrl}-${useVm}`;
36
+ const useCaseKey = currentUseCase || "default";
37
+ // If stationaryPublishedUrl is enabled and we already have a URL for this use case,
38
+ // use the stored URL and skip loading a new one
39
+ if (stationaryPublishedUrl &&
40
+ initialUrlsByUseCaseRef.current.has(useCaseKey)) {
41
+ const storedUrl = initialUrlsByUseCaseRef.current.get(useCaseKey);
42
+ setPreviewUrl(storedUrl);
43
+ return;
44
+ }
32
45
  // Check if we've already initiated a load for this exact sandbox config
33
46
  if (loadedSandboxRef.current === sandboxKey) {
34
47
  return;
@@ -39,6 +52,11 @@ gitUrl, }) {
39
52
  // Found cached URL for this exact combination, use it immediately
40
53
  setPreviewUrl(existingUrl);
41
54
  loadedSandboxRef.current = sandboxKey;
55
+ // Store as initial URL for this use case if stationaryPublishedUrl is enabled
56
+ if (stationaryPublishedUrl &&
57
+ !initialUrlsByUseCaseRef.current.has(useCaseKey)) {
58
+ initialUrlsByUseCaseRef.current.set(useCaseKey, existingUrl);
59
+ }
42
60
  return;
43
61
  }
44
62
  // If useVm is true, wait for resolution from context before loading
@@ -59,9 +77,14 @@ gitUrl, }) {
59
77
  browserUrl,
60
78
  useVm,
61
79
  resolution: vmResolution,
62
- apiKey,
80
+ apiKey
63
81
  })
64
82
  .then((url) => {
83
+ // Store as initial URL for this use case if stationaryPublishedUrl is enabled
84
+ if (stationaryPublishedUrl &&
85
+ !initialUrlsByUseCaseRef.current.has(useCaseKey)) {
86
+ initialUrlsByUseCaseRef.current.set(useCaseKey, url);
87
+ }
65
88
  setPreviewUrl(url);
66
89
  setSandboxError(null); // Clear error on success
67
90
  })
@@ -74,13 +97,21 @@ gitUrl, }) {
74
97
  });
75
98
  // Re-run when sandbox parameters change OR when resolution becomes available
76
99
  // eslint-disable-next-line react-hooks/exhaustive-deps
77
- }, [sandboxUid, browserUrl, useVm, vmResolution, apiKey]);
100
+ }, [
101
+ sandboxUid,
102
+ browserUrl,
103
+ useVm,
104
+ vmResolution,
105
+ apiKey,
106
+ currentUseCase,
107
+ stationaryPublishedUrl
108
+ ]);
78
109
  // If isFrame, just render GuardianDemo without the header/layout wrapper
79
110
  if (isFrame) {
80
111
  return (React.createElement("div", { className: "h-[100vh]" },
81
- React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError })));
112
+ React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView })));
82
113
  }
83
- return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { demoOptions: demoOptions, frameworkOptions: frameworkOptions, currentFramework: currentFramework, currentUseCase: currentUseCase, playgroundLogo: playgroundLogo, firstFrameworkByUseCase: firstFrameworkByUseCase, themecolor: themeColor })) : undefined, hasBodyPadding: false },
114
+ return (React.createElement(AppLayoutNoSidebar, { header: !hideHeader ? (React.createElement(Header, { sections: sections, onSelect: onSelect, playgroundLogo: playgroundLogo, themecolor: themeColor })) : undefined, hasBodyPadding: false },
84
115
  React.createElement("div", { className: cn("flex-1 min-h-0 flex flex-col", hideHeader && "pt-4") },
85
- React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError }))));
116
+ React.createElement(GuardianDemo, { CustomConsole: CustomConsole, GuideView: GuideView, browserUrl: previewUrl, playgroundUid: playgroundUid, useVm: useVm, codeZipFile: codeZipFile, completeCodeZipFile: completeCodeZipFile, variant: variant, themeColor: themeColor, hasPreview: hasPreview, isFrame: isFrame, isGuardian: true, gitUrl: gitUrl, isBrowserUrlReady: isBrowserUrlReady, sandboxId: sandboxUid, apiKey: apiKey, sandboxError: sandboxError, hasNetworkView: hasNetworkView }))));
86
117
  }
@@ -3,17 +3,24 @@ import React from "react";
3
3
  import GuardianComponent from "./guardian-component";
4
4
  import { useFrameParams } from "./hooks/use-frame-params";
5
5
  import { GuardianStyleWrapper } from "./guardian-style-wrapper";
6
+ import { buildGuardianConfig } from "./utils";
6
7
  /**
7
- * Guardian Playground component with nested config structure.
8
- * Expects config[useCaseId].frameworks[frameworkKey] structure.
8
+ * Guardian Playground component with tree-based tech stack selection.
9
+ * Uses sections from useTreeSelector for dynamic header rendering.
9
10
  *
10
- * When isFrame=true, reads framework from URL params client-side.
11
+ * Note: Frame param overrides for node types (architecture, frontend, backend, etc.)
12
+ * are now handled in Sandbox.tsx via the tree selector sync mechanism.
13
+ * This component receives the already-resolved framework from the tree selector.
11
14
  */
12
- export default function GuardianPlayground({ nestedConfig, useCase, framework: serverFramework, isFrame = false, apiKey, env, chatUid, theme = "dark", }) {
15
+ export default function GuardianPlayground({ sandboxConfig, sections, onSelect, useCase, framework, isFrame = false, apiKey, env, chatUid, theme = "dark", hasNetworkView = false, stationaryPublishedUrl }) {
13
16
  var _a;
14
- // When in frame mode, allow URL params to override framework
17
+ // Get frame params for iframeUrl override only
15
18
  const frameParams = useFrameParams();
16
- const framework = isFrame && frameParams.framework ? frameParams.framework : serverFramework;
19
+ // Build the nested config from use cases
20
+ const nestedConfig = buildGuardianConfig(sandboxConfig.useCases, {
21
+ playgroundUid: sandboxConfig.id,
22
+ playgroundLogo: sandboxConfig.playgroundLogo || (React.createElement("div", { className: "text-zinc-900 dark:text-white font-bold text-xl" }, sandboxConfig.name))
23
+ });
17
24
  const useCaseConfig = nestedConfig[useCase];
18
25
  if (!useCaseConfig) {
19
26
  return (React.createElement("div", { className: "flex items-center justify-center h-screen" },
@@ -32,14 +39,6 @@ export default function GuardianPlayground({ nestedConfig, useCase, framework: s
32
39
  useCase,
33
40
  "\"")));
34
41
  }
35
- // Build a map of use case ID to first available framework key
36
- const firstFrameworkByUseCase = {};
37
- Object.entries(nestedConfig).forEach(([useCaseId, uc]) => {
38
- const firstFrameworkKey = Object.keys(uc.frameworks)[0];
39
- if (firstFrameworkKey) {
40
- firstFrameworkByUseCase[useCaseId] = firstFrameworkKey;
41
- }
42
- });
43
42
  return (React.createElement(GuardianStyleWrapper, { themeColor: frameworkConfig.themeColor, theme: theme },
44
- React.createElement(GuardianComponent, { demoOptions: frameworkConfig.demoOptions, frameworkOptions: frameworkConfig.frameworkOptions, firstFrameworkByUseCase: firstFrameworkByUseCase, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false })));
43
+ React.createElement(GuardianComponent, { sections: sections, onSelect: onSelect, currentFramework: frameworkConfig.currentFramework, CustomConsole: frameworkConfig.CustomConsole, GuideView: frameworkConfig.GuideView, playgroundUid: frameworkConfig.playgroundUid, browserUrl: (_a = frameParams.iframeUrl) !== null && _a !== void 0 ? _a : frameworkConfig.browserUrl, useVm: frameworkConfig.useVm, playgroundLogo: frameworkConfig.playgroundLogo, sandboxUid: frameworkConfig.sandboxUid, name: frameworkConfig.name, description: frameworkConfig.description, codeZipFile: frameworkConfig.codeZipFile, completeCodeZipFile: frameworkConfig.completeCodeZipFile, variant: frameworkConfig.variant, themeColor: frameworkConfig.themeColor, hasPreview: frameworkConfig.hasPreview, currentUseCase: useCase, isFrame: isFrame, apiKey: apiKey, env: env, chatUid: chatUid, gitUrl: frameworkConfig.gitUrl, hideHeader: false, hasNetworkView: hasNetworkView, stationaryPublishedUrl: stationaryPublishedUrl })));
45
44
  }
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
  import React, { useEffect, useRef, useState } from "react";
3
3
  import { TAILWIND_CSS } from "../../../lib/generated-css";
4
+ import { SdkThemeProvider } from "./context/theme-context";
4
5
  /**
5
6
  * Style wrapper for Guardian components that injects Tailwind CSS
6
7
  * into the document without using Shadow DOM (preserves React context)
7
8
  */
8
- export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme = "dark", }) => {
9
+ export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme = "dark" }) => {
9
10
  const [stylesInjected, setStylesInjected] = useState(false);
10
11
  const [resolvedTheme, setResolvedTheme] = useState(theme === "system" ? "dark" : theme);
11
12
  const rootRef = useRef(null);
@@ -58,9 +59,10 @@ export const GuardianStyleWrapper = ({ children, themeColor = "#3b82f6", theme =
58
59
  rootRef.current.style.setProperty("--colors-secondary", secondaryColor);
59
60
  }
60
61
  }, [themeColor]);
61
- return (React.createElement("div", { ref: rootRef, className: `guardian-sdk-root bg-background text-foreground ${resolvedTheme === "dark" ? "dark" : ""}`, style: {
62
- width: "100%",
63
- height: "100%",
64
- fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
65
- } }, children));
62
+ return (React.createElement(SdkThemeProvider, { theme: theme },
63
+ React.createElement("div", { ref: rootRef, className: `guardian-sdk-root bg-background text-foreground ${resolvedTheme === "dark" ? "dark" : ""}`, style: {
64
+ width: "100%",
65
+ height: "100%",
66
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'
67
+ } }, children)));
66
68
  };