This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 97898852ea Issue #8486 : Harmonize the bottom panel and add an option 
to disable the embedded terminal (#8575)
97898852ea is described below

commit 97898852ea74a1edec00b77522729335fc180bae
Author: Matt Casters <[email protected]>
AuthorDate: Fri Sep 25 08:09:47 2026 +0200

    Issue #8486 : Harmonize the bottom panel and add an option to disable the 
embedded terminal (#8575)
    
    * Issue #8486 : Harmonize the bottom panel and add an option to disable the 
embedded terminal
    
    The bottom-left terminal button is now a panel button, with sidebar buttons 
for the terminal, search, the database workbench, and the VFS file explorer. An 
open tool stays highlighted while the panel is visible. Enable embedded 
terminal on the General tab turns the built-in terminal off without removing 
the other tools.
    
    * Issue #8486 : Open bottom-panel tools from the + tab
    
    The terminal, search, database, and VFS buttons toggled the panel instead 
of adding a tab. Those buttons are gone. The + tab on the panel opens a menu 
for a new terminal, search, database, or VFS file explorer tab.
    
    * Issue #8486 : Show the bottom panel and its tabs in the user manual
---
 .../assets/images/hop-gui/bottom-panel-empty.png   | Bin 0 -> 19428 bytes
 .../assets/images/hop-gui/bottom-panel-tabs.png    | Bin 0 -> 90347 bytes
 .../ROOT/pages/hop-gui/disable-ui-elements.adoc    |   6 +-
 .../modules/ROOT/pages/hop-gui/index.adoc          |  26 +
 .../ROOT/pages/hop-gui/perspective-ai-advisor.adoc |   2 +-
 .../pages/hop-gui/perspective-configuration.adoc   |   4 +
 .../ROOT/pages/hop-gui/perspective-database.adoc   |   1 +
 .../ROOT/pages/hop-gui/perspective-search.adoc     |   1 +
 .../ROOT/pages/hop-gui/vfs-file-explorer.adoc      |   2 +-
 .../main/java/org/apache/hop/ui/core/PropsUi.java  |  16 +
 .../main/java/org/apache/hop/ui/hopgui/HopGui.java |  71 ++-
 .../ui/hopgui/SidebarToolbarItemDescriptor.java    |   2 +-
 .../tabs/ConfigGeneralOptionsTab.java              |  41 ++
 .../ui/hopgui/search/ReferenceSearchResults.java   |   1 +
 .../ui/hopgui/search/SearchEverywhereDialog.java   |   1 +
 .../hop/ui/hopgui/terminal/HopGuiBottomDock.java   | 629 ++++++++++++++++-----
 .../hopgui/vfs/explorer/VfsFileExplorerViews.java  |  43 +-
 .../core/dialog/messages/messages_en_US.properties |   3 +
 .../ui/hopgui/messages/messages_en_US.properties   |   1 +
 .../terminal/messages/messages_en_US.properties    |   6 +-
 .../ui/hopgui/terminal/HopGuiBottomDockTest.java   |   9 +
 .../vfs/explorer/VfsFileExplorerViewsTest.java     |  33 ++
 22 files changed, 727 insertions(+), 171 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-empty.png
 
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-empty.png
new file mode 100644
index 0000000000..775b8f5718
Binary files /dev/null and 
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-empty.png
 differ
diff --git 
a/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-tabs.png 
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-tabs.png
new file mode 100644
index 0000000000..807dcf4992
Binary files /dev/null and 
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/bottom-panel-tabs.png 
differ
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/disable-ui-elements.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/disable-ui-elements.adoc
index 077e19f2e1..13e3902220 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/disable-ui-elements.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/disable-ui-elements.adoc
@@ -78,6 +78,10 @@ For each element that needs to be disabled, you can add an 
extra `<exclusion>` l
 * `@GuiKeyboardShortcut` – use the shortcut's `className#methodName`
 * `@HopMetadata` – use the metadata plugin id (from `key` or `name` in the 
annotation)
 * `@ExtensionPoint` – use the extension point plugin's `id`
-* `@ConfigPlugin` – use the config plugin's `id` 
+* `@ConfigPlugin` – use the config plugin's `id`
+
+Excluding `40010-menu-tools-terminal` forces the embedded terminal off for 
every user of that installation.
+The bottom panel, search, database workbench, and VFS file explorer stay 
available.
+Users who only want to turn their own terminal off can clear *Enable embedded 
terminal* on the General tab of the 
xref:hop-gui/perspective-configuration.adoc[Configuration perspective]. 
 
 
diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/index.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/index.adoc
index ea41c15584..a37b6ddc89 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/index.adoc
@@ -17,6 +17,7 @@ under the License.
 [[HopGui]]
 
 :description: Hop Gui is Hop's visual development environment where you will 
mainly design, run and debug workflows and pipelines, but Hop Gui has a lot 
more to offer: you can configure all of your metadata items like database 
connections, run configurations etc. You can search, manage files, version 
control your work and more.
+:imagesdir: ../assets/images
 
 
 = Hop Gui
@@ -40,3 +41,28 @@ Covered here are:
 * xref:hop-gui/shortcuts.adoc[Keyboard Shortcuts]: a list of the keyboard 
shortcuts that are available in Hop Gui.
 * xref:hop-gui/hop-web.adoc[Hop Web]: a web version of Hop Gui that provides 
the exact same functionality and user experience as the desktop version.
 For Docker volume mounts and upgrades, see xref:hop-web-docker.adoc[Hop Web in 
Docker].
+
+== Bottom panel
+
+The button at the bottom left of the window shows or hides the bottom panel.
+With the panel open and nothing in it, the tab bar shows only *+*.
+
+image::hop-gui/bottom-panel-empty.png[The bottom panel open and empty, with 
the + tab.,width="45%"]
+
+Click *+* and choose a tool: Terminal, Search, Database, or VFS File Explorer.
+Terminal and search add another tab each time.
+Database and the xref:hop-gui/vfs-file-explorer.adoc[VFS file explorer] open 
their tab, or focus it when it is already there.
+
+image::hop-gui/bottom-panel-tabs.png[Database, VFS File Explorer, and Search 
open together in the bottom panel.,width="90%"]
+
+The execution-results button stays above the panel button and is only there in 
the file explorer, on a pipeline or workflow.
+
+`CTRL-J` shows the terminal, and hides the panel when a terminal tab is 
already selected.
+`CTRL-SHIFT-J` opens another terminal.
+`CTRL-F` still opens Search Everywhere.
+*Show all results* opens a search tab in the bottom panel, and *+* → Search 
does the same for an empty query.
+
+In the xref:hop-gui/perspective-configuration.adoc[Configuration perspective], 
on the General tab, *Enable embedded terminal* turns the built-in terminal off 
on desktops where it misbehaves.
+Search, the database workbench, and the VFS file explorer stay available.
+Hop Web has no embedded terminal.
+An administrator can also force the terminal off by excluding 
`40010-menu-tools-terminal` in 
xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml].
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-ai-advisor.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-ai-advisor.adoc
index f40180bd83..c14077f3c0 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-ai-advisor.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-ai-advisor.adoc
@@ -226,7 +226,7 @@ The same workbench can run outside the perspective, like 
the Database perspectiv
 
 * *AI Help* on a pipeline or workflow toolbar (and canvas *Basic → AI Help*) 
opens that floating window so the graph stays visible.
 * *Float* on the session toolbar (or **Tools → AI Assistant window**) opens a 
non-modal window you can put beside the canvas and the execution log.
-* *Dock* opens it as a tab in the bottom dock.
+* *Dock* opens it as a tab in the bottom dock, in the same panel as the 
terminal, search, database, and VFS file explorer.
 
 image::hop-gui/ai-advisor/ai-advisor-floating-window.jpg[AI Assistant floating 
over a failed workflow and its execution log,width="90%"]
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc
index 13b3ee8da5..85d04e14a2 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-configuration.adoc
@@ -60,6 +60,10 @@ INFO: this setting complements the preview option that is 
used in the xref:pipel
 
 |Open last file at startup|reopen the last used files and select the last used 
file when Hop GUI starts|yes
 
+|Enable embedded terminal
+a|Show the embedded terminal in the bottom panel. Clear this on desktops where 
that terminal misbehaves. Search, the database workbench, and the VFS file 
explorer stay available. Hop Web has no embedded terminal. Excluding 
`40010-menu-tools-terminal` in 
xref:hop-gui/disable-ui-elements.adoc[disabledGuiElements.xml] forces it off.
+|yes
+
 |Autosave changed files|Automatically save changed workflow and pipelines 
files|yes
 
 |Automatically split hops
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
index 00ecbcc45f..e5d1757609 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
@@ -84,6 +84,7 @@ The same workbench can run outside the perspective, like 
Search:
 
 * *Float* on the connection toolbar (or **Tools → Database window**) opens a 
non-modal window you can put on another monitor while you stay in Data 
orchestration.
 * *Dock* opens it as a tab in the bottom dock (next to the terminal and search 
results).
+*+* on that panel's tab bar does the same.
 
 image::hop-gui/database/database-perspective-popup-dialog.png[The floating 
Database window,width="90%"]
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-search.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-search.adoc
index 9f04ed8fc5..85d780c3a3 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-search.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-search.adoc
@@ -44,6 +44,7 @@ Open search with `CTRL-F` or *Edit → Find*. Type in the 
search field to filter
 Results are grouped by *Open files* and *Project files*, then by type 
(Pipeline, Workflow, metadata type, and so on). Use the arrow keys to move, 
*Enter* to open the selection, and *Escape* to close the popup.
 
 *Show all results* hands the same query to a search tab in the bottom dock for 
deeper browsing (details panel, open selected item).
+*+* on that panel's tab bar opens another search tab.
 
 === What is searched
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/vfs-file-explorer.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/vfs-file-explorer.adoc
index e75194b923..6a8b694b08 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/vfs-file-explorer.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/vfs-file-explorer.adoc
@@ -22,7 +22,7 @@ under the License.
 The VFS File Explorer browses a folder on any xref:vfs.adoc[Apache VFS] 
location: the local disk, a zip file, or a named connection such as S3, FTP or 
HDFS.
 It is not the xref:hop-gui/perspective-file-explorer.adoc[File Explorer 
perspective], which stays on the project, and it is not the 
xref:hop-gui/file-dialog.adoc[file dialog], which is still what Hop uses to 
open and save a file.
 
-Open it from **Tools → VFS File Explorer**.
+Open it from **Tools → VFS File Explorer**, or from *+* on the bottom panel 
tab bar.
 The same browser can sit in the bottom panel while you work on a pipeline or 
workflow.
 Use the dock and undock buttons to move the current location between the 
window and that panel.
 Each host keeps its own tabs.
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java 
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index fa4aa12ae2..4a5c6b9de0 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -77,6 +77,10 @@ public class PropsUi extends Props {
   private static final String HIDE_MENU_BAR = "HideMenuBar";
   private static final String SORT_FIELD_BY_NAME = "SortFieldByName";
   private static final String CANVAS_GRID_SIZE = "CanvasGridSize";
+
+  /** Absent means the embedded terminal stays on. */
+  public static final String STRING_EMBEDDED_TERMINAL_ENABLED = 
"EmbeddedTerminalEnabled";
+
   private static final String AUTO_LAYOUT_DIRECTION = "AutoLayoutDirection";
   private static final String AUTO_LAYOUT_LAYER_SPACING = 
"AutoLayoutLayerSpacing";
   private static final String AUTO_LAYOUT_NODE_SPACING = 
"AutoLayoutNodeSpacing";
@@ -550,6 +554,18 @@ public class PropsUi extends Props {
     return !NO.equalsIgnoreCase(open);
   }
 
+  public void setEmbeddedTerminalEnabled(boolean enabled) {
+    setProperty(STRING_EMBEDDED_TERMINAL_ENABLED, enabled ? YES : NO);
+  }
+
+  /**
+   * True unless the user has turned the embedded terminal off. Hop Web and 
{@code
+   * disabledGuiElements.xml} are applied separately by {@code 
HopGuiBottomDock}.
+   */
+  public boolean isEmbeddedTerminalEnabled() {
+    return !NO.equalsIgnoreCase(getProperty(STRING_EMBEDDED_TERMINAL_ENABLED));
+  }
+
   public void setReloadingFilesOnChange(boolean reload) {
     setProperty(RELOAD_FILES_ON_CHANGE, reload ? YES : NO);
   }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index 6722a2470d..d65c6b167e 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -145,6 +145,7 @@ import 
org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
 import org.apache.hop.ui.hopgui.perspective.metadata.MetadataPerspective;
 import org.apache.hop.ui.hopgui.search.HopGuiSearchLocation;
 import org.apache.hop.ui.hopgui.search.SearchEverywhereDialog;
+import org.apache.hop.ui.hopgui.terminal.HopGuiBottomDock;
 import org.apache.hop.ui.hopgui.welcome.WelcomeDialog;
 import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
 import org.apache.hop.ui.util.EnvironmentUtils;
@@ -312,8 +313,8 @@ public class HopGui
   public static final String SIDEBAR_TOOLBAR_ITEM_EXECUTION_RESULTS =
       "HopGui-SidebarToolbar-ExecutionResults";
 
-  /** Id for the terminal toggle button in the sidebar bottom toolbar. */
-  public static final String SIDEBAR_TOOLBAR_ITEM_TERMINAL = 
"HopGui-SidebarToolbar-Terminal";
+  /** Id for the bottom-panel show/hide button in the sidebar bottom toolbar. 
*/
+  public static final String SIDEBAR_TOOLBAR_ITEM_PANEL = 
"HopGui-SidebarToolbar-Panel";
 
   public static final String DEFAULT_HOP_GUI_NAMESPACE = "hop-gui";
 
@@ -379,6 +380,22 @@ public class HopGui
     return terminalPanel;
   }
 
+  /**
+   * Apply the embedded-terminal option to the dock, the Tools menu, and the 
sidebar button. Safe to
+   * call before the dock exists.
+   */
+  public void applyEmbeddedTerminalOption() {
+    boolean enabled = HopGuiBottomDock.isTerminalCapabilityEnabled();
+    if (terminalPanel != null && !terminalPanel.isDisposed()) {
+      terminalPanel.setTerminalsEnabled(enabled);
+    }
+    if (mainMenuWidgets != null) {
+      
mainMenuWidgets.enableMenuItem(HopGuiBottomDock.ID_MAIN_MENU_TOOLS_TERMINAL, 
enabled);
+      
mainMenuWidgets.enableMenuItem(HopGuiBottomDock.ID_MAIN_MENU_TOOLS_NEW_TERMINAL,
 enabled);
+    }
+    refreshBottomToolbarItems();
+  }
+
   private static final PrintStream originalSystemOut = System.out;
   private static final PrintStream originalSystemErr = System.err;
 
@@ -2238,24 +2255,25 @@ public class HopGui
     fdBottomToolbar.bottom = new FormAttachment(100, -4);
     bottomToolbar.setLayoutData(fdBottomToolbar);
 
-    // Register built-in sidebar toolbar items (visibility depends on active 
perspective).
-    // File explorer: both terminal and execution. Other perspectives: 
terminal only.
-    // List order: terminal then execution; refresh draws in reverse so 
execution appears above.
+    // Register built-in sidebar toolbar items. The first item added sits at 
the bottom because
+    // refresh lays the list out in reverse. Execution results stays File 
Explorer only and is
+    // added last so it sits above the panel button. Tools in the bottom panel 
are added from the
+    // "+" tab, not from here.
     int sidebarIconSize = 24;
     sidebarToolbarDescriptors.add(
         SidebarToolbarItemDescriptor.builder()
-            .id(SIDEBAR_TOOLBAR_ITEM_TERMINAL)
-            .imagePath("ui/images/terminal.svg")
+            .id(SIDEBAR_TOOLBAR_ITEM_PANEL)
+            .imagePath("ui/images/dock-panel.svg")
             .imageSize(sidebarIconSize)
-            .tooltip("Toggle Terminal Panel")
+            .tooltip(BaseMessages.getString(PKG, 
"HopGui.Sidebar.BottomPanel.Tooltip"))
             .onSelect(
                 () -> {
                   if (terminalPanel != null) {
-                    terminalPanel.toggleTerminal();
+                    terminalPanel.toggleDock();
                   }
                 })
-            .selectedSupplier(() -> terminalPanel != null && 
terminalPanel.isTerminalVisible())
-            .available(!EnvironmentUtils.getInstance().isWeb())
+            .selectedSupplier(() -> terminalPanel != null && 
terminalPanel.isDockVisible())
+            .available(true)
             .build());
     sidebarToolbarDescriptors.add(
         SidebarToolbarItemDescriptor.builder()
@@ -2297,22 +2315,15 @@ public class HopGui
    * Add a main composite where the various perspectives can parent on to show 
stuff... Its area is
    * to just below the main toolbar and to the right of the perspectives 
toolbar.
    *
-   * <p>Wraps everything in a {@link 
org.apache.hop.ui.hopgui.terminal.HopGuiBottomDock} which hosts
-   * the perspectives in its top section and a tabbed dock (terminals and 
other tools such as the
-   * search results) in its bottom section. The integrated terminal is a gated 
capability: it is
-   * turned off on the web (no AWT/PTY there) and can be disabled in {@code
-   * disabledGuiElements.xml}.
+   * <p>Wraps everything in a {@link HopGuiBottomDock} which hosts the 
perspectives in its top
+   * section and a tabbed bottom panel (terminal, search, database, VFS file 
explorer, and other
+   * tools) below that. The integrated terminal is off on Hop Web, when 
excluded in {@code
+   * disabledGuiElements.xml}, and when the user clears Enable embedded 
terminal.
    */
   private void addMainPerspectivesComposite() {
-    boolean terminalsEnabled =
-        !EnvironmentUtils.getInstance().isWeb()
-            && 
!org.apache.hop.core.gui.plugin.GuiRegistry.getDisabledGuiElements()
-                .contains(
-                    
org.apache.hop.ui.hopgui.terminal.HopGuiBottomDock.ID_MAIN_MENU_TOOLS_TERMINAL);
-
-    terminalPanel =
-        new org.apache.hop.ui.hopgui.terminal.HopGuiBottomDock(
-            mainHopGuiComposite, this, terminalsEnabled);
+    boolean terminalsEnabled = HopGuiBottomDock.isTerminalCapabilityEnabled();
+
+    terminalPanel = new HopGuiBottomDock(mainHopGuiComposite, this, 
terminalsEnabled);
     FormData fdTerminalPanel = new FormData();
     fdTerminalPanel.top = new FormAttachment(0, 0);
     fdTerminalPanel.left = new FormAttachment(perspectivesSidebar, 0);
@@ -2338,6 +2349,7 @@ public class HopGui
 
     mainPerspectivesComposite = terminalPanel.getPerspectiveComposite();
     mainPerspectivesComposite.setLayout(new StackLayout());
+    applyEmbeddedTerminalOption();
   }
 
   public void setUndoMenu(IUndo undoInterface) {
@@ -2799,7 +2811,7 @@ public class HopGui
             SWT.MouseDown,
             e -> {
               if (d.getOnSelect() != null) d.getOnSelect().run();
-              updateVisual.run();
+              refreshSidebarToolbarButtonStates();
             });
         imgLabel.addListener(
             SWT.MouseEnter,
@@ -2817,7 +2829,7 @@ public class HopGui
             SWT.MouseDown,
             e -> {
               if (d.getOnSelect() != null) d.getOnSelect().run();
-              updateVisual.run();
+              refreshSidebarToolbarButtonStates();
             });
 
         updateVisual.run();
@@ -2874,8 +2886,9 @@ public class HopGui
               if (d.getOnSelect() != null) {
                 d.getOnSelect().run();
               }
-              canvas.setData("selected", 
d.getSelectedSupplier().getAsBoolean());
-              canvas.redraw();
+              // Refresh every button. Updating only the clicked one left the 
others highlighted
+              // after the panel was hidden and shown again.
+              refreshSidebarToolbarButtonStates();
             });
       }
     }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
