Skip to content

Add image selection and swapping in the visual editor#2717

Draft
azizmejri1 wants to merge 8 commits intodyad-sh:mainfrom
azizmejri1:feat/image-visual-editor
Draft

Add image selection and swapping in the visual editor#2717
azizmejri1 wants to merge 8 commits intodyad-sh:mainfrom
azizmejri1:feat/image-visual-editor

Conversation

@azizmejri1
Copy link
Collaborator

closes #2634

@wwwillchen
Copy link
Collaborator

@BugBot run

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @azizmejri1, 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 visual editing experience by introducing comprehensive image management capabilities. It allows users to intuitively change image sources within their components, supporting both URL-based and file upload methods. The changes span across the frontend for UI interaction, the backend for file persistence and Git integration, and the core AST analysis and IPC layers to ensure seamless real-time updates and accurate code transformation.

Highlights

  • Image Swap Popover Component: A new React component, ImageSwapPopover.tsx, has been added to provide a user interface for changing image sources. Users can now swap images by providing a URL or by uploading a local file.
  • Image Upload Handling: The backend now supports processing image uploads. Uploaded images are saved to the public/images directory with unique filenames, and these new files are automatically added to Git if the repository is initialized.
  • Visual Editor Integration: The visual editing toolbar (VisualEditingToolbar.tsx) has been updated to include the new image swap functionality. When an image element is selected, the popover appears, allowing users to modify its source. Changes are reflected in the iframe preview and stored as pending changes.
  • AST Analysis for Images: The Abstract Syntax Tree (AST) analysis utility (visual_editing_utils.ts) has been enhanced to detect <img> elements within components and extract their src attributes. This enables the visual editor to identify and manage images effectively.
  • IPC Communication for Images: New Inter-Process Communication (IPC) messages (modify-dyad-image-src and get-dyad-image-src) have been introduced. These messages facilitate real-time communication between the main application process and the iframe for dynamic image manipulation and retrieval.
Changelog
  • src/components/preview_panel/ImageSwapPopover.tsx
    • Added a new React component for image source selection and upload.
  • src/components/preview_panel/PreviewIframe.tsx
    • Updated to manage image-related state (hasImage, currentImageSrc) and pass it to the visual editing toolbar.
  • src/components/preview_panel/VisualEditingToolbar.tsx
    • Modified to import and render the ImageSwapPopover when an image element is selected.
    • Updated to handle image swap events by sending messages to the iframe and updating pending changes.
  • src/ipc/types/visual-editing.ts
    • Extended VisualEditingChangeSchema to include imageSrc and imageUpload properties.
    • Extended AnalyseComponentResultSchema to include hasImage and imageSrc properties.
  • src/pro/main/ipc/handlers/visual_editing_handlers.ts
    • Enhanced to process image uploads, save them to the public/images directory, and add them to Git.
    • Updated the fileChanges map to include imageSrc for component modifications.
    • Modified analyzeComponent return type to include image-related analysis results.
  • src/pro/main/utils/visual_editing_utils.test.ts
    • Added new test cases for image detection within components.
    • Added new test cases for transformContent functionality related to updating image src attributes.
  • src/pro/main/utils/visual_editing_utils.ts
    • Modified transformContent to update or add src attributes for <img> elements.
    • Modified analyzeComponent to detect <img> elements and extract their src attributes.
  • worker/dyad-visual-editor-client.js
    • Added new functions handleModifyImageSrc and handleGetImageSrc to update image sources in the iframe and retrieve them.
    • Integrated new image-related message types into the message listener for IPC communication.
Activity
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
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 adds a new feature to the visual editor for selecting and swapping images, either by providing a URL or by uploading a new file. However, my security audit identified two significant issues: an unrestricted file upload vulnerability in the IPC handler that could lead to stored XSS, and a sensitive data leak via postMessage due to the use of a wildcard target origin, which is not permissible for sensitive data as per repository guidelines. Additionally, there is some redundant logic for filename generation between the frontend and backend, and the AST analysis and transformation logic for finding <img> tags could be more robust by performing a recursive search. Addressing these vulnerabilities and improvements is crucial for maintaining the security and robustness of the application.

