diff --git a/apps/dashboard/src/lib/remote-development-environment/config-file.ts b/apps/dashboard/src/lib/remote-development-environment/config-file.ts index 1a32d1f76..6725dd735 100644 --- a/apps/dashboard/src/lib/remote-development-environment/config-file.ts +++ b/apps/dashboard/src/lib/remote-development-environment/config-file.ts @@ -3,6 +3,7 @@ import "server-only"; import { showOnboardingHexclaveConfigValue } from "@hexclave/shared/dist/config-authoring"; import { Config, isValidConfig } from "@hexclave/shared/dist/config/format"; import { detectImportPackageFromDir, renderConfigFileContent } from "@hexclave/shared/dist/config-rendering"; +import { captureError } from "@hexclave/shared/dist/utils/errors"; import { createHash } from "crypto"; import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs"; import { createJiti } from "jiti"; @@ -66,9 +67,12 @@ export async function readConfigFile(configFilePath: string): Promise<{ config: try { configModule = await jiti.import(configFilePath); } catch (error) { + // Capture the raw jiti/framework error for diagnostics, but don't attach it as `cause` on the thrown error: + // the dashboard's error formatter (errorToNiceString -> nicify) renders `Error.cause` recursively, which would + // leak the underlying framework stack/internals back into the user-facing message we're deliberately replacing. + captureError("remote-development-environment/readConfigFile", error); throw new Error( `Failed to load config file ${configFilePath}. If your config imports a value (e.g. defineHexclaveConfig) from a framework package such as "@hexclave/next", import it from that package's lightweight "/config" entrypoint instead, which doesn't load the framework runtime:\n\n import { defineHexclaveConfig } from "@hexclave/next/config";\n`, - { cause: error }, ); } if (!isConfigModule(configModule)) { diff --git a/packages/shared/src/config-rendering.ts b/packages/shared/src/config-rendering.ts index b3581631a..d28cc2588 100644 --- a/packages/shared/src/config-rendering.ts +++ b/packages/shared/src/config-rendering.ts @@ -13,6 +13,7 @@ export { parseHexclaveConfigFileContent, renderConfigFileContent }; const CONFIG_IMPORT_PACKAGES = [ "@hexclave/next", "@hexclave/react", + "@hexclave/tanstack-start", "@hexclave/js", "@hexclave/template", "@stackframe/stack", @@ -139,6 +140,7 @@ import.meta.vitest?.test("detectConfigImportPackage picks first matching package expect(detectConfigImportPackage(["@hexclave/next", "@hexclave/js"])).toBe("@hexclave/next"); expect(detectConfigImportPackage(["@hexclave/react", "@hexclave/js"])).toBe("@hexclave/react"); expect(detectConfigImportPackage(["@hexclave/js"])).toBe("@hexclave/js"); + expect(detectConfigImportPackage(["@hexclave/tanstack-start"])).toBe("@hexclave/tanstack-start"); // Hexclave names take priority over legacy stackframe names when both appear. expect(detectConfigImportPackage(["@stackframe/stack", "@hexclave/next"])).toBe("@hexclave/next"); // Legacy fallback still works for projects pinned to the last @stackframe/* release.