Skip to content

Fix preview iframe URL error & create debugging skill#2887

Merged
wwwillchen merged 2 commits intodyad-sh:mainfrom
wwwillchen-bot:fix-preview-iframe
Mar 5, 2026
Merged

Fix preview iframe URL error & create debugging skill#2887
wwwillchen merged 2 commits intodyad-sh:mainfrom
wwwillchen-bot:fix-preview-iframe

Conversation

@wwwillchen
Copy link
Copy Markdown
Collaborator

@wwwillchen wwwillchen commented Mar 4, 2026

@wwwillchen
Copy link
Copy Markdown
Collaborator Author

@BugBot run

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the developer experience by introducing a powerful new debugging skill for minified production errors and by fortifying the URL handling within the preview iframe. The debugging skill provides a structured approach to de-minify stack traces, while the iframe improvements prevent common URL-related errors and ensure consistent navigation history, ultimately leading to more reliable development and debugging workflows.

Highlights

  • New Debugging Skill: A new skill, dyad:debug-minified-error, has been added to assist in mapping minified error stack traces from production Dyad builds back to their original source locations using source maps. This skill provides a step-by-step guide for developers to de-minify stack traces.
  • Preview Iframe URL Handling Fix: Resolved an issue where the preview iframe could encounter TypeError: Invalid URL when handling relative URLs. The system now correctly resolves relative URLs against the app's base URL and includes robust error handling for URL parsing.
  • Improved Navigation History Management: The navigation history within the preview iframe now consistently stores absolute URLs by resolving relative paths. This ensures greater stability and accuracy in tracking user navigation and preserving URLs across hot module reloads (HMR).
Changelog
  • .claude/skills/debug-minified-error/SKILL.md
    • Added a new skill definition file for debugging minified errors.
    • Included detailed instructions for determining release versions, checking out commits, building, extracting ASAR files, and using source maps to resolve original source locations.
  • src/components/preview_panel/PreviewIframe.tsx
    • Implemented logic to resolve relative URLs to absolute URLs using the app's base URL before storing them in navigation history or current iframe URL references.
    • Added try-catch blocks around new URL() constructor calls to gracefully handle invalid URLs during pushState and replaceState operations, preventing crashes.
    • Updated the URL preservation mechanism to use the newly resolved absolute URLs.
    • Wrapped the new URL() call for displaying the pathname in the address bar with a try-catch block to prevent rendering errors if the stored URL is invalid.
Activity
  • No specific activity (comments, reviews, etc.) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with πŸ‘ and πŸ‘Ž on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

βœ… Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR fixes a TypeError: Invalid URL crash in the preview panel's address bar by (1) resolving relative URLs received from the iframe's pushState/replaceState messages into absolute URLs before storing them in navigationHistory, and (2) wrapping the address-bar new URL() display call in a try-catch with a "/" fallback. A new Claude skill (dyad:debug-minified-error) is also added to assist in de-minifying production error stack traces using source maps.

Key changes:

  • URL resolution on message receipt (PreviewIframe.tsx:625-632): payload.newUrl is now resolved against appUrl via new URL(resolvedUrl, appUrl ?? undefined).href before being stored. This prevents relative paths (e.g., /about) from ever entering navigationHistory as non-absolute URLs, which was the root cause of the downstream TypeError: Invalid URL.
  • Defensive address-bar rendering (PreviewIframe.tsx:1156-1163): The former unguarded new URL(navigationHistory[currentHistoryPosition]).pathname expression (which could throw for undefined or non-absolute entries) is now wrapped in a try-catch IIFE returning "/" on error.
  • New debugging skill (.claude/skills/debug-minified-error/SKILL.md): Step-by-step runbook for mapping minified Electron/Vite bundle stack frames back to original TypeScript source, intended to aid future incident investigation.

