Skip to content

feat(approval): make the approval card collapsible (#3007) - #3515

Closed
rodboev wants to merge 2 commits into
nesquena:masterfrom
rodboev:pr/collapsible-approval-card
Closed

feat(approval): make the approval card collapsible (#3007)#3515
rodboev wants to merge 2 commits into
nesquena:masterfrom
rodboev:pr/collapsible-approval-card

Conversation

@rodboev

@rodboev rodboev commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Closes #3007

Thinking Path

  • The approval card (static/index.html:454, role="alertdialog") slides up from behind the composer and occupies its full height whenever a tool-call approval is pending. Users reported that this obscures the tool-call rationale and context scrolled just above it. The request is to let them collapse the card to a thin header strip so the transcript remains readable.
  • The clarify card already has a working collapse pattern (shipped in Release CM: v0.51.115 (stage-pr2731 — 1-PR — clarify prompt collapse/expand with chevron-icon polish, Telegram-approved) #2765): a chevron button in .clarify-header calls toggleClarifyCardCollapsed(), which toggles a .collapsed class, updates aria-expanded, rotates the chevron via _syncClarifyCollapseButton, and adjusts the .messages padding-bottom via _syncClarifyTranscriptSpace so the last message is not hidden behind the dock. The approval card has none of these pieces.
  • Mirroring the clarify pattern exactly minimizes divergence and reviewer surface: the three clarify helpers (_syncClarifyCollapseButton, _syncClarifyTranscriptSpace, toggleClarifyCardCollapsed) have one-to-one approval equivalents. The CSS collapsed rules follow the same max-height approach.
  • Two a11y constraints shape the implementation. First, role="alertdialog" requires that the heading stays visible when collapsed (the .approval-header containing the warning icon and "Approval required" span must never be hidden). Second, aria-expanded on the chevron button must flip on toggle so screen readers announce the state change. Both constraints are satisfied by hiding only .approval-desc, .approval-cmd, .approval-counter, and .approval-btns via display:none under .approval-card.collapsed, while leaving .approval-header untouched.
  • The Enter shortcut for "Allow once" works via onceBtn.focus() in showApprovalCard (line 2757), not a document-level keydown. The button retains focus when the card opens, so the shortcut survives collapse without any special handling.
  • hideApprovalCard must clear .collapsed so the next approval always opens expanded. The sameApproval guard in showApprovalCard (line 2730) compares .visible and _approvalSignature before any class changes; clearing .collapsed in hideApprovalCard (before .visible is removed) does not interfere with that guard.
  • The transcript-space management (_syncApprovalTranscriptSpace) must be called from both showApprovalCard (to add approval-open to .messages) and hideApprovalCard (to remove it and approval-collapsed), mirroring how _syncClarifyTranscriptSpace is called from showClarifyCard / hideClarifyCard.

What Changed

  • static/index.html (line 458, inside .approval-header): added <button type="button" class="approval-collapse" id="approvalCollapse" aria-expanded="true" aria-label="Collapse approval" aria-controls="approvalDesc approvalCmd approvalCounter approvalBtns" onclick="toggleApprovalCardCollapsed()" title="Collapse approval"> with a chevron SVG, mirroring the clarifyCollapse button at line 494.
  • static/messages.js (after showApprovalCard): added _syncApprovalCollapseButton(card), _syncApprovalTranscriptSpace(card, opts), and toggleApprovalCardCollapsed(forceCollapsed) -- three helpers mirroring the clarify equivalents at lines 2949-3018.
  • static/messages.js (hideApprovalCard): added card.classList.remove("collapsed") after removing .visible, and a _syncApprovalTranscriptSpace(null) call to remove approval-open / approval-collapsed from .messages when the card hides.
  • static/messages.js (showApprovalCard): after card.classList.add("visible"), call _syncApprovalCollapseButton(card) then _syncApprovalTranscriptSpace(card, {immediate: true}), matching the order showClarifyCard uses. Ordering matters: _syncApprovalTranscriptSpace early-returns on a card that is not yet .visible, so syncing after the class is added is what actually adds approval-open to .messages and reserves transcript space on first paint.
  • static/style.css (after .approval-header at line 1322): added .approval-collapse button styles mirroring .clarify-collapse; added .approval-card.collapsed max-height rules; added display:none for .approval-desc, .approval-cmd, .approval-counter, .approval-btns under .approval-card.collapsed; added .messages.approval-open and .messages.approval-collapsed padding-bottom rules alongside the clarify equivalents at line 1349.
  • tests/test_3007_approval_card_collapse.py (new): static-assertion test confirming all structural invariants -- the three new JS functions are defined, .collapsed is cleared in hideApprovalCard, the CSS collapsed and transcript-space rules are present, and the approvalCollapse button appears in index.html with the correct aria-expanded and onclick attributes.

Why It Matters

When a tool triggers an approval request, the card can cover a significant portion of the transcript, hiding the tool-call output or rationale that informs the decision. A single-click collapse to a header strip lets users read the context without dismissing or responding to the approval, bringing the approval card to parity with the clarify card that shipped in #2765.

Verification

C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/test_3007_approval_card_collapse.py -v --timeout=60
C:\Apps\hermes\hermes-agent\venv\Scripts\python.exe -m pytest tests/ -v --timeout=60

On Windows, if pytest fails to collect due to the pre-existing conftest symlink-privilege issue, retry with --noconftest. The new static-assertion test does not depend on conftest fixtures.

Manual verification steps:

  1. Trigger a tool that requires approval; verify the chevron button appears in the approval card header.
  2. Click the chevron; confirm the card collapses to a thin header strip showing only the warning icon and "Approval required" heading, and that the transcript above is scrollable.
  3. Click the chevron again; confirm the card expands and "Allow once" is re-focusable via Tab or Enter.
  4. Approve or deny; trigger a new approval; confirm the card opens expanded (not in the previously-collapsed state).
  5. With a screen reader: confirm aria-expanded is announced as "false" when collapsed and "true" when expanded.

Risks / Follow-ups

  • role="alertdialog" signals modal intent; the heading and warning icon remain visible when collapsed, satisfying the assistive-technology expectation that the alert context is not hidden.
  • No browser-driven approval-UI tests exist in origin/master; the new test is a static-assertion test, consistent with the pattern in test_approval_card_layering.py and test_issue2883_clarify_padding.py. A Playwright-based test for the collapse toggle would be a useful follow-up.
  • The sameApproval guard in showApprovalCard is unaffected: it evaluates .visible and _approvalSignature before any class changes, and .collapsed is cleared only in hideApprovalCard.
  • If a future change adds an approval card max-height constraint (analogous to the clarify-card max-height:min(calc(100vh - 280px),420px)), the _syncApprovalTranscriptSpace measurement path would need updating too.
  • Unlike the clarify card, this PR does not add an _ensureApprovalResizeListener equivalent, so the reserved --approval-card-height is not recomputed on a window resize while the card stays open. The CSS fallback (var(--approval-card-height,260px)) keeps space reserved in that window, and the value re-measures on the next show or collapse toggle. Adding a resize listener that mirrors the clarify one is a reasonable follow-up if it proves noticeable.

Model Used

Claude Opus 4.8 via Claude Code CLI

@greptile-apps

greptile-apps Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a collapsible header strip to the approval card, mirroring the existing collapse pattern from the clarify card (#2765). The three new JS helpers (_syncApprovalCollapseButton, _syncApprovalTranscriptSpace, toggleApprovalCardCollapsed) are faithful mirrors of their clarify equivalents, the CSS rules follow the same max-height/display-none approach, and hideApprovalCard correctly clears .collapsed so each new approval opens expanded.

  • Collapse toggle: chevron button added to .approval-header; toggleApprovalCardCollapsed flips the class, syncs aria-expanded and the chevron direction, and re-measures transcript padding via _syncApprovalTranscriptSpace.
  • Transcript-space reservation: .messages.approval-open / .messages.approval-collapsed CSS classes mirror .messages.clarify-open / .messages.clarify-collapsed; measurement runs immediately + in a rAF + after a 420 ms settle.
  • State reset gap: showApprovalCard resets timing state inside if (!sameApproval) but does not remove .collapsed, so if a distinct new approval arrives via SSE while the card is still visible and collapsed, the new approval's description, command, and action buttons remain hidden.

Confidence Score: 4/5

Safe to merge with one targeted fix; the collapse feature works correctly in the normal respond→hide→show cycle but has a gap when a new approval arrives via SSE while the current card is still open and collapsed.

The implementation faithfully mirrors the clarify card collapse pattern and the normal user flow (respond → hideApprovalCard clears .collapsed → showApprovalCard opens expanded) is correct. The gap is in showApprovalCard's !sameApproval branch: it resets timing state but not .collapsed, so a distinct new approval arriving while the card is visible and collapsed will display its content hidden. A one-line fix inside the !sameApproval guard closes this.

static/messages.js — specifically the if (!sameApproval) block in showApprovalCard around line 2748

Important Files Changed

Filename Overview
static/messages.js Adds _syncApprovalCollapseButton, _syncApprovalTranscriptSpace, toggleApprovalCardCollapsed, and updates show/hide paths; showApprovalCard does not reset .collapsed for a new (different) approval while the card is still visible
static/index.html Adds approval-collapse button with correct aria attributes; id="approvalBtns" added to fix aria-controls reference; mirrors clarify-collapse structure cleanly
static/style.css Adds approval-collapse button styles and .approval-card.collapsed rules with display:none for content sections; approval-open/approval-collapsed transcript-space rules mirror clarify pattern correctly
tests/test_3007_approval_card_collapse.py Static-assertion tests covering function presence, CSS rules, and HTML structure; consistent with existing test_approval_card_layering.py and test_issue2883_clarify_padding.py patterns

Sequence Diagram

sequenceDiagram
    participant BE as Backend (SSE)
    participant SC as showApprovalCard
    participant HC as hideApprovalCard
    participant TC as toggleApprovalCardCollapsed
    participant CSS as .messages classes

    BE->>SC: approval event (pending)
    SC->>CSS: add approval-open, measure --approval-card-height
    Note over SC: _syncApprovalCollapseButton sets aria-expanded=true

    Note over SC,TC: User clicks chevron
    TC->>CSS: toggle .collapsed on card
    TC->>CSS: swap approval-open → approval-collapsed padding
    TC->>TC: "_syncApprovalCollapseButton (aria-expanded=false)"

    Note over SC,HC: User clicks Allow once / Deny
    SC->>HC: respondApproval → hideApprovalCard(true)
    HC->>CSS: remove approval-open, approval-collapsed
    HC->>HC: card.classList.remove("collapsed") ✓

    BE->>SC: next approval event
    SC->>CSS: add approval-open (card starts expanded) ✓
Loading

Reviews (2): Last reviewed commit: "fix(approval): add missing approvalBtns ..." | Re-trigger Greptile

Comment thread static/index.html
Comment thread static/style.css Outdated
aria-controls referenced approvalBtns but no element had that ID.
Also reorder focus rules so :focus-visible takes precedence over :hover.
@rodboev

rodboev commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Added id="approvalBtns" to the approval buttons wrapper so aria-controls resolves correctly. Also cleaned up the focus rules: :hover gets the interactive styles on its own, :focus-visible gets the blue outline ring. The prior ordering had :focus after :focus-visible at the same specificity, making the :focus-visible outline:none dead. Pushed in c1e25b8.

nesquena-hermes added a commit that referenced this pull request Jun 6, 2026
…3515) (#3697)

* feat(approval): make the approval card collapsible (#3515)

Adds a collapse toggle to the approval card header so users can shrink it
to a thin header strip and keep the tool-call rationale/transcript above
readable. Full ARIA (aria-expanded/controls/label), chevron swap, and
transcript reflow that preserves near-bottom scroll. Closes #3007.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.288 — Release JD (stage-r24)

* fix(approval): clear collapsed state for a distinct queued approval (#3515)

Codex regression-gate finding: showApprovalCard's sameApproval check didn't
include approval_id and didn't clear .collapsed in the !sameApproval branch, so
a NEW/parallel approval arriving while the card was already collapsed could
render collapsed with its command + action buttons hidden. Add approval_id to
the signature; clear .collapsed for a distinct approval before syncing. +2 regression tests.

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in v0.51.288 (Release JD, stage-r24) via #3697. 🎉 Closes #3007.

Thanks @rodboev — clean, well-built feature (full ARIA, chevron swap, transcript reflow that preserves near-bottom scroll). Nathan approved via screenshots (expanded + collapsed, desktop + mobile — the before/after nicely shows the transcript the collapsed strip reveals).

The regression gate (Codex) caught one edge worth noting: showApprovalCard's sameApproval signature didn't include approval_id and didn't clear .collapsed in the !sameApproval branch, so a NEW/parallel queued approval arriving while the card was collapsed could render collapsed with its command + buttons hidden. Fixed inline (added approval_id to the signature + clear .collapsed for a distinct approval, before the collapse-button/transcript sync) with 2 regression tests, so every fresh approval always opens expanded. Re-gated: full suite 7941 passed, Codex SAFE, Opus ship. 🙏

eleboucher pushed a commit to eleboucher/homelab that referenced this pull request Jun 6, 2026
…➔ 0.51.293) (#856)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.277` → `0.51.293` |

---

### Release Notes

<details>
<summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary>

### [`v0.51.293`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051293--2026-06-06--Release-JI-stage-s5--thinking-card-no-longer-renders-twice)

[Compare Source](nesquena/hermes-webui@v0.51.292...v0.51.293)

##### Fixed

- **The "Thinking" card no longer renders twice on a settled turn.** For a turn that had both a tool call and reasoning (e.g. think → call a tool → answer), the thinking card could appear once inside the collapsed **Activity** group at the top of the turn and again as a stranded second card below the answer and the `Done in …` footer. The thinking-only inline render path (added in v0.51.258 for [#&#8203;3592](nesquena/hermes-webui#3592)) now only fires when the turn has no Activity group of its own, and when it does render inline it inserts the card **above** the answer body instead of after the footer. Thinking that echoes the visible answer on a trailing reasoning-only message is also de-duplicated against the whole turn's answer text now, not just the same message's body. Genuinely thinking-only turns still show their thinking inline (the [#&#8203;3592](nesquena/hermes-webui#3592) fix is preserved, not reverted). ([#&#8203;3709](nesquena/hermes-webui#3709); supersedes [#&#8203;3708](nesquena/hermes-webui#3708))

### [`v0.51.292`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051292--2026-06-06--Release-JH-stage-s4--compression-exhausted-turns-surface-as-errors-not-fake-completions)

[Compare Source](nesquena/hermes-webui@v0.51.291...v0.51.292)

##### Fixed

- **Context-compression-exhausted turns are no longer finalized as a falsely "completed" response.** When Hermes Agent exhausts context compression in a long tool-heavy turn, the streamed result can end on a tool result or an assistant `tool_calls` turn with no final assistant answer. WebUI previously rendered that as a settled, completed reply. It now classifies a persisted transcript that ends in a tool/tool-call/empty-assistant tail (or an internal `[CONTEXT COMPACTION — REFERENCE ONLY]` marker) — and `compression_exhausted`/`failed`/`partial` agent results — as a terminal failure and surfaces a clear error instead. The compression session-id migration and pre-compression snapshot now run **before** the terminal-failure path returns, so frontend/backend session state stays consistent when exhaustion fires after the agent rotates `session_id`. ([#&#8203;3316](nesquena/hermes-webui#3316), [@&#8203;franksong2702](https://github.com/franksong2702); fixes [#&#8203;3315](nesquena/hermes-webui#3315))

### [`v0.51.291`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051291--2026-06-06--Release-JG-stage-s2--preserve-live-turn-content-when-switching-away-mid-stream)

[Compare Source](nesquena/hermes-webui@v0.51.290...v0.51.291)

##### Fixed

- **Switching away from a streaming session no longer loses the in-progress thinking/tool content.** When you clicked to another chat while a session was streaming during a quiet window (mid tool-execution or silent reasoning, between content events) and then switched back, the live turn's tool cards and thinking could disappear permanently — only the elapsed-time clock survived — until the response finished and the transcript re-rendered from the server. Cause: the live-turn DOM snapshot was only captured on content/`tool_complete` SSE events, so the switch-away teardown could run with a stale-or-absent snapshot, and the switch-back fallback rebuilt an empty thinking card. `closeLiveStream()` now snapshots the live turn **before** tearing the stream down, so switching back restores the exact state shown at switch-away. ([#&#8203;3668](nesquena/hermes-webui#3668))

### [`v0.51.290`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051290--2026-06-06--Release-JF-stage-s1--profile-providermodel-now-respected-in-session-resolution)

[Compare Source](nesquena/hermes-webui@v0.51.289...v0.51.290)

##### Fixed

- **Profile-bound sessions now resolve their provider and model from the profile** instead of silently falling back to the global active provider. Previously, when a chat was started under a profile and the model string was not `@provider:`-qualified (and no explicit provider was sent), the backend used the catalog's global active provider — so a profile wired to one provider/key could silently run on a different one, causing **wrong credentials/billing** and **silent context truncation** (the global default model's advertised context window could differ from what the provider actually served, so the provider dropped the oldest messages and long chats "forgot" earlier content). Resolution is now authoritative from the profile across all four runtime entry points (chat start, streaming worker incl. background/btw runs, and both deferred `/api/session` display resolvers); stale models are still repaired under the profile provider — including the `openai-codex` profile + stale `openai/…` slash-model case — while native slash IDs on OpenRouter/custom providers are preserved and explicit `@provider:` qualifiers still win. ([#&#8203;3448](nesquena/hermes-webui#3448), [@&#8203;rodboev](https://github.com/rodboev); fixes [#&#8203;3405](nesquena/hermes-webui#3405))

### [`v0.51.289`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051289--2026-06-06--Release-JE-hotfix--sidebar-ReferenceError-3696--scope-undef-prevention-gate)

[Compare Source](nesquena/hermes-webui@v0.51.288...v0.51.289)

##### Fixed

- **Sidebar no longer crashes with `ReferenceError: _sessionAttentionState is not defined`.** The session-attention helper was declared *inside* `renderSessionListFromCache()` and relied on function hoisting, but the top-level `_sidebarRowHasVisibleMessages` (reached via `renderSessionListFromCache` → `_partitionSidebarSessionRows`) called it bare — and hoisting is scoped to the enclosing function, so every sidebar cache-render threw and the session list went blank. `_sessionAttentionState` is now a top-level function reachable by both call sites. Regressed in [#&#8203;3672](nesquena/hermes-webui#3672) (v0.51.269). ([#&#8203;3696](nesquena/hermes-webui#3696))
- **Stale-stream terminal events no longer risk a `ReferenceError: source is not defined`.** `_bailOutOfTerminalEventsFromStaleStream` (declared inside `attachLiveStream`) called `_closeSource(source)` against a `source` that was not in its lexical scope — it would have thrown on the late-finalizing-stream path when the user is back in an active session. `source` is now threaded as an explicit parameter. Found by the new scope gate below during review. ([#&#8203;3696](nesquena/hermes-webui#3696))

##### Internal

- **New static-JS scope/undefined-reference gate (`scripts/scope_undef_gate.py`).** Models the WebUI's classic-`<script>` shared global scope and runs ESLint `no-undef` per file, flagging a function that is defined only *nested* but called from a sibling/top-level scope — the brick class behind [#&#8203;3696](nesquena/hermes-webui#3696) that `node --check`, source-presence tests, and the existing `no-const-assign` runtime gate all miss. Wired into the CI `lint` job alongside the `no-const-assign`/`no-import-assign` runtime gate, with an in-suite test (`tests/test_static_js_scope_undef.py`) and a focused structural regression test (`tests/test_issue3696_session_attention_scope.py`). ([#&#8203;3696](nesquena/hermes-webui#3696))

### [`v0.51.288`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051288--2026-06-06--Release-JD-stage-r24--collapsible-approval-card)

[Compare Source](nesquena/hermes-webui@v0.51.287...v0.51.288)

##### Added

- **The tool-call approval card can be collapsed to a thin header strip.** A chevron toggle in the approval-card header shrinks the card to just its "Approval required" heading so the tool-call rationale and transcript scrolled above it stay readable; clicking again re-expands it. Includes full ARIA (`aria-expanded`/`aria-controls`/`aria-label`), an icon swap, and transcript reflow that preserves a near-bottom scroll position. State resets to expanded for each new approval, so a fresh approval is never hidden. ([#&#8203;3515](nesquena/hermes-webui#3515), [@&#8203;rodboev](https://github.com/rodboev); closes [#&#8203;3007](nesquena/hermes-webui#3007))

### [`v0.51.287`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051287--2026-06-06--Release-JC-stage-r22--WeCom-session-classification--worker-profile-picker-hiding)

[Compare Source](nesquena/hermes-webui@v0.51.286...v0.51.287)

##### Fixed

- **WeCom gateway sessions are now classified as messaging conversations.** Rows arriving with raw sources `wecom` / `wecom_callback` are normalized into the messaging category (alongside weixin/telegram/discord/slack/email) and given proper "WeCom" / "WeCom Callback" display names, so they group and surface correctly in the sidebar. ([#&#8203;3653](nesquena/hermes-webui#3653), [@&#8203;franksong2702](https://github.com/franksong2702))

##### Changed

- **Worker profiles are hidden from the chat profile picker.** Worker profiles (used for orchestrator/Kanban dispatch) are no longer offered as normal human chat targets in the picker, while still appearing in the profile management view with a "Hidden from chat" badge. The active profile is never hidden. ([#&#8203;3662](nesquena/hermes-webui#3662), [@&#8203;Chukwuebuka-20](https://github.com/Chukwuebuka-20))

### [`v0.51.286`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051286--2026-06-06--Release-JB-stage-r21--sidebar-tab-reordering)

[Compare Source](nesquena/hermes-webui@v0.51.285...v0.51.286)

##### Added

- **Drag-reorder for sidebar tabs.** In Settings → Appearance, the "Sidebar tabs" chips (Tasks, Kanban, Skills, Memory, Spaces, Profiles, Todos, Insights, Logs) can be dragged to reorder how they appear in the left rail and sidebar nav, persisted via a sanitized `tab_order` setting (collapses duplicates, rejects `chat`/`settings`, strips non-strings). Chat and Settings stay fixed. Reorder is pointer/desktop-based (consistent with the existing Kanban drag-and-drop); the chips remain tappable for show/hide on touch. ([#&#8203;3067](nesquena/hermes-webui#3067), [@&#8203;ai-ag2026](https://github.com/ai-ag2026))

### [`v0.51.285`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051285--2026-06-06--Release-JA-stage-r19--update-reload-server-identity-race-fix)

[Compare Source](nesquena/hermes-webui@v0.51.284...v0.51.285)

##### Fixed

- **Don't reload the page until the *replacement* server is actually up after an update.** The post-update reload previously compared raw `/health` uptime, which couldn't distinguish a still-running old process from the restarted one (it could reload against the old process or hang). The client now reads a stable `server_started_at` identity before the update POST and reloads only once `/health` reports a *different* identity (with a null-baseline fallback). Both the force-update and regular apply paths read and pass the baseline. ([#&#8203;3654](nesquena/hermes-webui#3654), [@&#8203;franksong2702](https://github.com/franksong2702); [#&#8203;874](nesquena/hermes-webui#874))

### [`v0.51.284`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051284--2026-06-05--Release-IZ-stage-w4--sidebar-status-labels--cron-sessions-toggle)

[Compare Source](nesquena/hermes-webui@v0.51.283...v0.51.284)

##### Added

- **Manual session status labels (Todo / In Progress / Done).** Tag any session from its row's ⋯ menu with a colored status badge (blue Todo / amber In Progress / green Done), stored per-session in localStorage. The badge renders inline on the sidebar row and uses theme variables so it adapts to light/dark and skins. ([#&#8203;3570](nesquena/hermes-webui#3570), [@&#8203;rodboev](https://github.com/rodboev))
- **"Show cron sessions" preference** (Settings → Preferences). Surfaces cron-job output as conversations in the sidebar. Off by default and gated under "Show non-WebUI sessions" — only active once non-WebUI sessions are enabled — with a note that high-frequency jobs can flood the sidebar. ([#&#8203;3514](nesquena/hermes-webui#3514), [@&#8203;rodboev](https://github.com/rodboev); closes [#&#8203;2841](nesquena/hermes-webui#2841))

### [`v0.51.283`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051283--2026-06-05--Release-IY-stage-w2--composer-queue-hint-during-auto-compaction)

[Compare Source](nesquena/hermes-webui@v0.51.282...v0.51.283)

##### Fixed

- **The composer now tells you a message will queue during auto-compaction instead of looking dead.** While automatic compression runs, the send button previously went `disabled` with only a "Waiting for compression to finish" tooltip. It now shows a `queue` action with the placeholder + tooltip "Type a message — it will queue and send after compression", so you can type and have it sent automatically when compaction completes. ([#&#8203;3512](nesquena/hermes-webui#3512), [@&#8203;rodboev](https://github.com/rodboev); closes [#&#8203;3079](nesquena/hermes-webui#3079))

### [`v0.51.282`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051282--2026-06-05--Release-IX-stage-3544--surface-memoryskill-saves-in-Activity-summary)

[Compare Source](nesquena/hermes-webui@v0.51.281...v0.51.282)

##### Added

- **The collapsed Activity summary now shows when the agent saved a memory or updated a skill** — e.g. "Activity: 2 tools, 1 memory saved, 1 skill updated" — so persistent-state changes are visible at a glance without expanding the group. Detection matches the real tool action vocabularies (`memory`: add/replace count as saves, `remove` excluded; `skill_manage`: create/patch/edit/write\_file count as updates, delete/remove\_file excluded), and only completed, non-errored calls are counted. The memory/skill counts are subtracted from the tool count so it reflects only non-memory/skill tools. Classification is stamped as durable `data-*` attributes so the suffix survives the live tool-call group's HTML snapshot/restore on session switch. Sessions with no memory/skill writes render the unchanged "Activity: N tools" label. ([#&#8203;3544](nesquena/hermes-webui#3544), [@&#8203;rodboev](https://github.com/rodboev); closes [#&#8203;3340](nesquena/hermes-webui#3340))

### [`v0.51.281`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051281--2026-06-05--Release-IW-stage-verdigris--Verdigris-emeraldbronze-skin)

[Compare Source](nesquena/hermes-webui@v0.51.280...v0.51.281)

##### Added

- **New "Verdigris" appearance skin** — a dark-only emerald/forest-green palette (`#&#8203;0F1714` background, `#&#8203;121D18` sidebar) with bronze-gold accents (`#C89A5A`), named for the green-bronze patina on aged copper. Selectable in Settings → Appearance and via `/theme verdigris`. Fully scoped under `:root.dark[data-skin="verdigris"]` (no bleed into the default appearance or other skins), with component-level accents for the new-chat button, scrollbar, tool cards, tree viewer, session badges/tags, diff blocks, MCP status, and image lightbox. ([#&#8203;3602](nesquena/hermes-webui#3602), [@&#8203;rodboev](https://github.com/rodboev); closes [#&#8203;3357](nesquena/hermes-webui#3357))

### [`v0.51.280`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051280--2026-06-05--Release-IV-stage-p3i--Windows-self-update-restart-fix)

[Compare Source](nesquena/hermes-webui@v0.51.279...v0.51.280)

##### Fixed

- **Self-update now restarts correctly on Windows.** `os.execv` does not replace the current process on Windows (it spawns a new one while the old keeps running), so the old process held port 8787 and the new process failed to bind ("address already in use"), surfacing as "Update failed" after the timeout. On Windows the restart now launches a detached new process (`subprocess.Popen` with `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP`) and exits the old one immediately to release the port, plus a bounded bind-retry loop in `server_bind()` (up to 10s) to ride out the `SO_EXCLUSIVEADDRUSE` teardown window. POSIX behavior is unchanged (still `os.execv`). ([#&#8203;3647](nesquena/hermes-webui#3647), [@&#8203;jja881](https://github.com/jja881))

### [`v0.51.279`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051279--2026-06-05--Release-IU-stage-p3h--preserve-Activitystreaming-turn-on-mid-stream-scroll)

[Compare Source](nesquena/hermes-webui@v0.51.278...v0.51.279)

##### Fixed

- **Loading earlier messages during an active stream no longer wipes the Activity panel or the current streaming turn.** Two causes: (1) the message merge/dedup keys didn't include `tool_calls`, so assistant messages invoking *different* tools with identical empty content and same-second timestamps collapsed into one — dropping every state.db tool-call after the first the sidecar registered; (2) `_syncToolCallsForLoadedMessages` cleared `S.toolCalls` while `S.busy` blocked the `renderMessages` rebuild. `tool_calls` is now part of the merge/dedup/visible keys (with a preservation branch so distinct tool invocations within the sidecar timestamp window aren't skipped), and the frontend keeps the live tool-call/streaming state when paging in history. ([#&#8203;3665](nesquena/hermes-webui#3665), [@&#8203;mysoul12138](https://github.com/mysoul12138); fixes [#&#8203;3346](nesquena/hermes-webui#3346))

### [`v0.51.278`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051278--2026-06-05--Release-IT-stage-p3g--repair-inline-PDF-preview)

[Compare Source](nesquena/hermes-webui@v0.51.277...v0.51.278)

##### Fixed

- **Inline PDF preview in chat now renders again.** The PDF.js loader previously created a `<script>` with both `src` and `textContent` set (the latter is ignored when `src` is present), so PDF.js never initialized and the preview hung on the spinner before degrading to a download link. It now loads PDF.js via a blob module script that sets the worker source, passes `isEvalSupported:false` to harden the parser, and revokes the blob URL on load. CSP gains `blob:` in `script-src` and a scoped `worker-src blob: 'self' https://cdn.jsdelivr.net` to permit the worker. ([#&#8203;3652](nesquena/hermes-webui#3652), [@&#8203;xx77yy](https://github.com/xx77yy); closes [#&#8203;3649](nesquena/hermes-webui#3649))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/856
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…esquena#3515) (nesquena#3697)

* feat(approval): make the approval card collapsible (nesquena#3515)

Adds a collapse toggle to the approval card header so users can shrink it
to a thin header strip and keep the tool-call rationale/transcript above
readable. Full ARIA (aria-expanded/controls/label), chevron swap, and
transcript reflow that preserves near-bottom scroll. Closes nesquena#3007.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* docs(changelog): v0.51.288 — Release JD (stage-r24)

* fix(approval): clear collapsed state for a distinct queued approval (nesquena#3515)

Codex regression-gate finding: showApprovalCard's sameApproval check didn't
include approval_id and didn't clear .collapsed in the !sameApproval branch, so
a NEW/parallel approval arriving while the card was already collapsed could
render collapsed with its command + action buttons hidden. Add approval_id to
the signature; clear .collapsed for a distinct approval before syncing. +2 regression tests.

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

approval popup needs to be collapsible too

2 participants