diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
index 333fc15fac..dfcc3f76b8 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/dashboards/[dashboardId]/page-client.tsx
@@ -1,7 +1,8 @@
"use client";
-import { DashboardSandboxHost, type DashboardRuntimeError, type WidgetSelection } from "@/components/commands/create-dashboard/dashboard-sandbox-host";
+import { DashboardSandboxHost, stampEsmVersion, type DashboardRuntimeError, type WidgetSelection } from "@/components/commands/create-dashboard/dashboard-sandbox-host";
+import packageJson from "../../../../../../../../package.json";
import { useRouter, useRouterConfirm } from "@/components/router";
import { StreamingCodeViewer } from "@/components/streaming-code-viewer";
import { ActionDialog, Button, Typography, useToast } from "@/components/ui";
@@ -239,8 +240,9 @@ function DashboardDetailContent({
const handleCodeUpdate = useCallback((toolCall: ToolCallContent) => {
if (typeof toolCall.args.content === "string") {
- setPendingCode(toolCall.args.content);
- setCurrentTsxSource(toolCall.args.content);
+ const stamped = stampEsmVersion(toolCall.args.content, packageJson.version);
+ setPendingCode(stamped);
+ setCurrentTsxSource(stamped);
clearTimeout(codePhaseTimerRef.current);
setCodePhase("typing");
codePhaseTimerRef.current = setTimeout(() => {
@@ -450,7 +452,7 @@ function DashboardDetailContent({
}
+ toolComponents={ setCurrentTsxSource(stampEsmVersion(code, packageJson.version))} currentCode={currentTsxSource} />}
useOffWhiteLightMode
composerPlaceholder={currentHasSource ? undefined : DASHBOARD_COMPOSER_PLACEHOLDER}
runningStatusMessages={!isCreating ? UPDATE_STATUS_MESSAGES : undefined}
diff --git a/apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx b/apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx
index 6691a3bf4e..aa75b67d12 100644
--- a/apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx
+++ b/apps/dashboard/src/components/commands/create-dashboard/create-dashboard-preview.tsx
@@ -21,8 +21,9 @@ import { useChat, type UIMessage } from "@ai-sdk/react";
import { convertToModelMessages, DefaultChatTransport } from "ai";
import { memo, useCallback, useMemo, useRef, useState } from "react";
import { CmdKPreviewProps } from "../../cmdk-commands";
-import { DashboardSandboxHost } from "./dashboard-sandbox-host";
+import { DashboardSandboxHost, stampEsmVersion } from "./dashboard-sandbox-host";
import { StreamingCodeViewer } from "../../streaming-code-viewer";
+import packageJson from "../../../../package.json";
type DashboardArtifact = {
prompt: string,
@@ -176,18 +177,19 @@ const CreateDashboardPreviewInner = memo(function CreateDashboardPreviewInner({
phase = "idle";
}
- const displayCode = toolPart?.code ?? "";
+ const displayCode = toolPart?.code ? stampEsmVersion(toolPart.code, packageJson.version) : "";
if (toolPart?.state === "input-available" && !artifact && !finalizedRef.current) {
finalizedRef.current = true;
const sanitized = sanitizeGeneratedCode(toolPart.code);
+ const stamped = stampEsmVersion(sanitized, packageJson.version);
setArtifact({
prompt,
projectId,
runtimeCodegen: {
title: prompt.slice(0, 120),
description: "",
- uiRuntimeSourceCode: sanitized,
+ uiRuntimeSourceCode: stamped,
},
});
setIframeReady(false);
diff --git a/apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx b/apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx
index 30f5b5f90a..f0b9cee996 100644
--- a/apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx
+++ b/apps/dashboard/src/components/commands/create-dashboard/dashboard-sandbox-host.tsx
@@ -18,13 +18,57 @@ type DashboardArtifact = {
function html(strings: TemplateStringsArray, ...values: unknown[]): string {
return strings.reduce((result, str, i) => result + str + (values[i] ?? ''), '');
}
-
const isDev = process.env.NODE_ENV === "development";
+function getEsmFallbackVersion(version: string): string {
+ const parts = version.split(".");
+ if (parts.length !== 3) return version;
+ const patch = Number(parts[2]);
+ if (!Number.isInteger(patch) || patch <= 0) return version;
+ return `${parts[0]}.${parts[1]}.${patch - 1}`;
+}
+
+const ESM_VERSION_HEADER = "// @stack-esm-version:";
+const ESM_VERSION_REGEX = /^\/\/\s*@stack-esm-version:\s*(\S+)\s*$/m;
+
+function extractEsmVersion(sourceCode: string): string | null {
+ const match = sourceCode.match(ESM_VERSION_REGEX);
+ return match ? match[1] : null;
+}
+
+export function stampEsmVersion(sourceCode: string, version: string): string {
+ if (ESM_VERSION_REGEX.test(sourceCode)) {
+ return sourceCode.replace(ESM_VERSION_REGEX, `${ESM_VERSION_HEADER} ${version}`);
+ }
+ return `${ESM_VERSION_HEADER} ${version}\n${sourceCode}`;
+}
+
function getDependencyScripts(esmVersion: string, esmFallbackVersion: string, dashboardUrl: string): string {
if (isDev) {
return html`
`;
@@ -72,6 +114,30 @@ function getDependencyScripts(esmVersion: string, esmFallbackVersion: string, da
return html`
`;
}
@@ -117,8 +189,8 @@ function escapeScriptContent(code: string): string {
function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashboardUrl: string, initialTheme: "light" | "dark", showControls: boolean, initialChatOpen: boolean): string {
const sourceCode = escapeScriptContent(artifact.runtimeCodegen.uiRuntimeSourceCode);
const darkClass = initialTheme === "dark" ? "dark" : "";
- const esmVersion = packageJson.version;
- const esmFallbackVersion = "2.8.71";
+ const esmVersion = extractEsmVersion(artifact.runtimeCodegen.uiRuntimeSourceCode) ?? packageJson.version;
+ const esmFallbackVersion = getEsmFallbackVersion(esmVersion);
const devScriptSrc = isDev ? ` ${dashboardUrl}` : '';
const devConnectSrc = isDev ? ` ${dashboardUrl}` : '';
@@ -307,10 +379,18 @@ function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashbo
};
async function waitForDeps() {
- if (window.__depsReady) return;
- await new Promise(resolve => {
- window.addEventListener('deps-ready', resolve, { once: true });
- });
+ if (!window.__depsReady) {
+ await new Promise(resolve => {
+ window.addEventListener('deps-ready', resolve, { once: true });
+ });
+ }
+ if (window.__depsError) {
+ const error = new Error(window.__depsError.message || 'There was a problem loading custom dashboards. Please refresh the page and try again.');
+ if (window.__depsError.stack) {
+ error.stack = window.__depsError.stack;
+ }
+ throw error;
+ }
}
async function requestAccessToken() {
@@ -735,6 +815,13 @@ export const DashboardSandboxHost = memo(function DashboardSandboxHost({
return;
}
+ if (type === "dashboard-sandbox-dependency-error") {
+ const err = new Error(event.data.message ?? 'Unknown custom dashboard dependency error');
+ if (event.data.stack) err.stack = event.data.stack;
+ captureError('dashboard-sandbox-dependency-error', err);
+ return;
+ }
+
if (type === "dashboard-error-boundary") {
const err = new Error(event.data.message ?? 'Unknown dashboard error');
if (event.data.stack) err.stack = event.data.stack;