Confidence Score: 4/5

  • Safe to merge β€” the fix correctly addresses the root cause with appropriate fallbacks; only minor style suggestions remain.
  • The URL resolution logic is sound: new URL(relative, base) correctly promotes relative paths to absolute, and both the resolution step and the address-bar render are guarded with try-catch. No new state shape changes or breaking API surface. The skill file has no runtime impact. Minor points (missing consumer.destroy() and redundant full package build in the skill) are non-blocking.
  • No files require special attention β€” changes are well-scoped and low risk.

Important Files Changed

Filename Overview
src/components/preview_panel/PreviewIframe.tsx Two-part fix: resolve relative URLs from iframe pushState/replaceState messages to absolute URLs before storing, and wrap the address-bar new URL() display call in a try-catch. Both changes are correct and safe.
.claude/skills/debug-minified-error/SKILL.md New Claude skill providing step-by-step instructions to de-minify production Electron/Vite error stack traces using source maps. Well-documented; no production code impact.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["iframe message received\n(pushState / replaceState)"] --> B["Extract payload.newUrl"]
    B --> C{newUrl truthy?}
    C -- No --> Z["resolvedUrl = undefined\n(no history update)"]
    C -- Yes --> D["Attempt new URL(newUrl, appUrl)"]
    D -- Success --> E["resolvedUrl = absolute URL"]
    D -- Throws --> F["resolvedUrl = raw newUrl\n(fallback, keep original)"]
    E --> G{pushState or replaceState?}
    F --> G
    G -- pushState --> H["Append resolvedUrl to navigationHistory\nUpdate currentHistoryPosition\nUpdate currentIframeUrlRef"]
    G -- replaceState --> I["Replace current entry in navigationHistory\nUpdate currentIframeUrlRef"]
    H --> J{selectedAppId && appUrl?}
    I --> J
    J -- Yes --> K["Attempt new URL(resolvedUrl)\nCompare origins & pathname"]
    K -- non-root path --> L["setPreservedUrls[selectedAppId] = resolvedUrl"]
    K -- root path --> M["delete preservedUrls[selectedAppId]"]
    K -- Throws --> N["Skip preservation"]
    J -- No --> N

    subgraph AddressBar["Address Bar Display"]
        P["Read navigationHistory[currentHistoryPosition]"]
        P --> Q["try: new URL(entry).pathname"]
        Q -- Success --> R["Show pathname"]
        Q -- Throws --> S["Show '/'"]
    end
Loading

Last reviewed commit: dfac969

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

βœ… Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

πŸ” Dyadbot Code Review Summary

Verdict: βœ… YES - Ready to merge

Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard.

Good bug fix β€” resolving relative URLs before storing them in navigation history prevents new URL() from throwing in the render path. The try/catch in the address bar display is a solid defensive addition.

Issues Summary

Severity File Issue
🟑 MEDIUM src/components/preview_panel/PreviewIframe.tsx:625 Relative URL silently stored in history when appUrl is null

🟑 MEDIUM: Relative URL stored when appUrl is null
When appUrl is null/undefined and the iframe sends a relative URL (e.g. /dashboard), new URL(resolvedUrl, undefined) throws, and the catch block keeps the raw relative value. This relative URL leaks into navigationHistory and currentIframeUrlRef, causing downstream new URL() calls to fail (caught) and the address bar to always show /. Consider skipping the state update entirely when resolution fails and appUrl is unavailable, rather than storing an unresolvable URL.

🟒 Low Priority Notes (3 items)
  • Redundant ?? undefined β€” src/components/preview_panel/PreviewIframe.tsx:628 β€” appUrl ?? undefined is a no-op if appUrl is already string | undefined
  • IIFE in JSX β€” src/components/preview_panel/PreviewIframe.tsx:1156 β€” The try/catch IIFE is harder to scan than a pre-computed variable above the return
  • Comment explains what, not why β€” src/components/preview_panel/PreviewIframe.tsx:622 β€” Adding context on why relative URLs appear (SPA router behavior) would help future maintainers
🚫 Dropped False Positives (2 items)
  • Duplicated URL preservation logic β€” Dropped: This duplication is pre-existing across the pushState/replaceState branches and not introduced by this PR. Not fair to block a bug fix for pre-existing tech debt.
  • URL resolution edge case with base query/hash β€” Dropped: The URL spec handles this correctly, and the catch block covers malformed bases adequately.