@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary

Found 4 new issue(s) flagged by 3 independent reviewers (consensus voting: only issues identified by 2+ reviewers are reported).

Summary

Severity Count
🔴 HIGH 1
🟡 MEDIUM 3

Issues to Address

Severity File Issue
🔴 HIGH src/components/preview_panel/ImageSwapPopover.tsx:31 URL input shows stale value when switching between image components
🟡 MEDIUM src/components/preview_panel/ImageSwapPopover.tsx:45 Invalid file type silently rejected with no user feedback
🟡 MEDIUM src/components/preview_panel/ImageSwapPopover.tsx:55 Duplicate filename generation between client and server produces dead values
🟡 MEDIUM src/pro/main/ipc/handlers/visual_editing_handlers.ts:44 No server-side validation of uploaded file content type or size

See inline comments for details.

Generated by Dyadbot multi-agent code review (3 independent reviewers with consensus voting)

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Feb 14, 2026
@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

github-actions bot commented Feb 14, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@azizmejri1 azizmejri1 removed the needs-human:review-issue ai agent flagged an issue that requires human review label Feb 14, 2026
@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary (Round 2)

Found 7 new issue(s) flagged by 3 independent reviewers (consensus voting: only issues identified by 2+ reviewers are reported).
(5 issue(s) from previous review already addressed or acknowledged)

Summary

Severity Count
🔴 HIGH 1
🟡 MEDIUM 5
⚪ LOW 1

Issues to Address

Severity File Issue
🔴 HIGH src/components/preview_panel/ImageSwapPopover.tsx:43 Apply button accepts empty/invalid URLs with no validation
🟡 MEDIUM worker/dyad-visual-editor-client.js:260 Coordinates sent before new image loads, causing stale overlay
🟡 MEDIUM worker/dyad-visual-editor-client.js:280 handleGetImageSrc is dead code / silently fails when element missing
🟡 MEDIUM src/components/preview_panel/ImageSwapPopover.tsx:61 FileReader error not handled — user sees no feedback on failure
🟡 MEDIUM src/pro/main/ipc/handlers/visual_editing_handlers.ts:53 Single invalid image upload aborts ALL pending changes
🟡 MEDIUM src/components/preview_panel/VisualEditingToolbar.tsx:530 38-line inline onSwap handler with no user feedback on success
⚪ Low Priority Issues (1 item)
  • Hardcoded hex color #7f22fe instead of theme tokensrc/components/preview_panel/ImageSwapPopover.tsx:92

See inline comments for details on HIGH/MEDIUM issues.

Generated by Dyadbot multi-agent code review (3 independent reviewers with consensus voting)

@github-actions github-actions bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Feb 14, 2026
@wwwillchen
Copy link
Collaborator

@BugBot run

@azizmejri1 azizmejri1 removed the needs-human:review-issue ai agent flagged an issue that requires human review label Feb 18, 2026
@wwwillchen
Copy link
Collaborator

@BugBot run

Comment on lines 20 to 25
const VALID_IMAGE_TYPES = [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
];
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | duplication (1/3 reviewers, validated)

VALID_IMAGE_TYPES duplicated between client and server

This same list is defined independently here (as VALID_IMAGE_TYPES) and in visual_editing_handlers.ts (as VALID_IMAGE_MIME_TYPES). If one is updated without the other, the client and server validations diverge — the client might accept a type the server rejects, giving the user a confusing delayed error at apply time.

💡 Suggestion: Extract the list into a shared constant in src/ipc/types/visual-editing.ts and import it from both locations.

