Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

### Fixed

- **PR #2317** (refs #2312) — Appearance boot reconciliation now treats explicit `light`, `dark`, and `system` localStorage theme values as user selections when a prior Settings autosave failed. Previously only `system` was considered explicit, so users who picked `light` on a server defaulted to `dark` (or vice versa) could still be reverted on refresh.

- **PR #2275** by @ai-ag2026 — CLI/messaging continuation sessions (sessions stitched from a `parent_session_id` chain) now return their full transcript instead of an empty list. Pre-fix, `get_cli_session_messages()` called `_is_continuation_session()` while walking the parent chain, but `api/models.py` didn't import that helper. The exception was swallowed by `except Exception: return []`, so valid external sessions could fall through silently. Adds regression coverage that a stitched continuation chain returns a non-empty transcript.

- **PR #2277** by @eleboucher — Rootless container runtimes (k8s `runAsNonRoot: true`, OpenShift restricted SCC, `docker --user`, rootless Podman) no longer hit a cascade of permission errors at startup. Pre-fix, the rootless branch skipped the root init phase entirely, but root init also did rsync, `/uv_cache` permissions, `~/hermeswebui` home directory creation, and `/workspace` writability. `docker_init.bash` now distinguishes "no root init available" from "root init available but skipped", running the work that doesn't need root in the rootless branch too.
Expand Down
9 changes: 5 additions & 4 deletions static/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -1409,15 +1409,16 @@ function applyBotName(){
// localStorage into 'dark'/'default' BEFORE this code runs, so a truly
// empty (new-browser) state is indistinguishable from a user who chose
// the defaults. To avoid blocking server→client sync on first visit we
// only let localStorage override the server when it carries a NON-DEFAULT
// skin or a non-dark/light theme value (i.e. the user explicitly picked
// something). When localStorage is at the defaults, the server wins.
// only let localStorage override the server when it carries an explicit
// user-selectable theme value or a NON-DEFAULT skin. That keeps the
// server in charge for empty first-visit state while preserving explicit
// light/dark/system choices after a failed autosave.
const srvAppearance=_normalizeAppearance(s.theme,s.skin);
const lsTheme=(localStorage.getItem('hermes-theme')||'').trim().toLowerCase();
const lsSkin=(localStorage.getItem('hermes-skin')||'').trim().toLowerCase();
const lsAppearance=_normalizeAppearance(lsTheme||null,lsSkin||null);
const lsHasExplicitSkin=lsSkin&&lsSkin!=='default';
const lsHasExplicitTheme=lsTheme&&lsTheme==='system';
const lsHasExplicitTheme=lsTheme&&['system','light','dark'].includes(lsTheme);
const theme=lsHasExplicitTheme?lsAppearance.theme:srvAppearance.theme;
const skin=lsHasExplicitSkin?lsAppearance.skin:srvAppearance.skin;
localStorage.setItem('hermes-theme',theme);
Expand Down
7 changes: 7 additions & 0 deletions tests/test_batch_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ def test_theme_listener_cleanup_uses_stable_handler(self):
"_applyTheme must remove the previous OS-theme listener before adding a new one"
)

def test_boot_reconcile_treats_light_dark_as_explicit_theme_choices(self):
src = read("static/boot.js")
assert "['system','light','dark'].includes(lsTheme)" in src, (
"boot appearance reconciliation must preserve explicit light/dark/system "
"localStorage selections when a prior autosave failed"
)

def test_panels_hydrates_appearance_before_models_fetch(self):
src = read("static/panels.js")
skin_idx = src.index("const skinVal=(settings.skin||'default').toLowerCase();")
Expand Down
Loading