From c0fbc4e63fb7165f234759a8fda34313c86eda98 Mon Sep 17 00:00:00 2001 From: bifrost0x Date: Thu, 13 Aug 2026 11:53:30 +0200 Subject: [PATCH] Polish Quick Connect field hierarchy --- static/css/style.css | 67 ++++++++++++++++++++++-- static/js/i18n.js | 4 +- templates/index.html | 34 ++++++------ tests/e2e/quick-connect-redesign.spec.js | 41 +++++++++++++++ tests/test_key_management_ui.py | 4 +- tests/test_profile_launcher_ui.py | 6 +-- 6 files changed, 130 insertions(+), 26 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index 44561a5..4d96558 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -4826,6 +4826,42 @@ body.keyboard-open.notepad-focused .notepad-panel { margin-bottom: 20px; } +.quick-connect-details-card { + padding: 0; + overflow: clip; +} + +.quick-connect-details-card .quick-connect-card-header { + margin-bottom: 0; + padding: 18px 20px; +} + +.quick-connect-details-content { + padding: 18px 20px 20px; + border-top: 1px solid var(--border-color); +} + +.quick-connect-auth-method { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(180px, 44%); + align-items: center; + gap: 18px; + margin-bottom: 24px; +} + +.quick-connect-auth-method label { + margin-bottom: 0; +} + +.quick-connect-core-fields { + display: grid; + gap: 14px; +} + +.quick-connect-core-fields :is(.form-row, .form-group, .connection-profile-context) { + margin-bottom: 0; +} + .quick-connect-card-header.compact { margin-bottom: 14px; } @@ -4861,10 +4897,6 @@ body.keyboard-open.notepad-focused .notepad-panel { line-height: 1.45; } -.quick-connect-details-card > .form-group:last-child { - margin-bottom: 0; -} - .connection-profile-context { display: grid; grid-template-columns: auto minmax(0, 1fr); @@ -4998,6 +5030,18 @@ body.keyboard-open.notepad-focused .notepad-panel { margin-bottom: 0; } +.connection-advanced-content .post-connect-label { + color: var(--text-primary); + font-size: 13px; + letter-spacing: 0.3px; +} + +.connection-advanced-content .post-connect-preview-header { + color: var(--text-secondary); + font-size: 12px; + font-weight: 500; +} + @media (max-width: 900px) { .quick-connect-grid { grid-template-columns: minmax(0, 1fr); @@ -5018,6 +5062,21 @@ body.keyboard-open.notepad-focused .notepad-panel { padding: 16px; } + .quick-connect-details-card { + padding: 0; + } + + .quick-connect-details-card .quick-connect-card-header, + .quick-connect-details-content { + padding: 16px; + } + + .quick-connect-auth-method { + grid-template-columns: minmax(0, 1fr); + gap: 8px; + margin-bottom: 20px; + } + :is(.connection-advanced-settings, .recent-connections-card) { padding: 0; } diff --git a/static/js/i18n.js b/static/js/i18n.js index 2a88c75..2ba6b29 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -36,7 +36,7 @@ const translations = { 'connection.recentConnections': 'Recent Connections', 'connection.recentConnectionsHint': 'Stored only in this browser for your account.', 'connection.noRecentConnections': 'Your recent connections will appear here.', - 'connection.details': 'Connection details', + 'connection.details': 'Connection Details', 'connection.detailsHint': 'Enter the destination and choose how to authenticate.', 'connection.savedContext': 'Saved connection', 'connection.reviewBeforeConnect': 'Review the settings and provide any required credentials.', @@ -67,7 +67,7 @@ const translations = { 'connection.jumpHostHint': 'Manage jump hosts in the account menu.', 'connection.jumpHostPassword': 'Jump Host Password', 'connection.jumpHostPasswordHint': 'This bastion uses password auth — enter its password (never stored).', - 'connection.advancedSettings': 'Advanced settings', + 'connection.advancedSettings': 'Advanced Settings', 'connection.advancedSettingsHint': 'Jump host, post-connect actions, and persistence.', 'connection.commandSet': 'Commands after connecting (optional)', 'connection.commandSetHint': 'Runs on the remote host after a successful connection, not in WebSSH. Not run again when reconnecting to an existing tmux session.', diff --git a/templates/index.html b/templates/index.html index 0563996..b666be8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,7 +17,7 @@ - + @@ -419,11 +419,24 @@

Quick Conn
-

Connection details

+

Connection Details

Enter the destination and choose how to authenticate.

+
+
+ + +
+ +
@@ -509,7 +513,7 @@

Connection detail - Advanced settings + Advanced Settings Jump host, post-connect actions, and persistence. @@ -1225,7 +1229,7 @@

File Preview

- + diff --git a/tests/e2e/quick-connect-redesign.spec.js b/tests/e2e/quick-connect-redesign.spec.js index fe6de40..34f2fd7 100644 --- a/tests/e2e/quick-connect-redesign.spec.js +++ b/tests/e2e/quick-connect-redesign.spec.js @@ -85,6 +85,47 @@ test('presents a focused two-column quick connect without a saved-profile picker expect(geometry.detailsRight).toBeLessThanOrEqual(geometry.recentLeft); }); +test('uses the requested connection details and advanced settings hierarchy', async ({ page }) => { + await page.locator('#newConnectionBtn').click(); + + const hierarchy = await page.locator('#connectionModal').evaluate(modal => { + const detailsContent = modal.querySelector('.quick-connect-details-content'); + const authRow = modal.querySelector('.quick-connect-auth-method'); + const authLabel = authRow?.querySelector('label'); + const authSelect = authRow?.querySelector('select'); + const host = modal.querySelector('#hostInput'); + const jumpHostLabel = modal.querySelector('label[for="jumpHostSelect"]'); + const runAfterLabel = modal.querySelector('.post-connect-label'); + const previewLabel = modal.querySelector('.post-connect-preview-header'); + const style = element => element ? getComputedStyle(element) : null; + const rect = element => element ? element.getBoundingClientRect() : null; + + return { + detailsTitle: modal.querySelector('#connectionDetailsTitle')?.textContent.trim(), + advancedTitle: modal.querySelector('#connectionAdvancedSettings summary strong')?.textContent.trim(), + detailsBorder: style(detailsContent)?.borderTopWidth, + authLabelCenter: rect(authLabel) && (rect(authLabel).top + rect(authLabel).bottom) / 2, + authSelectCenter: rect(authSelect) && (rect(authSelect).top + rect(authSelect).bottom) / 2, + authBottom: rect(authRow)?.bottom, + hostTop: rect(host)?.top, + jumpHostFontSize: style(jumpHostLabel)?.fontSize, + runAfterFontSize: style(runAfterLabel)?.fontSize, + runAfterWeight: Number(style(runAfterLabel)?.fontWeight), + previewFontSize: style(previewLabel)?.fontSize, + previewWeight: Number(style(previewLabel)?.fontWeight), + }; + }); + + expect(hierarchy.detailsTitle).toBe('Connection Details'); + expect(hierarchy.advancedTitle).toBe('Advanced Settings'); + expect(hierarchy.detailsBorder).toBe('1px'); + expect(Math.abs(hierarchy.authLabelCenter - hierarchy.authSelectCenter)).toBeLessThan(2); + expect(hierarchy.authBottom).toBeLessThan(hierarchy.hostTop); + expect(hierarchy.runAfterFontSize).toBe(hierarchy.jumpHostFontSize); + expect(parseFloat(hierarchy.previewFontSize)).toBeLessThan(parseFloat(hierarchy.runAfterFontSize)); + expect(hierarchy.previewWeight).toBeLessThan(hierarchy.runAfterWeight); +}); + test('keeps modal actions fixed while expanded content scrolls inside', async ({ page }) => { await page.setViewportSize({ width: 1100, height: 700 }); await page.evaluate(() => { diff --git a/tests/test_key_management_ui.py b/tests/test_key_management_ui.py index e90e1ae..5fe2d84 100644 --- a/tests/test_key_management_ui.py +++ b/tests/test_key_management_ui.py @@ -102,9 +102,9 @@ def test_key_replacement_event_updates_ui_and_asset_version(): assert "socket.on('key_replaced'" in APP assert 'ProfileManager.upsertKeySummary(data.key)' in APP assert "filename='js/profile-manager.js') }}?v=11" in TEMPLATE - assert "filename='js/i18n.js') }}?v=10" in TEMPLATE + assert "filename='js/i18n.js') }}?v=11" in TEMPLATE assert "filename='js/app.js') }}?v=12" in TEMPLATE - assert "filename='css/style.css') }}?v=14" in TEMPLATE + assert "filename='css/style.css') }}?v=15" in TEMPLATE def test_socket_events_refresh_key_ui_without_resetting_profile_editor(): diff --git a/tests/test_profile_launcher_ui.py b/tests/test_profile_launcher_ui.py index f841de1..ccaa16e 100644 --- a/tests/test_profile_launcher_ui.py +++ b/tests/test_profile_launcher_ui.py @@ -24,8 +24,8 @@ def test_template_has_one_empty_pane_renderer_and_loads_launcher_utility_first() def test_merged_profile_frontend_assets_have_distinct_cache_versions(): template = read('templates/index.html') expected_versions = { - "filename='css/style.css'": '?v=14', - "filename='js/i18n.js'": '?v=10', + "filename='css/style.css'": '?v=15', + "filename='js/i18n.js'": '?v=11', "filename='js/command-workspace.js'": '?v=2', "filename='js/command-palette-utils.js'": '?v=1', "filename='js/profile-launcher-utils.js'": '?v=5', @@ -131,7 +131,7 @@ def test_mobile_launcher_stacks_status_below_profile_details(): def test_profile_launcher_stylesheet_uses_current_cache_version(): template = read('templates/index.html') - assert "filename='css/style.css') }}?v=14" in template + assert "filename='css/style.css') }}?v=15" in template def test_active_session_command_launcher_is_loaded_after_command_data_managers():