Skip to content

fix(desktop): avoid Explorer DnD image preview freeze on Windows - #66546

Open
stantheman0128 wants to merge 2 commits into
NousResearch:mainfrom
stantheman0128:fix/63682-dnd-image-freeze
Open

fix(desktop): avoid Explorer DnD image preview freeze on Windows#66546
stantheman0128 wants to merge 2 commits into
NousResearch:mainfrom
stantheman0128:fix/63682-dnd-image-freeze

Conversation

@stantheman0128

Copy link
Copy Markdown
Contributor

What does this PR do?

Dragging a PNG/JPEG from Windows Explorer into Hermes Desktop chat was freezing the UI. OS drops were building the composer chip preview via readFileDataUrl, which base64-loads the whole file over IPC (up to 16 MB). On a typical phone/camera image that stalls the renderer.

This keeps the existing path-based image.attach pipeline (so pasting a path still works the same), but prefers URL.createObjectURL from the dropped File for the chip/optimistic preview. Path-only attaches (paperclip) still use the IPC data-URL path. Blob preview URLs are revoked when chips are removed or cleared.

Related Issue

Fixes #63682

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

  • apps/desktop/src/app/chat/hooks/use-composer-actions.ts: resolveImageAttachmentPreview; OS drops and blob saves pass the in-hand File/Blob for preview
  • apps/desktop/src/store/composer.ts: revoke blob: preview URLs on remove/clear/replace
  • apps/desktop/src/lib/chat-runtime.ts: optimistic bubble renders blob: previews as markdown images
  • Tests for object-URL preview preference and optimistic blob refs

How to Test

  1. Launch Hermes Desktop on Windows.
  2. Drag a .png / .jpg from File Explorer into the chat composer.
  3. The UI should stay responsive and show an image chip (no freeze).
  4. Send a short prompt; vision / path attach should still work as before.
  5. Remove the chip; confirm no leftover blob URL leaks (no console errors).

Checklist

Code

  • I have read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this is not a duplicate
  • My PR contains only changes related to this fix
  • I ran the Desktop vitest coverage for the touched files (see Evidence)
  • I added tests for my changes
  • I tested on my platform: Windows 11

Documentation and Housekeeping

  • Documentation N/A for this Desktop preview path change
  • cli-config.yaml.example N/A
  • CONTRIBUTING.md / AGENTS.md N/A
  • Cross-platform: object URLs work on macOS/Linux too; Windows was the reported freeze
  • Tool schemas N/A

Evidence

cd apps/desktop
npx vitest run src/app/chat/hooks/use-composer-actions.test.ts src/lib/chat-runtime.test.ts src/store/composer.test.ts

Test Files  3 passed (3)
     Tests  45 passed (45)
python scripts/check-windows-footguns.py apps/desktop/src/app/chat/hooks/use-composer-actions.ts apps/desktop/src/store/composer.ts apps/desktop/src/lib/chat-runtime.ts
No Windows footguns found (0 file(s) scanned).
# TS-only change; hermetic smoke via local venv pytest
.venv/Scripts/python.exe -m pytest tests/tools/test_windows_compat.py -q
12 passed, 4 skipped

AI assistance

Prepared with AI assistance (Cursor/Grok). Human author: Stan Shih (@stantheman0128).

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) platform/windows Native Windows-specific behavior or breakage P3 Low — cosmetic, nice to have sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 17, 2026

@teknium1 teknium1 left a comment

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.

Thanks for isolating the expensive preview IPC path. The current main path still routes an OS-dropped image through attachmentPreviewDataUrl (apps/desktop/src/app/chat/hooks/use-composer-actions.ts:598, :421), and Electron then reads and base64-encodes the full file (apps/desktop/electron/main.ts:8051-8059).

Problems

  • The new generic clear() revocation is too early for the new optimistic blob: preview. Direct send clones attachments, clears the composer, then dispatches the clone (apps/desktop/src/app/chat/composer/hooks/use-composer-submit.ts:165-170). Revoking in that clear invalidates the blob URL before the optimistic message can use it.
  • Queueing has the same ownership issue: it stores attachments in the queued prompt and then clears the composer (apps/desktop/src/app/chat/composer/hooks/use-composer-queue.ts:174-179).

Suggested changes

  • Keep blob URLs alive across composer clear when a submitted or queued attachment snapshot retains them; revoke them only when the final optimistic/queued consumer is discarded or replaced.
  • Add direct-submit and queued-prompt lifecycle tests for that ownership handoff.

