* fix: workspace panel close button — no duplicate X on desktop, mobile X respects file preview
Two bugs fixed in the workspace right panel:
1. Duplicate X on desktop (bug): #btnClearPreview (the X icon) was always
visible alongside #btnCollapseWorkspacePanel (the chevron), producing two
close controls at once. Fixed in syncWorkspacePanelUI() — on desktop, the X
is now hidden when no file preview is open (display:none), and only shown
when the user is viewing a file. The chevron remains as the sole close
control in browse mode.
2. Mobile X collapses panel instead of dismissing file (bug): .mobile-close-btn
was calling closeWorkspacePanel() directly, which collapsed the whole panel
even when a file was open. Changed to handleWorkspaceClose(), which already
has the correct two-step logic: clear preview first, close panel only if
no preview is visible.
Files changed:
- static/boot.js: syncWorkspacePanelUI() hides btnClearPreview on desktop
when hasPreview is false, guarded by !isCompact so mobile is unaffected
- static/index.html: mobile-close-btn onclick changed from
closeWorkspacePanel() to handleWorkspaceClose()
- tests/test_sprint44.py: 10 new regression tests
- tests/test_mobile_layout.py: updated test_workspace_close_button_present()
to accept handleWorkspaceClose() as the valid onclick target
* fix: widen test_server_delete_invalidates_index window to 1200 chars
The test extracted a 600-char window starting from the session/delete
handler to check for SESSION_INDEX_FILE. Commit 3cc5839 added session_id
character validation and path traversal guards before the unlink call,
pushing SESSION_INDEX_FILE to ~764 chars from the match — beyond the
600-char limit, causing the test to fail on CI.
Widened the window to 1200 chars, which accommodates any reasonable
amount of guard code before the SESSION_INDEX_FILE.unlink() call.
* docs: v0.50.33 release — version badge and CHANGELOG
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
* fix: persist durable inflight reload snapshots
* fix: remove duplicate loadInflightState stub, update CHANGELOG test count
The stub added in the previous review branch is superseded by the author's
real localStorage-backed implementation in the cherry-picked commit 36051c0.
Remove the duplicate. Update CHANGELOG to 961 tests and document the durable
inflight state feature.
---------
Co-authored-by: Jordan SkyLF <jordan@skylinkfiber.net>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
* fix: preserve live session output across chat switches
(cherry picked from commit 401e3b643d25e8dad8c06883b478b3c3073f07a5)
* fix: preserve todo state after session reload
(cherry picked from commit 7ee093ba19978af23b79148df2f2347e2f1e5bde)
* fix: preserve live assistant anchor across rerenders
* fix: stream live reasoning and tool progress
* fix: recover inflight session state after reload
* fix: add loadInflightState stub + CHANGELOG v0.50.21
- static/ui.js: add loadInflightState() function (currently returns null —
the typeof guard in sessions.js means reload recovery works via the
else-path attachLiveStream call; this stub satisfies the guard cleanly
and documents the extension point for future localStorage-backed state)
- CHANGELOG.md: v0.50.21 entry; 960 tests (up from 949)
---------
Co-authored-by: Jordan SkyLF <jordan@skylinkfiber.net>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
* fix: silent errors, stale models, live model fetching (#373, #374, #375)
- api/streaming.py: detect empty agent response (_assistant_added check),
emit apperror(type='no_response' or 'auth_mismatch') instead of silent done
- api/streaming.py: add _token_sent flag so guard works for streaming agents
- static/messages.js: done handler belt-and-suspenders guard for zero replies
- static/messages.js: apperror handler labels 'no_response' type distinctly
- api/config.py: remove gpt-4o and o3 from _FALLBACK_MODELS and
_PROVIDER_MODELS['openai'] (superseded by gpt-5.4-mini and o4-mini)
- api/routes.py: new /api/models/live?provider= endpoint, fetches /v1/models
from provider API with B310 scheme check + SSRF guard
- static/ui.js: _fetchLiveModels() background fetch after static list loads,
appends new models to dropdown, caches per session, skips unsupported providers
Other:
- tests/test_issues_373_374_375.py: 25 new structural tests
- tests/test_regressions.py: extend done-handler window 1500->2500 chars
- CHANGELOG.md: v0.50.19 entry; 947 tests (up from 922)
* fix: SSRF hostname bypass + auth detection operator precedence
1. routes.py: SSRF guard used substring matching (any(k in hostname))
which allows bypass via hostnames like evil-ollama.attacker.com.
Changed to exact hostname matching against a fixed set of known
local hostnames (localhost, 127.0.0.1, 0.0.0.0, ::1).
2. streaming.py: _is_auth detection had a Python operator precedence
bug on the ternary expression. The line:
'AuthenticationError' in type(...).__name__ if _last_err else False
parsed as the ternary absorbing the rest of the or-chain when
_last_err was falsy. Fixed to: (_last_err and 'AuthenticationError' in ...)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: fix v0.50.20 CHANGELOG version number and test count (949 tests)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Polish workspace panel behavior and app dialogs
* Replace remaining emoji UI glyphs with Lucide icons
* Redesign composer footer around model and context controls
Move the model selector into the composer footer, replace the linear context pill with a compact circular badge plus tooltip, and remove the redundant topbar model pill.
Design credit and inspiration: Theo / T3 Code.
Reference implementation: https://github.com/pingdotgg/t3code/
* Remove obsolete activity bar
Drop the old activity bar, keep turn-scoped state in the composer footer, and route remaining non-chat status messages through toasts.
This leaves live tool cards and the message timeline as the primary progress UI, with the composer owning stop/cancel and brief turn status.
* Move workspace and model switching into composer footer
* Move profile switching into composer footer
* Refactor Hermes control center UI
* Redesign control center settings modal layout
Widen the modal to 860px, simplify the tab list to icon+label rows,
stretch the tab column's divider to full height, lock the panel to a
fixed height so switching tabs no longer resizes the outer shell, and
always open on the Conversation tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Put session item actions in a dropdown
* Use Hermes mark in sidebar control button
* Reset control center section on close
* Drop session-item left border indicator
Remove the left-border accent used for active, CLI, and project rows —
each state already has a dedicated cue (gold fill, cli badge, project
dot), so the border was redundant. Fully round the row, add 2px
bottom spacing between rows, and strip the matching JS/CSS overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Increase session search input vertical padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalise odd pixel values across UI
Snap padding, gap, and border-radius values to the 2/4/6/8/10/12 grid
across composer chips, sidebar panels, cron list, settings, approval
buttons, dropdowns, and inline message edit — eliminating the 7/9/11px
drift that was making sibling elements feel subtly misaligned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add missing #btnMobileFiles button and .mobile-files-btn CSS (for mobile QA suite)
The mobile layout regression suite (test_mobile_layout.py) requires:
- #btnMobileFiles onclick=toggleMobileFiles() in topbar chips
- .mobile-files-btn CSS rules for responsive show/hide at 640/900px breakpoints
Also adds max-width guard to .profile-dropdown to prevent clipping at narrow viewports.
* Improve composer footer mobile responsiveness and UX
- Collapse composer chips to icon-only at <=400px viewports
- Add model chip icon (CPU) so it remains tappable when labels are hidden
- Show send button always (disabled state when empty, hidden during streaming)
- Show context usage indicator on session load, not just after streaming
- Add cancel status fallback timeout to prevent stale "Cancelling..." text
- Update tests to match new send button and busy state behavior
* Fix duplicate files button and broken workspace close on mobile
Remove redundant #btnMobileFiles button that duplicated #btnWorkspacePanelToggle
in the mobile topbar. Fix workspace panel close button calling undefined
closeMobileFiles() — now calls closeWorkspacePanel().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix model chip icon vertical alignment in composer footer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix workspace toggle button hidden on desktop by conflicting CSS class
Remove mobile-files-btn class from #btnWorkspacePanelToggle — its
display:none!important rule was overriding workspace-toggle-btn visibility
on non-mobile viewports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix session actions dots button inaccessible on mobile sidebar
Always show the session actions trigger on mobile (no hover state on
touch devices) and restore right padding so text truncates with
ellipsis before the dots icon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer footer manage links not opening sidebar panel
The "Manage profiles" and "Manage workspaces" links in the composer
footer dropdowns called switchPanel() which only changes the active
panel content but doesn't open the sidebar. Replaced with
mobileSwitchPanel() which also opens the sidebar so the panel is
actually visible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Widen icon-only composer chips breakpoint from 400px to 768px
Move the icon-only chip styling up into the existing max-width:768px
media query so chips collapse to icon-only on tablets too, preventing
composer footer overflow on mid-size screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix composer-left vertical scrollbar by setting overflow-y:hidden
When overflow-x is set to auto, the CSS spec implicitly changes
overflow-y from visible to auto, allowing a vertical scrollbar to
appear from slight chip padding/border overflow. Explicitly set
overflow-y:hidden to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: resolve rebase conflicts and fix control center test assertions
- Resolved 4 conflicts during rebase onto master (workspace.js,
boot.js, index.html, test_sprint34.py)
- Fixed test_sprint34.py: _controlSection -> _settingsSection,
cc-tab -> settings-tabs (matching actual implementation)
- Fixed quoting syntax error in test assertion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update version badge in System tab to v0.49.4
* docs: update README and CHANGELOG for v0.50.0 UI refresh, bump version badge
---------
Co-authored-by: Aron Prins <pwf.aron@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Adds a bootstrap launcher and a blocking first-run onboarding wizard that guides
new users through minimum Hermes setup from the browser UI.
Supported provider flows: OpenRouter, Anthropic, OpenAI, custom OpenAI-compatible.
OAuth/terminal-first flows remain via 'hermes model'.
Security hardening applied during review:
- /api/onboarding/setup restricted to loopback when auth disabled
- Newline injection guard in _write_env_file
- esc() on setup.unsupported_note in onboarding.js
- Test isolation fix (send_key instead of bot_name in contamination test)
- Skip markers for PyYAML-dependent tests in agent-less environments
Tests: 693 passed (up from 679)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: gabogabucho <gabogabucho@gmail.com>
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255)
When base_url is configured in config.yaml, resolve_model_provider() now
trusts the configured provider/base_url entirely and skips the slash-based
OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom
being silently routed to OpenRouter, resulting in 401 errors.
Fixes#230
* test: mobile layout regression suite — 14 tests for every QA run (#254)
Adds tests/test_mobile_layout.py with 14 static regression tests that run
on every QA pass to catch mobile layout breakage before it reaches prod.
Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile
overlay, bottom nav, files button, profile dropdown z-index, chip overflow,
workspace close, 100dvh, 44px touch targets, 16px font-size on textarea.
* feat: /skills slash command lists and filters available Hermes skills (#257)
Adds /skills [query] command to commands.js. Fetches from /api/skills,
groups by category (alphabetically sorted), displays as a formatted
assistant message. Optional query filters by name, description, or category.
i18n keys added for en, de, zh, zh-Hant. 1 regression test added.
Fixes#248
* feat: shared app dialogs replace native confirm()/prompt() calls (#251)
Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed
by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt
call sites across panels.js, sessions.js, ui.js, workspace.js.
Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore,
ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant.
5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of
native dialog calls.
Extracted from PR #242.
* fix: Android Chrome mobile — workspace panel close + profile dropdown (#256)
Fix#247: toggleMobileFiles() now shows/hides the mobile overlay when
toggling the right workspace panel. New closeMobileFiles() helper closes
the panel with correct overlay state tracking. Overlay onclick calls both
closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x)
added to workspace panel header.
Fix#246: profile dropdown uses position:fixed;top:56px;right:8px at
max-width:900px, escaping the overflow-x:auto stacking context that was
clipping it on Android Chrome.
Fix applied during review: closeMobileSidebar() now checks if the right
panel is still open before hiding the overlay, preventing the overlay from
disappearing when only the sidebar is closed.
Fixes#247Fixes#246
* feat: session ⋯ action dropdown replaces per-row buttons (#252)
Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash)
with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full
keyboard (Escape), click-outside, scroll, and resize-reposition handling.
Position:fixed prevents sidebar clipping.
5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete
(danger style). Each with icon and descriptive subtitle.
Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons
asserts the new trigger and menu functions exist, old per-row classes are gone.
Extracted from PR #242.
* docs: v0.47.0 release notes, bump version, update test counts (645)
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Tracebacks exposed file paths, module names, and potentially secret
values from local variables. Now logged server-side only; clients
receive a generic error message.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>