fix(sidebar): keep minimized icon rail in sync with per-tab visibility (#5987)

* fix(sidebar): keep minimized icon rail in sync with per-tab visibility

Per-tab visibility (Customize UI / Appearance checkboxes, stored in
localStorage under `odysseus-ui-visibility`) was only applied to the full
sidebar elements — `UI_VIS_MAP` never targeted the collapsed `#icon-rail`
launchers. So a user who turned a tab off (e.g. Email) in the full view saw
every tab reappear when minimizing the sidebar to the icon rail.

Pair each tool/section selector with its `#rail-*` counterpart (mapping
mirrors `_railToolMap`), so `applyUIVis()` hides the rail launcher too.
Admin feature-flag handling is unaffected: the features-fetch reconcile at
app.js already re-applies `applyUIVis()`, so rail launchers now track admin
disables exactly like their sidebar buttons.

Adds a static regression test asserting every customizable tab pairs its
rail button.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(sidebar): extract UI visibility into testable module

Move UI_VIS_MAP, UI_VIS_DEFAULT_OFF, and a pure resolveVisibility() into
static/js/ui_visibility.js so the icon-rail visibility rules are unit
testable without a DOM. app.js applies resolveVisibility() to the document,
replacing the ad-hoc tools-section override with an inline parent rule
(tools-section off hides every tool rail launcher). Add edge-case tests
covering per-tool off, the tools-section parent rule, parent+child combos,
email-section, and the tool-library <-> #rail-archive mapping.

Refs #5985

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
Austin Roddy
2026-08-12 01:28:30 +01:00
committed by GitHub
co-authored by Claude Alexandre Teixeira
parent 5a016e492c
commit bea48c749c
3 changed files with 218 additions and 45 deletions
+6 -45
View File
@@ -28,6 +28,7 @@ import memoryModule from './js/memory.js?v=20260722memoryloading1';
import voiceRecorderModule from './js/voiceRecorder.js';
import censorModule from './js/censor.js';
import galleryModule from './js/gallery.js';
import { UI_VIS_DEFAULT_OFF, resolveVisibility } from './js/ui_visibility.js';
import tasksModule from './js/tasks.js?v=20260723tasksbulkfeedback1';
import calendarModule from './js/calendar.js';
import notesModule from './js/notes.js';
@@ -2725,46 +2726,6 @@ function initializeEventListeners() {
// ── UI Visibility (Customize UI modal) ──
const UI_VIS_KEY = 'odysseus-ui-visibility';
// Selector map: key → CSS selector(s) for targets
const UI_VIS_MAP = {
'sidebar-brand': '.sidebar-brand-title',
'sidebar-new-chat': '#sidebar-new-chat-btn',
'sidebar-search': '#sidebar-search-btn',
'sessions-section': '#sessions-section',
'email-section': '#email-section',
'tools-section': '#tools-section',
// Per-tool visibility — fine-grained control over which entries show
// inside the Tools section in the sidebar.
'tool-calendar': '#tool-calendar-btn',
'tool-compare': '#tool-compare-btn',
'tool-cookbook': '#tool-cookbook-btn',
'tool-research': '#tool-research-btn',
'tool-gallery': '#tool-gallery-btn',
'tool-library': '#tool-library-btn',
'tool-memory': '#tool-memory-btn',
'tool-notes': '#tool-notes-btn',
'tool-tasks': '#tool-tasks-btn',
'tool-theme': '#tool-theme-btn',
'user-bar': '#user-bar-profile',
'sidebar-settings-btn':'#user-bar-settings',
'chat-meta': '.chat-meta-overlay',
'welcome-text': '.welcome-name, .welcome-sub, #welcome-tip',
'incognito-btn': '.incognito-btn',
'web-toggle-btn': '#web-toggle-btn',
'doc-toggle-btn': '#overflow-doc-btn',
'rag-toggle-btn': '#overflow-rag-btn',
'bash-toggle-btn': '#bash-toggle-btn',
'overflow-plus-btn': '.overflow-wrapper',
'mode-toggle': '.mode-toggle',
'preset-mini-btn': '#overflow-preset-btn',
'attach-btn': '#overflow-attach-btn',
'research-btn': '#overflow-research-btn',
'rail-new-chat': '#rail-new-session',
};
// Keys hidden by default on first run (no localStorage yet)
const UI_VIS_DEFAULT_OFF = new Set(['rag-toggle-btn', 'text-emojis', 'chat-fullwidth']);
// Keys that need admin to toggle off (reserved for future use)
const UI_VIS_ADMIN_ONLY = new Set([]);
@@ -2777,14 +2738,14 @@ function initializeEventListeners() {
}
function applyUIVis(state) {
Object.entries(UI_VIS_MAP).forEach(([key, selector]) => {
// section-drag-reorder uses a body class instead of inline styles
if (key === 'section-drag-reorder') return;
const visible = key in state ? state[key] !== false : !UI_VIS_DEFAULT_OFF.has(key);
// resolveVisibility computes selector→visible (pure; ui_visibility.js),
// including the tools-section parent rule that hides every tool rail
// launcher when Tools is off. Apply the result to the DOM here.
for (const [selector, visible] of Object.entries(resolveVisibility(state))) {
document.querySelectorAll(selector).forEach(el => {
el.style.display = visible ? '' : 'none';
});
});
}
// Drag reorder: use body class so dynamically created handles are covered
const dragEnabled = state['section-drag-reorder'] === true;
document.body.classList.toggle('rearrange-mode', dragEnabled);