// // Copyright 2026 DXOS.org // import ErrorStackParser from 'error-stack-parser'; import React from 'react'; import { mx } from '@dxos/ui-theme'; type LocalFrame = { href: string; fileName: string }; export type ParsedStackFrame = ReturnType[number]; export type ErrorStackProps = { /** When set, these frames are shown instead of parsing `error`. */ frames?: ParsedStackFrame[]; /** Used when `frames` is omitted. */ error?: Error; }; /** * Parses `captureOwnerStack()` output (React dev) into frames for {@link ErrorStack}. * Prefixes a synthetic Error line when needed so `error-stack-parser` can read V8-style stacks. */ export const parseCaptureOwnerStack = (stack: string | null): ParsedStackFrame[] | null => { if (stack == null || stack.length === 0) { return null; } const err = new Error(); err.stack = stack; try { return ErrorStackParser.parse(err); } catch { err.stack = `Error\n${stack}`; try { return ErrorStackParser.parse(err); } catch { return null; } } }; /** * Renders a parsed error stack trace with tree connector symbols and clickable vscode:// links for local frames. */ export const ErrorStack = ({ error, frames: framesProp }: ErrorStackProps) => { const frames = framesProp ?? (error ? ErrorStackParser.parse(error) : []); if (frames.length === 0) { return null; } return (
{frames.map((frame, i) => { const isLast = i === frames.length - 1; const local = frame.fileName ? parseLocalFrame(frame.fileName, frame.lineNumber, frame.columnNumber) : undefined; const functionName = frame.functionName ?? ''; return (
{/* Tree connector: vertical line full-height + horizontal branch at midpoint */}
{local ? ( {functionName} ) : ( {functionName} )} {local?.fileName ?? ''} {local ? `${frame.lineNumber}:${frame.columnNumber}` : ''}
); })}
); }; /** * Parses a Vite `/@fs/` URL into a `vscode://` deep link and short filename. * Returns undefined if the URL cannot be resolved to a local file. */ const parseLocalFrame = (fileUrl: string, line?: number, col?: number): LocalFrame | undefined => { try { const { pathname } = new URL(fileUrl); if (!pathname.startsWith('/@fs/')) { return undefined; } const FILE_PREFIX = '/@fs'; const localPath = pathname.slice(FILE_PREFIX.length); // /@fs/Users/... → /Users/... const PKG_PREFIX = '/packages/'; const i = localPath.indexOf(PKG_PREFIX); const path = i === -1 ? localPath : localPath.substring(i + PKG_PREFIX.length); return { href: `vscode://file/${localPath}:${line ?? 1}:${col ?? 1}`, fileName: path, }; } catch { return undefined; } };