Automated hermes-sweeper review.

Comment thread apps/desktop/src/store/composer.ts Outdated
@@ -51,12 +62,17 @@ export function createComposerAttachmentScope($attachments = atom<ComposerAttach
}
},
clear() {
for (const attachment of $attachments.get()) {

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.

This generic clear runs after direct submit has cloned attachments for dispatchSubmit and after queueing has retained them. Revoking here invalidates the PR's new blob: optimistic preview before its retained consumer renders; defer revocation until that submitted/queued owner is released.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users labels Jul 18, 2026
@stantheman0128

Copy link
Copy Markdown
Contributor Author

Thanks for the catch on the blob URL ownership. You were right: clear() was revoking the preview before the optimistic message (and the queued snapshot) got to use it.

What changed in 52eca2e73:

  • clear({ retainPreviewUrls: true }) on direct submit and queue handoff so blob: URLs survive the composer wipe
  • Queue discard/replace paths revoke when the entry is the final consumer; drain remove uses retainPreviewUrls so submit can keep the preview
  • Lifecycle tests for both the direct-submit and queued-prompt handoffs

Evidence:

cd apps/desktop
npx vitest run src/store/composer.test.ts src/store/composer-queue.test.ts src/app/chat/hooks/use-composer-actions.test.ts src/lib/chat-runtime.test.ts

Test Files  4 passed (4)
     Tests  64 passed (64)
python scripts/check-windows-footguns.py apps/desktop/src/store/composer.ts apps/desktop/src/store/composer-queue.ts apps/desktop/src/app/chat/composer/hooks/use-composer-submit.ts apps/desktop/src/app/chat/composer/hooks/use-composer-queue.ts apps/desktop/src/app/session/hooks/use-background-queue-drain.ts
No Windows footguns found (0 file(s) scanned).

@stantheman0128
stantheman0128 force-pushed the fix/63682-dnd-image-freeze branch from 52eca2e to aecb504 Compare July 21, 2026 09:29
stantheman0128 and others added 2 commits July 23, 2026 15:38
Stan Shih (stantheman0128)

OS image drops used readFileDataUrl for chip previews, which base64-loads
the whole file over IPC and freezes Desktop on Windows. Prefer
URL.createObjectURL from the dropped File, and revoke blob previews on
chip remove/clear.

Closes NousResearch#63682

Co-authored-by: Cursor <cursoragent@cursor.com>
Stan Shih (stantheman0128)

Direct submit and queue clone attachments then clear the composer.
Revoking blob: URLs inside that clear invalidated the optimistic/queued
preview. Retain previews across handoff and revoke when the final
consumer is discarded or replaced.

Co-authored-by: Cursor <cursoragent@cursor.com>
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

One PR addresses #63682. #66546 replaces the freeze-causing IPC/base64 preview path for Windows Explorer image drops with object URLs while preserving path-based image attachment routing, and it adds ownership handoffs for direct submission and queued prompts.

Related pull requests

  • fix(desktop): avoid Explorer DnD image preview freeze on Windows #66546 best fix — (+335/-20) — n/a: The diff directly avoids reading and base64-encoding an in-hand dropped image over IPC and retains blob previews across composer-to-submit and composer-to-queue handoffs. Consistent with the contributor's keep_open COMMENTED review, those handoffs address the identified premature revocation, but the visible production diff still does not show final revocation when the optimistic consumer is discarded or replaced; the corresponding test performs that revocation manually.

Suggested consolidation

Keep #66546 open with a salvage path: retain its targeted object-URL preview fix and ownership-handoff tests, while adding and testing production-side revocation when the final optimistic consumer is discarded or replaced. It remains the recorded best-fix consolidation target for #63682, and there are no duplicate PRs to close.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I63682(["issue #63682 (open)"])
    P66546["PR #66546 (open)"]
    P66546 -->|best fix| I63682
    class I63682 open
    class P66546 open
    class P66546 best
    class P66546 target
    click I63682 "https://github.com/NousResearch/hermes-agent/issues/63682"
    click P66546 "https://github.com/NousResearch/hermes-agent/pull/66546"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 24 kB of PR diffs, 6 kB of issue/PR text, 2 kB of discussion (2 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have platform/windows Native Windows-specific behavior or breakage sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop GUI: Drag-and-drop image upload freezes the UI

4 participants