diff --git a/frontend/src/css/sidebar.css b/frontend/src/css/sidebar.css index 89e1436..7bff9c5 100644 --- a/frontend/src/css/sidebar.css +++ b/frontend/src/css/sidebar.css @@ -84,11 +84,19 @@ border: none; } +.sidebar-section-header.sidebar-section-header-group .section-title { + color: var(--sidebar-group-title); +} + .sidebar-section-header:hover { color: var(--text-secondary); background: var(--bg-hover); } +.sidebar-section-header.sidebar-section-header-group:hover .section-title { + color: var(--sidebar-group-title); +} + .sidebar-section-header .section-icon { width: 14px; text-align: center; @@ -174,18 +182,47 @@ background: var(--accent); } -/* Protocol colour indicator dot */ +/* Protocol indicator icon */ .sidebar-item .protocol-indicator { - width: 8px; - height: 8px; - border-radius: 50%; + width: 14px; + height: 14px; + border-radius: var(--radius-sm); flex-shrink: 0; - background: var(--text-tertiary); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: var(--weight-semibold); + line-height: 1; + background: var(--bg-overlay); + color: var(--text-tertiary); +} + +.sidebar-item .protocol-indicator::before { + content: '?'; +} + +.sidebar-item .protocol-indicator.proto-ssh::before { content: '⌨'; } +.sidebar-item .protocol-indicator.proto-telnet::before { content: '⇄'; } +.sidebar-item .protocol-indicator.proto-raw::before { content: '≋'; } +.sidebar-item .protocol-indicator.proto-github::before { content: 'GH'; } + +.sidebar-item .protocol-indicator.proto-ssh { + background: rgba(78, 205, 196, 0.18); + color: var(--protocol-ssh); +} +.sidebar-item .protocol-indicator.proto-telnet { + background: rgba(255, 167, 38, 0.18); + color: var(--protocol-telnet); +} +.sidebar-item .protocol-indicator.proto-raw { + background: rgba(171, 71, 188, 0.18); + color: var(--protocol-raw); +} +.sidebar-item .protocol-indicator.proto-github { + background: rgba(124, 138, 255, 0.18); + color: var(--protocol-github); } -.sidebar-item .protocol-indicator.proto-ssh { background: var(--protocol-ssh); } -.sidebar-item .protocol-indicator.proto-telnet { background: var(--protocol-telnet); } -.sidebar-item .protocol-indicator.proto-raw { background: var(--protocol-raw); } -.sidebar-item .protocol-indicator.proto-github { background: var(--protocol-github); } /* Text block (name + host) */ .sidebar-item-text { diff --git a/frontend/src/css/tokens.css b/frontend/src/css/tokens.css index 1085982..273d6fd 100644 --- a/frontend/src/css/tokens.css +++ b/frontend/src/css/tokens.css @@ -32,6 +32,7 @@ --text-tertiary: #707070; --text-disabled: #4a4a4a; --text-inverse: #1c1c1c; + --sidebar-group-title: #b89d5f; /* ----------------------------------------------------------------------- Accent colours — teal / cyan diff --git a/frontend/src/js/app.js b/frontend/src/js/app.js index 631681a..482ab78 100644 --- a/frontend/src/js/app.js +++ b/frontend/src/js/app.js @@ -684,10 +684,19 @@ class App { _applyPreferences() { const prefs = this.state.preferences; - // Sidebar width + // Sidebar width (drives the grid column width). + // Use the CSS variable so the workspace never sits underneath the sidebar. + const root = document.documentElement; const sidebar = document.getElementById('sidebar'); - if (sidebar && prefs.sidebarWidth) { - sidebar.style.width = `${prefs.sidebarWidth}px`; + const parsedSidebarWidth = Number(prefs.sidebarWidth); + if (Number.isFinite(parsedSidebarWidth) && parsedSidebarWidth > 0) { + const clampedWidth = Math.max(180, Math.min(500, Math.round(parsedSidebarWidth))); + root.style.setProperty('--sidebar-width', `${clampedWidth}px`); + } else { + root.style.removeProperty('--sidebar-width'); + } + if (sidebar) { + sidebar.style.width = ''; } // Status bar visibility @@ -695,6 +704,12 @@ class App { if (statusBar) { statusBar.style.display = prefs.showStatusBar === false ? 'none' : 'flex'; } + + // Refit any visible terminal after geometry changes. + requestAnimationFrame(() => { + this.terminals.fitActive(); + this.splitPanes.refitAll(); + }); } /** diff --git a/frontend/src/js/settings.js b/frontend/src/js/settings.js index 599af14..4bd647f 100644 --- a/frontend/src/js/settings.js +++ b/frontend/src/js/settings.js @@ -79,6 +79,9 @@ class Settings { if (this.app.terminals && this._prefs.terminalTheme) { this.app.terminals.applyTheme(this._prefs.terminalTheme); } + if (this.app && typeof this.app._applyPreferences === 'function') { + this.app._applyPreferences(); + } this.app.notifications.success('Settings saved'); this.close(); diff --git a/frontend/src/js/sidebar.js b/frontend/src/js/sidebar.js index 1032d82..40a4f0e 100644 --- a/frontend/src/js/sidebar.js +++ b/frontend/src/js/sidebar.js @@ -138,12 +138,16 @@ class Sidebar { _renderSection(title, key, profiles) { const section = createElement('div', 'sidebar-section'); const isCollapsed = this.collapsedSections.has(key); + const isGroupSection = key.startsWith('group:'); // Section header - const header = createElement('div', 'sidebar-section-header'); + const header = createElement( + 'div', + `sidebar-section-header ${isGroupSection ? 'sidebar-section-header-group' : ''}`.trim() + ); header.innerHTML = ` ${isCollapsed ? '▸' : '▾'} - ${escapeHtml(title)} + ${escapeHtml(title)} ${profiles.length} `; // Prevent mousedown from starting a text selection gesture in WKWebView, @@ -182,20 +186,24 @@ class Sidebar { item.classList.add('sidebar-item-active'); } - // Protocol colour dot - const dot = createElement('span', `protocol-indicator ${protocolColour(profile.protocol)}`); - item.appendChild(dot); + // Protocol icon + const protocolLabel = ""; // Seemed pretty naff to have this as well as the little icon badboy this._protocolLabel(profile.protocol); + const indicator = createElement('span', `protocol-indicator ${protocolColour(profile.protocol)}`, { + title: protocolLabel, + 'aria-label': protocolLabel, + }); + item.appendChild(indicator); // Text content const textWrapper = createElement('div', 'sidebar-item-text'); + const displayName = (profile.displayName || '').trim(); + const primaryLabel = displayName || profile.host || 'Untitled'; const nameEl = createElement('span', 'sidebar-item-name', { - textContent: profile.displayName || profile.host || 'Untitled', + textContent: primaryLabel, }); textWrapper.appendChild(nameEl); - const subtitle = profile.host - ? `${profile.host}:${profile.port}` - : profile.protocol.toUpperCase(); + const subtitle = protocolLabel; const subEl = createElement('span', 'sidebar-item-host', { textContent: truncate(subtitle, 30), }); @@ -424,6 +432,27 @@ class Sidebar { .sort((a, b) => b.score - a.score) .map((r) => r.profile); } + + /** + * Returns a display label for a protocol. + * @param {string} protocol + * @returns {string} + * @private + */ + _protocolLabel(protocol) { + switch (protocol) { + case 'github': + return 'GitHub'; + case 'ssh': + return 'SSH'; + case 'telnet': + return 'TELNET'; + case 'raw': + return 'RAW'; + default: + return (protocol || '').toUpperCase() || 'UNKNOWN'; + } + } } window.NexusTerminal = window.NexusTerminal || {};