index d499593de5..422afb5507 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/SidebarToolbarItemDescriptor.java
@@ -82,6 +82,6 @@ public class SidebarToolbarItemDescriptor {
    */
   @Builder.Default BooleanSupplier selectedSupplier = () -> false;
 
-  /** Whether this item is available (e.g. terminal only when not in web). */
+  /** Whether this item is available (e.g. a button that is always shown). */
   @Builder.Default boolean available = true;
 }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGeneralOptionsTab.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGeneralOptionsTab.java
index 6400eb0b01..4c2d6e6ee9 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGeneralOptionsTab.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigGeneralOptionsTab.java
@@ -21,6 +21,7 @@ package 
org.apache.hop.ui.hopgui.perspective.configuration.tabs;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.config.HopConfig;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.GuiRegistry;
 import org.apache.hop.core.gui.plugin.tab.GuiTab;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.ui.core.PropsUi;
@@ -30,6 +31,8 @@ import org.apache.hop.ui.core.gui.GuiResource;
 import org.apache.hop.ui.hopgui.HopGui;
 import 
org.apache.hop.ui.hopgui.perspective.configuration.ConfigurationPerspective;
 import org.apache.hop.ui.hopgui.shared.SashFormMemory;
+import org.apache.hop.ui.hopgui.terminal.HopGuiBottomDock;
+import org.apache.hop.ui.util.EnvironmentUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
@@ -58,6 +61,7 @@ public class ConfigGeneralOptionsTab {
   private Text wDefaultPreview;
   private Button wUseCache;
   private Button wOpenLast;
+  private Button wEmbeddedTerminal;
   private Button wReloadFileOnChange;
   private Button wAutoSave;
   private Button wAutoSplit;
@@ -97,6 +101,11 @@ public class ConfigGeneralOptionsTab {
       wDefaultPreview.setText(Integer.toString(props.getDefaultPreviewSize()));
       wUseCache.setSelection(props.useDBCache());
       wOpenLast.setSelection(props.openLastFile());
+      if (wEmbeddedTerminal != null
+          && !wEmbeddedTerminal.isDisposed()
+          && wEmbeddedTerminal.isEnabled()) {
+        wEmbeddedTerminal.setSelection(props.isEmbeddedTerminalEnabled());
+      }
       wReloadFileOnChange.setSelection(props.isReloadingFilesOnChange());
       wAutoSave.setSelection(!props.getAutoSave()); // Inverted logic
       wCopyDistribute.setSelection(props.showCopyOrDistributeWarning());
@@ -222,6 +231,29 @@ public class ConfigGeneralOptionsTab {
             margin);
     lastControl = wOpenLast;
 
+    // Embedded terminal. Hop Web has no PTY, so the checkbox is omitted 
there. An exclusion of the
+    // terminal menu forces it off and must not be overwritten when the other 
options are saved.
+    if (!EnvironmentUtils.getInstance().isWeb()) {
+      boolean forcedOff =
+          GuiRegistry.getDisabledGuiElements()
+              .contains(HopGuiBottomDock.ID_MAIN_MENU_TOOLS_TERMINAL);
+      wEmbeddedTerminal =
+          createCheckbox(
+              wGeneralComp,
+              "EnterOptionsDialog.EmbeddedTerminal.Label",
+              forcedOff
+                  ? "EnterOptionsDialog.EmbeddedTerminal.ForcedOff.ToolTip"
+                  : "EnterOptionsDialog.EmbeddedTerminal.ToolTip",
+              !forcedOff && props.isEmbeddedTerminalEnabled(),
+              lastControl,
+              margin);
+      if (forcedOff) {
+        wEmbeddedTerminal.setEnabled(false);
+        wEmbeddedTerminal.setSelection(false);
+      }
+      lastControl = wEmbeddedTerminal;
+    }
+
     // Reload file if changed on filesystem?
     wReloadFileOnChange =
         createCheckbox(
@@ -703,6 +735,15 @@ public class ConfigGeneralOptionsTab {
         Const.toInt(wDefaultPreview.getText(), props.getDefaultPreviewSize()));
     props.setUseDBCache(wUseCache.getSelection());
     props.setOpenLastFile(wOpenLast.getSelection());
+    if (wEmbeddedTerminal != null
+        && !wEmbeddedTerminal.isDisposed()
+        && wEmbeddedTerminal.isEnabled()) {
+      boolean embeddedTerminal = wEmbeddedTerminal.getSelection();
+      if (embeddedTerminal != props.isEmbeddedTerminalEnabled()) {
+        props.setEmbeddedTerminalEnabled(embeddedTerminal);
+        HopGui.getInstance().applyEmbeddedTerminalOption();
+      }
+    }
     props.setReloadingFilesOnChange(wReloadFileOnChange.getSelection());
     props.setAutoSave(
         !wAutoSave
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/search/ReferenceSearchResults.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/search/ReferenceSearchResults.java
index ad422b22d0..4c9f7de607 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/search/ReferenceSearchResults.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/search/ReferenceSearchResults.java
@@ -101,6 +101,7 @@ public final class ReferenceSearchResults {
     }
     Control content =
         dock.openToolTab(
+            dock.nextSearchToolId(),
             tabTitle,
             GuiResource.getInstance().getImage("ui/images/search.svg", 16, 16),
             true,
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/search/SearchEverywhereDialog.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/search/SearchEverywhereDialog.java
index 512088a39a..fe474e5359 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/search/SearchEverywhereDialog.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/search/SearchEverywhereDialog.java
@@ -742,6 +742,7 @@ public class SearchEverywhereDialog {
     }
     Control content =
         dock.openToolTab(
+            dock.nextSearchToolId(),
             searchTabTitle(searchString),
             GuiResource.getInstance().getImageSearch(),
             true,
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDock.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDock.java
index 6f1fcf1011..da258ab488 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDock.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDock.java
@@ -22,6 +22,7 @@ import lombok.Getter;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.GuiRegistry;
 import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
 import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
 import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
@@ -38,6 +39,10 @@ import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
 import org.apache.hop.ui.hopgui.perspective.TabClosable;
 import org.apache.hop.ui.hopgui.perspective.TabCloseHandler;
+import org.apache.hop.ui.hopgui.perspective.database.DatabaseWorkbenchViews;
+import org.apache.hop.ui.hopgui.search.HopGuiSearchResultsPanel;
+import org.apache.hop.ui.hopgui.search.SearchEverywhereDialog;
+import org.apache.hop.ui.hopgui.vfs.explorer.VfsFileExplorerViews;
 import org.apache.hop.ui.util.EnvironmentUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
@@ -53,19 +58,21 @@ import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.swt.widgets.ToolBar;
 import org.eclipse.swt.widgets.ToolItem;
 
 /**
- * Bottom dock for Hop GUI. It wraps the main perspectives composite in a 
vertical SashForm, with
+ * Bottom panel for Hop GUI. It wraps the main perspectives composite in a 
vertical SashForm, with
  * the perspectives in the top section and a tabbed dock in the bottom section 
that persists across
  * perspective switches.
  *
- * <p>The dock hosts two kinds of tabs in one {@link CTabFolder}: terminal 
tabs (the integrated
- * command line, a gated capability - see {@link #terminalsEnabled}) and 
generic "tool" tabs opened
- * through {@link #focusOrOpenToolTab} (e.g. the search results view). 
Terminal-specific behaviour
- * (the "+" tab, font sizing, save/restore) only applies to terminal tabs.
+ * <p>The dock hosts terminal tabs (a capability that can be turned off — see 
{@link
+ * #terminalsEnabled}) and generic tool tabs opened through {@link 
#focusOrOpenToolTab} (search,
+ * database, VFS file explorer, AI workbench). The "+" tab opens a menu to add 
one of those tabs.
+ * Font sizing and terminal save/restore apply only to terminal tabs.
  */
 @GuiPlugin(name = "Terminal panel", description = "Terminal panel")
 public class HopGuiBottomDock extends Composite implements TabClosable {
@@ -75,6 +82,12 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
   public static final String ID_MAIN_MENU_TOOLS_TERMINAL = 
"40010-menu-tools-terminal";
   public static final String ID_MAIN_MENU_TOOLS_NEW_TERMINAL = 
"40020-menu-tools-new-terminal";
 
+  /** Selected-tool id for a terminal tab. Tool tabs use their own {@code 
DATA_TOOL_ID}. */
+  public static final String TOOL_ID_TERMINAL = "terminal";
+
+  /** Tool-id prefix for search-results tabs opened in this dock. */
+  public static final String SEARCH_TOOL_ID_PREFIX = "search-";
+
   private final HopGui hopGui;
 
   private SashForm verticalSash;
@@ -83,16 +96,30 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
   private Composite terminalComposite;
   @Getter private CTabFolder terminalTabs;
   private CTabItem newTerminalTab;
+
+  /** Last real tab, so dismissing the "+" menu can leave that tab selected. */
+  private CTabItem lastContentTab;
+
   @Getter private boolean terminalVisible = false;
   @Getter private int terminalHeightPercent = 35;
   private boolean isClearing = false;
   private int terminalCounter = 1;
 
   /**
-   * Whether the terminal capability is available (turned off on web or via 
disabledGuiElements).
+   * Whether the terminal capability is available. Off on Hop Web, when {@code
+   * disabledGuiElements.xml} excludes the terminal menu, or when the user 
clears Enable embedded
+   * terminal. Updated at runtime by {@link #setTerminalsEnabled(boolean)}.
    */
   @Getter private boolean terminalsEnabled = true;
 
+  /** Font-size toolbar items. Present only while {@link #terminalsEnabled} is 
true. */
+  private ToolBar dockToolBar;
+
+  private ToolItem increaseFontItem;
+  private ToolItem decreaseFontItem;
+  private ToolItem resetFontItem;
+  private ToolItem fontSeparatorItem;
+
   /** Counter used to give each non-singleton tool tab (e.g. a search result 
tab) a unique id. */
   private int toolTabCounter = 1;
 
@@ -138,6 +165,23 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
     createContents();
   }
 
+  /**
+   * The embedded terminal is on only when the user left it enabled, this is 
not Hop Web, and {@code
+   * disabledGuiElements.xml} does not exclude the terminal menu.
+   */
+  public static boolean isTerminalCapabilityEnabled(
+      boolean web, boolean disabledByRegistry, boolean userEnabled) {
+    return userEnabled && !web && !disabledByRegistry;
+  }
+
+  /** Live capability check used by the sidebar and by {@code HopGui} at 
startup. */
+  public static boolean isTerminalCapabilityEnabled() {
+    return isTerminalCapabilityEnabled(
+        EnvironmentUtils.getInstance().isWeb(),
+        
GuiRegistry.getDisabledGuiElements().contains(ID_MAIN_MENU_TOOLS_TERMINAL),
+        PropsUi.getInstance().isEmbeddedTerminalEnabled());
+  }
+
   /** Create the UI structure */
   private void createContents() {
     setLayout(new FormLayout());
@@ -193,16 +237,7 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
     terminalTabs.setLayoutData(fdTabs);
 
     createTerminalToolbar();
-
-    // The "+" (new terminal) tab only exists when the terminal capability is 
available.
-    if (terminalsEnabled) {
-      newTerminalTab = new CTabItem(terminalTabs, SWT.NONE);
-      newTerminalTab.setText("+");
-      newTerminalTab.setToolTipText(
-          BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Tooltip"));
-      Composite newTerminalPlaceholder = new Composite(terminalTabs, SWT.NONE);
-      newTerminalTab.setControl(newTerminalPlaceholder);
-    }
+    createNewTerminalTab();
 
     new TabCloseHandler(this);
     new TabFolderReorder(terminalTabs);
@@ -212,42 +247,30 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
         SWT.Selection,
         event -> {
           CTabItem item = terminalTabs.getSelection();
-          // When only the "+" tab exists, getSelection() can be null; create 
a new terminal.
-          if (terminalsEnabled
-              && item == null
-              && terminalTabs.getItemCount() == 1
-              && !isClearing
-              && !isClosingTab[0]) {
-            createNewTerminal(null, null);
-            return;
-          }
-          if (item == newTerminalTab) {
-            // Creation is handled by MouseDown so we don't double-create when 
both fire
+          if (item == null || item == newTerminalTab) {
             return;
           }
-
-          if (item != null) {
-            ITerminalWidget widget = (ITerminalWidget) 
item.getData("terminalWidget");
-            if (widget != null) {
-              Composite composite = widget.getTerminalComposite();
-              if (composite != null && !composite.isDisposed()) {
-                composite.forceFocus();
-              }
+          lastContentTab = item;
+          ITerminalWidget widget = (ITerminalWidget) 
item.getData("terminalWidget");
+          if (widget != null) {
+            Composite composite = widget.getTerminalComposite();
+            if (composite != null && !composite.isDisposed()) {
+              composite.forceFocus();
             }
           }
+          hopGui.refreshSidebarToolbarButtonStates();
         });
 
-    // Ensure + tab click always creates a terminal (e.g. when it's the only 
tab and
-    // Selection doesn't fire because selection doesn't change)
+    // The "+" tab is not a tool. A click opens a menu of the tabs this panel 
can add.
     terminalTabs.addListener(
         SWT.MouseDown,
         event -> {
-          if (!terminalsEnabled) {
+          if (isClearing || isClosingTab[0]) {
             return;
           }
           CTabItem item = terminalTabs.getItem(new Point(event.x, event.y));
-          if (item == newTerminalTab && !isClearing && !isClosingTab[0]) {
-            createNewTerminal(null, null);
+          if (item == newTerminalTab) {
+            showNewTabMenu(new Point(event.x, event.y));
           }
         });
 
@@ -288,6 +311,18 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
   }
 
   public void createNewTerminal(String workingDirectory, String shellPath, 
String customTabName) {
+    createNewTerminal(workingDirectory, shellPath, customTabName, true);
+  }
+
+  /**
+   * @param reveal when true, show the dock, select the new tab, and focus the 
terminal. Restore
+   *     passes false so a hidden session is recreated without stealing the 
current tool tab.
+   */
+  private void createNewTerminal(
+      String workingDirectory, String shellPath, String customTabName, boolean 
reveal) {
+    if (!terminalsEnabled) {
+      return;
+    }
     if (shellPath == null) {
       shellPath = TerminalShellDetector.detectDefaultShell();
     }
@@ -296,7 +331,11 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
       workingDirectory = getDefaultWorkingDirectory();
     }
 
-    CTabItem terminalTab = new CTabItem(terminalTabs, SWT.CLOSE, 1);
+    int insertAt = terminalTabs.getItemCount();
+    if (newTerminalTab != null && !newTerminalTab.isDisposed()) {
+      insertAt = Math.min(1, terminalTabs.getItemCount());
+    }
+    CTabItem terminalTab = new CTabItem(terminalTabs, SWT.CLOSE, insertAt);
 
     String terminalId = "terminal-" + terminalCounter++ + "-" + 
System.currentTimeMillis();
 
@@ -329,18 +368,26 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
 
     registerTerminal(terminalId, workingDirectory, shellPath);
 
-    terminalTabs.setSelection(terminalTab);
-
-    if (!terminalVisible) {
-      showTerminal();
+    if (reveal) {
+      terminalTabs.setSelection(terminalTab);
+      if (!isDockVisible()) {
+        showDock();
+      }
+      focusTerminalComposite(terminalTab);
     }
+  }
 
+  private void focusTerminalComposite(CTabItem terminalTab) {
+    if (terminalTab == null || terminalTab.isDisposed()) {
+      return;
+    }
+    ITerminalWidget terminalWidget = (ITerminalWidget) 
terminalTab.getData("terminalWidget");
+    if (terminalWidget == null) {
+      return;
+    }
     getDisplay()
         .asyncExec(
             () -> {
-              if (terminalWidget == null) {
-                return;
-              }
               Composite composite = terminalWidget.getTerminalComposite();
               if (composite != null && !composite.isDisposed()) {
                 composite.setFocus();
@@ -388,7 +435,12 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
     }
   }
 
-  /** Make the dock visible (without forcing a terminal to be created). */
+  /** True when the bottom panel is on screen. */
+  public boolean isDockVisible() {
+    return terminalVisible;
+  }
+
+  /** Make the dock visible without creating a terminal. */
   public void showDock() {
     if (!terminalVisible) {
       verticalSash.setMaximizedControl(null);
@@ -400,16 +452,17 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
     }
   }
 
-  /** Show the dock and make sure at least one terminal is present (the Ctrl+J 
behaviour). */
-  public void showTerminal() {
-    showDock();
-    if (terminalsEnabled && countTerminalTabs() == 0) {
-      createNewTerminal(null, null);
+  /** Show or hide the bottom panel. Does not create a terminal. */
+  public void toggleDock() {
+    if (isDockVisible()) {
+      hideDock();
+    } else {
+      showDock();
     }
   }
 
-  /** Hide the dock */
-  public void hideTerminal() {
+  /** Hide the bottom panel. */
+  public void hideDock() {
     if (terminalVisible) {
       terminalVisible = false;
       verticalSash.setMaximizedControl(perspectiveComposite);
@@ -418,6 +471,126 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
     }
   }
 
+  /** Show the dock and make sure at least one terminal is present. */
+  public void showTerminal() {
+    if (!terminalsEnabled) {
+      return;
+    }
+    showDock();
+    if (countTerminalTabs() == 0) {
+      createNewTerminal(null, null);
+    }
+  }
+
+  /** Hide the dock. Kept for callers that still use the terminal name. */
+  public void hideTerminal() {
+    hideDock();
+  }
+
+  /**
+   * Show the dock and focus a terminal tab, creating one when none is open. 
Does nothing when the
+   * terminal capability is off.
+   */
+  public void focusTerminal() {
+    if (!terminalsEnabled) {
+      return;
+    }
+    CTabItem terminal = findLastTerminalTab();
+    if (terminal == null) {
+      showTerminal();
+      return;
+    }
+    selectTab(terminal);
+    focusTerminalComposite(terminal);
+  }
+
+  /**
+   * Id of the selected tab: {@link #TOOL_ID_TERMINAL} for a terminal, the 
tool id for a tool tab,
+   * or null for the "+" tab and an empty selection.
+   */
+  public String getSelectedToolId() {
+    if (terminalTabs == null || terminalTabs.isDisposed()) {
+      return null;
+    }
+    CTabItem selected = terminalTabs.getSelection();
+    if (selected == null || selected == newTerminalTab || 
selected.isDisposed()) {
+      return null;
+    }
+    if (selected.getData("terminalWidget") != null) {
+      return TOOL_ID_TERMINAL;
+    }
+    Object toolId = selected.getData(DATA_TOOL_ID);
+    return toolId instanceof String id ? id : null;
+  }
+
+  /**
+   * True when {@code toolId} is the selected tab. A value of {@link 
#SEARCH_TOOL_ID_PREFIX} matches
+   * any search-results tab.
+   */
+  public boolean isToolSelected(String toolId) {
+    String selected = getSelectedToolId();
+    if (selected == null || toolId == null) {
+      return false;
+    }
+    if (SEARCH_TOOL_ID_PREFIX.equals(toolId)) {
+      return selected.startsWith(SEARCH_TOOL_ID_PREFIX);
+    }
+    return toolId.equals(selected);
+  }
+
+  /** Select a tab and make sure the dock is visible. */
+  public void selectTab(CTabItem item) {
+    if (item == null || item.isDisposed()) {
+      return;
+    }
+    showDock();
+    terminalTabs.setSelection(item);
+    hopGui.refreshSidebarToolbarButtonStates();
+  }
+
+  /** Content control stored on a tool tab, or null. */
+  public Control getToolContent(CTabItem item) {
+    if (item == null || item.isDisposed()) {
+      return null;
+    }
+    Object content = item.getData(DATA_TOOL_CONTENT);
+    return content instanceof Control control ? control : null;
+  }
+
+  /** Last open terminal tab, or null. */
+  public CTabItem findLastTerminalTab() {
+    CTabItem last = null;
+    if (terminalTabs == null || terminalTabs.isDisposed()) {
+      return null;
+    }
+    for (CTabItem item : terminalTabs.getItems()) {
+      if (item != newTerminalTab && item.getData("terminalWidget") != null) {
+        last = item;
+      }
+    }
+    return last;
+  }
+
+  /** Last tool tab whose id starts with {@code prefix}, or null. */
+  public CTabItem findLastToolTabByPrefix(String prefix) {
+    if (prefix == null || terminalTabs == null || terminalTabs.isDisposed()) {
+      return null;
+    }
+    CTabItem last = null;
+    for (CTabItem item : terminalTabs.getItems()) {
+      Object id = item.getData(DATA_TOOL_ID);
+      if (id instanceof String toolId && toolId.startsWith(prefix)) {
+        last = item;
+      }
+    }
+    return last;
+  }
+
+  /** Next id for a search-results tab ({@code search-1}, {@code search-2}, 
...). */
+  public String nextSearchToolId() {
+    return SEARCH_TOOL_ID_PREFIX + (toolTabCounter++);
+  }
+
   /** Number of real terminal tabs currently open (excludes the "+" tab and 
any tool tabs). */
   private int countTerminalTabs() {
     int count = 0;
@@ -470,7 +643,21 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
    */
   public Control openToolTab(
       String title, Image image, boolean closable, Function<Composite, 
Control> contentFactory) {
-    return createToolTab("tool-" + (toolTabCounter++), title, image, closable, 
contentFactory);
+    return openToolTab(null, title, image, closable, contentFactory);
+  }
+
+  /**
+   * Always open a new tool tab. {@code toolId} is stored on the tab so it can 
be found again; a
+   * blank id gets a generated {@code tool-} id.
+   */
+  public Control openToolTab(
+      String toolId,
+      String title,
+      Image image,
+      boolean closable,
+      Function<Composite, Control> contentFactory) {
+    String id = StringUtils.isEmpty(toolId) ? "tool-" + (toolTabCounter++) : 
toolId;
+    return createToolTab(id, title, image, closable, contentFactory);
   }
 
   private Control createToolTab(
@@ -520,7 +707,10 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
     return null;
   }
 
-  /** Toggle terminal panel visibility */
+  /**
+   * Show the terminal, or hide the panel when a terminal tab is already 
selected. Does nothing when
+   * the terminal capability is off.
+   */
   @GuiMenuElement(
       root = HopGui.ID_MAIN_MENU,
       id = ID_MAIN_MENU_TOOLS_TERMINAL,
@@ -532,10 +722,10 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
     if (EnvironmentUtils.getInstance().isWeb() || !terminalsEnabled) {
       return;
     }
-    if (terminalVisible) {
-      hideTerminal();
+    if (isDockVisible() && isToolSelected(TOOL_ID_TERMINAL)) {
+      hideDock();
     } else {
-      showTerminal();
+      focusTerminal();
     }
   }
 
@@ -584,6 +774,7 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
     }
 
     tabItem.dispose();
+    hopGui.refreshSidebarToolbarButtonStates();
   }
 
   /** Get the terminal tabs folder (implements TabClosable interface) */
@@ -635,47 +826,21 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
 
   /** Create toolbar with font size controls and panel controls 
(maximize/minimize, close) */
   private void createTerminalToolbar() {
-    ToolBar toolBar = new ToolBar(terminalTabs, SWT.FLAT);
-    terminalTabs.setTopRight(toolBar, SWT.RIGHT);
-    PropsUi.setLook(toolBar);
+    dockToolBar = new ToolBar(terminalTabs, SWT.FLAT);
+    terminalTabs.setTopRight(dockToolBar, SWT.RIGHT);
+    PropsUi.setLook(dockToolBar);
 
     GuiResource gui = GuiResource.getInstance();
     if (PropsUi.getInstance().isDarkMode()) {
-      toolBar.setBackground(gui.getColorWhite());
+      dockToolBar.setBackground(gui.getColorWhite());
     } else {
-      toolBar.setBackground(terminalTabs.getBackground());
+      dockToolBar.setBackground(terminalTabs.getBackground());
     }
 
-    // Font sizing only applies to terminal tabs, so the controls are 
terminal-gated.
-    if (terminalsEnabled) {
-      // Font size: increase
-      ToolItem increaseFontItem = new ToolItem(toolBar, SWT.PUSH);
-      increaseFontItem.setImage(
-          GuiResource.getInstance().getImage("ui/images/zoom-in.svg", 16, 16));
-      increaseFontItem.setToolTipText(
-          BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.IncreaseFont"));
-      increaseFontItem.addListener(SWT.Selection, e -> 
increaseTerminalFontSize());
-
-      // Font size: decrease
-      ToolItem decreaseFontItem = new ToolItem(toolBar, SWT.PUSH);
-      decreaseFontItem.setImage(
-          GuiResource.getInstance().getImage("ui/images/zoom-out.svg", 16, 
16));
-      decreaseFontItem.setToolTipText(
-          BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.DecreaseFont"));
-      decreaseFontItem.addListener(SWT.Selection, e -> 
decreaseTerminalFontSize());
-
-      // Font size: reset to 100%
-      ToolItem resetFontItem = new ToolItem(toolBar, SWT.PUSH);
-      
resetFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-100.svg",
 16, 16));
-      resetFontItem.setToolTipText(
-          BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.ResetFont"));
-      resetFontItem.addListener(SWT.Selection, e -> resetTerminalFontSize());
-
-      new ToolItem(toolBar, SWT.SEPARATOR);
-    }
+    createFontToolItems();
 
     // Maximize/Minimize button
-    final ToolItem maximizeItem = new ToolItem(toolBar, SWT.PUSH);
+    final ToolItem maximizeItem = new ToolItem(dockToolBar, SWT.PUSH);
     maximizeItem.setImage(GuiResource.getInstance().getImageMaximizePanel());
     maximizeItem.setToolTipText(
         BaseMessages.getString(PKG, "HopGuiTerminalPanel.Toolbar.Maximize"));
@@ -683,7 +848,7 @@ public class HopGuiBottomDock extends Composite implements 
TabClosable {
         SWT.Selection,
         e -> {
           if (verticalSash.getMaximizedControl() == null) {
-            // Maximize terminal panel
+            // Maximize the bottom panel
             verticalSash.setMaximizedControl(bottomPanelComposite);
             
maximizeItem.setImage(GuiResource.getInstance().getImageMinimizePanel());
             maximizeItem.setToolTipText(
@@ -699,15 +864,226 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
         });
 
     // Close button
-    final ToolItem closeItem = new ToolItem(toolBar, SWT.PUSH);
+    final ToolItem closeItem = new ToolItem(dockToolBar, SWT.PUSH);
     closeItem.setImage(GuiResource.getInstance().getImageClose());
     closeItem.setToolTipText(BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.Close"));
-    closeItem.addListener(SWT.Selection, e -> hideTerminal());
+    closeItem.addListener(SWT.Selection, e -> hideDock());
 
-    int height = toolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
+    int height = dockToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
     terminalTabs.setTabHeight(Math.max(height, terminalTabs.getTabHeight()));
   }
 
+  /** Font-size controls sit in front of maximize and close, and only while 
the terminal is on. */
+  private void createFontToolItems() {
+    if (!terminalsEnabled || dockToolBar == null || dockToolBar.isDisposed()) {
+      return;
+    }
+    if (increaseFontItem != null && !increaseFontItem.isDisposed()) {
+      return;
+    }
+    increaseFontItem = new ToolItem(dockToolBar, SWT.PUSH, 0);
+    
increaseFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-in.svg",
 16, 16));
+    increaseFontItem.setToolTipText(
+        BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.IncreaseFont"));
+    increaseFontItem.addListener(SWT.Selection, e -> 
increaseTerminalFontSize());
+
+    decreaseFontItem = new ToolItem(dockToolBar, SWT.PUSH, 1);
+    
decreaseFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-out.svg",
 16, 16));
+    decreaseFontItem.setToolTipText(
+        BaseMessages.getString(PKG, 
"HopGuiTerminalPanel.Toolbar.DecreaseFont"));
+    decreaseFontItem.addListener(SWT.Selection, e -> 
decreaseTerminalFontSize());
+
+    resetFontItem = new ToolItem(dockToolBar, SWT.PUSH, 2);
+    
resetFontItem.setImage(GuiResource.getInstance().getImage("ui/images/zoom-100.svg",
 16, 16));
+    resetFontItem.setToolTipText(
+        BaseMessages.getString(PKG, "HopGuiTerminalPanel.Toolbar.ResetFont"));
+    resetFontItem.addListener(SWT.Selection, e -> resetTerminalFontSize());
+
+    fontSeparatorItem = new ToolItem(dockToolBar, SWT.SEPARATOR, 3);
+  }
+
+  private void disposeFontToolItems() {
+    disposeToolItem(fontSeparatorItem);
+    disposeToolItem(resetFontItem);
+    disposeToolItem(decreaseFontItem);
+    disposeToolItem(increaseFontItem);
+    fontSeparatorItem = null;
+    resetFontItem = null;
+    decreaseFontItem = null;
+    increaseFontItem = null;
+  }
+
+  private static void disposeToolItem(ToolItem item) {
+    if (item != null && !item.isDisposed()) {
+      item.dispose();
+    }
+  }
+
+  /** The "+" tab is always present. Choosing an entry adds that tool without 
hiding the panel. */
+  private void showNewTabMenu(Point locationInFolder) {
+    CTabItem current = terminalTabs.getSelection();
+    if (current != null && current != newTerminalTab && !current.isDisposed()) 
{
+      lastContentTab = current;
+    }
+    Menu menu = new Menu(terminalTabs);
+    if (terminalsEnabled) {
+      addNewTabMenuItem(
+          menu,
+          BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Terminal"),
+          GuiResource.getInstance().getImageTerminal(),
+          () -> createNewTerminal(null, null));
+    }
+    addNewTabMenuItem(
+        menu,
+        BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Search"),
+        GuiResource.getInstance().getImageSearch(),
+        this::openNewSearchTab);
+    addNewTabMenuItem(
+        menu,
+        BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Database"),
+        GuiResource.getInstance().getImageDatabase(),
+        () -> DatabaseWorkbenchViews.openDock(hopGui));
+    addNewTabMenuItem(
+        menu,
+        BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Vfs"),
+        GuiResource.getInstance().getImageFolder(),
+        () -> VfsFileExplorerViews.openOrFocusDock(hopGui));
+    CTabItem restore = lastContentTab;
+    menu.addListener(
+        SWT.Hide,
+        event ->
+            getDisplay()
+                .asyncExec(
+                    () -> {
+                      if (!terminalTabs.isDisposed()
+                          && (terminalTabs.getSelection() == null
+                              || terminalTabs.getSelection() == newTerminalTab)
+                          && restore != null
+                          && !restore.isDisposed()) {
+                        terminalTabs.setSelection(restore);
+                      }
+                      if (!menu.isDisposed()) {
+                        menu.dispose();
+                      }
+                    }));
+    Point displayPoint = terminalTabs.toDisplay(locationInFolder);
+    menu.setLocation(displayPoint);
+    menu.setVisible(true);
+  }
+
+  private void addNewTabMenuItem(Menu menu, String text, Image image, Runnable 
action) {
+    MenuItem item = new MenuItem(menu, SWT.PUSH);
+    item.setText(text);
+    if (image != null && !image.isDisposed()) {
+      item.setImage(image);
+    }
+    item.addListener(SWT.Selection, event -> action.run());
+  }
+
+  /** A new search-results tab. Search can keep several queries open next to 
each other. */
+  private void openNewSearchTab() {
+    String title =
+        BaseMessages.getString(
+            SearchEverywhereDialog.class, 
"SearchEverywhereDialog.ShowAll.TabTitle");
+    Control content =
+        openToolTab(
+            nextSearchToolId(),
+            title,
+            GuiResource.getInstance().getImageSearch(),
+            true,
+            container -> new HopGuiSearchResultsPanel(container, hopGui));
+    if (content instanceof HopGuiSearchResultsPanel panel) {
+      panel.focusSearchField();
+    }
+  }
+
+  private void createNewTerminalTab() {
+    if (newTerminalTab != null && !newTerminalTab.isDisposed()) {
+      return;
+    }
+    newTerminalTab = new CTabItem(terminalTabs, SWT.NONE, 0);
+    newTerminalTab.setText("+");
+    newTerminalTab.setToolTipText(
+        BaseMessages.getString(PKG, "HopGuiTerminalPanel.NewTab.Tooltip"));
+    Composite placeholder = new Composite(terminalTabs, SWT.NONE);
+    newTerminalTab.setControl(placeholder);
+  }
+
+  /**
+   * Turn the embedded terminal on or off without restarting. Turning it off 
closes PTY tabs and
+   * drops the font controls. The "+" tab stays, without a Terminal entry. 
Turning the terminal on
+   * puts the font controls back and does not open a shell.
+   */
+  public void setTerminalsEnabled(boolean enabled) {
+    if (this.terminalsEnabled == enabled) {
+      return;
+    }
+    this.terminalsEnabled = enabled;
+    if (!enabled) {
+      // Save first. Disposing the tabs must not write an empty list over the 
open sessions, so a
+      // later restore (after the user turns the terminal back on) can 
recreate them.
+      saveOpenTerminals();
+      disposeTerminalTabs();
+      disposeFontToolItems();
+      if (!hasContentTabs()) {
+        hideDock();
+      }
+    } else {
+      createFontToolItems();
+    }
+  }
+
+  /**
+   * Drop terminal tabs and their PTY widgets without updating the saved 
terminal list. {@link
+   * #closeTab} is the path that forgets a tab the user closed.
+   */
+  private void disposeTerminalTabs() {
+    java.util.List<CTabItem> itemsToClose = new java.util.ArrayList<>();
+    for (CTabItem item : terminalTabs.getItems()) {
+      if (!item.isDisposed() && item.getData("terminalWidget") != null) {
+        itemsToClose.add(item);
+      }
+    }
+    for (CTabItem item : itemsToClose) {
+      disposeTerminalTab(item);
+    }
+    hopGui.refreshSidebarToolbarButtonStates();
+  }
+
+  private void disposeTerminalTab(CTabItem item) {
+    if (item == null || item.isDisposed()) {
+      return;
+    }
+    ITerminalWidget widget = (ITerminalWidget) item.getData("terminalWidget");
+    if (widget != null) {
+      widget.dispose();
+    }
+    Control tabControl = item.getControl();
+    item.dispose();
+    if (tabControl != null && !tabControl.isDisposed()) {
+      tabControl.dispose();
+    }
+  }
+
+  /** True when a tab other than "+" is open (a terminal or a tool). */
+  private boolean hasContentTabs() {
+    for (CTabItem item : terminalTabs.getItems()) {
+      if (item != newTerminalTab && !item.isDisposed()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean hasToolTabs() {
+    for (CTabItem item : terminalTabs.getItems()) {
+      if (!item.isDisposed() && item.getData(DATA_TOOL_ID) != null) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private void increaseTerminalFontSize() {
     terminalFontSizePercent = Math.min(200, terminalFontSizePercent + 10);
     applyFontSizeToAllTerminals();
@@ -864,27 +1240,12 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
     isClearing = true;
 
     try {
+      // Persist this project's terminals before the tabs go away. 
disposeTerminalTabs does not
+      // write the audit, so the list survives for the next visit to this 
project.
       saveOpenTerminals();
-
-      java.util.List<CTabItem> itemsToClose = new java.util.ArrayList<>();
-      for (CTabItem item : terminalTabs.getItems()) {
-        if (item != newTerminalTab && !item.isDisposed()) {
-          itemsToClose.add(item);
-        }
-      }
-
-      for (CTabItem item : itemsToClose) {
-        if (!item.isDisposed()) {
-          ITerminalWidget widget = (ITerminalWidget) 
item.getData("terminalWidget");
-          if (widget != null) {
-            widget.dispose();
-          }
-          item.dispose();
-        }
-      }
-
-      if (terminalVisible) {
-        hideTerminal();
+      disposeTerminalTabs();
+      if (!hasContentTabs()) {
+        hideDock();
       }
     } finally {
       isClearing = false;
@@ -923,14 +1284,9 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
     try {
       String namespace = HopNamespace.getNamespace();
 
-      int existingCount = 0;
-      for (CTabItem item : terminalTabs.getItems()) {
-        if (item != newTerminalTab) {
-          existingCount++;
-        }
-      }
-
-      if (existingCount > 0) {
+      // Tool tabs (search, database, VFS, AI) stay open across a project 
switch and must not block
+      // restoring this project's terminals.
+      if (countTerminalTabs() > 0) {
         return;
       }
 
@@ -980,14 +1336,16 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
         return;
       }
 
-      // If panel was hidden when saved, create terminals without showing the 
panel
-      boolean wasVisible = terminalVisible;
-      if (!savedPanelVisible) {
-        terminalVisible = true; // prevent createNewTerminal from calling 
showTerminal()
-      }
+      // A tool tab the user is looking at stays put when this project last 
had the panel hidden.
+      // Otherwise a visible save selects the restored terminal, and a hidden 
save with no tool tab
+      // keeps the shells off screen.
+      boolean keepToolTab = !savedPanelVisible && hasToolTabs() && 
isDockVisible();
+      boolean reveal = savedPanelVisible;
 
       for (String terminalId : auditList.getNames()) {
-        if (STATE_PANEL_VISIBLE_KEY.equals(terminalId)) {
+        if (STATE_PANEL_VISIBLE_KEY.equals(terminalId)
+            || STATE_TERMINAL_FONT_SIZE_PERCENT_KEY.equals(terminalId)
+            || STATE_TERMINAL_HEIGHT_PERCENT_KEY.equals(terminalId)) {
           continue;
         }
         String customTabName = null;
@@ -1010,12 +1368,11 @@ public class HopGuiBottomDock extends Composite 
implements TabClosable {
           }
         }
 
-        createNewTerminal(workingDir, shellPath, customTabName);
+        createNewTerminal(workingDir, shellPath, customTabName, reveal);
       }
 
-      if (!savedPanelVisible) {
-        terminalVisible = wasVisible;
-        hideTerminal();
+      if (!savedPanelVisible && !keepToolTab) {
+        hideDock();
       }
     } catch (Exception e) {
       hopGui
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViews.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViews.java
index 51d744c1f6..50640161c4 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViews.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViews.java
@@ -168,7 +168,48 @@ public class VfsFileExplorerViews {
     if (!isDockOpen(hopGui)) {
       return false;
     }
-    return hopGui.getTerminalPanel().isTerminalVisible();
+    return hopGui.getTerminalPanel().isDockVisible();
+  }
+
+  /**
+   * Show the explorer in the bottom panel. An explorer that is already open 
keeps its location. The
+   * first open uses the project home, or the user home when that is not set.
+   */
+  public static void openOrFocusDock(HopGui hopGui) {
+    if (hopGui == null) {
+      return;
+    }
+    HopGuiBottomDock dock = hopGui.getTerminalPanel();
+    if (dock == null || dock.isDisposed()) {
+      return;
+    }
+    if (isDockOpen(hopGui)) {
+      CTabItem item = dock.findToolTab(DOCK_TOOL_ID);
+      dock.selectTab(item);
+      Control control = dock.getToolContent(item);
+      if (control instanceof VfsFileExplorer explorer && 
!explorer.isDisposed()) {
+        explorer.activate();
+      }
+      return;
+    }
+    openDock(hopGui, defaultDockLocation(hopGui));
+  }
+
+  private static String defaultDockLocation(HopGui hopGui) {
+    try {
+      if (hopGui.getVariables() != null) {
+        String projectHome = hopGui.getVariables().getVariable("PROJECT_HOME");
+        if (StringUtils.isNotEmpty(projectHome)) {
+          projectHome = hopGui.getVariables().resolve(projectHome);
+          if (StringUtils.isNotEmpty(projectHome)) {
+            return projectHome;
+          }
+        }
+      }
+    } catch (Exception e) {
+      // Fall back to the user home.
+    }
+    return System.getProperty("user.home");
   }
 
   @GuiMenuElement(
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
index e6ce3c9167..cf8ecde5a4 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties
@@ -185,6 +185,9 @@ EnterOptionsDialog.KeyboardShortuts=Keyboard Shortcuts
 EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.Label=Maximum execution 
logging text size:
 EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.ToolTip=The maximum size of 
the log text shown in the execution information perspective to prevent out of 
memory errors.
 EnterOptionsDialog.OpenLastFileStartup.Label=Reopen tabs on startup
+EnterOptionsDialog.EmbeddedTerminal.Label=Enable embedded terminal
+EnterOptionsDialog.EmbeddedTerminal.ToolTip=Turn this off on desktops where 
the built-in terminal misbehaves. Search, Database, and VFS File Explorer in 
the bottom panel stay available.
+EnterOptionsDialog.EmbeddedTerminal.ForcedOff.ToolTip=The embedded terminal is 
disabled in disabledGuiElements.xml and cannot be turned on here.
 EnterOptionsDialog.ReloadFileOnChange.Label=Reload file if changed on 
filesystem
 EnterOptionsDialog.ReloadFileOnChange.ToolTip=Automatically reload files when 
they are changed outside of Apache Hop
 EnterOptionsDialog.RestartWarning.DialogMessage=Please restart the Hop GUI to 
apply look and feel changes
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
index bc02ac1e94..67cdf0f13c 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/messages/messages_en_US.properties
@@ -143,6 +143,7 @@ HopGui.Menu.View=&View
 HopGui.Menu.View.FullScreen=Full screen
 HopGui.Menu.View.Terminal=&Terminal
 HopGui.Menu.View.NewTerminal=&New Terminal
+HopGui.Sidebar.BottomPanel.Tooltip=Show or hide the bottom panel
 HopGui.Menu.Redo.Available=Redo \: {0}
 HopGui.Menu.Redo.NotAvailable=Redo \: not available
 HopGui.Menu.Run=&Run
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
index 75b9b89cfc..ef438c38b6 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/terminal/messages/messages_en_US.properties
@@ -21,7 +21,11 @@ HopGuiTerminalPanel.Menu.NewTerminal=New Terminal
 HopGuiTerminalPanel.Toolbar.IncreaseFont=Increase font size
 HopGuiTerminalPanel.Toolbar.DecreaseFont=Decrease font size
 HopGuiTerminalPanel.Toolbar.ResetFont=Reset font to 100%
-HopGuiTerminalPanel.NewTab.Tooltip=Create a new terminal
+HopGuiTerminalPanel.NewTab.Tooltip=Open a new tab
+HopGuiTerminalPanel.NewTab.Terminal=Terminal
+HopGuiTerminalPanel.NewTab.Search=Search
+HopGuiTerminalPanel.NewTab.Database=Database
+HopGuiTerminalPanel.NewTab.Vfs=VFS File Explorer
 HopGuiTerminalPanel.Tab.Tooltip=Terminal: {0} in {1}
 HopGuiTerminalPanel.ShellName.Default=Terminal
 HopGuiTerminalPanel.Toolbar.Maximize=Maximize terminal panel
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDockTest.java 
b/ui/src/test/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDockTest.java
index 3b86bb96d0..889aef8c56 100644
--- 
a/ui/src/test/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDockTest.java
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/terminal/HopGuiBottomDockTest.java
@@ -179,4 +179,13 @@ class HopGuiBottomDockTest {
     assertNotNull(defaultWorkingDir, "User home should not be null");
     assertFalse(defaultWorkingDir.isEmpty(), "User home should not be empty");
   }
+
+  @Test
+  void terminalCapabilityRequiresTheUserOptionOnTheDesktop() {
+    assertTrue(HopGuiBottomDock.isTerminalCapabilityEnabled(false, false, 
true));
+    assertFalse(HopGuiBottomDock.isTerminalCapabilityEnabled(false, false, 
false));
+    assertFalse(HopGuiBottomDock.isTerminalCapabilityEnabled(true, false, 
true));
+    assertFalse(HopGuiBottomDock.isTerminalCapabilityEnabled(false, true, 
true));
+    assertFalse(HopGuiBottomDock.isTerminalCapabilityEnabled(true, true, 
false));
+  }
 }
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViewsTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViewsTest.java
new file mode 100644
index 0000000000..0839249d6f
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/vfs/explorer/VfsFileExplorerViewsTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.hopgui.vfs.explorer;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.junit.jupiter.api.Test;
+
+class VfsFileExplorerViewsTest {
+
+  @Test
+  void openOrFocusDockToleratesNullHopGui() {
+    assertDoesNotThrow(() -> VfsFileExplorerViews.openOrFocusDock(null));
+    assertFalse(VfsFileExplorerViews.isExplorerDockVisible(null));
+    assertFalse(VfsFileExplorerViews.isDockOpen(null));
+  }
+}

Reply via email to