Repository navigation
feat(terminal): add a phone keyboard visibility control - #1973
Conversation
On a touch screen the terminal's key bar gets a Show keyboard / Hide keyboard control after the Input line switch and Paste (#1046). Show focuses the field of the current input mode inside the tap (the input line, else xterm's textarea); Hide blurs only this pane's field. The pressed state and the name follow data-keyboard as lib/viewport.ts measured it, never the focus, so a hardware keyboard reads as down. The control sends no key, changes no setting or input mode and fits or resizes nothing; while a word is composed in either field, a direct commit is pending or Paste still reads the clipboard (its own focus would bring a hidden keyboard back), it is aria-disabled and refuses the tap without deferring anything. Safari's Hangul or dictation preedit, which has no composition events, is typed by Hide's blur as a key-bar key types it first. Without a field it may focus (held, ended, offline) it is disabled, and an observer has none. xterm reports its textarea's focus and blur as input while an application has focus reporting on (DECSET 1004), so the control holds a flag around its own focus() and blur(): onData drops exactly those two reports, and reassertGrid leaves the grid alone. A focus the user makes still reports. Unlike the keys, the control is a Tab stop with its localized name as a tooltip. Disabled keys draw in --text-dim, and the bar's focus ring is drawn inside the key so the scroller no longer clips it. It stays in the bar rather than AoE's floating button; DESIGN.md says why. Adds keyboardToggleAction and subscribeKeyboardUp to lib/keyboard.ts with unit tests, ko/ja/zh entries in the terminal dictionary part, a changelog fragment, and scripts/terminal-keyboard-demo-regression.ts in the browser lane. Its captures run the clock past the demo's one-shot turn completion and do not draw the droplet it raises, instead of waiting for that droplet: the clock runs while the page and its fonts load, so a slow load let the droplet leave before the wait began. Refs #1046
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 8 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Merge pass review report. Overall: PASSED. This is a client-only change in the terminal input path. The Astra gate (omo-native-gate-reviewer, gpt-6-astra) approved 5d19f31. Tests:
Independent probes:
Screenshots: phone and desktop captures were inspected. The control appears only on touch screens. Bridge traps: the control sends no input apart from the focus reports a user's own focus would send. It resizes, takes over and refocuses nothing, and acts on no sibling pane. Not covered:
|
…2333) Closes the phone keyboard-control bug that #1973's control hid underneath: the control is there, but a tap's meaning could not be trusted. ### Problem On a phone, the key bar over the keyboard has an Input line switch, Paste and a **Show keyboard / Hide keyboard** control (#1046, #1973). Two things went wrong with it: - The keyboard control fights itself. Tapping Hide makes the keyboard open; the open control shows armed over a keyboard that is visibly up; and tapping several times in a row makes the keyboard open and close in bursts, the label and the glyph flipping without ever matching the keyboard for long. - After Android's back gesture (the keyboard put away, the field left focused), tapping the control does nothing. Switching Input line / direct typing rides the same keyboard: the switch alone raises it and puts it away, so changing where you type looks like a broken keyboard button. ### Cause The control had no state of its own. Its label, glyph, `aria-pressed` and the action the tap took were each re-read, at tap time, from `data-keyboard` — a visual-viewport measurement that flips in the middle of the keyboard's own slide (Chromium's geometry overshoots the resting height and no API says when it settled). A tap therefore meant whichever side of the animation the last measurement happened to land on, and a measurement blind to a zoomed page or a floating keyboard kept the control lying for good. ### Change - New `src/lib/keyboardControl.ts`: a small store that keeps the **tap's word** (last tap wins) until the keyboard measurement confirms it and holds still (120 ms, past the overshoot) or a 700 ms settle window runs out. A composition or a pending commit refuses the tap before any state changes, and a tap this pane has no target for records nothing. One derivation (`view`) is what the label, the glyph, `aria-pressed` and the next tap's action all read, so the control can never say one thing and do another. - `lib/viewport.ts` feeds each measurement to the store; `data-keyboard`'s meaning for every other reader (shell height, grid hold, CSS) is untouched. - `toggleKeyboard` now only performs the focus and blur, inside the tap's own gesture: Show blur-and-refocuses an already focused field, which is what re-raises the keyboard a back gesture put away, and Hide's blur leaving no field focused anywhere reads as the keyboard going where the measurement is blind. - The Input line switch is no keyboard gesture: direct to line hands the grid's keyboard to the input line, focus and all, in a layout effect before paint, gated on touch. ### What it improves - Hide puts the keyboard away the first time, every time, and the control always says what its next tap will do — the armed state can no longer disagree with the keyboard. - Rapid taps end in the last tap's state instead of an open/close storm. - The keyboard comes back after the back gesture. - Switching input modes keeps the keyboard up where you had it, instead of raising and dismissing it by side effect. --------- Co-authored-by: devswha <devswha@gmail.com>
…-in) (#2966) Fixes #1635. ## What A new opt-in setting, **Settings > Terminal > Keyboard only from the key bar** (off by default, a device setting kept in its own storage key like the other recent opt-ins). With it on, on a touch screen: - While this pane's key-bar keyboard control reads **Show keyboard**, the input line and xterm's helper textarea carry `inputmode="none"`. A tap on the grid, the line, Send or a key-bar key still focuses them as before, but the browser raises no soft keyboard. - **Show keyboard** removes the attribute from the field it focuses, inside the same tap, so the keyboard comes up. - **Hide keyboard**, or the system putting the keyboard away (Android's back gesture, read from the measured viewport), turns the control back to Show, and the attribute comes back. With it off (the default), nothing changes: no field gets `inputmode`, except the existing `none` on xterm's field in Input line mode. The hold is `setting && coarse pointer && !requested`. `requested` is a per-pane state that the control's Show tap sets. Hide clears it, and so do a keyboard measured going down (the back gesture, through `lib/keyboardControl.ts`'s notifications) and the focus leaving the pane's fields. Nothing is held while the control cannot be pressed (offline, input not ready, held by another bridge, ended, view only). The line still takes a draft there, so a tap raises the keyboard as with the setting off (omo re-review). The focus-out check accepts only the line or xterm's field, not other fields in the pane such as the find bar. The hold is deliberately not the control's displayed direction: that falls back to the viewport measurement after its settle window, and the measurement is blind to an iPad floating keyboard or a zoomed page (Codex review). The line gets it as a prop. xterm's field gets it through its own layout effect in `useTerminalSends`, separate from the mode-switch effect, so a re-hold never re-runs that effect's blur or handoff. ## Differences from the issue's proposal - The issue proposed a setting that is on by default and is switched off to opt in. This one is named for the opt-in instead and is off by default. The behaviour is the same. - The Input line / Direct typing switch does not move to a long press. Since #1973 it has its own key beside the keyboard control, so no long press is needed. **Design reference:** Termux's KEYBOARD extra key, the keyboard toggle of a phone terminal app. Reading is the rest state, and typing is asked for. DESIGN.md has the entry. ## Tests - `scripts/terminal-keyboard-demo-regression.ts`: a new `keyboard-on-request` scenario on a 390-wide phone runs with the setting off and on, in line and direct mode. At rest, after a tap on the line or grid, after Show (`inputmode` checked as the tap's own focus runs), with the keyboard up, after the system dismissal, after Hide, after an unmeasured Show outlives the settle window, and after the focus leaves the pane's fields, it checks the field's `inputmode` and focus. An offline step (the line still raises a keyboard for a draft) was added in the second round. Each review step fails without its fix; the unmeasured step fails with (`an unmeasured keyboard Show raised is not taken back`). **Failing first:** with the `src/` change stashed, it fails at `on, line: at rest the field raises no keyboard` (actual `null`, expected `"none"`). With the change, the whole script passes, including every existing scenario. - `src/lib/settings.test.ts`: the default, sanitising, device scope, and the setting's own key surviving an old tab's whole-record save. ## Screenshots Settings > Terminal at 390 and 1280, in light and dark, were checked by eye with the row off and on. The row sits right after Key bar and uses the same Toggle as its neighbours. Nothing else changes visually. ## Validation - `bun run check fast`: pass (on the merged head, via the heavy runner). - `bun scripts/terminal-keyboard-demo-regression.ts`: pass. - `bun test src/lib/settings.test.ts`: pass. - Not run: the full browser suite (CI runs it), and a native iOS or Android keyboard check. Chromium's synthetic viewport stands in for the keyboard here; `inputmode="none"` is the platforms' documented way to suppress the soft keyboard (Chrome on Android, Safari on iOS).
Closes #1046. Related: #1635 (not implemented here, see below).
What
On a touch screen the terminal's key bar gets a Show keyboard / Hide keyboard control (
[data-key="keyboard"]), after the Input line switch and Paste. It moves the soft keyboard without changing the input mode:PanelBottomOpen/PanelBottomClose) andaria-pressedfollow the keyboard statelib/viewport.tsmeasured (data-keyboard), never the focus. A hardware keyboard, or an occlusion the browser cannot report, reads as down.ESC [ I/ESC [ O. The control holds a flag around its ownfocus()/blur(), andonDatadrops exactly those two reports while the flag is held. A focus the user makes still reports.aria-disabledand refuses the tap in place. That keeps the composition, and keeps a paste's own focus from bringing a hidden keyboard back. View only has no control. Held, ended and offline panes show a disabled one.--text-dim, and the bar's focus ring is drawn inside the key so the scroller no longer clips it.Design reference: the platform's own dismiss key (iPadOS's keyboard key, Android's ▽). It lives in the key bar with the mode switch, because this app keeps its touch controls in the bar and its menus. That is where it differs from agent-of-empires' floating
KeyboardFab. It also follows the measured keyboard rather than input focus, as the issue asks. DESIGN.md has the entry. No code was copied.Not in this PR: the rest of #1046
#1046 also proposes an optional arrow-joystick key and light haptic feedback. Neither is here. The maintainer's status on #1046 named the keyboard key as the work left to ship. If the joystick should stay tracked, change
ClosestoRefs, or keep the item on #1053.Not in this PR: #1635
#1635 proposes an opt-in "keyboard only when I ask" model: a setting that sets
inputmode="none"on every terminal field while the keyboard is hidden, with the mode switch moved to a long press. That is a separate, larger design, and the control here does not do it. Taps on the grid, the line and the key bar behave as they do on main. This control would be the natural toggle for that model if it lands.Tests and checks
src/lib/keyboard.test.ts:keyboardToggleActionandsubscribeKeyboardUp.scripts/terminal-keyboard-demo-regression.ts(browser lane), on the real client over the demo transport as a phone. It checks:It also writes a 390 / 1440, light / dark, en / ko capture set.
Determinism (the open item on Terminal (phone): a keyboard show/hide button, and an optional arrow joystick key #1046): the captures used to wait for the demo's one-shot finish droplet to attach. The page's clock runs on its own while the page and its fonts load, so a slow load let the droplet expire before that wait, and the run hung. The captures now run the clock past the completion and do not draw the droplet (as
sidebar-filter-demo-regression.tsdoes), so no step waits on a transient.scripts/key-bar-customization-demo-regression.ts: an empty key list keeps the fixed controlsdirect,paste,keyboard.Reviews
ESC [ O). Kept: the sibling really loses the focus, and a tap into this pane's line does the same. Refusing Show there would leave the selected pane without its keyboard. DESIGN.md now says this.Local checks
bun run check fastpassed on the pushed commit: workflow syntax, generated types and themes, typecheck, build and unit tests.terminal-keyboard-demo-regression.tspassed 3 runs in a row (2 before the review fixes, 1 on the final commit).keyboard-viewport-demo-regression.tsandkey-bar-customization-demo-regression.tspassed.