Store expanded directory paths in localStorage keyed by workspace path
(key: 'hermes-webui-expanded:{workspacePath}'). On root load (loadDir('.')),
restore the saved set for the current workspace and pre-fetch dir contents
for any restored expanded directories so the tree renders fully on first
paint without requiring a second click to expand.
Saves on every expand/collapse toggle. Switching workspaces automatically
picks up that workspace's own saved state. Per-workspace (not per-session)
so the same tree state is shared across sessions using the same workspace,
which is the natural expectation.
Root cause: _DEFAULT_HERMES_HOME was evaluated at module import time from
os.getenv('HERMES_HOME'). HERMES_HOME is a MUTABLE env var -- init_profile_state()
at server startup calls _set_hermes_home() which writes to os.environ['HERMES_HOME'].
If the sticky active_profile file pointed to e.g. 'webui', HERMES_HOME was set to
~/.hermes/profiles/webui BEFORE api/profiles.py imported. So _DEFAULT_HERMES_HOME
resolved to ~/.hermes/profiles/webui. Then switch_profile('webui') computed:
home = ~/.hermes/profiles/webui / 'profiles' / 'webui'
= ~/.hermes/profiles/webui/profiles/webui -- doesn't exist -> 404 ValueError
Fix: replace the one-liner assignment with _resolve_base_hermes_home() which:
1. Checks HERMES_BASE_HOME env var (explicit override)
2. Checks HERMES_HOME -- but if it looks like a profiles/ subdir (parent.name ==
'profiles'), walks up two levels to the actual base
3. Falls back to Path.home() / '.hermes'
This means the server can start with HERMES_HOME pointing to any profile and
_DEFAULT_HERMES_HOME will still correctly point to ~/.hermes.
Also fix: api() helper in workspace.js was throwing new Error(await res.text())
which surfaced raw JSON to the UI: 'Switch failed: {"error":"Profile X does not exist."}'
Now parses the JSON and extracts j.error so the toast shows clean human-readable text.
Regression tests added in test_sprint23.py:
- test_profile_switch_base_home_not_subdir: static analysis verifying the resolver
- test_api_helper_returns_clean_error_message: verifies api() parses JSON errors
- test_profile_switch_resolve_base_home_logic: verifies the profiles/ subdir detection
Tests: 426 passed, 0 failed.
- Fix stale tree cache: clear _dirCache and _expandedDirs on root nav
- Fix clearPreview: prompt before discarding unsaved preview edits
- Update UI version label from v0.17.1 to v0.20
- Add Sprint 18 entry to CHANGELOG.md
- Update SPRINTS.md current state to v0.20
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- File preview auto-close: clearPreview() extracted as named function
and called from loadDir(). Navigating directories (breadcrumbs, up
button, folder clicks) now automatically closes the right panel
file preview instead of leaving stale content visible.
- Thinking/reasoning display: assistant messages with structured content
arrays containing type=thinking or type=reasoning blocks now render
as collapsible gold-themed cards above the response text. Collapsed
by default, click header to expand. Works with Claude extended thinking
and o3 reasoning tokens when preserved in the message array.
- Workspace tree view (Issue #22): directories expand/collapse in-place
with toggle arrows. Single-click toggles, double-click navigates
(breadcrumb view). Subdirectory contents fetched lazily and cached.
Indentation shows nesting depth. Empty directories show "(empty)".
S._expandedDirs tracks open state, S._dirCache caches fetched entries.
Tests: 295 passed, 23 pre-existing failures, 0 regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track A: Workspace breadcrumb navigation
- Breadcrumb path bar with clickable segments when inside subdirectories
- Up button in panel header for parent directory navigation
- S.currentDir state tracking; file ops stay in current directory
- New file/folder creation respects current subdirectory
Track B: Slash commands foundation
- New commands.js module (7th JS module) with command registry and parser
- Built-in commands: /help, /clear, /model, /workspace, /new
- Autocomplete dropdown on / input with arrow/tab/enter/escape navigation
- Unrecognized commands pass through to agent normally
Track C: Send key setting (closes#26)
- send_key added to settings defaults in api/config.py
- Settings panel dropdown: Enter (default) vs Ctrl/Cmd+Enter
- Keydown handler rewritten for autocomplete + send key preference
- Setting loaded on boot, persisted to settings.json
5 new tests, 242 total (219 passing, 22 pre-existing failures, 0 regressions).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Hermes WebUI runs behind a reverse proxy with HTTP basic auth
(e.g. Caddy basic_auth), browsers embed credentials in the page URL.
The Fetch API and EventSource reject requests constructed from URLs
that include credentials (per Fetch spec, all modern browsers).
Fix: construct all fetch() and EventSource URLs via
new URL(path, location.origin) which strips credentials from the
base URL. Add credentials:"include" to ensure auth headers are
forwarded on each request.