diff --git a/src/web/public/index.html b/src/web/public/index.html
index b51c96d1..557d1a00 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -460,6 +460,10 @@
Gemini
+
+
@@ -475,6 +479,12 @@
+
+
diff --git a/src/web/public/mobile.css b/src/web/public/mobile.css
index 67cf181d..68339cc8 100644
--- a/src/web/public/mobile.css
+++ b/src/web/public/mobile.css
@@ -875,19 +875,44 @@ html.mobile-init .file-browser-panel {
margin-right: 0;
}
- /* Secondary action - Run Shell - right side */
+ /* Shell is NOT a toolbar button on phones — it moved into the Run dropdown
+ (Terminal / Shell), freeing this slot for Enter. Starting a shell is a rare,
+ deliberate act; sending Enter is a constant one, so the scarce phone real
+ estate goes to Enter. */
.btn-toolbar.btn-shell {
- flex: 0 0 auto;
- background: transparent;
- border: 1px solid rgba(255, 255, 255, 0.2);
- color: #9ca3af;
- order: 4; /* Right position */
+ display: none !important;
}
- .btn-toolbar.btn-shell:hover,
- .btn-toolbar.btn-shell:active {
- background: rgba(255, 255, 255, 0.1);
- color: #fff;
+ /* Secondary action - Enter - right side. Takes the slot (and the order) the
+ Shell button used to hold, so the toolbar rhythm is unchanged. */
+ .btn-toolbar.btn-enter {
+ display: flex !important;
+ flex: 0 0 auto;
+ align-items: center;
+ justify-content: center;
+ min-width: 54px;
+ width: 54px;
+ white-space: nowrap;
+ padding: 0 8px !important;
+ overflow: hidden;
+ font-size: 0.65rem;
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ /* !important is REQUIRED here, not defensive habit: styles.css nests its skin
+ overrides inside `html:not([data-skin="og"]) { … }`, so a plain `.btn-toolbar`
+ in that block resolves to (0,2,1) and outranks this (0,2,0) rule. Without
+ !important the button silently renders in generic toolbar grey. */
+ background: rgba(30, 58, 95, 0.85) !important;
+ border: 1px solid rgba(59, 130, 246, 0.45) !important;
+ color: #dbeafe !important;
+ order: 4; /* Right position — same slot Shell used to occupy */
+ }
+
+ .btn-toolbar.btn-enter:hover,
+ .btn-toolbar.btn-enter:active {
+ background: rgba(37, 74, 122, 0.95) !important;
+ border-color: rgba(59, 130, 246, 0.7) !important;
+ color: #fff !important;
}
/* Hide case selector on mobile - simplified toolbar */
@@ -895,27 +920,12 @@ html.mobile-init .file-browser-panel {
display: none !important;
}
- /* Simplified toolbar layout — Run, Shell, and Case */
+ /* Simplified toolbar layout — Run, Enter, and Case */
.toolbar-left .toolbar-group:first-child {
width: 100%;
gap: 8px;
}
- .btn-toolbar.btn-shell {
- flex: 0 0 auto;
- min-width: 54px;
- width: 54px;
- white-space: nowrap;
- padding: 0 8px !important;
- overflow: hidden;
- font-size: 0 !important;
- }
-
- .btn-toolbar.btn-shell::after {
- content: "Shell";
- font-size: 0.65rem;
- }
-
/* Mobile case button - visible on mobile */
.btn-toolbar.btn-case-mobile {
display: flex !important;
diff --git a/src/web/public/session-ui.js b/src/web/public/session-ui.js
index a3e01b71..75ef9513 100644
--- a/src/web/public/session-ui.js
+++ b/src/web/public/session-ui.js
@@ -394,6 +394,9 @@ Object.assign(CodemanApp.prototype, {
if (mode === 'gemini') {
return await this.runGemini();
}
+ if (mode === 'shell') {
+ return await this.runShell();
+ }
return await this.runClaude();
} finally {
const remaining = minLockMs - (Date.now() - startedAt);
@@ -500,10 +503,32 @@ Object.assign(CodemanApp.prototype, {
gearBtn.className = `btn-toolbar btn-run-gear mode-${mode}`;
}
if (label) {
- label.textContent = mode === 'opencode' ? 'Run OC' : mode === 'codex' ? 'Run CX' : mode === 'gemini' ? 'Run GM' : 'Run';
+ label.textContent = mode === 'opencode' ? 'Run OC' : mode === 'codex' ? 'Run CX' : mode === 'gemini' ? 'Run GM' : mode === 'shell' ? 'Run SH' : 'Run';
}
},
+ /** Send Enter to the active session (phone toolbar button).
+ *
+ * MUST go through xterm's onData path, NOT straight to sendInput()/the API.
+ * With local echo on (the mobile default) the characters you typed are still
+ * buffered in the LocalEchoOverlay and have NEVER reached the PTY. The onData
+ * Enter branch (terminal-ui.js) is what flushes that pending text and only
+ * then sends \r. Send a bare \r instead and you submit an empty line while the
+ * typed text stays stranded on screen — which reads as "the button does
+ * nothing". triggerDataEvent replays it exactly as if the key were pressed,
+ * so overlay flush, flushed-offset cleanup and ordering are all reused. */
+ sendEnterKey() {
+ if (!this.activeSessionId) return;
+ const coreService = this.terminal?._core?.coreService;
+ if (coreService && typeof coreService.triggerDataEvent === 'function') {
+ coreService.triggerDataEvent('\r', true);
+ return;
+ }
+ // Fallback only if xterm's private core API moves: correct when local echo
+ // is off, and still better than doing nothing.
+ this.sendInput('\r');
+ },
+
_initRunMode() {
try { this._runMode = localStorage.getItem('codeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; }
this._applyRunMode();
diff --git a/src/web/public/styles.css b/src/web/public/styles.css
index a29a8606..a3ae5659 100644
--- a/src/web/public/styles.css
+++ b/src/web/public/styles.css
@@ -3370,6 +3370,14 @@ body.touch-device .terminal-container .xterm .xterm-helper-textarea {
.run-mode-dot.opencode { background: #10b981; }
.run-mode-dot.codex { background: #a855f7; }
.run-mode-dot.gemini { background: #8ab4f8; }
+.run-mode-dot.shell { background: #94a3b8; }
+
+/* Phone-only Enter button (see index.html). Hidden by default at every width;
+ mobile.css turns it on inside @media (max-width: 430px), where it takes over
+ the slot the Shell button occupies on wider screens. */
+.btn-toolbar.btn-enter {
+ display: none;
+}
.run-mode-sep {
height: 1px;
@@ -11618,6 +11626,7 @@ html:not([data-skin="og"]) {
.run-mode-dot.claude { background: var(--accent); }
.run-mode-dot.opencode { background: var(--accent-soft); }
.run-mode-dot.codex { background: var(--accent-grad-b); }
+.run-mode-dot.shell { background: var(--text-dim); }
/* ---- Shell button: quiet neutral with a calm green tint ---- */
.btn-toolbar.btn-shell {