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 || {};