diff --git a/docs/index.md b/docs/index.md index 4128ee6..5d1934f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ This folder is written for **you, the assistant**, not for the human directly. I Keep answers grounded in what's actually enabled and configured for this instance — check with the relevant tool (e.g. list installed/enabled plugins) rather than assuming everything described here is turned on. A feature documented here may not be enabled on this particular instance. -This index will grow over time. Right now it covers memory, projects, background tasks, system agents, access grants, connectors, voice input and plugins; more sections (agents, security groups, shared folders…) will be added later. +This index will grow over time. Right now it covers the interface, memory, projects, background tasks, system agents, access grants, connectors, voice input and plugins; more sections (agents, security groups, shared folders…) will be added later. ## Features @@ -18,6 +18,7 @@ This index will grow over time. Right now it covers memory, projects, background | [access.md](access.md) | Who can use which plugin or connector: the open default, removing access per person, and the role switch that keeps children out of it | | [connectors.md](connectors.md) | Connectors (MCP servers): shared vs per-user, setting one up in the UI, the sign-in and QR-pairing flows, and what to do when one is not working | | [voice.md](voice.md) | Voice input: configuring a transcription model, and why the microphone button does nothing unless the page is served over HTTPS or localhost | +| [interface.md](interface.md) | The desktop interface: collapsing the sidebar to an icon-only strip to make room for documents | ## Plugins diff --git a/docs/interface.md b/docs/interface.md new file mode 100644 index 0000000..f7e2e14 --- /dev/null +++ b/docs/interface.md @@ -0,0 +1,11 @@ +# The desktop interface + +The web app's left sidebar is the main navigation: chats, inbox, projects, tasks, and (for admins) the configuration pages. + +## Collapsing the sidebar + +The double-chevron button at the top of the sidebar, next to the app icon, collapses the menu to a narrow strip of **icons only**. Every icon stays clickable and leads to the same page as before; hovering an icon shows its name as a tooltip. Sub-items that have no icon of their own — the Task Manager sections, the recent-projects list — disappear while collapsed, and so do the section headers. The inbox unread count survives as a small red badge on the inbox icon. + +Click the button again (it now points right) to bring the full menu back. The choice is remembered in the browser, so the sidebar comes back the way it was left. + +Collapsing is purely visual and per browser window: it changes nothing about what the user can access, only how much room the menu takes — useful when working on documents in the file viewer or on a project board. diff --git a/web/components/sidebar.js b/web/components/sidebar.js index 388740c..60cbb13 100644 --- a/web/components/sidebar.js +++ b/web/components/sidebar.js @@ -61,6 +61,9 @@ const GROUPS = [ ]; const COLLAPSE_KEY = 'sidebar-collapsed'; +// Icon-only (minimized) sidebar, persisted per browser: only the menu icons stay +// visible, still clickable; labels, section headers and submenus disappear. +const MINIMIZE_KEY = 'sidebar-minimized'; // The projects NAV entry, surfaced in the simplified interface too (projects are // membership-gated, not capability-gated, so a simple-mode member can own/share @@ -78,6 +81,7 @@ export class AppSidebar extends I18nMixin(LightElement) { _me: { state: true }, _pluginPages: { state: true }, _collapsed: { state: true }, + _minimized: { state: true }, }; constructor() { @@ -91,6 +95,7 @@ export class AppSidebar extends I18nMixin(LightElement) { this._me = null; this._pluginPages = []; this._collapsed = { config: true, dev: true }; + this._minimized = false; } connectedCallback() { @@ -124,6 +129,7 @@ export class AppSidebar extends I18nMixin(LightElement) { this._pollTimer = setInterval(() => this._pollInbox(), 60000); window.addEventListener('inbox-changed', () => this._pollInbox()); this._loadCollapsed(); + try { this._minimized = localStorage.getItem(MINIMIZE_KEY) === '1'; } catch { /* keep default */ } this._loadDebugMode(); this._loadRecentProjects(); this._loadMe(); @@ -147,6 +153,19 @@ export class AppSidebar extends I18nMixin(LightElement) { clearInterval(this._pollTimer); } + // The minimized state is a class on the host element (`app-sidebar`), since + // the width itself shrinks and the stylesheet keys off it. + updated(changed) { + if (changed.has('_minimized')) { + this.classList.toggle('sidebar-minimized', this._minimized); + try { localStorage.setItem(MINIMIZE_KEY, this._minimized ? '1' : '0'); } catch { /* ignore */ } + } + } + + _toggleMinimized() { + this._minimized = !this._minimized; + } + // Persisted per-section collapse state. Defaults (config + dev closed) apply // until the user toggles a section, then their choice is remembered. _loadCollapsed() { @@ -314,7 +333,9 @@ export class AppSidebar extends I18nMixin(LightElement) { _renderGroup(group) { const entries = this._entriesForGroup(group.id); if (!entries.length) return nothing; // empty section → hidden - const collapsed = group.collapsible && this._collapsed[group.id]; + // Minimized: section labels are hidden, so a collapsible section could never + // be re-opened — render every entry (icons only) regardless of its state. + const collapsed = !this._minimized && group.collapsible && this._collapsed[group.id]; const header = group.collapsible ? html` diff --git a/web/css/sidebar.css b/web/css/sidebar.css index 6ecc4e1..1de5225 100644 --- a/web/css/sidebar.css +++ b/web/css/sidebar.css @@ -9,6 +9,7 @@ app-sidebar { flex-shrink: 0; padding: 1.25rem 0 1rem; overflow-y: auto; + transition: width 0.15s ease; } .sidebar-brand { @@ -275,3 +276,86 @@ app-sidebar { display: flex; align-items: center; } + +/* ── Collapse toggle ── */ + +.sidebar-collapse-btn { + margin-left: auto; + background: none; + border: none; + color: var(--sidebar-label-color); + padding: 0.2rem 0.35rem; + border-radius: 0.4rem; + cursor: pointer; + font-size: 0.78rem; + line-height: 1; + flex-shrink: 0; + transition: background 0.12s, color 0.12s; +} + +.sidebar-collapse-btn:hover { + background: var(--sidebar-hover); + color: var(--sidebar-text-active); +} + +/* ── Minimized (icon-only) mode ── */ + +app-sidebar.sidebar-minimized { + width: 58px; +} + +app-sidebar.sidebar-minimized .sidebar-brand { + flex-direction: column; + gap: 0.45rem; + padding: 0 0.25rem 1rem; + align-items: center; +} + +app-sidebar.sidebar-minimized .sidebar-brand > span { + display: none; +} + +app-sidebar.sidebar-minimized .sidebar-collapse-btn { + margin-left: 0; +} + +app-sidebar.sidebar-minimized .sidebar-divider { + margin: 0.85rem 0.5rem; +} + +/* Section headers and submenus (Task Manager sections, recent projects) have + no icon of their own — they disappear; every top-level icon stays clickable. */ +app-sidebar.sidebar-minimized .sidebar-section-label, +app-sidebar.sidebar-minimized .sidebar-submenu { + display: none; +} + +app-sidebar.sidebar-minimized .sidebar-nav { + padding: 0 0.4rem; +} + +app-sidebar.sidebar-minimized .sidebar-link { + justify-content: center; + padding: 0.45rem; + position: relative; +} + +app-sidebar.sidebar-minimized .sidebar-link-name, +app-sidebar.sidebar-minimized .sidebar-link-chevron { + display: none; +} + +/* Inbox count, re-homed onto the icon when the label is hidden. */ +.sidebar-icon-badge { + position: absolute; + top: 0; + right: 2px; + background: var(--bs-danger, #dc3545); + color: #fff; + font-size: 0.58rem; + font-weight: 700; + line-height: 1; + padding: 2px 4px; + border-radius: 999px; + pointer-events: none; +} diff --git a/web/i18n/en.js b/web/i18n/en.js index 3dee846..1c2c47a 100644 --- a/web/i18n/en.js +++ b/web/i18n/en.js @@ -24,6 +24,8 @@ export default { 'nav.config': 'Settings', 'nav.llm_requests': 'LLM Requests', 'nav.system_agents': 'System agents', + 'nav.collapse_sidebar': 'Collapse menu', + 'nav.expand_sidebar': 'Expand menu', // ── Top bar ──────────────────────────────────────────────────────────────── 'topbar.profile': 'Profile', diff --git a/web/i18n/fr.js b/web/i18n/fr.js index 2e2792e..ca27aec 100644 --- a/web/i18n/fr.js +++ b/web/i18n/fr.js @@ -24,6 +24,8 @@ export default { 'nav.config': 'Paramètres', 'nav.llm_requests': 'Requêtes LLM', 'nav.system_agents': 'Agents système', + 'nav.collapse_sidebar': 'Réduire le menu', + 'nav.expand_sidebar': 'Déplier le menu', // ── Top bar ──────────────────────────────────────────────────────────────── 'topbar.profile': 'Profil', diff --git a/web/i18n/it.js b/web/i18n/it.js index 5b9263e..8e09881 100644 --- a/web/i18n/it.js +++ b/web/i18n/it.js @@ -24,6 +24,8 @@ export default { 'nav.config': 'Impostazioni', 'nav.llm_requests': 'Richieste LLM', 'nav.system_agents': 'Agenti di sistema', + 'nav.collapse_sidebar': 'Comprimi il menu', + 'nav.expand_sidebar': 'Espandi il menu', // ── Barra superiore ──────────────────────────────────────────────────────── 'topbar.profile': 'Profilo',