Generated by Dyadbot multi-agent code review

Comment on lines +625 to +632
let resolvedUrl = payload?.newUrl;
if (resolvedUrl) {
try {
resolvedUrl = new URL(resolvedUrl, appUrl ?? undefined).href;
} catch {
// If it can't be resolved at all, keep the raw value
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM | state-management

Relative URL silently stored in history when appUrl is null

When appUrl is null/undefined and the iframe sends a relative URL (e.g. /dashboard), new URL(resolvedUrl, undefined) throws, and the catch block preserves the raw relative value. This relative URL then leaks into navigationHistory and currentIframeUrlRef, causing downstream new URL() calls to throw (caught but silently swallowed) and the address bar to always show / instead of the actual path.

πŸ’‘ Suggestion: When resolution fails, set resolvedUrl to undefined (or null) so the if (type === "pushState" && resolvedUrl) guard prevents storing an unresolvable URL:

Suggested change
let resolvedUrl = payload?.newUrl;
if (resolvedUrl) {
try {
resolvedUrl = new URL(resolvedUrl, appUrl ?? undefined).href;
} catch {
// If it can't be resolved at all, keep the raw value
}
}
let resolvedUrl = payload?.newUrl;
if (resolvedUrl) {
try {
resolvedUrl = new URL(resolvedUrl, appUrl ?? undefined).href;
} catch {
// If we can't resolve to absolute, don't store the relative URL
// as it will cause downstream URL parsing failures
resolvedUrl = undefined;
}
}

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Confidence score: 3/5

  • There is a concrete medium-high risk in src/components/preview_panel/PreviewIframe.tsx: iframe message-driven navigation is not validating URLs/schemes, so non-http/https or non-string inputs could be accepted and lead to unsafe navigation behavior.
  • The docs issue in .claude/skills/debug-minified-error/SKILL.md is lower impact (accuracy/usability), but it can still mislead debugging workflows and should be corrected soon.
  • This lands at a 3 because the top issue is user-impacting and high-confidence (7/10 severity, 9/10 confidence), while the second issue is non-blocking documentation correctness.
  • Pay close attention to src/components/preview_panel/PreviewIframe.tsx, .claude/skills/debug-minified-error/SKILL.md - enforce strict URL validation for iframe navigation messages and fix the source-map version guidance.
Prompt for AI agents (unresolved issues)

Check if these issues are valid β€” if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/preview_panel/PreviewIframe.tsx">

<violation number="1" location="src/components/preview_panel/PreviewIframe.tsx:628">
P1: Validate navigation URLs from iframe messages before accepting them. Restrict to string inputs and allow only http/https schemes; reject and log anything else.

(Based on your team's feedback about validating navigation URLs from messages.) [FEEDBACK_USED]</violation>
</file>

<file name=".claude/skills/debug-minified-error/SKILL.md">

<violation number="1" location=".claude/skills/debug-minified-error/SKILL.md:182">
P2: The `source-map` version note is factually incorrect (0.7.x is the async Promise-based API; 0.6.x is synchronous), which can mislead users during debugging.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

let resolvedUrl = payload?.newUrl;
if (resolvedUrl) {
try {
resolvedUrl = new URL(resolvedUrl, appUrl ?? undefined).href;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Validate navigation URLs from iframe messages before accepting them. Restrict to string inputs and allow only http/https schemes; reject and log anything else.

(Based on your team's feedback about validating navigation URLs from messages.)

View Feedback

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At src/components/preview_panel/PreviewIframe.tsx, line 628:

<comment>Validate navigation URLs from iframe messages before accepting them. Restrict to string inputs and allow only http/https schemes; reject and log anything else.

(Based on your team's feedback about validating navigation URLs from messages.) </comment>

<file context>
@@ -620,33 +620,43 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
+        let resolvedUrl = payload?.newUrl;
+        if (resolvedUrl) {
+          try {
+            resolvedUrl = new URL(resolvedUrl, appUrl ?? undefined).href;
+          } catch {
+            // If it can't be resolved at all, keep the raw value
</file context>
Fix with Cubic

- React stack frames (reconciler functions like `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.
- If the error is `TypeError: Invalid URL`, look for unguarded `new URL()` calls in render paths.
- If the error is during React rendering, the topmost frame is the component whose render threw.
- The `source-map` package version 0.6.x uses `new SourceMapConsumer(rawMap)` which returns a Promise. Version 0.5.x is synchronous.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The source-map version note is factually incorrect (0.7.x is the async Promise-based API; 0.6.x is synchronous), which can mislead users during debugging.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At .claude/skills/debug-minified-error/SKILL.md, line 182:

<comment>The `source-map` version note is factually incorrect (0.7.x is the async Promise-based API; 0.6.x is synchronous), which can mislead users during debugging.</comment>

<file context>
@@ -0,0 +1,193 @@
+- React stack frames (reconciler functions like `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.
+- If the error is `TypeError: Invalid URL`, look for unguarded `new URL()` calls in render paths.
+- If the error is during React rendering, the topmost frame is the component whose render threw.
+- The `source-map` package version 0.6.x uses `new SourceMapConsumer(rawMap)` which returns a Promise. Version 0.5.x is synchronous.
+- Source paths in the map often have relative prefixes like `../../../` β€” strip these mentally or programmatically to get the repo-relative path.
+
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces two main changes. First, it fixes a crash in the preview iframe that occurred when an invalid URL was present in the navigation history. This is addressed by wrapping the URL parsing in a try-catch block. Second, it improves URL handling for pushState and replaceState events by correctly resolving relative URLs against the application's base URL. A new skill for debugging minified errors has also been added.

My feedback focuses on making the new URL resolution logic more robust by handling resolution failures more explicitly.

Note: Security Review did not run due to the size of the PR.

Comment on lines +629 to +631
} catch {
// If it can't be resolved at all, keep the raw value
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of the catch block keeps the raw value of resolvedUrl if new URL() throws. This means an invalid URL string could be propagated, relying on subsequent try-catch blocks to handle the error again. This could be made more robust.

If URL resolution fails, it's safer to nullify resolvedUrl. This would prevent the if (type === "pushState" && resolvedUrl) and else if (type === "replaceState" && resolvedUrl) blocks from executing with an invalid URL, making the control flow clearer.

          } catch (e) {
            // If URL resolution fails, we can't reliably use this URL.
            // Log the error and nullify resolvedUrl to prevent further processing.
            console.error(`Failed to resolve URL '${payload?.newUrl}' with base '${appUrl}':`, e);
            resolvedUrl = null;
          }

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Mar 4, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 237 1 7 6

Summary: 237 passed, 1 failed, 7 flaky, 6 skipped

Failed Tests

🍎 macOS

  • refresh.spec.ts > refresh preserves current route
    • Error: expect(locator).toBeVisible() failed

πŸ“‹ Re-run Failing Tests (macOS)

Copy and paste to re-run all failing spec files locally:

npm run e2e \
  e2e-tests/refresh.spec.ts

⚠️ Flaky Tests

🍎 macOS

  • local_agent_basic.spec.ts > local-agent - dump request (passed after 1 retry)
  • local_agent_connection_retry.spec.ts > local-agent - recovers from connection drop (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > setup banner shows correct state when node.js is installed (passed after 1 retry)
  • setup_flow.spec.ts > Setup Flow > node.js install flow (passed after 1 retry)
  • setup.spec.ts > setup ai provider (passed after 1 retry)
  • uncommitted_files_banner.spec.ts > uncommitted files banner (passed after 1 retry)
  • undo.spec.ts > undo after assistant with no code (passed after 1 retry)

πŸ“Š View full report

@wwwillchen wwwillchen merged commit e3baf85 into dyad-sh:main Mar 5, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant