feat(desktop): add i18n system with Chinese (zh) translation - #37568
Closed
wang-lch wants to merge 1 commit into
Closed
feat(desktop): add i18n system with Chinese (zh) translation#37568wang-lch wants to merge 1 commit into
wang-lch wants to merge 1 commit into
Conversation
- Add lightweight i18n module (src/locales/) with en/zh translations
- Add language switcher in Settings > Appearance
- Translate all desktop UI components (~600 strings across 30+ files)
- Persist language preference to localStorage
- Support parameterized translations with {placeholder} syntax
- Type-safe translations with DeepStringify<T> utility type
austinpickett
added a commit
that referenced
this pull request
Jun 2, 2026
- composer/text-utils.ts: revert paste-image dedupe — PR #37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR #37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com>
19 tasks
austinpickett
added a commit
that referenced
this pull request
Jun 2, 2026
* fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR #37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR #37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Yuki-14544869
pushed a commit
to Yuki-14544869/hermes-agent
that referenced
this pull request
Jun 4, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
davidgut1982
pushed a commit
to davidgut1982/hermes-agent
that referenced
this pull request
Jun 5, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
Superseded by #38241, which we merged as the desktop i18n foundation + Simplified Chinese translation. It aligns with our existing web/src/i18n pattern (typed Translations, I18nProvider/useI18n, display.language config key), was already green across CI, and merged cleanly. Thanks for the work on localization — this is exactly the direction we wanted, and your effort helped confirm it. #38241 |
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
alt-glitch
pushed a commit
that referenced
this pull request
Jun 14, 2026
* fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR #37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR #37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
kossteg
pushed a commit
to kossteg/hermes-agent
that referenced
this pull request
Jun 16, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
T02200059
pushed a commit
to T02200059/hermes-agent
that referenced
this pull request
Jun 18, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
donbowman
pushed a commit
to donbowman/hermes-agent
that referenced
this pull request
Jul 13, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…#37536) * fix(desktop): triage 24 GUI quality-of-life fixes across sidebar, composer, tool cards, messaging, and platform plumbing A grab-bag of high-leverage UX fixes plus a few backend touches that the GUI needs to behave correctly on Windows. Sidebar / sessions - Decrement $sessionsTotal on delete + archive so "Load N more" stops claiming removed rows are still on the server. - Hide the "Group by workspace" toggle when no unpinned sessions exist. - Accept Cmd/Ctrl+N as a "new session" accelerator (in addition to bare Shift+N), and render the kbd hint per-platform. - Switch the statusbar to overflow-x-clip so untitled sessions don't paint a horizontal scrollbar at the bottom of the window. Messaging + Cron - Add [-webkit-app-region: no-drag] to the page-search input so clicks reach the field instead of routing to the OS window-drag handler. - Replace single-letter PlatformAvatar with brand glyphs from @icons-pack/react-simple-icons (telegram, discord, matrix, signal, whatsapp, mattermost, wechat, qq, ...). Letter monogram fallback for Slack / Dingtalk / Feishu / WeCom (removed from Simple Icons at brand owner request). - Drop the duplicate "Create first cron" button in the empty state. Composer - Dedupe pasted images by (name, size, lastModified, type) instead of Blob identity; Chromium hands us the same screenshot via both clipboard.items and clipboard.files with fresh File instances. - Enable spellcheck on the contentEditable, configure Chromium's spellchecker with the system locale on whenReady, and add replaceMisspelling + "Add to dictionary" entries to the context menu. - Render user messages through a minimal markdown pipeline (inline backtick code + fenced ``` blocks) while keeping @file:/@image: directive chips intact. - max-h-[60vh] overflow-y-auto + collisionPadding on the prompt-snippet submenu. - Bake cursor-pointer into the <Button> primitive (with disabled:cursor-default) and into titlebarButtonClass. Dialogs + tabs + version - Default DialogContent now has max-h-[85vh] overflow-y-auto so long bodies scroll instead of falling off-screen. - Right-rail preview tabs close on middle-click (button === 1), with an onMouseDown swallow to suppress Chromium autoscroll. - New refreshDesktopVersion() helper called from About mount, after every update check, and on throttled window focus so About reflects the just-installed binary. Keys + Artifacts + Terminal - Drop the global "Show advanced" toggle in KeysSettings. Provider groups now default-expand when they have any key set. - Extend openExternalUrl to handle file:// via shell.openPath, with showItemInFolder fallback when the OS can't open the file. - New lib/ansi.ts SGR parser + <AnsiText> component, applied to terminal/execute_code tool output. - ToolView gained stdout / stderr / rendersAnsi; tool-fallback renders the two streams as separate labeled blocks with stderr in a neutral tone (not destructive — many CLIs log info on stderr). - Drop 'stderr' from ERROR_MSG_KEYS in tool-result-summary. Paths + platform - resolveHermesCwd skips process.cwd() when packaged and prefers a user-configurable default project directory. - New hermes:setting:defaultProjectDir:{get,set,pick} IPC handlers + preload bridge + global.d.ts typing + a "Default project directory" row in Sessions settings. - FileOperations.delete_path(path, recursive=True) on the abstract base; ShellFileOperations.delete_file rewritten to run a cross- platform python3 -c snippet so deletes work on Windows shells (which have no rm/rm -rf). Fallback to `python` when `python3` isn't on PATH. - README troubleshooting block split into macOS/Linux + Windows PowerShell recipes. - Tightened renderer favicon links in index.html + added color-scheme and theme-color meta. Backend lifecycle (renderer-side mitigation) - New noteSessionActivity() heartbeat + session.ts watchdog: an 8-minute silence on the stream auto-clears stuck $workingSessionIds entries so "Session Busy" never gets permanently wedged. Wired into useSessionStateCache so every state update refreshes the timer. i18n spike - docs/desktop-i18n-rfc.md scoping a future language-switcher PR (recommends react-intl, audits IME/RTL/CJK in the composer + chat bubbles, 4-PR rollout plan, ~3-4 eng-weeks for the first non-English locale). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): replace native OS scrollbar in portaled dropdown menus Radix's DropdownMenuPrimitive.Portal renders content under document.body, outside the `.scrollbar-dt` scope on #root. Whenever a menu's max-height clipped its content (even by a pixel — common for the composer "+" menu that opens upward near the bottom of the window), the user saw the OS's chunky native scrollbar painted across the whole menu. Bake a thin, slot-styled scrollbar onto DropdownMenuContent and DropdownMenuSubContent via [scrollbar-width:thin] + WebKit pseudo-element arbitrary variants. The submenu also gets a max-h tied to --radix-dropdown-menu-content-available-height so long snippet lists scroll cleanly instead of running off the bottom of the viewport. Drop the now- redundant max-h-[60vh] override on the prompt-snippet submenu. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): unbork dropdown menu — submenu opens, parent isn't a circle Two regressions from the previous dropdown-scrollbar fix: - The parent menu rendered as a rounded oval. Long Tailwind v4 arbitrary- variant strings like [&::-webkit-scrollbar-thumb]:rounded-full inside a cn() call were being mis-resolved so the `rounded-full` leaked onto the menu container itself. Replaced the whole tower of arbitrary variants with a real `.dt-portal-scrollbar` class in styles.css that mirrors what `.scrollbar-dt` already does for #root descendants. Plain CSS, no Tailwind parser ambiguity. - The Prompt snippets submenu didn't open. Radix publishes --radix-dropdown-menu-content-available-height on Content but NOT on SubContent, so the `max-h` bound to that variable computed to 0 and the submenu collapsed to zero height. Switched SubContent to a fixed max-h-80 (≈20rem) which is plenty for a snippet list and never collapses. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): promote prompt snippets from Radix submenu to a real Dialog The submenu refused to open when the parent dropdown was anchored at the bottom of the window (composer "+" button) — Radix's collision detection + SubContent positioning was fighting us. Rather than keep tuning side / sideOffset / collisionPadding / max-h until something stuck, replace the DropdownMenuSub with a clicked DropdownMenuItem that opens a proper Dialog. Side benefits over the submenu: - Each snippet gets a description line, so a glance is enough to pick one. - Focus management is handled by Dialog automatically. - Easy to grow (search, custom user snippets, categories) without another round of Radix positioning bugs. Also extract types/interfaces to the bottom of the file per workspace convention. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): move cron 'New cron' button off the top bar into the body Reverses the previous direction on cron empty-state dedup. The body button is more discoverable for first-time users (it's anchored next to the "No scheduled jobs yet" copy that explains the feature) and frees the top bar from a global CTA that wasn't pulling its weight. - Empty (zero jobs): EmptyState renders the "Create first cron" button again, like the original design. - Empty (search filtered out all jobs): no button, just "Try a broader search query" copy. - Has jobs: small inline header above the list shows `N/M active` plus a single "New cron" button (right-aligned). The rows themselves already cover edit/pause/trigger/delete, so this is the only "create" affordance. Also drop the dead `<div className="hidden">…</div>` enabledCount line the previous patch left behind; the count is now visible in the new header instead of hidden. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop): address Copilot review on PR 37536 - sessions-settings: guard the WHOLE bridge call rather than chaining `?.settings.foo().then(...)` — the latter throws when `window.hermesDesktop` is undefined (non-Electron / Vitest contexts) because the chain short-circuits to `undefined.then(...)`. - file_operations: drop `Path.unlink(missing_ok=True)` (Py>=3.8) so the generated delete snippet still works on remote backends running Python 3.7. The existing FileNotFoundError handler covers the same case and works back to 3.4. - ansi.test.ts: add focused Vitest coverage for the SGR parser (basic/bright colors, bold toggles, default-fg reset, coalescing, 256-color / truecolor arg consumption, non-SGR CSI drop, empty SGR full-reset) so future refactors can't silently regress terminal rendering. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(desktop/updates): swallow refreshDesktopVersion bridge errors `refreshDesktopVersion()` is called best-effort with `void` from `checkUpdates()`, `startUpdatePoller()`, and the window focus handler. If the IPC bridge rejects (main process shutting down during reload, bridge not yet ready on first paint), the rejection surfaces as an unhandled promise rejection in the renderer. Wrap the call in try/catch and return null on failure so callers can keep the existing fire-and-forget pattern safely. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(desktop): drop work duplicated by other in-flight PRs - composer/text-utils.ts: revert paste-image dedupe — PR NousResearch#37596 ships the same fix with a cleaner content-key approach and a Vitest file (text-utils.test.ts). Letting that PR own the change. - docs/desktop-i18n-rfc.md: delete the i18n scoping RFC — PR NousResearch#37568 has already shipped a working i18n surface (homegrown nanostores `t()` helper over en/zh dictionaries), so the RFC's framework recommendation (`react-intl`) is now obsolete and would just contradict the implementation that's actually landing. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a lightweight i18n (internationalization) system to the Hermes Desktop app with full Chinese (简体中文) translation support.
What's included
New files (3):
src/locales/en.ts— English translation dictionary (~600 strings)src/locales/zh.ts— Chinese translation (complete counterpart)src/locales/index.ts— i18n engine with React hooks and localStorage persistenceModified files (30+):
All major desktop UI components updated to use the translation system.
Features
hermes.desktop.language)DeepStringify<T>utility typetp('key', { param })syntaxuseTranslations()for destructured access,useT()for dot-path accesst()for use in standalone functions outside React componentsArchitecture
The system is designed to be easily extensible — adding a new language only requires creating a new translation file (e.g.,
ja.ts) and adding it to theLANGUAGESmap.How to use
Screenshots
The language switcher appears at the bottom of the Appearance settings panel, alongside the existing Color Mode, Tool Call Display, and Theme sections.
Tested: TypeScript compilation passes with zero errors (
tsc --noEmit).