Commit Graph

4089 Commits

Author SHA1 Message Date
Teknium 1c9de87ffb fix: drop duplicate composingRef declaration from rebase weave 2026-08-15 01:03:57 -07:00
Teknium 89df391580 fix(desktop): keybinds skip IME composition keys; pin draft survival across Settings navigation
- use-keybinds: bail out of the global keydown dispatcher while an IME
  composition is active. Windows Chinese IMEs use Ctrl+, as their
  punctuation toggle, which also matched nav.settings and navigated away
  mid-word — destroying the unsent composer draft (#41079).
- use-composer-draft.test: regression-pin the unmount-stash/remount-restore
  contract so route navigation (Settings) can never again drop an unsent
  draft with the React tree.
- use-composer-actions.test: the bounded-preview pipeline keys attachments
  to the durable path and resolves thumbnails asynchronously; the dropped-
  screenshot test now asserts the durable-path contract instead of the
  retired full-res previewUrl field.
2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d0642e61e5 fix(desktop): preserve legacy attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz e2a2fba4aa fix(desktop): preserve in-flight attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 462e7d8e6b fix(desktop): preserve tile attachment ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 193c199b23 fix(desktop): make image attachment updates occurrence-safe 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 7d5e9757ec fix(desktop): preserve image preview ownership across drafts 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 6dfe63e1e2 fix(desktop): preserve image attachment occurrence ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 5e09b4008b fix(desktop): serialize composer image previews 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d345f0831f fix(desktop): bound multi-image thumbnail raster cost 2026-08-15 01:03:57 -07:00
David Metcalfe 485e1e8f69 fix(desktop): keep full-res previewUrl separate from downscaled thumbnail
Addresses review feedback on #68744: downscaling inside
attachmentPreviewDataUrl made previewUrl the 2048px PNG, but current main
(d8cb73b4ab) feeds that field to ImageLightbox and useImageDownload, so
large attachments would open and download at reduced resolution.

- attachmentPreviewDataUrl returns the full-resolution data URL again
- ComposerAttachment gains thumbnailUrl?: string (store/composer.ts)
- attachImagePath stores previewUrl (full-res) + thumbnailUrl (downscaled)
- Attachment pill renders thumbnailUrl ?? previewUrl; lightbox/download
  keep the full-res previewUrl
- optimisticAttachmentRef prefers thumbnailUrl for the in-flight bubble
  display ref (same main-thread decode freeze at send time)
- Attachment-level regression test in use-composer-actions.test.ts:
  full-res previewUrl preserved while thumbnailUrl is a separate
  downscaled value (4000x3000 -> 2048x1536)
2026-08-15 01:03:57 -07:00
David Metcalfe 24fc613318 fix(desktop): downscale large images in composer preview to prevent UI freeze 2026-08-15 01:03:57 -07:00
Johnny 0d447712a0 fix(desktop): keep composer stable across compression 2026-08-15 01:03:57 -07:00
Gille 224530211e fix(desktop): let composer status titles use row width 2026-08-15 01:03:57 -07:00
xrwang8 47970a9e4c fix(desktop): clear edit-composer submitting latch after 200ms cooldown
- Add submitting state + IME composition guard to prevent double-submit
- submitEdit() sets latch, then clears after 200ms timeout
- handleKeyDown() guards on composing so IME Enter doesn't submit
- Shift+Enter inserts newline without submitting
- Test validates Enter calls onEdit, latch clears for second session

Fixes #70771

Signed-off-by: xrwang8 <xrwang8@gmail.com>
2026-08-15 01:03:57 -07:00
mollusk 652ae4877a fix(desktop): let the edit composer scroll long prompts
The inline edit composer caps height at max-h-48 but had no overflow
rule, so long prompts were clipped with no way to reach the tail.
Add overflow-y-auto to match the main composer editor.
2026-08-15 01:03:57 -07:00
Lester Liang 49fbca1880 fix(desktop): prevent accidental composer popout 2026-08-15 01:03:57 -07:00
Denis 354abd7c57 fix(desktop): keep empty composer from collapsing
Fixes #68134\nFixes #68095
2026-08-15 01:03:57 -07:00
infinitycrew39 2a82c6ffbc style(desktop): align empty-state width with full chat column 2026-08-15 01:03:57 -07:00
infinitycrew39 006a4a8873 fix(desktop): restore full-width chat composer column 2026-08-15 01:03:57 -07:00
hermes-seaeye[bot] af585de28e fmt(js): npm run fix on merge (#86801)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 07:46:57 +00:00
Teknium 03c85a1c7c test(desktop): port gateway-file-download tests to vitest electron project
The salvaged branch predates the electron test project's node:test -> vitest
migration (test:desktop:platforms is now `vitest run --project electron`).
Import `test` from vitest so the suites are collected; assertions stay on
node:assert/strict per the existing electron test convention.
2026-08-15 00:37:00 -07:00
Paul Armbruster 27b003996a fix(desktop): stream native gateway downloads + 404 data-url fallback
Addresses both review findings on the remote-gateway download PR:

1. Unbounded buffering (finding #1). fetchBuffer / fetchBufferViaOauthSession
   accumulated the entire response (then copied it again via Buffer.concat)
   before saveGatewayFile even opened the save dialog, so a large gateway file
   could exhaust the native process. Both auth paths now stream: once response
   headers arrive the connect timeout is cleared, the filename is derived, the
   save dialog is shown, and the body is piped to the chosen destination with
   backpressure. A read/write error tears down the stream and unlinks the
   partial file. The byte-moving, data-URL decoding, and filename/path helpers
   are extracted into gateway-file-download.ts so they're unit-testable without
   Electron.

2. No fallback for older gateways (finding #2). saveGatewayFile required the new
   /api/fs/download route. Desktop and the remote gateway update independently,
   so a gateway predating this PR 404s. Added a 404-only compatibility fallback
   to the existing capped /api/fs/read-data-url route (bounded, so it only
   serves smaller files — enough to keep older backends working).

Tests: gateway-file-download.test.ts covers streaming, backpressure,
error-cleanup (unlink on write/response error), data-URL decoding, filename
derivation (incl. traversal reduction), and 404 detection;
gateway-file-download-transport.test.ts asserts both transports stream (no
whole-body Buffer.concat) and that the 404 fallback is wired. Both registered
in the desktop platform test list. Server-side /api/fs/download tests
(streaming + sensitive-file reject) already pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 00:37:00 -07:00
Paul BlackSwan d5a865882a fix(desktop): save remote gateway files natively 2026-08-15 00:37:00 -07:00
Dean Chen 0bc7070cf8 fix(desktop): gate image copy on decoded contents 2026-08-15 00:36:15 -07:00
Dean Chen 3c570c61ff fix(desktop): preserve copy image for large remote media 2026-08-15 00:36:15 -07:00
DannyFengTianYu 07b9090259 fix(desktop): preserve complete history when branching
Read the durable display transcript when creating a branch instead of copying the compacted model projection. Hydrate the Desktop branch boundary from persisted history, avoid stale whole-chat counts, and seed the new tile from the backend snapshot. Add regression coverage for compacted histories, visible-message counts, selected prefixes, and hydration races.

(cherry picked from commit c3d2d759ae104395adde215b2e293ccf8e895684)
2026-08-15 00:35:40 -07:00
mj 73e9b1a124 docs(desktop): align getGateway docstring with $gateway atom subscription
Residual hunk from PR #84832's picker-rebind commit; the functional
surfaces.tsx change landed on main via #86250.

(cherry picked from commit 990936d6f6e752ddb474787c1f7fdc3ad4f4dc60, docstring hunk only)
2026-08-15 00:35:28 -07:00
mj 20e5d78f54 fix(desktop): recover failed gateway catalogs
(cherry picked from commit ef1d2cc5e541f37fbd12b41f686a4c18a9b85320)
2026-08-15 00:35:28 -07:00
mj 1ef7a0bdda fix(desktop): recover empty model catalogs
(cherry picked from commit 72a06b9e0b392c494be011dc74ccf948ca693e3f)
2026-08-15 00:35:28 -07:00
Teknium ddf6456794 test: stub virtualizer.measure in virtual-session-list mock (density invalidation call) 2026-08-15 00:34:57 -07:00
Teknium 29c28136fe style(desktop): prettier/eslint pass on salvaged files 2026-08-15 00:34:57 -07:00
Teknium 865e4e7178 fix(desktop): default session list density to compact
Keeps the sidebar byte-identical for existing users until they opt into
comfortable/detailed themselves — per the no-default-change salvage rule.
2026-08-15 00:34:57 -07:00
David Metcalfe a33ac1e95e feat(desktop): add session list density modes
Squashed from #68124, resolved against current main (inbox cards, marquee
titles, actions cluster). Adds a Settings → Appearance 'Session list
density' preference — compact (default, unchanged), comfortable
(+deterministic metadata line), detailed (+initial-request preview) —
with density-aware virtualizer estimates.

Closes #68119

(cherry picked from commits 93bc19400a..568f78a8fc)
2026-08-15 00:34:57 -07:00
chillerno1 c6ec8a95e6 test(desktop): cover docked zone chevron direction
(cherry picked from commit 95358428f95175ef57376da334b69279618cce6a)
2026-08-15 00:34:57 -07:00
chillerno1 ac0d0f8897 fix(desktop): flip zone collapse chevron to action direction
Collapsed tool zones (terminal/logs) kept a down chevron after minimize,
so the restore affordance looked like another collapse. Point the icon in
the action direction — down when expanded, up when collapsed — matching
master-detail collapsible detail headers. Same fix for floating panes.

(cherry picked from commit 3392aeb9dba0ed9e9cabfb96b6010e46fa453ca1)
2026-08-15 00:34:57 -07:00
Isak du Plessis 10bf145e30 feat(desktop): collapse thinking by default
(cherry picked from commit 14fd1aace2c76c31f3b48e4b9bf9943c74582d21)
2026-08-15 00:34:57 -07:00
Teknium 15e7c563b3 style: eslint --fix on salvaged IME test (curly + padding rules) 2026-08-15 00:33:40 -07:00
Teknium aa95939469 test(desktop): cover IME composition guards in keybind combo resolution 2026-08-15 00:33:40 -07:00
Teknium 587788405a fix(desktop): extend stale-flag self-heal and keyCode 229 guard to the edit composer
Widen the composer-side IME fixes to the inline edit composer: the same
missed-compositionend wedge and post-compositionend keyCode 229 Enter
apply to its handleKeyDown path.
2026-08-15 00:33:40 -07:00
BAS Lam 9f2e6d05ab fix(desktop): ignore IME composition keydowns in keybind combo resolution
Chinese/Japanese/Korean IMEs emit keydown events during composition that
carry preedit keystrokes and the commit keypress (Enter/Space/Shift for
candidate selection). Treating them as combos fires unrelated keybinds —
e.g. typing 你 with a CJK IME could dispatch session.new and silently
open a new session.

Guard comboFromEvent():
- Bail out entirely while composing (event.isComposing or key === 'Process')
- Ignore keydowns whose event.key is a bare modifier name but whose code
  is a regular key — legacy IMEs that synthesize keystrokes (Q9 2002 sends
  key="Control" with code="KeyW") would otherwise canonicalize into
  phantom combos like mod+w that close the active tab.

Tested with Q9 (九方) legacy IME on Windows.
2026-08-15 00:33:40 -07:00
AIalliAI 39e7607794 fix(desktop): recover from a stale IME composition flag that wedged the composer
A missed compositionend (focus jump, input-source switch, programmatic DOM
swap mid-preedit) left composingRef stuck true, and the stuck flag silently
swallowed every Enter in handleEditorKeyDown and every Send-button submit via
the form onSubmit guard — no error, no RPC, until the composer remounted. For
CJK IME users (where even ASCII typing runs through composition) this read as
"Enter has no effect; messages cannot be sent", degrading composer instance
by composer instance.

Recover in two places, both grounded in invariants Chromium guarantees:

- keydown: every keydown during a genuine composition carries
  isComposing=true, so when the native flag says we're not composing, clear
  the stale ref before the guard reads it.
- blur: a composition never survives focus loss, so clear the flag
  unconditionally — this is what unblocks the Send button path, which has no
  native composition flag to consult.

The genuine-IME protection (#37483 class) is untouched: Enter with
isComposing=true is still swallowed.

Fixes #44135
2026-08-15 00:33:40 -07:00
liyunlong 202e5b813a fix(desktop): block Enter keyCode 229 after IME compositionend
macOS Chinese IME (and some 3rd-party Windows IMEs) emit Enter with
keyCode 229 (legacy VK_PROCESSKEY) after compositionend, while
isComposing is already false. The existing guard only checked
isComposing and the composingRef, so this Enter slipped through and
submitted the message before the committed text was fully in the DOM.

Add an explicit keyCode 229 check in handleEditorKeyDown.  keyCode is
deprecated, but it is the only reliable signal for this IME commit
Enter on Chromium-based browsers.  Includes a dom-repro test that
simulates the macOS IME sequence.

Fixes: "中文混英文按 Enter 直接上屏"
2026-08-15 00:33:40 -07:00
izumi0uu 3d51c40997 fix(desktop): prevent IME submit in inline edit composer 2026-08-15 00:33:40 -07:00
Takumi Sato 20beee774c fix(desktop): prevent Enter from submitting during IME composition
Check event.nativeEvent.isComposing in Enter-to-submit branches so CJK (Japanese, Chinese, Korean) and other compositional input methods commit the candidate instead of firing the send handler.

Applied to all five sites in the desktop renderer that currently handle Enter with no composition guard:

- chat composer main submit and trigger popover (apps/desktop/src/app/chat/composer/index.tsx)
- message edit composer submit and trigger popover (apps/desktop/src/components/assistant-ui/thread.tsx)
- onboarding API key and auth code inputs (apps/desktop/src/components/desktop-onboarding-overlay.tsx)
- session rename input (apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx)

Fixes #37483
2026-08-15 00:33:40 -07:00
Teknium 1025cb0e66 fix(desktop): stop deleted sessions resurrecting through racing list fetches
A session deleted in the sidebar disappeared optimistically but flashed
back when any list fetch raced the in-flight DELETE RPC — the backend
page still carried the doomed row until the transaction committed
(#50928, reproduced with 'Load more' and auto-refresh). The optimistic
tombstone ($removedSessionIds) was only honored by the recents slice of
refreshSessions; the messaging slice, the per-platform pager, and
refreshMessagingSessions ingested backend pages unfiltered.

Extract the tombstone filter into dropTombstoned() and apply it at every
session-list ingestion point. Tombstones only exist while a delete or
archive is in flight (they self-clear on confirmation and are removed
immediately on failure), so non-destructive refresh paths are untouched.

Fixes #50928
2026-08-15 00:33:32 -07:00
Gorde Minchel e6708af1f2 feat(desktop): confirm before deleting a session
Deleting a session in the desktop app fired instantly on click — the CLI
path (hermes sessions delete) asks y/N by default, so one misclick (Archive
and Delete sit right next to each other) permanently destroyed a conversation
with no dialog and no undo (#61470).

Route every delete entry point (sidebar rows, tab menus, the chat header,
context menus — all share useSessionActions) through a shared
DeleteSessionDialog built on ConfirmDialog. ConfirmDialog gains an
onOpenAutoFocus prop so dialogs with no input keep focus off the close
button (a11y).

Tests: menu delete now asks; cancel keeps the session; Enter confirms;
Escape cancels; delete item disabled without onDelete; the same guard
applies via SessionContextMenu.
2026-08-15 00:33:32 -07:00
PRATHAMESH75 8e35ff0a62 fix(desktop): confirm before clearing the entire Enabled Toolsets list (#73319)
Config settings auto-save on a 550ms debounce with no undo. The
'Enabled Toolsets' list is rendered by the generic ConfigField with no
destructive-change guard, so a stray select-all + Backspace (or any edit
that empties the list) is persisted the moment Settings closes —
silently disabling memory, terminal, web search, delegation, and most
tools. Recovery required CLI intervention.

Guard the one destructive transition: when the enabled-toolsets list
goes from non-empty to empty, window.confirm() before applying it (the
same pattern env-var removal already uses in toolset-config-panel.tsx).
Every other edit passes through untouched.

The decision is a pure helper (clearsEnabledToolsets) so it is unit
tested directly rather than through a full settings render. New i18n key
toolsetsWipeConfirm added to en + zh; partial locales inherit the
English string via defineLocale fallback.

Fixes #73319
2026-08-15 00:33:32 -07:00
Teknium 6b39ae490d fix(desktop): let sidebar wheel scrolling chain past the virtualized list
With 25+ sessions the recents list virtualizes into its own nested
scroller inside the sidebar's scroll container. Both carried
overscroll-contain, so once the inner scroller hit a scroll boundary the
wheel gesture was consumed instead of chaining to the outer sidebar
scroller — read as a mid-list wheel dead-zone while scrollbar drag kept
working. Drop the containment on the inner scroller only; the outer
sidebar scroller keeps overscroll-contain so the gesture still never
escapes the sidebar.

Fixes #84964
2026-08-15 00:33:22 -07:00
Inspired-atm db1d8983e5 fix(desktop): no-op branch switch on non-repo project lanes
Non-repo explicit projects (plain folders) get a main-checkout lane whose
label is the folder basename, not a branch. Clicking "+" (new session) on
such a lane calls switchBranchInRepo -> switchBranch, which sanitizes the
basename to "" and throws "Branch name is required.", aborting the
session creation. Short-circuit switchBranch for roots that are not git
work trees so callers proceed with a plain session.

Fixes #83028
2026-08-15 00:33:11 -07:00