diff --git a/src/webterm/static/js/terminal.js b/src/webterm/static/js/terminal.js
index 788d400..d0a2370 100644
--- a/src/webterm/static/js/terminal.js
+++ b/src/webterm/static/js/terminal.js
@@ -25,7 +25,7 @@ For tests, pass a Ghostty instance directly:
resize: none;
font-size: 16px;
caret-color: transparent;
- `,this.element.style.position="relative",this.element.appendChild(V),this.mobileInput=V,V.addEventListener("beforeinput",(j)=>{let $=null;switch(j.inputType){case"insertLineBreak":$="\r";break;case"deleteContentBackward":$="";break;case"deleteContentForward":$="\x1B[3~";break}if($)j.preventDefault(),this.send(["stdin",$])}),V.addEventListener("input",()=>{let j=V.value;if(j){let $=j;if(this.shiftActive&&j.length===1)$=j.toUpperCase();if(this.ctrlActive&&j.length===1){let P=$.toUpperCase().charCodeAt(0);if(P>=65&&P<=90)$=String.fromCharCode(P-64)}this.send(["stdin",$]),V.value="",this.deactivateModifiers()}}),V.addEventListener("keydown",(j)=>{let $=j.ctrlKey||this.ctrlActive,P=j.shiftKey||this.shiftActive;if(j.ctrlKey&&j.key.length===1&&!j.altKey&&!j.metaKey){let R=j.key.toUpperCase().charCodeAt(0);if(R>=65&&R<=90){j.preventDefault(),this.send(["stdin",String.fromCharCode(R-64)]);return}}let X=null,K=!1;switch(j.key){case"Escape":X="\x1B",K=!0;break;case"ArrowUp":case"ArrowDown":case"ArrowRight":case"ArrowLeft":{let R=j.key==="ArrowUp"?"A":j.key==="ArrowDown"?"B":j.key==="ArrowRight"?"C":"D";if($&&P)X=`\x1B[1;6${R}`;else if($)X=`\x1B[1;5${R}`;else if(P)X=`\x1B[1;2${R}`;else X=`\x1B[${R}`;K=!0;break}case"Tab":if(P)X="\x1B[Z";else X="\t";j.preventDefault(),K=!0;break}if(X){if(j.preventDefault(),this.send(["stdin",X]),K)this.deactivateModifiers()}});let Z=()=>{this.mobileInput?.focus()};this.element.addEventListener("touchend",Z,{passive:!0}),this.element.addEventListener("click",Z)}setupMobileKeybar(){let V=document.createElement("div");V.className="mobile-keybar",V.innerHTML=`
+ `,this.element.style.position="relative",this.element.appendChild(V),this.mobileInput=V,V.addEventListener("beforeinput",(j)=>{let $=null;switch(j.inputType){case"insertLineBreak":$="\r";break;case"deleteContentBackward":$="";break;case"deleteContentForward":$="\x1B[3~";break}if($)j.preventDefault(),this.send(["stdin",$]),this.deactivateModifiers()}),V.addEventListener("input",()=>{let j=V.value;if(j){let $=j;if(this.shiftActive&&j.length===1)$=j.toUpperCase();if(this.ctrlActive&&j.length===1){let P=$.toUpperCase().charCodeAt(0);if(P>=65&&P<=90)$=String.fromCharCode(P-64)}this.send(["stdin",$]),V.value="",this.deactivateModifiers()}}),V.addEventListener("keydown",(j)=>{let $=j.ctrlKey||this.ctrlActive,P=j.shiftKey||this.shiftActive;if(j.ctrlKey&&j.key.length===1&&!j.altKey&&!j.metaKey){let R=j.key.toUpperCase().charCodeAt(0);if(R>=65&&R<=90){j.preventDefault(),this.send(["stdin",String.fromCharCode(R-64)]);return}}let X=null,K=!1;switch(j.key){case"Escape":X="\x1B",K=!0;break;case"ArrowUp":case"ArrowDown":case"ArrowRight":case"ArrowLeft":{let R=j.key==="ArrowUp"?"A":j.key==="ArrowDown"?"B":j.key==="ArrowRight"?"C":"D";if($&&P)X=`\x1B[1;6${R}`;else if($)X=`\x1B[1;5${R}`;else if(P)X=`\x1B[1;2${R}`;else X=`\x1B[${R}`;K=!0;break}case"Tab":if(P)X="\x1B[Z";else X="\t";j.preventDefault(),K=!0;break}if(X){if(j.preventDefault(),this.send(["stdin",X]),K)this.deactivateModifiers()}});let Z=()=>{this.mobileInput?.focus()};this.element.addEventListener("touchend",Z,{passive:!0}),this.element.addEventListener("click",Z)}setupMobileKeybar(){let V=document.createElement("div");V.className="mobile-keybar",V.innerHTML=`
diff --git a/src/webterm/static/js/terminal.ts b/src/webterm/static/js/terminal.ts
index 3d17fea..5552939 100644
--- a/src/webterm/static/js/terminal.ts
+++ b/src/webterm/static/js/terminal.ts
@@ -654,6 +654,8 @@ class WebTerminal {
if (seq) {
e.preventDefault();
this.send(["stdin", seq]);
+ // Clear modifiers after sending special keys from soft keyboard
+ this.deactivateModifiers();
}
});