Map a minified error stack trace from a production Dyad build back to original source locations using source maps.
Given a minified error stack trace from a production Dyad build (referencing app.asar/.vite/renderer/main_window/assets/index-*.js), map each frame back to the original TypeScript source file, line, and column.
$ARGUMENTS: The full error message and stack trace from the minified production build. Should contain lines like:
TypeError: Invalid URL
at FOt (file:///usr/lib/dyad/resources/app.asar/.vite/renderer/main_window/assets/index-XXXX.js:1432:7223)
You must know which Dyad release version this error occurred in. Check if the user provided it in $ARGUMENTS or in conversation context.
Do not assume or guess the version.
Look up the GitHub release for that version to find the exact commit hash:
gh release view v<VERSION> --repo dyad-sh/dyad --json tagCommitish,targetCommitish
If the release tag doesn't resolve directly, find the commit from the tag:
git ls-remote --tags origin "v<VERSION>"
Then check out that commit:
git checkout <commit-hash>
npm install
npm run package
This ensures the local build matches the exact code that produced the error's minified bundle.
Find the built app.asar in the out/ directory and extract it to a temp directory:
find out/ -name "app.asar" -print -quit
npx @electron/asar extract <path-to-app.asar> /tmp/dyad-asar-extracted
If out/ doesn't exist or has no app.asar, the build may have failed — check the build output for errors.
Look for .js.map files alongside the renderer bundle:
find /tmp/dyad-asar-extracted/.vite/renderer/main_window/assets -name "*.map" 2>/dev/null
If no source maps exist in the extracted asar (which is typical for production builds), do a renderer-only build with source maps:
npx vite build --config vite.renderer.config.mts --outDir /tmp/dyad-sourcemap-build --sourcemap
This produces an index-*.js and index-*.js.map in /tmp/dyad-sourcemap-build/assets/.
Important: The build hash will differ from the error stack trace's hash. That's fine — we match by minified function names, not by line/column from the error directly.
For each function name in the error stack trace (e.g., FOt, xO, PR), find its position in the newly built bundle:
// Search for each function name and record line:column positions
node -e "
const fs = require('fs');
const content = fs.readFileSync('/tmp/dyad-sourcemap-build/assets/<index-file>.js', 'utf8');
const lines = content.split('\n');
const names = ['FOt', 'xO', ...]; // from stack trace
for (const name of names) {
for (let i = 0; i < lines.length; i++) {
let col = lines[i].indexOf(name);
while (col !== -1) {
console.log(name + ' at Line ' + (i+1) + ', Col ' + col);
col = lines[i].indexOf(name, col + 1);
}
}
}
"
Disambiguation: If a function name appears multiple times:
const FOt= or function FOt() is usually the one referenced in the stack trace.Use the source-map package (available in node_modules) to resolve each position:
node -e "
const fs = require('fs');
const { SourceMapConsumer } = require(require.resolve('source-map', {paths: [process.cwd()]}));
async function main() {
const rawMap = JSON.parse(fs.readFileSync('/tmp/dyad-sourcemap-build/assets/<index-file>.js.map', 'utf8'));
const consumer = await new SourceMapConsumer(rawMap);
const positions = [
{name: 'FOt', line: <line>, col: <col>},
// ... one entry per stack frame
];
for (const pos of positions) {
const orig = consumer.originalPositionFor({line: pos.line, column: pos.col});
console.log(pos.name + ':');
console.log(' -> ' + orig.source + ':' + orig.line + ':' + orig.column + ' (name: ' + orig.name + ')');
}
}
main().catch(console.error);
"
The topmost non-React frame is usually the root cause. For that frame's line in the minified bundle, search for the specific expression that throws (e.g., all new URL( calls) and map each to the original source:
// Find all occurrences of the throwing expression on the relevant minified line
// and map each to original source
This narrows down the exact expression within a large component.
Present the mapped stack trace in a clear format:
Original stack trace:
1. ErrorBanner (src/components/preview_panel/PreviewIframe.tsx:1148:22)
2. React internals (renderWithHooks, reconcileChildren, etc.)
...
Distinguish between:
Read the original source file at the identified line and show the surrounding context (5-10 lines). Explain why the expression throws and suggest a fix if obvious.
renderWithHooks, beginWork, completeWork, etc.) can be identified by their patterns — they bubble up from the actual throw site. Focus on the topmost non-React frame.TypeError: Invalid URL, look for unguarded new URL() calls in render paths.source-map package version 0.6.x uses new SourceMapConsumer(rawMap) which returns a Promise. Version 0.5.x is synchronous.../../../ — strip these mentally or programmatically to get the repo-relative path.After reporting, restore the repo and clean up temp files:
git checkout -
npm install
rm -rf /tmp/dyad-asar-extracted /tmp/dyad-sourcemap-build