Apply
</Button>
</div>
) : (
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | accessibility (1/3 reviewers, validated)

Error messages not announced to screen readers

The URL and file error messages are rendered as plain <p> elements. Screen reader users won't be informed when these appear because there's no role="alert" or aria-live region. Additionally, the URL input lacks aria-invalid and aria-describedby to associate it with the error.

💡 Suggestion: Add role="alert" to the error paragraphs and connect them to their inputs:

{urlError && <p id="url-error" role="alert" className="text-xs text-red-500">{urlError}</p>}
// and on the Input:
aria-invalid={!!urlError}
aria-describedby={urlError ? "url-error" : undefined}

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary

Verdict: 🤔 NOT SURE - Potential issues

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

Issues Summary

Severity File Issue
🟡 MEDIUM src/components/preview_panel/PreviewIframe.tsx:310 handleTextUpdated silently drops imageSrc/imageUpload from pending changes
🟡 MEDIUM src/components/preview_panel/ImageSwapPopover.tsx:184 "Current source" display does not update after swapping
🟡 MEDIUM src/pro/main/utils/visual_editing_utils.ts:437 Dynamic image sources silently overwritten with static value
🟢 Low Priority Notes (4 items)
  • Orphan images on failure - src/pro/main/ipc/handlers/visual_editing_handlers.ts:82 — Image files are written to disk and git-staged before the AST transform runs. If the transform fails, orphan images remain with no cleanup.
  • textContent inconsistency - src/components/preview_panel/VisualEditingToolbar.tsx:175existing?.textContent || "" converts undefined to empty string, while imageSrc: existing?.imageSrc correctly preserves undefined. The || "" could theoretically wipe text content if the dialog's text cache fails.
  • Generic error message - src/components/preview_panel/PreviewIframe.tsx:548 — Image load error says "Image failed to load" without indicating which URL failed.
  • MAX_IMAGE_SIZE naming - src/pro/main/ipc/handlers/visual_editing_handlers.ts:32 — Name suggests bytes but value is base64 string length (10M chars ≈ 7.5MB decoded). Consider MAX_IMAGE_BASE64_LENGTH for clarity.
🚫 Dropped False Positives (9 items)
  • Filename sanitization allows leading dots — Dropped: The timestamp prefix ensures the final filename (e.g., 1709123456-.htaccess) is never a hidden file.
  • Mutating input change.imageSrc — Dropped: The mutation is intentional and the data flow works correctly. Code pattern preference, not a bug.
  • mkdir called inside loop — Dropped: recursive: true makes repeated calls idempotent. Negligible performance impact.
  • Redundant && foundElement check — Dropped: Too minor, the early return above guarantees foundElement is truthy.
  • handleImageSwap duplicates pending-changes pattern — Dropped: Two similar instances doesn't warrant abstraction. Premature to refactor now.
  • No visual confirmation after upload — Dropped: The preview iframe updating IS the visual feedback. Filename on button provides additional confirmation.
  • No feedback if img not in DOM — Dropped: AST analysis and DOM should be in sync. Edge case from hot-reload timing unlikely to cause user confusion.
  • Only first img detected in multi-image components — Dropped: Reasonable design choice for the first iteration. Not a bug.
  • No base64 prefix validation — Dropped: Data always comes from FileReader.readAsDataURL() within the same application. Format is guaranteed.

Generated by Dyadbot multi-agent code review

<p className="text-xs font-mono truncate mt-1" title={currentSrc}>
{currentSrc || "none"}
</p>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | stale-ui-state

"Current source" display does not update after swapping

currentSrc comes from the initial analyzeComponent AST analysis and only updates when a different component is selected. After the user swaps an image (via URL or upload), the footer still shows the old source value, creating a confusing disconnect — the user just changed the image but the UI says the source is still the old one.

💡 Suggestion: Track a local appliedSrc state that updates when onSwap is called, and display that in the footer. For example:

const [appliedSrc, setAppliedSrc] = useState(currentSrc);
// In useEffect: setAppliedSrc(currentSrc);
// In handlers: setAppliedSrc(newSrc); before calling onSwap(...);

let hasImage = false;
let imageSrc: string | undefined;

const tagName = foundElement.openingElement.name;
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | edge-case

Dynamic image sources silently overwritten with static value

When an <img> has a dynamic src (e.g., src={myImgUrl} or src={props.imageUrl}), extractStaticSrc returns undefined but hasImage is still set to true. The user sees the image swap popover with "Current source: none" — misleading, since there IS a source, it's just dynamic.

More critically, if the user swaps the image, transformContent will replace the dynamic expression (e.g., {myImgUrl}) with a static string literal, silently breaking the dynamic behavior with no warning.

💡 Suggestion: Add a check for dynamic image sources analogous to the existing isDynamic detection. When the <img> has a src attribute but extractStaticSrc returns undefined, set a flag like isDynamicImage: true and either disable the swap popover or show a warning in the UI.

console.error("Failed to analyze component", err);
setIsDynamicComponent(false);
setHasStaticText(false);
setHasImage(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | data-integrity

handleTextUpdated (line 310) silently drops imageSrc and imageUpload from pending changes

The handleTextUpdated function (line 292-322, outside this diff hunk) builds a new pending change entry that preserves styles from the existing entry but does not preserve imageSrc or imageUpload. If a user first swaps an image (which stores imageSrc + imageUpload in pending changes via handleImageSwap), and then edits text on the same component, the image change data is silently lost.

This is the same class of bug that was previously fixed in sendStyleModification (which now correctly preserves these fields on lines 176-177 of VisualEditingToolbar.tsx).

💡 Suggestion: Add imageSrc: existing?.imageSrc and imageUpload: existing?.imageUpload to the pending change object in handleTextUpdated at line 310-318.

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary

Verdict: ✅ YES - Ready to merge

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

All previously flagged HIGH issues have been resolved. Good test coverage with both unit tests and E2E. The remaining issues are code health/polish concerns suitable for follow-up.

Issues Summary

Severity File Issue
🟡 MEDIUM visual_editing_handlers.ts:78-107 Orphaned image files on transform failure
🟡 MEDIUM VisualEditingToolbar.tsx:316-352 Pending change construction duplicated across 3 locations
🟡 MEDIUM ImageSwapPopover.tsx No dark mode support
🟡 MEDIUM ImageSwapPopover.tsx:40-58 Failed image URL saved as pending change with no undo path
🚫 Dropped False Positives (7 items)
  • sendStyleModification defaults textContent to empty string — Dropped: Pre-existing code not changed in this PR (diff context only)
  • Timestamp collision when multiple images uploaded — Dropped: Sequential async processing and user interaction make same-millisecond uploads practically impossible
  • Mutating input parameter between loops — Dropped: Both loops are in the same function ~30 lines apart; pattern is local and clear
  • MAX_IMAGE_SIZE naming ambiguity — Dropped: Comment already clarifies "~7.5MB decoded"
  • Redundant null check on foundElement — Dropped: Defensive coding, too minor
  • Client placeholder image path discarded by backend — Dropped: Already covered by existing comment Query | Question about model availability via OpenRouter #3 (filename generation duplication)
  • skipOverlayElement skips text content — Dropped: Rare edge case, function is already well-guarded with multiple checks

Generated by Dyadbot multi-agent code review

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Multi-agent review: 4 MEDIUM issues found. See summary comment for details.

});
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | data-integrity (2/3 reviewers)

