* fix(workspace): add .html/.htm to MIME_MAP so HTML preview renders correctly
MIME_MAP was missing entries for .html and .htm. The server fell back to
Content-Type: application/octet-stream, which browsers refuse to render as
HTML in an iframe — causing a blank white preview.
The rest of the pipeline was already correct: the iframe exists in
static/index.html, openFile() in static/workspace.js routes .html to
showPreview('html'), and _handle_file_raw() in api/routes.py sets the
correct CSP sandbox header when ?inline=1 is present. The only missing
piece was the MIME type.
* test(workspace): lock in MIME_MAP entry for .html/.htm
PR #1070 added .html/.htm → text/html to MIME_MAP in api/config.py
to fix the blank workspace HTML preview iframe. Without a direct
assertion on the MIME_MAP entries, the fix could silently regress
(the existing test_779_html_preview.py tests cover the iframe wiring,
the inline=1 query handling, and the CSP sandbox header — but none of
them touch MIME_MAP itself).
Add a single regression test that asserts MIME_MAP['.html'] and
MIME_MAP['.htm'] are both 'text/html' so any future removal of those
entries fails CI immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(composer): raise .approval-card.visible z-index above .queue-card
.queue-card has z-index:2. .approval-card.visible had no z-index, so the
queue flyout would render on top of the approval card when both were visible
simultaneously — obscuring the Allow/Deny buttons.
Fix: add z-index:3 to .approval-card.visible so approvals always render
above the queue flyout. Approval is a blocking, security-relevant interaction
and must never be obscured by passive UI elements.
* test(composer): pin approval-card z-index > queue-card invariant
PR #1071 raises .approval-card.visible to z-index:3 so the security-
relevant Allow / Deny buttons stay clickable when the queue flyout is
also open. Without a regression test, a future CSS edit could silently
drop the z-index back below queue-card (z-index:2) and reintroduce the
bug — there is no automated UI test covering this stacking interaction.
Add a focused regex check that pins the invariant:
.approval-card.visible z-index must be strictly greater than
.queue-card z-index.
Modeled on the existing CSS-regex regression style in
tests/test_mobile_layout.py (test_profile_dropdown_not_clipped_by_overflow).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: intercept /steer /interrupt /queue before busy-mode routing in send()
Root cause: slash commands entered while the agent is busy never reached
the command dispatcher. send() enters the busy block and returns early at
line ~50, so the slash-command intercept (~line 56) is never reached.
The text was queued as a plain message. When it drained after the turn
ended, cmdSteer / cmdInterrupt ran on an idle session, saw no active stream,
and showed "No active task to stop."
Fix: at the top of the busy block, before checking busyMode, check if the
text starts with / and is one of the three control commands. If so, dispatch
the handler immediately and return. This lets the user type /steer, /interrupt,
or /queue at any time — including while the agent is mid-stream — and have
them execute against the live session.
Two new regression tests added:
- test_slash_commands_intercepted_before_busymode_routing: verifies the
intercept appears before the busyMode routing in the busy block
- test_steer_intercept_calls_handler_directly: verifies the intercept calls
_bc.fn(_pc.args) and returns, not queues
* test(busy-intercept): pin sync input-clear before await in slash intercept
PR #1072's intercept clears the msg input before awaiting the handler.
Order matters: if the await happens first (or if the clear is moved
inside the handler), the input still shows '/steer foo' for the duration
of the await. A reflexive second Enter press during that window — common
while waiting for the toast — re-runs send(): either re-fires the
handler (double-steer) or, if the turn just ended, falls through to the
non-busy slash dispatcher and drops a confusing "No active task to stop."
Add test_steer_intercept_clears_input_before_await pinning the order so
this UX invariant cannot silently regress.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: update steer i18n and settings copy — steer no longer interrupts
With the real /steer implementation (agent.steer() via /api/chat/steer),
steer injects a correction mid-turn WITHOUT interrupting the current stream.
The previous copy said "falls back to interrupt", "Steer (interrupt + send)",
etc. — accurate only for the old placeholder, not the real implementation.
Changes across all 6 locales (en/ru/es/de/zh/zh-Hant):
cmd_steer: "falls back to interrupt" removed
settings_busy_input_mode_steer: "interrupt + send" → "mid-turn correction"
cmd_steer_fallback: "interrupted" → "queued for next turn"
busy_steer_fallback: "interrupted instead" → "queued for next turn"
settings_desc_busy_input_mode: "currently falls back to interrupt" removed
Also:
static/index.html: inline fallback text updated to match
static/commands.js: internal comment clarified (fallback = queue+cancel,
not "interrupt mode" which implies the primary action)
* fix(renderer): group consecutive blockquote lines into single element
Root cause: the old rule `s.replace(/^> (.+)$/gm, ...)` had three bugs:
1. `.+` required at least one character — bare `>` lines (blank
continuation lines) did not match and passed through as literal `>`
2. Each matching line became its own `<blockquote>` element — a 10-line
blockquote produced 10 stacked `<blockquote>` tags with no grouping
3. When a fenced code block sat inside a blockquote, the fence-stash
pass consumed the code content and left orphaned `>` lines that the
old `.+` pattern could not match
Fix: replace the single-line regex with a group-based approach that matches
one or more consecutive `>` lines as a single block, strips the `>` prefix
from each line, passes each non-empty line through inlineMd(), turns blank
`>` lines into `<br>`, and wraps the entire group in one `<blockquote>`.
14 regression tests added covering:
- Single-line blockquotes (regression)
- Multi-line grouping (2 and 10 lines)
- Two separate blockquotes staying separate
- Bare `>` and `>text` (no space) edge cases
- Blank continuation lines → <br>
- Bold / italic / inline-code inside blockquotes
- Blockquote followed by normal paragraph
* fix(renderer): drop empty trailing line from blockquote match
The new group-based blockquote rule introduced in this PR captures the
trailing newline in its (?:\n|$) clause. After block.split('\n') that
trailing newline produces an empty final element. The original filter
only dropped lone bare '>' artifacts on the last line, so the empty
final element survived, and the .map(blank → '<br>') step turned it
into a phantom <br> immediately before </blockquote>.
Visible symptom: any blockquote whose source ends with \n (the common
case — a quote followed by another paragraph or end-of-message) renders
with an extra blank line at the bottom of the quote.
Reproducer:
'> Hello\n\nThe rest of the message.'
→ '<blockquote>Hello\n<br></blockquote>\nThe rest of the message.'
^^^ phantom <br>
Fix: replace the single-line filter with a while-loop that pops trailing
lines while they are either empty OR a bare '>'. This matches the
intent the Python test mirror in tests/test_blockquote_rendering.py
already had (the mirror was correct; the JS was not — that's why
the original tests passed despite the bug).
Also add four new regression tests in TestNoPhantomTrailingBr that pin
the no-trailing-<br> invariant for the common shapes:
- input ending with \n
- quote followed by paragraph (the real-world case)
- multi-line quote ending with \n
- quote with blank continuation + trailing \n (internal <br> stays,
trailing <br> does not)
Verified end-to-end with node against the actual JS regex.
244 renderer-adjacent tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(renderer): comprehensive markdown fixes — strikethrough, task lists, CRLF, nested blockquotes
Five additional fixes on top of the blockquote grouping from the initial commit:
1. CRLF normalisation: strip \r\n → \n at start of renderMd so Windows
line endings do not produce stray \r characters in rendered output
2. Strikethrough: ~~text~~ → <del>text</del> in both inlineMd() (for use
inside blockquotes/lists) and the outer pass (for plain paragraphs).
Added <del> to SAFE_TAGS and SAFE_INLINE so it is not HTML-escaped.
3. Task lists: - [x] / - [ ] items in unordered lists render as ✅/☐
via task-done/task-todo span wrappers. Checks [X] (uppercase) too.
4. Nested blockquotes: >> / >>> etc. now recurse so each level gets its
own <blockquote> element rather than passing through as literal >.
Implemented by extracting the blockquote rule into _applyBlockquotes()
which calls itself recursively on the stripped inner content.
5. Lists inside blockquotes: > - item now renders <ul><li> inside the
blockquote instead of a literal "- item" string. Task list items work
inside blockquotes too (> - [x] done → ✅ inside <blockquote><ul>).
Also fixed test_issue342.py search window (5000→10000 chars) — the CRLF
strip at the top of renderMd pushed the autolink regex past the old limit.
68 new tests in test_renderer_comprehensive.py + test_blockquote_rendering.py
covering all constructs, edge cases, and combinations.
* fix(renderer): restore space in blockquote prefix-strip regex
Commit 04e7b53 changed the blockquote prefix-strip regex from
/^>[ \t]?/ (consume "> ", "\t>", or just ">")
to
/^>[\t]?/ (only consume "\t>" or just ">")
The space character was dropped from the character class. Since
practically every blockquote an LLM produces is "> " (greater-than
followed by a space), this leaves a leading space artifact on every
stripped blockquote line. Worse, the leading space breaks the
list-detection regex `^(?: )?[-*+] ` inside the new `_applyBlockquotes`
helper — that regex requires either zero or two leading spaces, never
one — so the new "list inside blockquote" feature never fired for
the canonical input shape `> - item`.
Reproducer (against the actual ui.js via node, before the fix):
> Hello world → <blockquote> Hello world</blockquote>
^ phantom leading space
> Steps: → <blockquote>Steps:
> - one - one
> - two - two</blockquote>
^ literal text, NOT a <ul>; lists-in-quote feature broken
> - [x] done → blockquote with literal "[x] done", no checkbox span
Tests passed despite the bug because tests/test_blockquote_rendering.py
and tests/test_renderer_comprehensive.py validate against a Python
mirror (`_apply_blockquotes`) whose strip regex is `^>[ \t]?` — i.e.
the mirror is correct, the JS is not, and the static-mirror tests
can't catch the divergence. Same shape of bug as commit 94d63d0
(phantom <br> in trailing line) where the mirror was right and the JS
was wrong.
Fix: restore the space character in the strip regex's character class.
Add tests/test_renderer_js_behaviour.py — 11 tests that drive the
ACTUAL renderMd via node and assert on rendered output for the most
common LLM shapes (single-line quote, multi-line quote, list inside
quote, task list inside quote, nested >>>, strikethrough inside and
outside quote, top-level task list, quote followed by heading,
multi-paragraph quote with list, CRLF normalisation).
Verified: the buggy regex makes 6 of those 11 tests fail; the corrected
regex makes all 11 pass.
Suite: 2354 passed, 0 new failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Collapse agent session compression chains
* Restore upstream changelog entries
* fix(agent_sessions): bubble active compression chains to top by tip last_activity
The original PR merge kept the chain head's id/title/started_at and overrode
id/model/message_count/ended_at/end_reason from the tip — but did NOT override
last_activity. Since the projected list is sorted by last_activity DESC and
the WebUI sidebar surfaces updated_at = last_activity, an actively-used
compression chain whose tip is being edited NOW would sort by the ROOT's
old last_activity and fall below recently touched standalone sessions.
Reproducer (with the harness against actual code, before the fix):
- root: started 30 days ago, last msg 30 days ago
- tip: started 28 days ago (parent_session_id=root), last msg 5 seconds ago
- standalone: last msg 2 days ago
Sidebar order with original PR:
[0] standalone (48h ago)
[1] active_tip (last_activity=root's 720h ago) ← wrong
Sidebar order after fix:
[0] active_tip (last_activity=tip's 0h ago) ← correct
[1] standalone (48h ago)
This matches Hermes Agent's own list_sessions_rich projection at
hermes_state.py:903-909, which overrides "last_active" from the tip
exactly so that the agent CLI's session list orders the same way.
Add ``last_activity`` to the merge-from-tip key list, update the existing
test_compression_chain_collapses_to_latest_tip_in_sidebar assertion to
expect tip-derived updated_at, and add
test_compression_chain_bubbles_to_top_by_tip_activity locking in the
bubble-to-top invariant — without this regression test the previous
behaviour passed CI because no test exercised the sort order against a
mixed set of chains and standalone sessions.
The chain head's started_at (created_at) and title remain preserved, so
users can still find the conversation by its original date and name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: v0.50.216 release notes and version bump
Compression chains, renderer fixes, HTML preview, approval z-index, /steer fix.
* chore: gitignore local-only review harness directory
Adds .local-review/ to .gitignore so renderer drivers, sample inputs,
fixture builders, and other reviewer scratch files do not accidentally
get committed. Nothing under that path is ever shared in the repo;
keeping the entry tracked makes the boundary explicit for any future
contributor who creates the directory locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Keep reasoning chip visible for None effort
* test(reasoning): pin chip render output via node, not just source regex
The PR's static checks in test_reasoning_chip_btw_fixes.py validate the
shape of _applyReasoningChip (no display='none' literal, the right
classList.toggle call exists, the right label literals are in the
function body) but pass even if the runtime detail is wrong — for
example if `inactive` were inverted, _normalizeReasoningEffort
mishandled whitespace, or _formatReasoningEffortLabel returned the
wrong literal for an unknown input.
Add tests/test_reasoning_chip_js_behaviour.py — 11 tests that drive
the actual _applyReasoningChip() via node and assert on the rendered
DOM state for each effort value:
TestChipAlwaysVisible
- empty / null -> "Default" label, inactive=true
- "none" -> "None" label, inactive=true
- "low"/"high" -> verbatim label, inactive=false
TestNormalizationEdgeCases
- "NONE" -> normalises to "None"
- " none " -> trims and normalises
- unknown junk -> falls through visible, never hidden
TestTitleAttributeAccessibility
- title attribute carries the human-readable label for tooltip /
screen-reader use
Sanity-checked against master's pre-fix ui.js: 11/11 fail (bug caught).
Against this PR's ui.js: 11/11 pass.
This pattern (drive the actual JS via node) caught two regex-only
regressions in PR #1073 where the Python mirror was correct while the
JS was broken. Same protection added here so the chip-visibility
contract can't silently break in a future refactor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: add #1074 to v0.50.216 changelog, bump test count to 2428
* fix(i18n): restore broken Unicode in Russian and Spanish steer strings
Commit 56c7a14 (fix: update steer i18n and settings copy) accidentally
stripped the `\u` prefix from Unicode escape sequences in two locales,
producing garbled literal hex strings visible to users:
Spanish (es):
- cmd_steer: correcci00f3n → corrección
- cmd_steer_fallback: 2014 en cola → — en cola
- busy_steer_fallback: 2014 en cola → — en cola
- settings_desc_busy_input_mode: qu00e9, est00e1, correcci00f3n → qué, está, corrección
- settings_busy_input_mode_steer: correcci00f3n → corrección
Russian (ru):
- settings_desc_busy_input_mode: the entire Cyrillic string was
replaced with raw 4-hex-char code-points without the \u prefix
(041e043f... instead of actual Cyrillic). Decoded:
"Определяет поведение при отправке сообщения во время работы
агента. Очередь ждёт; Прерывание отменяет и начинает заново;
Steer внедряет коррекцию без прерывания."
Fix: write the correct characters directly (UTF-8 is the file encoding
so embedding them literally is cleaner than \u escapes for long text).
All other locales (en, de, zh, zh-Hant) were not affected — confirmed
by grepping for bare hex run-ons in the updated file.
Verified: node --check static/i18n.js passes; full pytest suite green
(2365 passed, 47 skipped).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: remove duplicate compression chain entry from [Unreleased]
---------
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Frank Song <franksong2702@gmail.com>
27 KiB
Hermes Web UI: Full Parity Roadmap
Goal: Full 1:1 parity with the Hermes CLI experience via a clean dark web UI. Everything you can do from the CLI terminal, you can do from this UI.
Last updated: v0.50.216 (April 26, 2026) — 2428 tests collected Tests: 2107 collected (
pytest tests/ --collect-only -q) Source: /
Sprint History (Completed)
Current Architecture Status
| Layer | Location | Status |
|---|---|---|
| Python server | /server.py (~165 lines) + api/ modules (~5000 lines) | Thin shell + QuietHTTPServer + auth middleware + business logic in api/ |
| HTML template | /static/index.html (~600 lines) | Served from disk |
| CSS | /static/style.css (~1050 lines) | Served from disk, incl. mobile responsive, KaTeX, table styles |
| JavaScript | /static/{ui,workspace,sessions,messages,panels,boot,commands,icons,i18n,login}.js | 10 modules, ~7100 lines total |
| Docker | Dockerfile, docker-compose.yml, .dockerignore | python:3.12-slim, multi-arch (amd64+arm64) |
| CI/CD | .github/workflows/release.yml | Auto-release + GHCR publish on tag push |
| Runtime state | ~/.hermes/webui-mvp/sessions/ | Session JSON files |
| Test server | Port 8788 (conftest.py), port 8789 (browser sanity) | Isolated, wiped per run |
| Production server | Port 8787 | SSH tunnel from Mac |
Feature Parity Checklist
Chat and Agent
- Send messages, get SSE-streaming responses
- Switch models per session (10 models, grouped by provider)
- Composer-scoped model picker in footer (moved from sidebar to align with per-conversation model selection)
- Multi-provider API support: use any Hermes agent API provider (OpenAI, Anthropic, Google, etc.) directly, not just OpenRouter (Sprint 11)
- Custom endpoint model discovery: auto-detect models from Ollama, LM Studio, and other local LLM servers via base_url (PR #18)
- Upload files to workspace (drag-drop, click, clipboard paste)
- File tray with remove button
- Tool progress shown inline in the conversation via live tool cards
- Approval card for dangerous commands (Allow once/session/always, Deny)
- Approval polling + SSE-pushed approval events
- INFLIGHT guard: switch sessions mid-request without losing response
- Session restores from localStorage on page load
- Reconnect banner if page reloaded mid-stream
- Copy message to clipboard (hover icon on each bubble)
- Edit last user message and regenerate
- Branch/fork conversation (Wave 3)
- Token/cost estimate per message (Sprint 23)
Tool Visibility
- Tool progress in live tool cards (kept out of the composer/footer chrome)
- Approval card with all 4 choices
- Tool call cards inline (collapsed, show name/args/result)
Workspace / Files
- Workspace panel defaults closed and opens only for active browsing or preview
- Browse workspace directory tree with type icons
- Preview text/code files (read-only)
- Preview markdown files (rendered, tables supported)
- Preview image files (PNG, JPG, GIF, SVG, WEBP inline)
- Edit files inline (Edit button, Enter to save, Escape to cancel)
- Create new file (+ button in panel header)
- Delete file (hover trash, confirmation modal)
- File name truncation with tooltip for long names
- Right panel resizable (drag inner edge)
- Syntax highlighted code preview (Prism.js)
- Rename file (Sprint 14)
- Create folder (Sprint 14)
- Shared app modal for confirm/input flows (Sprint 33)
Sessions
- Create session (+ button or Cmd/Ctrl+K)
- Load session (click in sidebar)
- Delete session (hover trash, toast, correct fallback)
- Auto-title from first user message
- Rename session title (double-click in sidebar, Enter saves, Escape cancels)
- Filter/search sessions by title (live filter box)
- Date group headers (Today / Yesterday / Earlier)
- Download session as Markdown transcript
- Export session as JSON (full messages + metadata)
- Session inherits last-used workspace on creation
- Session content search (search message text across sessions)
- Session tags / labels (Sprint 14)
- Archive sessions (Sprint 14)
- Clear conversation (wipe messages, keep session) (Wave 3)
- Import session from JSON (Sprint 12)
- Pin/star sessions to top of list (Sprint 12)
- Duplicate session (Sprint 13)
- Session projects / folders (Sprint 15)
Workspace Management
- Add workspace with path validation (must be existing directory)
- Remove workspace
- Rename workspace display name
- Quick-switch workspace from topbar dropdown
- Sidebar live workspace display (name + path, updates in real time)
- New sessions inherit last used workspace
- Workspace list persists to workspaces.json
- Workspace reorder (drag) (Wave 2)
Scheduled Tasks (Cron)
- View all cron jobs (Tasks sidebar tab)
- View last run output per job (auto-loaded on expand)
- Expand job to see prompt, schedule, last output
- Run job manually (Run now button)
- Pause / Resume job
- Create cron job from UI (+ New job form with name, schedule, prompt, delivery)
- Edit existing cron job
- Delete cron job
- View full cron run history (expandable per job)
- Skill picker in cron create form (Sprint 23)
Skills
- List all skills grouped by category (Skills sidebar tab)
- Search/filter skills by name, description, category
- View full SKILL.md content in right preview panel
- Create skill
- Edit skill
- Delete skill
- View skill linked files (Sprint 23)
Memory
- View personal notes (MEMORY.md) rendered as markdown (Memory tab)
- View user profile (USER.md) rendered as markdown (Memory tab)
- Last-modified timestamp on each section
- Add/edit memory entry inline
Configuration
- Settings panel (default model, default workspace) (Sprint 12)
- Send key preference (Enter or Ctrl+Enter) (Sprint 17)
- Password authentication (Sprint 19)
- Enable/disable toolsets per session (deferred)
Notifications
- Cron job completion alerts (Sprint 13)
- Background agent error alerts (Sprint 13)
Workspace
- Breadcrumb navigation in subdirectories (Sprint 17)
- Workspace tree view with expand/collapse (Sprint 18, Issue #22)
- File preview auto-close on directory navigation (Sprint 18)
Slash Commands
- Command registry + autocomplete dropdown (Sprint 17)
- Built-in: /help, /clear, /model, /workspace, /new (Sprint 17)
Security
- Password auth with signed cookies (Sprint 19, Issue #23)
- Security headers (X-Content-Type-Options, X-Frame-Options) (Sprint 19)
- POST body size limit (20MB) (Sprint 19)
Thinking / Reasoning
- Collapsible thinking cards for extended-thinking models (Sprint 18)
Voice
- Voice input via Web Speech API (Sprint 20)
Mobile
- Mobile responsive layout — hamburger sidebar, sidebar tabs on phones, files slide-over (Sprint 21 + later mobile nav simplification)
Profiles
- Multi-profile support — create, switch, delete profiles (Sprint 22, Issue #28)
Advanced / Future
- Subagent session tree -- show subagent hierarchy in sidebar with expand/collapse (PR #75)
- Specialized tool card renderers -- diff viewer, terminal output, todo checklist views (PR #75)
- Streaming performance -- rAF-throttled token rendering (Sprint 24, PR #81)
- Workspace git detection -- branch name and dirty status badge (Sprint 24, PR #82)
- Collapsible date groups -- click group headers to collapse (Sprint 24, PR #80)
- Context usage indicator -- compact circular badge in composer footer (Sprint 24, PR #83; refreshed April 10, 2026)
- LLM-generated session titles -- auto-title via small model instead of first-message substring (PR #75)
- Workspace git detection -- show branch name, dirty status in workspace header (PR #75)
- Clarify dialog -- agent can ask clarifying questions that block until user responds (PR #75)
- Gateway approval polling -- support blocking approvals from messaging gateway (PR #75)
- Unified session storage -- SessionDB shared between webui and CLI (PR #75)
- TTS playback of responses (deferred)
- Background task cancel (composer footer stop button)
- Code execution cell (deferred)
- Desktop application (Sprint 25, PLANNED)
- Pluggable UI themes -- Dark, Light, Slate, Solarized, Monokai, Nord (Sprint 26, v0.34)
- Extended slash command / skill integration (deferred)
- Virtual scroll for large lists (deferred)
Sprint 7: Wave 2 Core -- Cron/Skill/Memory CRUD + Session Content Search (COMPLETED)
Theme: "Wave 2 Core -- Cron/Skill/Memory CRUD + Session Content Search"
Track A: Bug Fixes
| Item | Description |
|---|---|
| Activity bar sizing | Activity bar sometimes overlaps first message on short viewports |
| Model dropdown sync | Model chip in topbar sometimes shows stale model after session switch |
| Cron output truncation | Long cron output in the tasks panel overflows its container |
Track B: Features
| Feature | What | Value |
|---|---|---|
| Session content search | Search message text across all sessions, not just titles. GET /api/sessions/search already does title search; extend to message content with a configurable depth limit | High: the single most-requested nav feature after rename |
| Cron edit + delete | Edit an existing cron job (name, schedule, prompt, delivery) inline in the tasks panel. Delete with confirm. POST /api/crons/update and /api/crons/delete | High: closes the cron CRUD gap (create was Sprint 6) |
| Skill create + edit | A "New skill" form in the Skills panel. Name, category, SKILL.md content in a textarea editor. Save calls POST /api/skills/save (writes to ~/.hermes/skills/). Edit opens existing skill in the same editor | High: biggest remaining CLI gap after cron |
Track C: Architecture
| Item | What |
|---|---|
| Phase E: app.js module split (start) | Split app.js (1332 lines) into logical modules: sessions.js, chat.js, workspace.js, panels.js, ui.js. Serve via ES module imports in index.html. This is Phase E completion. |
| Health endpoint improvement | Add active_streams, uptime_seconds to /health response (Phase G) |
| Git init | git init , first commit, push to private GitHub repo |
Tests
- ~20 new pytest tests (cron update/delete, skill save, session content search)
- TESTING.md: Sections 29-31 (cron edit, skill edit, session search)
- Estimated total after Sprint 7: ~126
Wave 2: Full CRUD and Interaction Parity
Status: In progress. Sprint 6 completed cron create and workspace management. Remaining Wave 2 items targeted for Sprints 7-8.
Sprint 2.0: Workspace Management (COMPLETE Sprint 5+6)
All workspace features delivered: add/validate/remove/rename workspaces, topbar quick-switch, sidebar live display, new sessions inherit last workspace. See Sprint 5 completed section.
Sprint 2.1: Cron Job Management (Partial -- Sprint 7 for remaining)
- View all jobs (Sprint 3)
- Run / pause / resume (Sprint 3)
- Create job from UI (Sprint 6)
- Edit job
- Delete job
- Full cron run history
Sprint 2.2: Skill Management (Partial -- Sprint 7 for remaining)
- List all skills with categories (Sprint 3)
- View SKILL.md content (Sprint 3)
- Create skill
- Edit skill
- Delete skill
Sprint 2.3: Memory Write (Sprint 7)
- View notes + profile (Sprint 3)
- Edit notes inline
Sprint 2.4: Todo Management (Wave 2)
- View current todo list (sidebar Todo panel, parsed from session history)
Sprint 2.5: Session Content Search (Sprint 7)
- Session title search (Sprint 4)
- Message content search across sessions
Sprint 2.6: Session Rename (COMPLETE Sprint 4)
Double-click any session title in the left sidebar to edit inline. Enter saves, Escape cancels. Topbar updates immediately.
Completed Waves (Summary)
| Wave | Theme | Key Deliverables |
|---|---|---|
| Wave 2 | Full CRUD + Interaction | Cron/skill/memory CRUD, session search, workspace management, session rename |
| Wave 3 | Power Features | Tool call cards, multi-model dropdown, resizable panels, file actions, conversation controls |
| Wave 4 | Settings + Notifications | Settings panel, cron alerts, background error banner |
| Wave 5 | Session Continuity | Session tags, archive, projects/folders |
| Wave 6 | Agentic Features | Background task cancel, voice input (Web Speech API) |
| Wave 7 | Production Hardening | Password auth, security headers, mobile responsive, Docker + GHCR CI |
User Requested Features
Community-requested enhancements tracked from GitHub issues. All shipped.
| Feature | Issue | Shipped | Sprint |
|---|---|---|---|
| Workspace tree view | #22 | Done | Sprint 18 |
| Docker container + GHCR images | #7 | Done | Sprint 21 + v0.28.1 CI |
| Authentication | #23 | Done | Sprint 19 |
| Send key / personalization | #26 | Done | Sprint 17 |
| Multi-profile support | #28 | Done | Sprint 22 |
| Mobile responsive UI | #21 | Done | Sprint 21 |
| Profile creation in Docker | #44 | Done | v0.27 |