Skip to content

Commit eb8724b

Browse files
committed
feat(mobile): replace the phone Shell button with Enter, move Shell into Run
On phones the toolbar slot held "Shell", which starts a rarely-needed session type. Sending Enter is a constant need on a touch keyboard, so the slot now holds a dark blue Enter button and shell launching moves into the expandable Run dropdown (Terminal / Shell, label "Run SH"). Desktop and tablet are unchanged: the green Run Shell button stays exactly where it was. Enter goes through xterm's own input path: coreService.triggerDataEvent('\r', true) NOT through sendInput() or a direct POST to /input. localEchoEnabled defaults to MobileDetection.isTouchDevice(), so on a phone the characters you type are buffered client-side in the LocalEchoOverlay and have never reached the PTY. The onData Enter branch in terminal-ui.js is what flushes that buffer before sending \r. A bare \r submits an empty line and leaves the typed text stranded on screen, which presents as "the Enter button does nothing". Replaying the keypress reuses the overlay flush, the flushed-offset cleanup and the 80ms text-before-CR ordering instead of reimplementing them. Verified with local echo forced on: before the fix the overlay still held "echo OLD_WAY" after Enter; after it, pendingText is empty and the command executes in the pane. The !important on the Enter button's colors is required, not habit: styles.css nests its skin overrides inside `html:not([data-skin="og"]) { … }`, so a plain .btn-toolbar there resolves to (0,2,1) and outranks .btn-toolbar.btn-enter at (0,2,0). Without it the button renders in generic toolbar grey. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cb6c252 commit eb8724b

4 files changed

Lines changed: 80 additions & 26 deletions

File tree

src/web/public/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,10 @@ <h3 class="history-title" id="historyTitle">Resume Conversation</h3>
460460
<span class="run-mode-dot gemini"></span>Gemini
461461
</button>
462462
<div class="run-mode-sep"></div>
463+
<button class="run-mode-option" data-mode="shell" onclick="app.setRunMode('shell')">
464+
<span class="run-mode-dot shell"></span>Terminal / Shell
465+
</button>
466+
<div class="run-mode-sep"></div>
463467
<div class="run-mode-header">Recent Sessions</div>
464468
<div class="run-mode-history" id="runModeHistory"></div>
465469
</div>
@@ -475,6 +479,12 @@ <h3 class="history-title" id="historyTitle">Resume Conversation</h3>
475479
<button class="btn-toolbar btn-shell" onclick="app.runShell()" title="Run Shell">
476480
Run Shell
477481
</button>
482+
<!-- Phone-only: replaces the Shell button on ≤430px (Shell moves into the Run
483+
dropdown there). Sends a bare Enter to the active session, the complement
484+
to the accessory bar's Esc. Hidden everywhere else — see styles.css. -->
485+
<button class="btn-toolbar btn-enter" onclick="app.sendEnterKey()" title="Send Enter">
486+
Enter
487+
</button>
478488
<div class="tab-count-group" title="Instance count">
479489
<button class="tab-count-btn" onclick="app.decrementShellCount()"></button>
480490
<input type="number" id="shellCount" class="tab-count-input" value="1" min="1" max="20" readonly>

src/web/public/mobile.css

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -875,47 +875,57 @@ html.mobile-init .file-browser-panel {
875875
margin-right: 0;
876876
}
877877

878-
/* Secondary action - Run Shell - right side */
878+
/* Shell is NOT a toolbar button on phones — it moved into the Run dropdown
879+
(Terminal / Shell), freeing this slot for Enter. Starting a shell is a rare,
880+
deliberate act; sending Enter is a constant one, so the scarce phone real
881+
estate goes to Enter. */
879882
.btn-toolbar.btn-shell {
883+
display: none !important;
884+
}
885+
886+
/* Secondary action - Enter - right side. Takes the slot (and the order) the
887+
Shell button used to hold, so the toolbar rhythm is unchanged. */
888+
.btn-toolbar.btn-enter {
889+
display: flex !important;
880890
flex: 0 0 auto;
881-
background: transparent;
882-
border: 1px solid rgba(255, 255, 255, 0.2);
883-
color: #9ca3af;
884-
order: 4; /* Right position */
891+
align-items: center;
892+
justify-content: center;
893+
min-width: 54px;
894+
width: 54px;
895+
white-space: nowrap;
896+
padding: 0 8px !important;
897+
overflow: hidden;
898+
font-size: 0.65rem;
899+
font-weight: 600;
900+
letter-spacing: 0.01em;
901+
/* !important is REQUIRED here, not defensive habit: styles.css nests its skin
902+
overrides inside `html:not([data-skin="og"]) { … }`, so a plain `.btn-toolbar`
903+
in that block resolves to (0,2,1) and outranks this (0,2,0) rule. Without
904+
!important the button silently renders in generic toolbar grey. */
905+
background: rgba(30, 58, 95, 0.85) !important;
906+
border: 1px solid rgba(59, 130, 246, 0.45) !important;
907+
color: #dbeafe !important;
908+
order: 4; /* Right position — same slot Shell used to occupy */
885909
}
886910