Orphaned image files on source code transform failure

Image files are written to public/images/ and git add-ed (lines 78-107) before the source code transformation runs (lines 142-161). If transformContent throws an error or the source file write fails, the image files remain on disk and staged in git with no corresponding source code reference. There is no cleanup in the catch block.

💡 Suggestion: Track the written file paths and delete them in the catch block, or defer the file writes until after all source code transforms succeed.

return updated;
});
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | duplication (2/3 reviewers)

Pending change construction logic duplicated across 3 locations

The pattern of building a full VisualEditingChange object (with componentId, componentName, relativePath, lineNumber, styles, textContent, imageSrc, imageUpload) is repeated in:

  1. sendStyleModification (this file, line ~169)
  2. handleImageSwap (this file, line ~337)
  3. handleTextUpdated (PreviewIframe.tsx, line ~310)

Each location must remember to preserve all fields from other edit types. This fragile pattern has already caused bugs (existing comments #14 and #24 about dropping imageSrc/imageUpload and textContent).

💡 Suggestion: Extract a shared mergePendingChange(existing, partial) helper that automatically preserves all unrelated fields when applying a partial update.

</div>
)}

{/* Current source display */}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | dark-mode (1/3 reviewers, validated)

No dark mode support in ImageSwapPopover

This component uses hardcoded light-mode colors (text-gray-500, text-red-500, border-t) with no dark: variants. Other components in the codebase consistently provide dark mode variants (e.g., text-gray-600 dark:text-gray-400). In dark mode, helper text and error messages will have poor contrast against the dark background.

