diff --git a/docs/agents/client-platform.md b/docs/agents/client-platform.md
index 561ea6ef6f..b1150fc4c6 100644
--- a/docs/agents/client-platform.md
+++ b/docs/agents/client-platform.md
@@ -7,6 +7,13 @@
- `.chat-input-controls` stays while its model picker is open (`:not(:has(...))`, a rule of its own): the picker's panel lives in that row and its filter is the field the keyboard opened for, so hiding the row hid the picker and blurred the filter (#1271). See [models.md](models.md#model-selector-and-the-mobile-keyboard).
- Anything fixed to the bottom of a phone screen sits inside `--app-viewport-height`, never at `bottom: 0`: iOS keeps the layout viewport, and with it `bottom: 0`, under the keyboard. The drawer and the right panel use `top: 0` with that height; the sidebar's bottom sheet (`.sidebar-sheet-layer` in `app/sidebar-menu.css`) is a layer of that height with the sheet at its bottom, and drops its safe-area padding under `html[data-keyboard-open]`.
+## The composer's phone controls panel (`components/ChatInput.tsx`)
+- The controls row's ⋯ toggle opens the reasoning, tools, Compact, Stop and sound controls as a floating window over the composer, one control per 34px row (icon, label, its state at the right). Before, they fanned out as one no-wrap strip of 24px icon+label chips `bottom: 0` over the same row: at 390px it was 231px wide, sat on the model selector and covered the whole left half once a control was added.
+- It is absolutely positioned against the composer's fieldset (`.chat-input-shell`, now `position: relative`) at `right: 4; bottom: 4`, not against the row: the panel's bottom edge lands on the composer's, so it covers the draft and the attach/model row and grows up over the transcript. Never above the shell: a window that starts at the shell's top reads as another banner and leaves the composer showing under it. The row itself is `position: static` on a phone so the shell is the containing block. Desktop keeps its chips: the panel's mobile-only styles are the `isMobile` branch of the same nodes.
+- No `overflow` on the panel: a row's own reasoning/tools menu opens above it, outside the panel's box, and `overflow: auto` would clip it. It holds at most five rows, so it never needs to scroll.
+- Those two menus are sized for the screen inside the panel: `right: 0` and `maxWidth: calc(100vw - 16px)` (they used to grow right from the row at `left: 0`, which put the tools menu's 288px past the edge), and `maxHeight: calc(100dvh - 230px)` with `overflow-y: auto` — the room above the panel's first row is the viewport less its own ~190px and that row's 34px, so a full reasoning list scrolls instead of running off the top of the screen.
+- It stays inside `.chat-input-controls`, so the keyboard-open rule hides it with the row; its header holds the title and the ✕ that collapses it (`chat.moreControls`, `chat.collapseControls`). The row labels are `chat.controls*` (`Reasoning`, `Tools`, `Sound`, `On`/`Off`), and outside-click closing is unchanged: the panel is inside the `controlsMenuRef` subtree.
+
## Terminal extra keys (`components/TerminalExtraKeys.tsx`, `lib/terminal-extra-keys.ts`)
- A touch device held in portrait gets Termux's two key rows under the terminal (`ESC / - HOME ↑ END PGUP`, `TAB CTRL ALT ← ↓ → PGDN`). Portrait comes from `screen.orientation` (then `window.orientation`), never from the viewport: the soft keyboard makes the viewport wider than tall, and the keys must stay while it is open. Left and right are Noto Sans Mono's `↓` and `↑` rotated 90deg: its latin subset has `↑`/`↓` but no `←`/`→`, which would fall back to a heavier system font.
- The keys are the panel's last grid row, so xterm's `ResizeObserver` refits above them instead of being covered. Buttons call `preventDefault()` on pointer and mouse down so the hidden xterm textarea keeps focus and the keyboard stays up.
diff --git a/lib/i18n/messages/en.ts b/lib/i18n/messages/en.ts
index cd3d53ef39..67329ee920 100644
--- a/lib/i18n/messages/en.ts
+++ b/lib/i18n/messages/en.ts
@@ -586,6 +586,11 @@ export const enLocale: LocalePlugin = {
"chat.defaultThinking": "Default reasoning level for new chats",
"chat.moreControls": "More controls",
"chat.collapseControls": "Collapse controls",
+ "chat.controlsReasoning": "Reasoning",
+ "chat.controlsTools": "Tools",
+ "chat.controlsSound": "Sound",
+ "chat.controlsOn": "On",
+ "chat.controlsOff": "Off",
"chat.shell": "Shell",
"terminal.title": "Terminal",
"terminal.tabLabel": "Terminal: {name}",
diff --git a/lib/i18n/messages/zh-CN.ts b/lib/i18n/messages/zh-CN.ts
index bb1c64868e..eaa51717fc 100644
--- a/lib/i18n/messages/zh-CN.ts
+++ b/lib/i18n/messages/zh-CN.ts
@@ -586,6 +586,11 @@ export const zhCNLocale: LocalePlugin = {
"chat.defaultThinking": "新会话默认推理级别",
"chat.moreControls": "更多控件",
"chat.collapseControls": "收起控件",
+ "chat.controlsReasoning": "推理",
+ "chat.controlsTools": "工具",
+ "chat.controlsSound": "声音",
+ "chat.controlsOn": "开",
+ "chat.controlsOff": "关",
"chat.shell": "Shell",
"terminal.title": "终端",
"terminal.tabLabel": "终端:{name}",
diff --git a/lib/i18n/messages/zh-TW.ts b/lib/i18n/messages/zh-TW.ts
index fe29c7f3bc..dac17913c1 100644
--- a/lib/i18n/messages/zh-TW.ts
+++ b/lib/i18n/messages/zh-TW.ts
@@ -586,6 +586,11 @@ export const zhTWLocale: LocalePlugin = {
"chat.defaultThinking": "新工作階段預設推理層級",
"chat.moreControls": "更多控制項",
"chat.collapseControls": "收起控制項",
+ "chat.controlsReasoning": "推理",
+ "chat.controlsTools": "工具",
+ "chat.controlsSound": "聲音",
+ "chat.controlsOn": "開",
+ "chat.controlsOff": "關",
"chat.shell": "Shell",
"terminal.title": "終端機",
"terminal.tabLabel": "終端機:{name}",