* feat(sidebar): add show_cron_sessions toggle to surface cron sessions (#3514, #2841)
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* feat(sidebar): add manual session status labels (#3570)
Co-authored-by: Rod Boev <rod.boev@gmail.com>
* docs(changelog): v0.51.284 — Release IZ (stage-w4)
* fix(settings): persist show_cron_sessions in the explicit Save Settings path too (#3514)
Codex regression-gate follow-up: the autosave path (_preferencesPayloadFromUi)
included show_cron_sessions but the explicit saveSettings() button path read/saved
show_cli_sessions and dropped the cron checkbox — clicking Save Settings silently
omitted it. Read settingsShowCronSessions + add body.show_cron_sessions (gated on
CLI sessions, mirroring autosave).
* fix(settings): gate show_cron_sessions identically in BOTH save paths (#3514)
Codex round-2: my saveSettings() gate exposed that the autosave path
(_preferencesPayloadFromUi) posted the raw cron checkbox state ungated, so
show_cli_sessions=false + show_cron_sessions=true could persist via autosave.
Gate autosave on showCliCb too; update the regression test to assert both
paths gate on settingsShowCliSessions.
---------
Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
Adds the Verdigris dark-only appearance skin (emerald/forest-green + bronze-gold),
renamed from the contributor's 'Hermes Agent' to a descriptive material name per
maintainer naming convention. Registered across all 5 skin sites (config allowlist,
boot.js swatch, index.html FOUC map, i18n in 12 locales, scoped CSS palette) + test.
Also fixes the zeus i18n test (zeus is no longer the trailing skin token).
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
## Release v0.51.258 — Release HZ (stage-r6)
Fresh-arrival low-risk pair (both @rodboev, landed in the last sweep window).
### Fixed
| Issue | Fix |
|-------|-----|
| #3597 | The "update available" banner now shows from **any panel** (Settings → System "Check now", etc.), not just the Chat view — it was positioned inside the chat surface so it only rendered there. |
| #3592 | Under Simplified Tool Calling, an assistant turn with **thinking but no tool calls** now renders that thinking inline on settlement instead of burying it in an empty collapsed activity group. |
### Review fix absorbed (Codex)
#3592's inline-render `continue` skipped the activity-group creation that carried the turn's `data-turn-duration`, but the footer still suppressed the "Done in …" duration for any `assistantThinking` turn → thinking-only turns silently lost their duration display. Fixed: footer duration is now suppressed **only** for turns that actually build an activity group (`toolCallAssistantIdxs.has(mi)`), so thinking-only inline turns keep "Done in …". + regression test.
### Gate
- Full pytest suite: **7631 passed, 0 failed**
- ESLint: CLEAN · browser-smoke: CLEAN
- Codex (regression): SHIP-ONLY-WITH-FIXES (duration-drop) → fixed → **SAFE TO SHIP**
### Sweep note
#3603 + #3604 (sidebar CLI-session classification, same author/area) were **not** included — they assert contradictory models for a sidecar-less `source='cli'` recovery row; flagged on both PRs for the author to reconcile.
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
## Release v0.51.254 — Release HV (stage-r2)
Phase-2 medium wave 1 — 4 PRs (UI/mobile/cancel fixes + an un-held model dedup).
### Fixed
| Issue/PR | Author | Fix |
|----------|--------|-----|
| #3528 | @franksong2702 | Render partial tool calls after cancel — interrupted turns keep their `_partial_tool_calls` rows in the transcript + fallback tool-cards. (Codex confirmed it stays render-only, not forwarded to the provider API.) |
| #3550 | @lurebat | Android offline recovery soft-reattaches the live stream instead of hard-reloading the page on a transient background/disconnect. |
| #3479 | @mvanhorn | iOS Safari no longer snaps the conversation to the top when a handoff/compression card is inserted mid-stream or on `refreshSession()`. |
| #3478 | @JayC-L | **Un-held:** named custom providers (`@custom:name:model`) dedup against bare model IDs without regressing Ollama multi-colon tags (`qwen2.5:7b-instruct-q4`). Only `@custom:` IDs strip the two-segment prefix. |
### Hold-sweep note
#3478/#3489 was held yesterday for an Ollama multi-colon-tag regression risk (a blanket `lastIndexOf` would lose the model). The author pushed a scoped fix (only `@custom:` IDs use `lastIndexOf`); I verified `_normId` in node against the regression cases — Ollama bare tags are preserved. Un-held + shipped.
### Gate
- Full pytest suite: **7588 passed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): **SAFE TO SHIP** — #3552 partial-tool-calls verified render-only (no `_API_SAFE_MSG_KEYS` leak / no 400-on-strict-provider, the v0.50.251 #1375 trap); #3551 no EventSource double-subscribe; #3541 no regression vs the #3525 scroll-follow shipped in v0.51.253; #3489 no over-dedup.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Co-authored-by: lurebat <lurebat@users.noreply.github.com>
Co-authored-by: mvanhorn <mvanhorn@users.noreply.github.com>
Co-authored-by: JayC-L <JayC-L@users.noreply.github.com>
## Release v0.51.252 — Release HT (stage-q24)
Two trivially-safe @rodboev changes (independent).
### Fixed
| Issue | Author | Fix |
|-------|--------|-----|
| #2481 | @rodboev | The floating "selected-text reply" button now has `user-select:none`, so its own label can't get caught in a text selection (no bleed-through). CSS one-liner. |
### Docs
- README **Compatibility** section: upgrade WebUI + hermes-agent together until the stable agent API (#2491) lands. (@rodboev)
### Dropped from this batch
- **#2977 `/use` skill command** was staged here but **dropped** — the Codex regression gate found an async stale-directive race (`cmdUse()` awaits `/api/skills` but `send()` doesn't await the handler → a fast next send can miss it, or a stale directive leaks to a later message) plus an over-eager `finally` clear that silently discards the directive on a local slash-command early-return. Held with `changes-requested` + a detailed rework note (tracked pending promise + clear-on-consume). Concept approved; needs lifecycle hardening.
### Gate
- Full pytest suite: **7570 passed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): **SAFE TO SHIP** — `user-select:none` scoped to the button only, README docs-only, no `/use` code remains
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
## Release v0.51.244 — Release HL (stage-q16)
UX-approved direction via Telegram (workspace drag-drop polish you requested). All 4 drag-drop flows verified live in-browser.
### Added
| PR | Author | Feature |
|----|--------|---------|
| #3402 / #3424 | @pamnard | **Drop OS files/folders onto a specific workspace folder row or breadcrumb** to upload into that directory (not just the current dir). OS folder drops are traversed (`webkitGetAsEntry`/`readEntries`) preserving nested structure. Uploads via the existing `/api/workspace/upload` (no new backend). |
### Fixed
- **Composer drop-zone jank**: dragging a workspace file (or OS file) over the composer footer rendered a translucent overlay that let the textarea/chips/icons bleed through and collide with the hint text. Now a clean, fully-opaque box with a single centered **context-aware** label — *"Drop to insert workspace reference"* (workspace file → `@path` insert) vs *"Drop files to attach"* (OS file → message attach).
- **Drag-drop handler coexistence (CORE, caught in review)**: #3424's OS-upload binding assigned `el.ondrop` on folder rows, which **overwrote** the drag-to-move handler from #3422 (also `el.ondrop`) — silently breaking move-to-folder (the ws-path drop fell through to the composer as an `@path` insert). Fixed by binding the OS-upload handlers via `addEventListener` so they compose; each handler gates on its own drag type.
### Drag-drop matrix — all verified LIVE in-browser (real drag→drop, asserted on disk)
| Flow | Result |
|------|--------|
| OS image → composer footer | ✓ attaches |
| workspace file → composer footer | ✓ inserts `@path` |
| workspace file → workspace folder | ✓ moves on disk (report.md → docs/) |
| OS file → workspace folder | ✓ uploads into target folder |
### Scope note
#3424's PR branch carried the OLD pre-hardening `_handle_file_move`. Applied **frontend-only** — master's hardened move backend (v0.51.243, TOCTOU/symlink fixes) is untouched (Codex confirmed no `api/routes.py` diff).
### Gate
- Full pytest suite: **7542 passed, 9 skipped, 3 xpassed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): CORE handler-clobber → fixed → **SAFE TO SHIP**
Co-authored-by: pamnard <pamnard@users.noreply.github.com>
## Release v0.51.243 — Release HK (stage-q15)
UX-approved direction via Telegram (workspace feature you named). Backend security hardened through 3 Codex rounds.
### Added
| PR | Author | Feature |
|----|--------|---------|
| #3402 / #3422 | @pamnard | **Drag a file or folder in the workspace tree onto another folder row (or breadcrumb segment) to move it** within the workspace. New `POST /api/file/move`. Drop handlers use `stopPropagation` so the composer `@path` drag (#1097) and OS-file upload-drop (#3411) are unchanged. |
### Verified
- Live end-to-end: `notes.txt` → `docs/` confirmed on disk; legit moves return the correct `new_path`.
- 13 tests (incl. folder-into-self/descendant guard, existing-target collision, and 3 security regressions).
### Security hardening absorbed (3 Codex rounds — all fixed + regression-tested)
1. **TOCTOU symlink race on destination**: a path-based `source.rename(dest)` could be raced by swapping `dest_dir` to an external symlink between validation and rename. Now opens both parent dirs via the workspace-anchored `open_anchored_fd` (openat + `O_NOFOLLOW`, same helper as the upload hardening) and uses `os.rename(leaf, leaf, src_dir_fd=…, dst_dir_fd=…)` with an fd-based collision check; path-based fallback only where `dir_fd` is unsupported.
2. **Symlinked workspace root** returned a confusing 400 after a successful move — returned `new_path` now computed against `ws_root.resolve()`.
3. **Symlinked source entry**: `safe_resolve` follows the final symlink, so moving `link.txt` would move its *target* and dangle the link — now rejected via no-follow `lstat` on the lexical path.
### Gate
- Full pytest suite: **7530 passed, 9 skipped, 3 xpassed, 0 failed**
- ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): SHIP-ONLY-WITH-FIXES ×3 → all applied + tested → **SAFE TO SHIP**
Co-authored-by: pamnard <pamnard@users.noreply.github.com>
## Release v0.51.242 — Release HJ (stage-q14)
UX-approved via Telegram (Nathan — dark/light/mobile screenshots).
### Added
| PR | Author | Feature |
|----|--------|---------|
| #3440 | @t3chn0pr13st | **Graphite appearance skin** — a quiet, neutral-gray "workbench" alternative to the default gold/cream. Selectable from Settings → Appearance and `/theme skin graphite`. Both light + dark palettes on the existing CSS-variable token system; tightened typography, shadows, active-sidebar spacing, code-block framing. |
### Why it's safe
- **Fully scoped + additive**: every new CSS rule (and every `!important`) is under `[data-skin="graphite"]` — Codex verified zero bleed into the default appearance or other skins. `api/config.py` keeps the default skin as `default` and only *adds* `graphite` to the allowed set. No i18n keys dropped (only the `/theme` help string gains `graphite`).
- Opt-in; a user has to select it. Default experience unchanged.
### Test-robustness fix (absorbed)
The new graphite scoped selectors (e.g. `:root[data-skin="graphite"] .session-item.active .session-time{…}`) appear in `style.css` *before* the canonical unscoped rules, which broke 3 naive first-occurrence CSS-contract tests (`test_issue677` scroll-btn-overlay, `test_issue856_pinned_indicator_layout`, `test_workspace_panel_session_list`). Fixed those 3 to anchor on the canonical **unscoped** rule (start-of-line regex) instead of the first `.selector` match — robust against this and future skins. Verified they still pass on clean master CSS (invariant preserved, not weakened).
### Gate
- Full pytest suite: **7517 passed, 9 skipped, 3 xpassed, 0 failed**
- ESLint runtime gate: CLEAN · ruff: CLEAN · browser-smoke: CLEAN
- Codex (regression): **SAFE TO SHIP** (verified all CSS scoped, default skin unchanged, no i18n key loss)
- Vision-verified dark + light + mobile; UX-approved by Nathan via Telegram
Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>
* Release v0.51.228 (stage-p12): workspace tree-drop #3411 + large-markdown preview #3378#3411 (@pamnard): stopPropagation on workspace file-tree OS-file dragenter/dragover/drop
so a tree drop uploads to the workspace WITHOUT also attaching to the composer.
#3378 (@starGazerK): raise md rich-render ceiling 64KB/1500L->256KB/5000L + backend
file read 200KB->400KB, add 'Render as markdown anyway' force button (reuses cached
raw content, no extra fetch).
* fix(workspace): force-render uses fresh path-scoped cache, blocked while dirty (#3378 Codex follow-up)
Codex review of #3378 found the markdown force-render path had two SILENT bugs:
(1) saving a md file from the plain-text fallback didn't update _previewRawContent,
so a later force-render showed stale pre-save content; (2) the cache-reuse check
'path===_previewCurrentPath' was tautological (var just assigned), so a force-render
after a file switch could render the previous file's cached content. Fixes: track
_previewRawContentPath (set on fetch AND save), reuse cache only when it matches the
requested path, and block force-render while the editor is dirty/open. +3 regression
tests. (#3411 was cleared clean by Codex.)
---------
Co-authored-by: nesquena-hermes <[email protected]>
* feat(composer): replace mobile config-button sliders icon with context-usage ring (#3062, @NottheGuy007)
Squashed net diff of #3062 (the PR branch's tip commits were deletes of files not
present in our tree). Replaces the composerMobileCtxBadge text badge with an SVG
progress ring (ctx-arc + ctx-num) showing real-time context-window usage: ring
fill via stroke-dashoffset, centered percentage, color-coded green<=50%
orange<=85% red>85%. Ring resets to 0%/green on new session.
* feat(activity): add 'expand activity feed by default' appearance setting (#3080, @AJV20)
Squashed net diff of #3080. Adds a Settings -> Appearance checkbox
(activity_feed_expanded_default, default off) to expand new Activity disclosures
by default; preserves manual per-turn collapse/expand (explicit toggle still
wins); live 'Waiting on model' rows explain what the agent is doing before/after
tool calls. i18n keys for all locales.
* docs(changelog): v0.51.226 — context-usage ring (#3062) + activity-feed default-expand setting (#3080)
* test(mobile): update touch-target test for #3062 ring (badge removed)
#3062 replaced the composerMobileCtxBadge text badge with the SVG context-usage
ring (composerMobileCtxRing) but left 3 stale references in tests/test_mobile_layout.py.
The full suite caught test_mobile_composer_primary_controls_keep_touch_friendly_sizing
asserting the removed .composer-mobile-ctx-badge CSS rule + #composerMobileCtxBadge
element. Updated the assertion to the new ring: confirm composerMobileCtxRing exists,
the old badge is fully gone (not dangling), and the ring SVG is aria-hidden so it
stays a decorative overlay that doesn't steal the config button's 44px touch target
(which is still asserted via .composer-mobile-config-btn above). 56/56 mobile-layout
tests pass. Codex: no production JS dereferences the removed badge.
---------
Co-authored-by: nesquena-hermes <[email protected]>
Nathan screenshot feedback on the Plugins card:
- Open button rendered as a yellow block with INVISIBLE text: --accent-text
resolves to the same gold as --accent in the default theme (text==bg). Switched
to a ghost/outline button (accent text + border on the card surface; fills on
hover) — always legible regardless of theme.
- Removed the redundant DOUBLE 'Enabled' badge (the dashboard-specific badge
duplicated the generic activation badge; kept the generic one).
- Toggle slider knob was hard to see on the gold 'on' state; added a drop shadow.
Also Opus SHOULD-FIX: _VALID_PLUGIN_TAB_PATH now rejects a leading '//'
(protocol-relative URL → remote origin in iframe.src). Test updated.
Nathan feedback: the download icon read as 'oddly placed' on dark generated
images. Geometry was already correct (8px inset on the image corner) — the
issue was low contrast: a flat rgba(0,0,0,.55) button blends into a dark image.
Add a subtle 1px white border, soft shadow, and 3px backdrop-blur so the button
reads as a clear chip-on-image regardless of the underlying pixels (the standard
treatment for on-image controls). CSS-only.
The .msg-artifact-image span was stretching wider than the image (inline-block
in a block context filled the line), so the absolutely-positioned download
button (right:8px of the wrapper) floated ~240px to the right of the image.
Add width:fit-content + max-width so the wrapper hugs the image; the button now
correctly overlays the image's top-right corner. line-height:0 removes inline
descender gap.
Nathan feedback round 2:
- #3220: generated images were rendering at the 120x90 upload-thumbnail size
(too small for the subject of the message). Now render at natural aspect
ratio up to 360px (max-height 360, responsive max-width), lightbox preserved.
- #3223: drop the per-item subtitle/description lines from the session action
menu — show only icon + label (VS Code / browser / ChatGPT pattern). The
description is preserved as a hover tooltip (title=). Makes the menu ~40%
shorter, less crowded, and structurally less prone to viewport clipping.
#3220: redesign generated-image rendering from a permanent bordered card
(filename + Open/Download buttons) to a clean inline image with click-to-zoom
lightbox + a hover/focus-revealed Download overlay, matching ChatGPT/Claude/
Gemini. Drops redundant Open (lightbox already covers it). Tests updated.
#3337: fix two-tone code background — Prism's prism-tomorrow theme styled the
parent <pre> gray while the <code> was navy var(--code-bg), so dark theme showed
a gray frame around navy code. Override BOTH .preview-code[class*=language-] and
its <code> to var(--code-bg) (mirrors the chat code-block fix at .msg-body pre).
#3223: fix action-menu clipping — the new Regenerate-title row made the 9-item
menu tall enough to overflow the viewport bottom when opened on a top-anchored
row at short viewports (e.g. 1280x720). _positionSessionActionMenu now clamps
the menu within both viewport edges and caps max-height with scroll when the
menu is taller than the viewport.
Per maintainer UX direction: drop the text badge pill ("APPROVAL" /
"N QUESTIONS") in favor of color-coding the existing right-side status dot
plus the colored left rail. Red dot (--error) for pending approvals, amber
dot (--warning) for pending clarifies; theme-driven, visible even when the
session is not streaming/unread. Approval dot gently pulses (disabled under
prefers-reduced-motion). Quieter and consistent with the existing pin/unread
dot family — the row no longer needs to truncate its title to fit a pill.
Updated test_session_attention_badges to assert the dot classes
(is-attention-approval/clarify) instead of the removed badge element/styles.
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Cyberpunk-inspired dark-first skin with neon purple (#B347FF) accent
and cyan (#00DDFF) highlights. Full palette for both light and dark
modes with component-level overrides (glow effects, purple scrollbars,
tool cards, tree viewer, session badges, MCP status indicators).
- static/style.css: 37 CSS rules (light + dark palette + components)
- static/boot.js: Neon entry in _SKINS array
- static/index.html: neon:1 in skin allowlist
Follows the existing skin pattern (modeled after Nous).
- Rename _escHandler to _keyHandler (now handles nav keys too)
- Store counter reference (lb._counterEl) to avoid DOM query on every nav
- Remove dead 'let counter = null' and 'hasNav' closure variable
- Use lb._navImages directly in keyboard handler for consistency
- Add null guard on lb.querySelector('img') in _navigateLightbox
- Inline _updateLightboxCounter one-liner
- Fix CSS section comment 'Image lightbox close' → 'Image lightbox'
- Fix CHANGELOG placeholder (#PR → #2967)
When multiple images appear in the same message, clicking any image
now opens a lightbox with prev/next navigation buttons (‹ / ›) and
keyboard support (← / →). An image counter (e.g. '3 / 5') is shown at
the bottom of the overlay.
- _openImgLightbox now receives the clicked <img> element to find
sibling images within the same message container
- New _openImgLightboxWithNav, _navigateLightbox, _updateLightboxCounter
- CSS: .img-lightbox-nav (prev/next buttons), .img-lightbox-counter
- Close button (×), Escape key, and click-outside-to-close preserved
The tool-card border-subtle was so faint that the cards visually melted
into the surrounding prose once the cursor left the conversation. Bumps
the resting border to --border-muted and adds a 2px left edge so a tool
output row reads as metadata at a glance, even on light skins where
border-subtle is barely visible. Hover still escalates to --border2.
Verified by loading a session with mixed tool calls and assistant prose
on the light theme and confirming the tool cards are now identifiable
without mousing over them.
Refs #2867
Cherry-picked via 3-way apply onto stage HEAD.
Resolved workspace.js conflict: kept master's #2716 sessionId-capture
stale-session guard (closure-scoped sessionId check after await), AND
added PR's renderSessionArtifacts() call to refresh the new Artifacts
tab when the file tree updates. Wrapped in typeof check for defense.
Co-authored-by: AJV20 <abdielvc@me.com>
Cherry-picked via 3-way apply of net delta against stage HEAD. All 8 files
applied cleanly including the new static/pwa-startup.js.
Co-authored-by: AJV20 <abdielvc@me.com>
Add `PATCH /api/mcp/servers/{name}` endpoint that accepts `{"enabled": bool}`,
updates `mcp_servers.<name>.enabled` in config.yaml, and calls `reload_config()`.
Mirrors the existing DELETE pattern.
Also wire the previously-defined-but-unrouted `_handle_mcp_server_delete` into
`handle_delete`, and `_handle_mcp_server_update` into a new `handle_put` +
`do_PUT` in server.py — fixing a pre-existing bug where those handlers existed
but were never reachable over HTTP.
UI: add a toggle button in each MCP server row in the system settings panel
(panels.js). Clicking it calls PATCH and reloads the list. Toggle button is
styled with `.mcp-toggle-enabled` / `.mcp-toggle-disabled` CSS classes. The
`toggle_supported` flag in the list response is now `True`.
i18n: add 5 new keys (`mcp_enable_server`, `mcp_disable_server`,
`mcp_enabled_toast`, `mcp_disabled_toast`, `mcp_toggle_failed`) to all 9
non-English locales (English values as placeholder translations).
Tests: add `TestMcpToggle` class with 7 tests covering disable, enable,
404-not-found, empty name, missing field, response payload, and URL-encoded name.
Update `test_empty_config` and visibility panel assertions to reflect
`toggle_supported: True` and the new toggle button in panels.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three small fixes from Copilot's review:
1. static/style.css:1354 - removed spaces inside `clamp(...)` args to
match the file's existing compact style (no spaces after commas in
neighboring declarations like `transition:border-color .2s,box-shadow .2s`).
2. CHANGELOG.md - wrapped the long single-line entry across multiple lines
with standard Markdown continuation indentation for cleaner diffs.
3. CHANGELOG.md - normalized `~1300 px` to `~1300px` for unit-formatting
consistency.
No behavior change. Same one-line CSS rule, just tightened formatting.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`.composer-box` had a hardcoded `max-width: 780px` since the early
v0.50.x layout pass. On wide displays (1440p+, 2880px ultrawides)
this leaves significant unused horizontal space AND squeezes the
composer-footer chips (workspace, model, reasoning, context %)
against each other inside the 780px box.
When the context-percentage ring appears (active token usage), the
workspace chip truncates to "Fou..." instead of showing the full
workspace name. Model + reasoning chips also lose room. The chip
strip horizontally-scrolls inside .composer-left, so the rightmost
chips effectively hide behind context %.
The constraint isn't "Reading flow looks better at 780px" — the
textarea is min-height:64px, max-height:200px and wraps naturally,
so users on wide displays get the SAME readable text wrap regardless
of box width. Only the footer chips suffer.
Fix: clamp(780px, 60vw, 1100px). Preserves the 780px floor (no
regression on viewports < 780px since clamp's first arg is the
minimum) while letting wider viewports use up to 1100px (60% of
viewport width, capped). 1100px gives ~40% more horizontal room for
the footer chips without filling the entire screen at extreme widths.
Per-viewport behavior:
<= 780 px → 780 px (hard floor) — zero change vs current
1280 px → 60vw = 768 → floored to 780 — zero change
1440 px → 60vw = 864 — +84 px room
1920 px → 60vw = 1152 → capped at 1100 — +320 px room
2880 px → 60vw = 1728 → capped at 1100 — +320 px room
One line in static/style.css. CHANGELOG entry. No JS. No new deps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Squashed from 2 author commits:
- d2237e23 feat: surface live activity timeline
- eee57ec0 fix: satisfy activity timeline CI guards
Frontend-only telemetry from existing stream events. Replaces empty
Thinking… placeholder with observable run status (Waiting on model /
Waiting on tool result / Working for …). New CSS, new test file.
- Rewrote _kanbanRenderMarkdown() from basic paragraph wrapper to a
line-by-line block processor supporting headings, code blocks, lists,
task lists, tables, blockquotes, horizontal rules, and strikethrough.
- Added CSS for all new elements (table borders, code blocks, checkboxes,
blockquote accent, heading sizing, etc.).
- Dropped white-space: pre-wrap from .kanban-task-preview-body and
.kanban-detail-row-main since markdown now handles layout.
- Applied _kanbanRenderMarkdown() to task description (was esc()) and
comment body (was esc()) in the task detail view.
Squashed from 2 author commits:
- a1017d02 initial fix: flex:0 0 auto on all 5 chip wraps
- bf54ba50 Copilot review fix-up: consolidate into single rule
Closes#2740. CSS-only, no JS changes. Default-width layout unchanged,
only affects narrow-viewport overflow regime via composer-left's existing
overflow-x:auto.
Add Hepburn skin with full light/dark palette derived from the
Hepburn TUI theme. Brand color #c6246a with pink-magenta accents.
- Light: soft pink surfaces (#fff3f7 / #fbe4ed)
- Dark: deep aubergine (#110a0f / #1e0f19)
- Accent: #d44a7a (light) / #f278ad (dark)
- Styled: send button, new chat button, tool cards, session indicator
Also fix settings panel skin picker to prioritize localStorage
over server defaults, so newly selected skins reflect correctly
in the dropdown.
Original PR: #2676 by @lucasrc
Adds POST /api/skills/toggle endpoint that flips skills.disabled in
config.yaml, and a UI toggle in the Skills panel that shows all skills
(including disabled ones) with a per-skill on/off control.
- Backend: new endpoint validates skill exists in filesystem before
toggling. Read-modify-write wrapped in _cfg_lock for thread safety.
Writes through to platform_disabled.webui when present.
- Frontend: each skill-item now has a toggle switch; disabled skills
appear muted but still listed (previously they were filtered out).
- i18n: new toggle keys translated across all 9 non-English locales.
- Tests: round-trip test for disabled list normalization + toggle
endpoint behavior.
Squash-merged from contributor's branch (19 commits + 1 merge commit)
onto current master via the cherry-pick-stale-contributor-prs procedure.
- Replace text 'Collapse'/'Expand' button labels with Lucide chevron SVG
icons (chevron-down expanded → click to collapse, chevron-up collapsed
→ click to expand). Matches the iconographic design language of the
rest of the chrome (composer buttons, sidebar controls).
ARIA label + title attributes carry the same semantics for assistive
tech, so no accessibility regression vs. the text labels.
- Fix collapsed-card edge clipping at viewport bottom. Original
.clarify-card { bottom: -24px } was sized for the expanded card
(300-420px tall); adding a 72px collapsed variant pushed the header
below the parent's visible region. Override bottom to 8px and reduce
inner padding for the collapsed state so the entire header sits cleanly
inside the viewport at both desktop and mobile sizes (verified card
fits with ~115px margin desktop / ~125px margin mobile).
Per Nathan's 2026-05-22 UX feedback on the screenshot package.
Light-theme review revealed white text on gold chips (color: var(--bg-page)) was
washed out and hard to read. Switched to fixed dark text #1a1a1a with font-weight
600 so the on-state reads clearly on the gold accent in both light and dark
themes. Off-state unchanged (muted text on transparent).
Per deep-review verdict SHIP-WITH-FIXES on PR #2636:
1. Profile-switch reconciliation: _refreshProfileSwitchBackground now re-fetches
/api/settings and re-applies hidden_tabs for the new profile. Without this,
Profile A's hidden-tabs choice stayed in effect under Profile B until the
user opened Settings → Appearance.
2. A11y: switched chips from role=button + aria-pressed to role=switch +
aria-checked. The pressed/not-pressed wording confused screen-reader users
because chip-off looks like the off state. Added role=group +
aria-labelledby on the container, and a :focus-visible style on the chips.
3. Server-side belt-and-suspenders: api/config.py now strips 'chat' and
'settings' from hidden_tabs at validation time, matching the client's apply-
time filter. A tampered POST can no longer persist the forbidden values.
3 new regression tests added (chat/settings rejection, profile-switch wiring,
chip a11y attributes).
Co-authored-by: FrancescoFarinola <francesco.farinola@example.com>
Adds a reload button to the app titlebar visible only in PWA standalone
or fullscreen mode, and a pull-to-refresh gesture on the messages container
that smooth-scrolls to the top before activating.
The reload button sits next to the message count label and provides a
one-tap refresh for users who installed the WebUI as a home-screen app
where browser navigation controls are unavailable.
The pull-to-refresh gesture detects downward drag at the top of the
message list, shows a visual indicator ('Pull to refresh' / 'Release to refresh'),
and reloads on release past the 80px threshold. When triggered mid-conversation,
it smooth-scrolls to the top first.
Opus advisor on stage-371 caught three issues during pre-release review:
1. RTL salvage missed KaTeX math (display equations + inline LaTeX), diff
blocks, CSV tables (column order must read left-to-right regardless of
chat direction), and .skill-file-path. The first salvage commit only
covered pre/code/kbd/samp/tt and tool-call bodies. Added a second
force-LTR block covering: .katex, .katex-block, .katex-display,
.katex-html, .katex-inline, .diff-block (+children), .csv-table-wrap,
.csv-table (+children), .skill-file-path. Severity: KaTeX is the most
user-visible gap — any user rendering math under RTL would see flipped
equations.
2. Quota chip @media (max-width:1400px) hide rule conflicted at exactly
1400px with the existing @media (min-width:1400px) .messages-inner
rule — chip was hidden AT the wide-desktop boundary where it should
first appear. Changed to (max-width:1399.98px). Visually verified at
1400px: chip now correctly visible there.
3. Dead .icon-btn.provider-quota-chip selector — chip never has icon-btn
class. Removed.
Test added: test_rtl_math_and_tables_stay_ltr (pins the 4 new LTR
surfaces). Also removed dead code in test_rtl_code_blocks_stay_ltr
(unused code_block variable).
Per stage-fix protocol: SHIP-with-followup applied on the stage rather
than the source PR, since #2409 is already merged-into-stage and
nesquena-approved. Stage-371 review-bypass batch path still holds.
- Remove duplicate mobile-close-btn from HTML
- Remove dead .mobile-close-btn CSS rules; unhide .close-preview at all viewports
- Change btnClearPreview tooltip from 'Hide workspace panel' to 'Close'
- Update tests across test_sprint41.py, test_sprint44.py, test_issue781.py,
and test_mobile_layout.py to match new single-button model
Keep distinct generated summary categories, route update-summary generation through the configured auxiliary model first, disclose capped large-range summary input, and constrain long summary panels.