💡 Suggestion: Add dark: variants: text-gray-500 dark:text-gray-400 for helper text, text-red-500 dark:text-red-400 for errors, and use a theme-aware border variable for the separator.

return;
}
setUrlError(null);
onSwap(trimmed);
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | error-recovery (1/3 reviewers, validated)

Failed image URL is saved as a pending change with no undo path

When a user enters a URL and clicks Apply, onSwap immediately stores the URL in pending changes. If the image fails to load, a toast error appears ("Image failed to load"), but the broken URL remains in pending changes. If the user then clicks "Save Changes", the broken image URL is written to the source file. There is no indication in the popover that the URL failed, and no way to revert other than manually re-entering the old URL.

💡 Suggestion: Consider removing or marking the pending image change when dyad-image-load-error is received, or show the error inline in the popover so the user has context.

claude and others added 8 commits February 27, 2026 23:19
Enable users to select <img> elements in the visual editor and swap their
source by entering a URL or uploading a new file. This extends the existing
visual editing pipeline with image-specific detection, transformation, and UI.

Changes:
- Extend IPC schemas with imageSrc and imageUpload fields
- Add image detection to AST analyzeComponent (self and child <img>)
- Add image src AST transformation in transformContent
- Handle image file uploads to public/images/ in save handler
- Add modify-dyad-image-src iframe message handler for live preview
- Create ImageSwapPopover component with URL and Upload modes
- Wire up hasImage/currentImageSrc state through PreviewIframe to toolbar
- Add comprehensive tests for image analysis and transformation

https://claude.ai/code/session_01BtuNFxQdcBRQGoy78fDR9r
- Add useEffect to sync urlValue when switching between image components
- Add error message when user selects unsupported file type
- Remove duplicate filename generation from client (server is sole authority)
- Add server-side MIME type validation and file size limit for image uploads
- Use recursive search for nested <img> elements in transformContent
- Use recursive search for nested <img> elements in analyzeComponent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add URL validation with error feedback in ImageSwapPopover
- Add FileReader onerror handler for failed file reads
- Add image onerror handler to update overlay coordinates on load failure
- Fix double-wrapped error message in visual editing handler
- Extract inline onSwap handler into named handleImageSwap function
- Remove dead handleGetImageSrc code and get-dyad-image-src handler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix data-loss bug: sendStyleModification now preserves imageSrc and imageUpload in pending changes
- Extract duplicated image src extraction logic into extractStaticSrc helper
- Add image load error feedback: preview iframe now shows toast when image fails to load

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 235 2 6 6

Summary: 235 passed, 2 failed, 6 flaky, 6 skipped

Failed Tests

🍎 macOS

  • auto_approve.spec.ts > auto-approve
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • template-create-nextjs.spec.ts > create next.js app
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed

📋 Re-run Failing Tests (macOS)

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

npm run e2e \
  e2e-tests/auto_approve.spec.ts \
  e2e-tests/template-create-nextjs.spec.ts

⚠️ Flaky Tests

