diff --git a/tui/web_templates/app_index.html b/tui/web_templates/app_index.html
index 298a60a..333e9eb 100644
--- a/tui/web_templates/app_index.html
+++ b/tui/web_templates/app_index.html
@@ -165,6 +165,70 @@
}
})();
+ // Mobile keyboard control: prevent keyboard on every tap, add toggle button
+ (function() {
+ if (!("ontouchstart" in window)) return;
+
+ var kbOpen = false;
+
+ // Create floating keyboard toggle button
+ var btn = document.createElement("div");
+ btn.id = "kb-toggle";
+ btn.textContent = "⌨";
+ btn.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:9999;" +
+ "width:44px;height:44px;border-radius:50%;background:rgba(94,11,167,0.85);" +
+ "color:#fff;font-size:22px;display:flex;align-items:center;justify-content:center;" +
+ "cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.4);user-select:none;";
+ document.addEventListener("DOMContentLoaded", function() {
+ document.body.appendChild(btn);
+ });
+
+ function setKeyboard(open) {
+ kbOpen = open;
+ var ta = document.querySelector(".xterm-helper-textarea");
+ if (!ta) return;
+ if (open) {
+ ta.removeAttribute("readonly");
+ ta.setAttribute("inputmode", "text");
+ ta.focus();
+ btn.style.background = "rgba(0,204,0,0.85)";
+ } else {
+ ta.setAttribute("inputmode", "none");
+ ta.setAttribute("readonly", "readonly");
+ ta.blur();
+ btn.style.background = "rgba(94,11,167,0.85)";
+ }
+ }
+
+ btn.addEventListener("click", function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ setKeyboard(!kbOpen);
+ });
+
+ // Block xterm from auto-focusing its textarea on touch
+ document.addEventListener("touchend", function(e) {
+ if (e.target === btn || btn.contains(e.target)) return;
+ if (kbOpen) return; // user wants keyboard open
+ setTimeout(function() {
+ var active = document.activeElement;
+ if (active && active.classList && active.classList.contains("xterm-helper-textarea")) {
+ active.blur();
+ }
+ }, 50);
+ }, { passive: true });
+
+ // Disable keyboard on xterm textarea once it appears
+ var checkReady = setInterval(function() {
+ var ta = document.querySelector(".xterm-helper-textarea");
+ if (ta) {
+ clearInterval(checkReady);
+ ta.setAttribute("readonly", "readonly");
+ ta.setAttribute("inputmode", "none");
+ }
+ }, 200);
+ })();
+
// Touch-to-scroll: translate swipe into mouse wheel for Textual scrollable containers
(function() {
var touchStartY = null;