887-
.btn-toolbar.btn-shell:hover,
888-
.btn-toolbar.btn-shell:active {
889-
background: rgba(255, 255, 255, 0.1);
890-
color: #fff;
911+
.btn-toolbar.btn-enter:hover,
912+
.btn-toolbar.btn-enter:active {
913+
background: rgba(37, 74, 122, 0.95) !important;
914+
border-color: rgba(59, 130, 246, 0.7) !important;
915+
color: #fff !important;
891916
}
892917

893918
/* Hide case selector on mobile - simplified toolbar */
894919
.case-select-group {
895920
display: none !important;
896921
}
897922

898-
/* Simplified toolbar layout — Run, Shell, and Case */
923+
/* Simplified toolbar layout — Run, Enter, and Case */
899924
.toolbar-left .toolbar-group:first-child {
900925
width: 100%;
901926
gap: 8px;
902927
}
903928

904-
.btn-toolbar.btn-shell {
905-
flex: 0 0 auto;
906-
min-width: 54px;
907-
width: 54px;
908-
white-space: nowrap;
909-
padding: 0 8px !important;
910-
overflow: hidden;
911-
font-size: 0 !important;
912-
}
913-
914-
.btn-toolbar.btn-shell::after {
915-
content: "Shell";
916-
font-size: 0.65rem;
917-
}
918-
919929
/* Mobile case button - visible on mobile */
920930
.btn-toolbar.btn-case-mobile {
921931
display: flex !important;

src/web/public/session-ui.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ Object.assign(CodemanApp.prototype, {
394394
if (mode === 'gemini') {
395395
return await this.runGemini();
396396
}
397+
if (mode === 'shell') {
398+
return await this.runShell();
399+
}
397400
return await this.runClaude();
398401
} finally {
399402
const remaining = minLockMs - (Date.now() - startedAt);
@@ -500,8 +503,30 @@ Object.assign(CodemanApp.prototype, {
500503
gearBtn.className = `btn-toolbar btn-run-gear mode-${mode}`;
501504
}
502505
if (label) {
503-
label.textContent = mode === 'opencode' ? 'Run OC' : mode === 'codex' ? 'Run CX' : mode === 'gemini' ? 'Run GM' : 'Run';
506+
label.textContent = mode === 'opencode' ? 'Run OC' : mode === 'codex' ? 'Run CX' : mode === 'gemini' ? 'Run GM' : mode === 'shell' ? 'Run SH' : 'Run';
507+
}
508+
},
509+
510+
/** Send Enter to the active session (phone toolbar button).
511+
*
512+
* MUST go through xterm's onData path, NOT straight to sendInput()/the API.
513+
* With local echo on (the mobile default) the characters you typed are still
514+
* buffered in the LocalEchoOverlay and have NEVER reached the PTY. The onData
515+
* Enter branch (terminal-ui.js) is what flushes that pending text and only
516+
* then sends \r. Send a bare \r instead and you submit an empty line while the
517+
* typed text stays stranded on screen — which reads as "the button does
518+
* nothing". triggerDataEvent replays it exactly as if the key were pressed,
519+
* so overlay flush, flushed-offset cleanup and ordering are all reused. */
520+
sendEnterKey() {
521+
if (!this.activeSessionId) return;
522+
const coreService = this.terminal?._core?.coreService;
523+
if (coreService && typeof coreService.triggerDataEvent === 'function') {
524+
coreService.triggerDataEvent('\r', true);
525+
return;
504526
}
527+
// Fallback only if xterm's private core API moves: correct when local echo
528+
// is off, and still better than doing nothing.
529+
this.sendInput('\r');
505530
},
506531

507532
_initRunMode() {

src/web/public/styles.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,14 @@ body.touch-device .terminal-container .xterm .xterm-helper-textarea {
33703370
.run-mode-dot.opencode { background: #10b981; }
33713371
.run-mode-dot.codex { background: #a855f7; }
33723372
.run-mode-dot.gemini { background: #8ab4f8; }
3373+
.run-mode-dot.shell { background: #94a3b8; }
3374+
3375+
/* Phone-only Enter button (see index.html). Hidden by default at every width;
3376+
mobile.css turns it on inside @media (max-width: 430px), where it takes over
3377+
the slot the Shell button occupies on wider screens. */
3378+
.btn-toolbar.btn-enter {
3379+
display: none;
3380+
}
33733381

33743382
.run-mode-sep {
33753383
height: 1px;
@@ -11618,6 +11626,7 @@ html:not([data-skin="og"]) {
1161811626
.run-mode-dot.claude { background: var(--accent); }
1161911627
.run-mode-dot.opencode { background: var(--accent-soft); }
1162011628
.run-mode-dot.codex { background: var(--accent-grad-b); }
11629+
.run-mode-dot.shell { background: var(--text-dim); }
1162111630

1162211631
/* ---- Shell button: quiet neutral with a calm green tint ---- */
1162311632
.btn-toolbar.btn-shell {

0 commit comments

Comments
 (0)