🍎 macOS

  • annotator.spec.ts > annotator - capture and submit screenshot (passed after 1 retry)
  • context_manage.spec.ts > manage context - smart context (passed after 1 retry)
  • local_agent_basic.spec.ts > local-agent - dump request (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.spec.ts > setup ai provider (passed after 1 retry)
  • toggle_screen_sizes.spec.ts > Toggle Screen Size Tests > should switch between device modes (passed after 1 retry)

📊 View full report

@github-actions
Copy link
Contributor

🔍 Dyadbot Code Review Summary

Verdict: ✅ YES - Ready to merge

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

Issues Summary

Severity File Issue
🟡 MEDIUM src/pro/main/ipc/handlers/visual_editing_handlers.ts:26 Client/server image size limits are mismatched
🚫 Dropped False Positives (20 items)
  • Inner path.traverse double-write on parent+child img - Dropped: Users select one component at a time; parent+child img changes in same batch not possible in current UI flow
  • Timestamp filename collisions - Dropped: Sequential loop processing makes same-millisecond collision with identical filenames extremely unlikely
  • Separate conditionals in handleImageSwap (2/3 reviewers) - Dropped: React's synchronous execution prevents selectedComponent from changing between blocks; partially covered by existing comment on pending change duplication
  • querySelector vs AST ordering mismatch - Dropped: Inherent first-img-only limitation already covered by existing comments
  • getComputedStyle can throw on detached elements - Dropped: Element was just clicked, extremely unlikely to be detached
  • base64 regex may not strip prefix - Dropped: FileReader.readAsDataURL always produces standard format
  • ImageUploadData duplicates Zod type - Dropped: Common pattern for component prop interfaces; 3-field shape duplication is acceptable
  • Coordinate-sending postMessage duplication - Dropped: Only 2 instances, below threshold for extraction
  • Inconsistent textContent fallback - Dropped: Pre-existing issue in sendStyleModification, not introduced by this PR
  • Redundant && foundElement check - Dropped: Trivial, dead condition after early return
  • No comment on first-img-only limitation - Dropped: Already covered by existing comments
  • No loading indicator during file read - Dropped: For typical images FileReader is near-instant; 7.5MB files are rare
  • No visual confirmation after Apply - Dropped: Related to existing "current source doesn't update" comment; preview updates behind popover
  • SVG not in VALID_IMAGE_MIME_TYPES - Dropped: Likely intentional security decision (SVG can contain scripts); accept attribute prevents selection
  • URL input auto-focus missing - Dropped: Minor polish
  • Long URLs overflow current source display - Dropped: Too minor
  • No timeout for image load/error events - Dropped: Edge case for local dev tool; network hangs rare in localhost context
  • Icon-only Swap Image trigger - Dropped: Consistent with existing toolbar icon-only pattern
  • Generic error message for uploads - Dropped: Uploads use data URLs which almost never fail to load
  • No override for overlay skip - Dropped: Acceptable for initial implementation

Generated by Dyadbot multi-agent code review

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Multi-agent review: 1 issue found

} from "../../utils/visual_editing_utils";
import { normalizePath } from "../../../../../shared/normalizePath";

const MAX_IMAGE_SIZE = 10_000_000; // ~7.5MB decoded
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 MEDIUM | size-limit-mismatch (2/3 reviewers)

Client/server image size limits are mismatched

The client-side check in ImageSwapPopover.tsx allows files up to 7.5 * 1024 * 1024 = 7,864,320 bytes. When base64-encoded (with data URL prefix), this produces ~10,485,760 characters — which exceeds the server-side MAX_IMAGE_SIZE of 10,000,000.

A user could upload a file that passes client validation (e.g., a 7.4 MB image) but gets rejected by the server with a confusing "image is too large" error after they've already seen it previewed successfully.

💡 Suggestion: Either derive the server limit from the client limit (accounting for base64 overhead: Math.ceil(7.5 * 1024 * 1024 / 3) * 4 + 100), or define a shared raw-byte-size constant and derive both checks from it. Also fix the comment which says ~7.5MB decoded but the actual decoded size is ~7.3MB.

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.

Add image selection and swapping in the visual editor

3 participants