From 78fe7ee76f9838a220bf948dab48f9af860f0741 Mon Sep 17 00:00:00 2001
From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Date: Fri, 7 Aug 2026 17:59:00 +0200
Subject: [PATCH] fix(ui): use listbox for domain redirects and key terminal
canvases
Replace the service domain www-redirect native select with the shared
listbox (htmlId for stable trigger IDs) and allow dropdown overflow.
Key terminal target/session canvases for Livewire DOM branches, and
enable terminal browser debug via ?terminal-debug= plus theme logs.
---
resources/js/terminal.js | 41 ++++++++++++++++++-
.../views/components/forms/listbox.blade.php | 9 +++-
.../project/service/domains.blade.php | 23 ++++-------
.../views/livewire/terminal/index.blade.php | 4 +-
.../Feature/RealtimeTerminalPackagingTest.php | 5 ++-
tests/Feature/ServiceDomainsTest.php | 8 ++--
tests/Feature/TerminalPageHeaderTest.php | 10 ++++-
tests/v4/Browser/ServiceConfigurationTest.php | 5 ++-
8 files changed, 77 insertions(+), 28 deletions(-)
diff --git a/resources/js/terminal.js b/resources/js/terminal.js
index 25867e500..bdcc3824d 100644
--- a/resources/js/terminal.js
+++ b/resources/js/terminal.js
@@ -8,7 +8,14 @@ import {
} from './terminal-session-timer.js';
import { FitAddon } from '@xterm/addon-fit';
-const terminalDebugEnabled = import.meta.env.DEV;
+const terminalDebugParameter = new URLSearchParams(window.location.search).get('terminal-debug');
+
+if (terminalDebugParameter === '1' || terminalDebugParameter === '0') {
+ localStorage.setItem('coolify-terminal-debug', terminalDebugParameter);
+}
+
+const terminalDebugEnabled = import.meta.env.DEV
+ || localStorage.getItem('coolify-terminal-debug') === '1';
const baseApplicationTerminalTheme = {
black: '#675f70',
@@ -393,9 +400,15 @@ export function initializeTerminalComponent() {
setTerminalTheme(themeName) {
if (!applicationTerminalThemes[themeName]) {
+ logTerminal('warn', '[Terminal Theme] Unknown theme', {
+ requestedTheme: themeName,
+ availableThemes: Object.keys(applicationTerminalThemes),
+ });
return;
}
+ logTerminal('log', '[Terminal Theme] Applying theme', this.terminalThemeDebugSnapshot(themeName));
+
if (themeName === 'system') {
applicationTerminalThemes.system = createSystemTerminalTheme();
}
@@ -417,10 +430,36 @@ export function initializeTerminalComponent() {
this.term.options.cursorBlink = cursorBlink;
this.term.refresh(0, Math.max(0, this.term.rows - 1));
this.term.focus();
+ logTerminal('log', '[Terminal Theme] Theme applied', this.terminalThemeDebugSnapshot(themeName));
});
}
},
+ terminalThemeDebugSnapshot(themeName) {
+ const shell = this.$el.closest('.application-console-shell');
+ const viewport = this.term?.element?.querySelector('.xterm-viewport');
+ const screen = this.term?.element?.querySelector('.xterm-screen');
+
+ return {
+ requestedTheme: themeName,
+ selectedTheme: this.selectedTheme,
+ terminalExists: Boolean(this.term),
+ terminalOpened: Boolean(this.term?.element),
+ terminalActive: this.terminalActive,
+ connectionState: this.connectionState,
+ shellTheme: shell?.dataset.consoleTheme,
+ shellBackground: shell ? getComputedStyle(shell).background : null,
+ shellThemeBackground: shell ? getComputedStyle(shell).getPropertyValue('--console-theme-background') : null,
+ shellThemeOpacity: shell ? getComputedStyle(shell).getPropertyValue('--console-theme-opacity') : null,
+ shellPseudoBackground: shell ? getComputedStyle(shell, '::before').background : null,
+ viewportBackground: viewport ? getComputedStyle(viewport).background : null,
+ screenBackground: screen ? getComputedStyle(screen).background : null,
+ xtermBackground: this.term?.options.theme?.background,
+ xtermForeground: this.term?.options.theme?.foreground,
+ xtermCursor: this.term?.options.theme?.cursor,
+ };
+ },
+
resetTerminal() {
if (this.term) {
this.$wire.dispatch('error', 'Terminal websocket connection lost. Reconnecting...');
diff --git a/resources/views/components/forms/listbox.blade.php b/resources/views/components/forms/listbox.blade.php
index 050bea8b3..0dd64ccf2 100644
--- a/resources/views/components/forms/listbox.blade.php
+++ b/resources/views/components/forms/listbox.blade.php
@@ -1,5 +1,6 @@
@props([
'id' => null,
+ 'htmlId' => null,
'label' => null,
'helper' => null,
'required' => false,
@@ -14,6 +15,10 @@
'tooltip' => true,
])
+@php
+ $triggerId = ($htmlId ?? $id).'-trigger';
+@endphp
+
@if ($label)
{{--
@@ -23,7 +28,7 @@
--}}
{{-- Fixed h-4 matches the helper icon so side-by-side fields align with or without a helper. --}}
-
@else
+ class="application-settings-section-body is-flush mt-1 w-full scroll-mt-28 overflow-visible">
@foreach ($domainGroups as $appId => $rows)
@php
$app = collect($serviceApps)->firstWhere('id', (int) $appId);
@@ -190,21 +190,14 @@
{{ $heading }}
@can('update', $service)
-
- {{ $redirectLabel }}
-
-
-
+
@else
{{ $redirectLabel }}
diff --git a/resources/views/livewire/terminal/index.blade.php b/resources/views/livewire/terminal/index.blade.php
index 123c3af43..6939049f9 100644
--- a/resources/views/livewire/terminal/index.blade.php
+++ b/resources/views/livewire/terminal/index.blade.php
@@ -102,7 +102,7 @@
}
}">
@if ($selected_uuid === 'default')
-
@@ -161,7 +161,7 @@
@else
-
diff --git a/tests/Feature/RealtimeTerminalPackagingTest.php b/tests/Feature/RealtimeTerminalPackagingTest.php
index 5754ddc52..2295531cc 100644
--- a/tests/Feature/RealtimeTerminalPackagingTest.php
+++ b/tests/Feature/RealtimeTerminalPackagingTest.php
@@ -183,11 +183,12 @@ it('mounts the realtime terminal utilities in local development compose files',
'maxio dev compose' => 'docker-compose-maxio.dev.yml',
]);
-it('keeps terminal browser logging restricted to Vite development mode', function () {
+it('keeps terminal browser logging restricted to development or explicit diagnostics', function () {
$terminalClient = file_get_contents(base_path('resources/js/terminal.js'));
expect($terminalClient)
- ->toContain('const terminalDebugEnabled = import.meta.env.DEV;')
+ ->toContain('const terminalDebugEnabled = import.meta.env.DEV')
+ ->toContain("localStorage.getItem('coolify-terminal-debug') === '1'")
->toContain("logTerminal('log', '[Terminal] WebSocket connection established.');")
->not->toContain("console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.');");
});
diff --git a/tests/Feature/ServiceDomainsTest.php b/tests/Feature/ServiceDomainsTest.php
index 11fdc0993..b9cb8cd70 100644
--- a/tests/Feature/ServiceDomainsTest.php
+++ b/tests/Feature/ServiceDomainsTest.php
@@ -104,9 +104,11 @@ it('groups configured domains with their service redirect and excludes services
expect($html)
->toContain("service-domain-group-{$this->apiApp->id}")
- ->toContain("service-domain-redirect-{$this->apiApp->id}")
- ->toContain("wire:model.change=\"serviceRedirects.{$this->apiApp->id}\"")
- ->toContain("wire:target=\"serviceRedirects.{$this->apiApp->id}\"")
+ ->toContain("id=\"service-domain-redirect-{$this->apiApp->id}-trigger\"")
+ ->toContain("serviceRedirects.{$this->apiApp->id}")
+ ->toContain('class="listbox-trigger"')
+ ->toContain('application-settings-section-body is-flush mt-1 w-full scroll-mt-28 overflow-visible')
+ ->not->toContain("