diff --git a/CHANGELOG.md b/CHANGELOG.md index 139ec3ef98f..bd9fb389110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/static/boot.js b/static/boot.js index e967f292b63..ca421007551 100644 --- a/static/boot.js +++ b/static/boot.js @@ -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); diff --git a/tests/test_batch_fixes.py b/tests/test_batch_fixes.py index b784444847d..62663cc99d6 100644 --- a/tests/test_batch_fixes.py +++ b/tests/test_batch_fixes.py @@ -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();")