dsh-mobile-flow 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -15
- package/README.zh.md +19 -15
- package/lib/client.js +74 -50
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Two mobile fixes for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) Web UI:
|
|
6
6
|
|
|
7
7
|
1. **In-flow composer** — on narrow screens (≤720px) the input bar and AI confirmation cards scroll with the page instead of pinning to the viewport floor, so swiping up gives the message transcript the full screen back.
|
|
8
|
-
2. **Native input takeover** — on narrow screens the draft surface becomes a native `<textarea>`, sidestepping the Lexical contenteditable IME flow that swallows text on Android keyboards (voice input most of all).
|
|
8
|
+
2. **Native input takeover** — on narrow screens **and on any touch device** (a large tablet included) the draft surface becomes a native `<textarea>`, sidestepping the Lexical contenteditable IME flow that swallows text on Android/HarmonyOS keyboards (voice input most of all).
|
|
9
9
|
|
|
10
10
|
A client-side overlay plus one slot component. No product source is modified; removing the plugin restores the stock behavior exactly.
|
|
11
11
|
|
|
@@ -21,18 +21,19 @@ The stock composer's text surface is a **Lexical contenteditable** (`createEdito
|
|
|
21
21
|
|
|
22
22
|
## What it does
|
|
23
23
|
|
|
24
|
-
On viewports ≤720px (the same breakpoint the official question card uses):
|
|
24
|
+
On viewports ≤720px (the same breakpoint the official question card uses) the first four are layout fixes; the rest key off **"narrow screen or touch device"** — the IME problem has nothing to do with screen size, and a HarmonyOS tablet loses typed text at 1280px just the same (user report 2026-10-05: MatePad + Doubao voice input):
|
|
25
25
|
|
|
26
26
|
1. **The composer seat joins the document flow** — input bar and confirmation cards now live at the end of the transcript. Swipe up and they scroll out of view; the transcript becomes full-screen.
|
|
27
27
|
2. **Short conversations still dock to the bottom** — when the transcript is shorter than one screen, the message area stretches so the composer stays flush with the viewport floor, visually identical to the stock behavior. Long conversations get the full-screen treatment.
|
|
28
28
|
3. **Floating controls re-anchor** — the back-to-bottom button and turn navigator no longer reserve height for the sticky seat and sit close to the viewport floor again.
|
|
29
29
|
4. **Slim edges** — the content-layer paddings that keep the transcript, header, input card, and confirmation cards well off the screen edges (16–32px per side) shrink to a slim 4px side inset (8px top), reclaiming most of the wasted width without the cramped feel of full-bleed. The page shell itself never adds whitespace; the gaps come entirely from these paddings.
|
|
30
|
-
5. **No auto-focus on session switch** — the stock UI returns focus to the input box on every mount / session switch (a desktop convenience), which pops the on-screen keyboard over half the screen on phones.
|
|
31
|
-
6. **Workspace row actions always visible** — the trailing buttons on workspace and session rows (the ⋯ menu with rename/delete and the + for a new session in that workspace) surface on hover only; touch has no hover, so narrow viewports show them whenever the sidebar list is rendered.
|
|
32
|
-
7. **Native input takeover** — the draft surface is drawn by a native textarea whose metrics match the stock box exactly (geometry is measured from the live composer every frame the card relayouts). Enter still performs the official send gesture (including `/` menu arbitration and the busy-Enter policy), file pastes are forwarded to the official attachment intake, and locked states follow the product's own editability gate.
|
|
30
|
+
5. **No auto-focus on session switch** — the stock UI returns focus to the input box on every mount / session switch (a desktop convenience), which pops the on-screen keyboard over half the screen on phones (and is just as unwelcome on a tablet). Narrow viewports and touch devices swallow that programmatic focus; tapping the input box still focuses it normally.
|
|
31
|
+
6. **Workspace row actions always visible** — the trailing buttons on workspace and session rows (the ⋯ menu with rename/delete and the + for a new session in that workspace) surface on hover only; touch has no hover, so narrow viewports and touch devices show them whenever the sidebar list is rendered.
|
|
32
|
+
7. **Native input takeover** — narrow screens and touch devices are taken over by default (a mouse-driven desktop is left alone); the draft surface is drawn by a native textarea whose metrics match the stock box exactly (geometry is measured from the live composer every frame the card relayouts). Enter still performs the official send gesture (including `/` menu arbitration and the busy-Enter policy), file pastes are forwarded to the official attachment intake, and locked states follow the product's own editability gate.
|
|
33
33
|
8. **The send button follows the field** — as soon as the field holds text, the send button reads as enabled (a live blue circle instead of the disabled grey one) and one tap sends it (queue/steer while a turn streams). While typing that state is painted by pure CSS with **zero DOM writes**, so the "no DOM movement while typing" invariant stands.
|
|
34
|
+
9. **The switch is not a phone control** — the tool-row 「输入法✓/✗」 chip renders on every viewport, so a large tablet always has a one-tap say in this; it is also the fallback for a device the default gate fails to recognise as touch.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
**A mouse-driven desktop is completely unaffected** (a touch device is taken over however wide it is — that is the point of this change).
|
|
36
37
|
|
|
37
38
|
## How it works
|
|
38
39
|
|
|
@@ -45,7 +46,7 @@ The plugin ships a browser half (`exports["./client"]`, declared via `dsh.client
|
|
|
45
46
|
## Requirements
|
|
46
47
|
|
|
47
48
|
- DeepSeek Harness Web profile (`dsh web`), any recent 0.1.x release
|
|
48
|
-
- Selectors and slot contracts verified against 0.1.2-rc.1
|
|
49
|
+
- Selectors and slot contracts verified against 0.1.2-rc.1, 0.1.5-rc.1 and 0.2.0-rc.2 (2026-10-04, by the live probes below plus a static audit of the shipped client bundles); they target product slot contracts that are stable within a version line but may need small updates after a major product revamp
|
|
49
50
|
|
|
50
51
|
## Install
|
|
51
52
|
|
|
@@ -65,16 +66,18 @@ Restart `dsh web`, then refresh the browser page.
|
|
|
65
66
|
|
|
66
67
|
## Overrides (optional)
|
|
67
68
|
|
|
68
|
-
The native takeover is on by default **on narrow viewports
|
|
69
|
+
The native takeover is on by default **on narrow viewports and on any touch device** — screen size is not the criterion. To override it — for instance to exercise it on a desktop browser:
|
|
69
70
|
|
|
70
71
|
```js
|
|
71
72
|
// Browser console, same origin, persists
|
|
72
73
|
localStorage.setItem("dsh-mobile-flow:input", "off"); // never take over
|
|
73
|
-
localStorage.setItem("dsh-mobile-flow:input", "on"); //
|
|
74
|
-
localStorage.setItem("dsh-mobile-flow:input", "force"); //
|
|
75
|
-
localStorage.removeItem("dsh-mobile-flow:input"); // back to default
|
|
74
|
+
localStorage.setItem("dsh-mobile-flow:input", "on"); // always take over (any viewport, desktop included)
|
|
75
|
+
localStorage.setItem("dsh-mobile-flow:input", "force"); // same as "on" (what the URL flag =1 writes)
|
|
76
|
+
localStorage.removeItem("dsh-mobile-flow:input"); // back to default: narrow or touch
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
The easier route is the 「输入法✓/✗」 chip in the tool row: it reports **what this device is actually doing** (not the stored preference), and one tap flips between "always" and "never".
|
|
80
|
+
|
|
78
81
|
One-shot override: `?dsh-mobile-input=1` (force) / `=0` (off).
|
|
79
82
|
|
|
80
83
|
Diagnostics have their own persisted switch (the tool-row 诊断 button writes it; no URL typing needed):
|
|
@@ -95,20 +98,21 @@ localStorage.removeItem("dsh-mobile-flow:growth"); // back to default
|
|
|
95
98
|
|
|
96
99
|
## Verify
|
|
97
100
|
|
|
98
|
-
Open a session on a phone (or a desktop DevTools window narrowed to ≤720px)
|
|
101
|
+
Open a session on a phone (or a desktop DevTools window narrowed to ≤720px) — **a tablet works the same at any width**:
|
|
99
102
|
|
|
100
103
|
- Swipe up a few screens: the input bar and confirmation cards scroll away with the messages.
|
|
101
104
|
- The draft surface should be a native textarea (the page carries `[data-mobile-input]`). Type with an IME or voice input: text must not be cleared.
|
|
102
105
|
- Send with the send button or Enter (same gesture as the stock composer).
|
|
103
106
|
- Send-button state: **with text in the field the button is live** (the grey→live flip follows the field's content); one tap sends one message, first tap included.
|
|
104
|
-
- The tool row shows **
|
|
107
|
+
- The tool row shows **one chip**, 「输入法✓」(the escape hatch); a 600 ms long press on it calls out the 「诊断」 chip.
|
|
108
|
+
- On 0.2.0 the first load of a fresh browser profile shows the product's 「预览版说明」 preview notice; acknowledge it (继续) before verifying — it takes focus and paints over the composer, so nothing under it can be typed into.
|
|
105
109
|
- The v0.6.0 regression to watch: **no layout movement while typing** — the field's height stays put until focus
|
|
106
110
|
leaves, and only then follows the content.
|
|
107
|
-
- Widen the window
|
|
111
|
+
- Widen the window: the stock behavior returns on a **mouse-driven** desktop; a tablet/phone stays taken over however wide it gets (the tool row keeps the 「输入法✓」 chip).
|
|
108
112
|
|
|
109
113
|
## Update discipline (this plugin's convention)
|
|
110
114
|
|
|
111
|
-
1. **Hot updates only**: after a change,
|
|
115
|
+
1. **Hot updates only**: after a change, **a page refresh is enough** — never make the user clear caches. Client bundles are served `immutable`, but the rev is recomputed from the artifact file's mtime/size by the client-modules HMR watch (measured 2026-10-06: new code served in a fresh page with no `dsh web` restart); only a disabled watch needs a restart.
|
|
112
116
|
2. **Always keep an escape hatch**: anything that takes over stock UI behaviour must be revertible from the page itself (see the 输入法✓/✗ button below), so a bad build never blocks normal use.
|
|
113
117
|
|
|
114
118
|
## Compatibility hardening (v0.5.1 / v0.5.2 / v0.6.0, after HarmonyOS / ArkWeb feedback)
|
package/README.zh.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)Web UI 的移动端插件,两件事:
|
|
6
6
|
|
|
7
7
|
1. **in-flow composer**:窄屏(≤720px)时输入框与 AI 确认选择框随页面滚动,而不是钉死在屏幕底部——上滑阅读时消息区拿回全屏空间。
|
|
8
|
-
2.
|
|
8
|
+
2. **原生输入框接管**:窄屏**或触屏设备**(大屏平板同样在内)时输入区换成原生 `<textarea>`,绕开 Lexical contenteditable 在 Android/鸿蒙输入法(尤其是语音输入)下吞字/清空已输入文字的问题。
|
|
9
9
|
|
|
10
10
|
纯客户端实现(CSS overlay + 一个 slot 组件),不修改产品源码;卸载插件即完全还原官方行为。
|
|
11
11
|
|
|
@@ -21,18 +21,19 @@
|
|
|
21
21
|
|
|
22
22
|
## 实际效果
|
|
23
23
|
|
|
24
|
-
≤720px
|
|
24
|
+
≤720px 视口(与官方问题卡片的断点一致)下,前四条是布局修复;后几条按「窄屏或触屏设备」生效——**输入法问题跟屏幕大小无关**,鸿蒙平板在 1280px 上照样吞字(2026-10-05 用户反馈:MatePad + 豆包语音输入):
|
|
25
25
|
|
|
26
26
|
1. **composer 座位回归文档流** —— 输入框和确认卡片位于消息流末尾。上滑时随内容滚出视口,消息全屏可读。
|
|
27
27
|
2. **短会话仍然贴底** —— 消息不足一屏时消息区自动拉伸,输入框保持贴视口底,视觉与官方行为完全一致;长会话则享受全屏阅读。
|
|
28
28
|
3. **浮动控件重新锚定** —— 回到底部按钮、轮次导航不再为吸底座位预留高度,回到贴近视口底的位置。
|
|
29
29
|
4. **纤细边距** —— 消息区、会话头部、输入卡、确认卡片与屏幕边缘之间的内容层 padding(每边 16–32px)收窄为左右 4px、顶部 8px 的纤细边距,收复大部分被浪费的宽度,又不像完全贴边那样局促。页面外壳本身不产生空白,间隙完全来自这些 padding。
|
|
30
|
-
5. **切会话不自动聚焦** ——
|
|
31
|
-
6. **workspace 行操作按钮常显** —— 分组行与会话行尾部的按钮(⋯ 菜单含重命名/删除、+ 在该 workspace
|
|
32
|
-
7. **原生输入框接管** ——
|
|
30
|
+
5. **切会话不自动聚焦** —— 官方在每次挂载/切换会话时把焦点还给输入框(桌面便利设计),手机上会弹出输入法占掉半屏,平板上也一样挡事。窄屏与触屏设备上拦截这类程序化聚焦;你主动点输入框时照常聚焦。
|
|
31
|
+
6. **workspace 行操作按钮常显** —— 分组行与会话行尾部的按钮(⋯ 菜单含重命名/删除、+ 在该 workspace 新建会话)官方仅在悬停时显示;触屏没有悬停,窄屏与触屏设备下侧边栏列表渲染时常显这两个按钮。
|
|
32
|
+
7. **原生输入框接管** —— 窄屏或触屏设备默认接管(鼠标操作的桌面端不动),输入区绘制成原生 textarea,外观、内边距、字号行高与官方完全一致(几何按官方输入区实时测量对齐)。回车仍是官方发送手势(含 `/` 菜单仲裁、busy-Enter 策略),粘贴图片转发给官方附件入口,锁定态跟随官方可编辑性开关。
|
|
33
33
|
8. **发送按钮跟「输入框里有什么」走** —— 只要输入框里有字,右下角发送按钮就是**可点的亮色**(不再是灰的),点一下即发送(运行中的回合则是排队/引导);打字期间这个状态由纯 CSS 驱动,**零 DOM 写入**,不破坏「打字期零 DOM 变动」这条硬约束。
|
|
34
|
+
9. **开关不分屏幕大小** —— 工具行的「输入法✓/✗」在任何视口都显示(不只在手机上),所以「大屏平板要不要用原生输入框」永远有一键可切的地方;它同时也兼容「本插件没识别出这是触屏设备」的情况。
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
**鼠标操作的桌面端完全不受影响**(触屏设备即使屏幕很宽也会接管——这正是本次改动要的结果)。
|
|
36
37
|
|
|
37
38
|
## 工作原理
|
|
38
39
|
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
## 环境要求
|
|
46
47
|
|
|
47
48
|
- DeepSeek Harness Web profile(`dsh web`),任意较新的 0.1.x 版本
|
|
48
|
-
- 选择器与 slot 契约已在 0.1.2-rc.1
|
|
49
|
+
- 选择器与 slot 契约已在 0.1.2-rc.1、0.1.5-rc.1 与 0.2.0-rc.2 上验证(0.2.0 复核于 2026-10-04,用下方真机探针 + 装机客户端包静态审查);它们瞄准产品 slot 契约,同一版本线内稳定,产品大改版后可能需要小幅更新
|
|
49
50
|
|
|
50
51
|
## 安装
|
|
51
52
|
|
|
@@ -65,16 +66,18 @@ dsh plugin --profile web add github:imroc/dsh-mobile-flow
|
|
|
65
66
|
|
|
66
67
|
## 开关(可选)
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
原生输入框接管默认**在窄屏(≤720px)或触屏设备上启用**——跟屏幕多大无关。需要覆盖时(例如在桌面浏览器上验证):
|
|
69
70
|
|
|
70
71
|
```js
|
|
71
72
|
// 浏览器控制台,同源页面均生效,持久化
|
|
72
73
|
localStorage.setItem("dsh-mobile-flow:input", "off"); // 永远用官方输入框
|
|
73
|
-
localStorage.setItem("dsh-mobile-flow:input", "on"); //
|
|
74
|
-
localStorage.setItem("dsh-mobile-flow:input", "force"); //
|
|
75
|
-
localStorage.removeItem("dsh-mobile-flow:input"); //
|
|
74
|
+
localStorage.setItem("dsh-mobile-flow:input", "on"); // 永远接管(任何视口,含桌面)
|
|
75
|
+
localStorage.setItem("dsh-mobile-flow:input", "force"); // 与 "on" 等价(URL 参数 =1 写的也是它)
|
|
76
|
+
localStorage.removeItem("dsh-mobile-flow:input"); // 恢复默认:窄屏或触屏设备自动接管
|
|
76
77
|
```
|
|
77
78
|
|
|
79
|
+
页面上更省事的办法:点工具行的「输入法✓/✗」——它显示的是**当前这台设备实际有没有在接管**(不是存了什么偏好),点一下即在「永远接管」与「永远不接管」之间切换。
|
|
80
|
+
|
|
78
81
|
一次性覆盖也可用 URL 参数:`?dsh-mobile-input=1`(强制)/`=0`(关闭)。
|
|
79
82
|
|
|
80
83
|
诊断开关同理(持久化,点工具行「诊断」按钮即可,无需手打 URL):
|
|
@@ -95,19 +98,20 @@ localStorage.removeItem("dsh-mobile-flow:growth"); // 恢复默认
|
|
|
95
98
|
|
|
96
99
|
## 验证
|
|
97
100
|
|
|
98
|
-
手机上(或桌面 DevTools 缩到 ≤720px
|
|
101
|
+
手机上(或桌面 DevTools 缩到 ≤720px 窄窗)打开一个会话;**平板上(任何宽度)同样适用**:
|
|
99
102
|
|
|
100
103
|
- 往上滑几屏——输入框和确认卡片应随消息滚出屏幕。
|
|
101
104
|
- 输入区应是原生 textarea(页面里存在 `[data-mobile-input]`)。用输入法打字/语音输入,文字不应被自动清除。
|
|
102
105
|
- 发送:点发按钮或按回车(回车等同官方发送手势)。
|
|
103
106
|
- 发送按钮状态:**输入框里有字时按钮就是亮色可点的**(灰→亮由字段内容驱动);点一下即发送,一次点击一条消息。
|
|
104
|
-
-
|
|
107
|
+
- 工具行日常只有**一个小按钮**「输入法✓」(逃生通道);在它上面**长按 600ms** 才会叫出「诊断」(再长按收起)。
|
|
108
|
+
- 0.2.0 上全新浏览器 profile 首次打开会弹产品的「预览版说明」提示框,先点「继续」再验证——它会夺走焦点并盖在输入框上,不点掉的话下面什么都打不进去。
|
|
105
109
|
- 关键回归点(v0.6.0):**打字期间不要有任何布局跳动**——字段高度在聚焦期间不变,失焦后才按内容长高。
|
|
106
|
-
-
|
|
110
|
+
- 拉宽窗口:**用鼠标的桌面才恢复官方行为**;平板/手机即使屏幕很宽也应保持接管(工具行有「输入法✓」)。
|
|
107
111
|
|
|
108
112
|
## 更新纪律(本插件的约定)
|
|
109
113
|
|
|
110
|
-
1.
|
|
114
|
+
1. **必须支持热更新**:改完**刷新页面即可**,不要让用户自己清缓存——客户端产物带 `immutable` 缓存,但 rev 是按产物文件 mtime/size 重算的(client-modules 的 HMR 监视,2026-10-06 实测:改完不重启 dsh-web,新开页面即拿到新码);只有监视被关掉时才需要重启 dsh-web。
|
|
111
115
|
2. **必须预留逃生通道**:凡是接管官方 UI 行为的功能,都要能在页面上一条操作回退(见下文「输入法✓/✗」按钮),改坏了不影响正常使用。
|
|
112
116
|
|
|
113
117
|
## 兼容性加固(v0.5.1 / v0.5.2 / v0.6.0,HarmonyOS / ArkWeb 实测反馈)
|
package/lib/client.js
CHANGED
|
@@ -88,13 +88,26 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
" display: none !important;",
|
|
89
89
|
" }",
|
|
90
90
|
"}",
|
|
91
|
+
"/* Touch devices of any width: a tablet has no hover either, so the same",
|
|
92
|
+
" two affordances stay visible there. Only these two rules ride the",
|
|
93
|
+
" coarse-pointer query — the layout transforms above are phone tuning",
|
|
94
|
+
" (a tablet keeps the stock docked composer and stock paddings). */",
|
|
95
|
+
"@media (any-pointer: coarse) {",
|
|
96
|
+
" [class*='_rowActions'] {",
|
|
97
|
+
" display: inline-flex !important;",
|
|
98
|
+
" }",
|
|
99
|
+
" [class*='_sessionRow'] [class*='_time'] {",
|
|
100
|
+
" display: none !important;",
|
|
101
|
+
" }",
|
|
102
|
+
"}",
|
|
91
103
|
].join("\n");
|
|
92
104
|
|
|
93
105
|
/* Native-input takeover styles. These ride OUTSIDE the media query on
|
|
94
|
-
purpose: the JS half decides when the takeover is live (narrow viewport
|
|
95
|
-
by default, or an explicit override) and marks the card
|
|
96
|
-
data-mobile-input-active, so a forced takeover also works on a
|
|
97
|
-
viewport. Every rule below is inert until that marker is
|
|
106
|
+
purpose: the JS half decides when the takeover is live (a narrow viewport
|
|
107
|
+
or a touch device by default, or an explicit override) and marks the card
|
|
108
|
+
with data-mobile-input-active, so a forced takeover also works on a
|
|
109
|
+
desktop viewport. Every rule below is inert until that marker is
|
|
110
|
+
present. */
|
|
98
111
|
var INPUT_CSS = [
|
|
99
112
|
"/* ── dsh-mobile-flow: native textarea takeover ── */",
|
|
100
113
|
"/* The stock draft surface keeps its box (it is what sizes the card) but",
|
|
@@ -197,11 +210,13 @@ window.__ModuleLoader__.load({
|
|
|
197
210
|
" -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z' fill='%23000'/%3E%3C/svg%3E\") center / 16px 16px no-repeat;",
|
|
198
211
|
" mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z' fill='%23000'/%3E%3C/svg%3E\") center / 16px 16px no-repeat;",
|
|
199
212
|
"}",
|
|
200
|
-
"/* Escape hatch in the composer tool row (
|
|
201
|
-
" returns the stock input box when the native
|
|
202
|
-
" we cannot test on
|
|
203
|
-
"
|
|
204
|
-
"
|
|
213
|
+
"/* Escape hatch in the composer tool row (every viewport — a tablet needs it",
|
|
214
|
+
" as much as a phone): one tap returns the stock input box when the native",
|
|
215
|
+
" one misbehaves on a device we cannot test on, and it is also how a device",
|
|
216
|
+
" the default gate does not detect asks for the takeover. Its neighbour",
|
|
217
|
+
" switches the diagnostics surfaces on and off (a tokenized URL is rewritten",
|
|
218
|
+
" before plugins load, so a phone cannot always pass ?dsh-mobile-input=...",
|
|
219
|
+
" from the address bar). */",
|
|
205
220
|
"[data-mobile-input-toggle],",
|
|
206
221
|
"[data-mobile-input-copy],",
|
|
207
222
|
"[data-mobile-input-diagnostics] {",
|
|
@@ -333,7 +348,8 @@ window.__ModuleLoader__.load({
|
|
|
333
348
|
diagnostics.add("h=" + window.innerHeight
|
|
334
349
|
+ " growth=" + readGrowthMode()
|
|
335
350
|
+ " input=" + String(readOverride())
|
|
336
|
-
+ " narrow=" + String(window.matchMedia !== undefined && window.matchMedia(NARROW_QUERY).matches)
|
|
351
|
+
+ " narrow=" + String(window.matchMedia !== undefined && window.matchMedia(NARROW_QUERY).matches)
|
|
352
|
+
+ " touch=" + String(isTouchDevice()));
|
|
337
353
|
ctx.effect(function () {
|
|
338
354
|
return watchKeyboard(diagnostics);
|
|
339
355
|
});
|
|
@@ -377,11 +393,12 @@ window.__ModuleLoader__.load({
|
|
|
377
393
|
}, createNativeInputToggle(diagnostics)));
|
|
378
394
|
}
|
|
379
395
|
|
|
380
|
-
/* 5) No auto-focus on session switch (narrow viewports):
|
|
381
|
-
unlock effect focuses the contenteditable on every mount /
|
|
382
|
-
switch (packages/client/ui-conversation/.../InputBar.tsx, deps
|
|
396
|
+
/* 5) No auto-focus on session switch (narrow viewports and touch devices):
|
|
397
|
+
InputBar's unlock effect focuses the contenteditable on every mount /
|
|
398
|
+
session switch (packages/client/ui-conversation/.../InputBar.tsx, deps
|
|
383
399
|
[locked, sessionId, editor]) — a desktop convenience that pops the
|
|
384
|
-
IME over half the screen on phones
|
|
400
|
+
IME over half the screen on phones AND on tablets (a big screen has no
|
|
401
|
+
room for a software keyboard either). Swallow programmatic focus() on
|
|
385
402
|
the composer's editable unless the user just tapped it (or is
|
|
386
403
|
keyboard-navigating); taps reach the box via the normal focus path.
|
|
387
404
|
Question-card textareas (autoFocus on purpose) are not contenteditable
|
|
@@ -410,7 +427,7 @@ window.__ModuleLoader__.load({
|
|
|
410
427
|
var nativeFocus = HTMLElement.prototype.focus;
|
|
411
428
|
HTMLElement.prototype.focus = function (opts) {
|
|
412
429
|
if (
|
|
413
|
-
narrow.matches &&
|
|
430
|
+
(narrow.matches || isTouchDevice()) &&
|
|
414
431
|
this.isContentEditable === true &&
|
|
415
432
|
typeof this.closest === "function" &&
|
|
416
433
|
this.closest("[data-composer-seat]") !== null &&
|
|
@@ -477,6 +494,24 @@ window.__ModuleLoader__.load({
|
|
|
477
494
|
the `dsh-mobile-input` query parameter for one page load. */
|
|
478
495
|
var OVERRIDE_KEY = "dsh-mobile-flow:input";
|
|
479
496
|
var NARROW_QUERY = "(max-width: 720px)";
|
|
497
|
+
/* The takeover answers a MOBILE IME, and a mobile IME has nothing to do with
|
|
498
|
+
the viewport width: a HarmonyOS/Android tablet runs the same keyboard (and
|
|
499
|
+
loses typed text the same way) at 1280px. Touch is the honest signal the
|
|
500
|
+
page can read, so the default gate is "narrow OR touch" — a mouse-driven
|
|
501
|
+
desktop, where the Lexical editor is not at risk, stays untouched.
|
|
502
|
+
(Reported 2026-10-05 from a MatePad + Doubao voice input.) */
|
|
503
|
+
var TOUCH_QUERY = "(any-pointer: coarse)";
|
|
504
|
+
|
|
505
|
+
function isTouchDevice() {
|
|
506
|
+
try {
|
|
507
|
+
if (typeof window.matchMedia === "function" && window.matchMedia(TOUCH_QUERY).matches === true) return true;
|
|
508
|
+
} catch (error) { /* fall through to the capability probe */ }
|
|
509
|
+
try {
|
|
510
|
+
if (typeof navigator === "object" && navigator !== null
|
|
511
|
+
&& typeof navigator.maxTouchPoints === "number" && navigator.maxTouchPoints > 0) return true;
|
|
512
|
+
} catch (error) { /* no navigator: treat as a mouse device */ }
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
480
515
|
|
|
481
516
|
function readOverride() {
|
|
482
517
|
try {
|
|
@@ -806,33 +841,23 @@ window.__ModuleLoader__.load({
|
|
|
806
841
|
return media !== null && media.matches === true;
|
|
807
842
|
}
|
|
808
843
|
|
|
809
|
-
/**
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
var
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
var update = function () { setNarrow(media.matches === true); };
|
|
824
|
-
update();
|
|
825
|
-
if (typeof media.addEventListener === "function") media.addEventListener("change", update);
|
|
826
|
-
else if (typeof media.addListener === "function") media.addListener(update);
|
|
827
|
-
return function () {
|
|
828
|
-
if (typeof media.removeEventListener === "function") media.removeEventListener("change", update);
|
|
829
|
-
else if (typeof media.removeListener === "function") media.removeListener(update);
|
|
830
|
-
};
|
|
831
|
-
}, [media]);
|
|
832
|
-
return narrow;
|
|
844
|
+
/**
|
|
845
|
+
* Whether the takeover should own the surface on this viewport.
|
|
846
|
+
*
|
|
847
|
+
* Preference first ("on"/"force" = always, "off" = never); with no stored
|
|
848
|
+
* preference the DEVICE decides: narrow viewports (the original gate) or
|
|
849
|
+
* any touch device (a tablet's IME is the same problem at a larger width).
|
|
850
|
+
* @param media - the narrow-viewport matcher, or null.
|
|
851
|
+
*/
|
|
852
|
+
function takeoverLive(media) {
|
|
853
|
+
var override = readOverride();
|
|
854
|
+
if (override === "on" || override === "force") return true;
|
|
855
|
+
if (override === "off") return false;
|
|
856
|
+
if (media !== null && media.matches === true) return true;
|
|
857
|
+
return isTouchDevice();
|
|
833
858
|
}
|
|
834
859
|
|
|
835
|
-
/**
|
|
860
|
+
/** Viewport + device flag, live across rotation, resize and override edits. */
|
|
836
861
|
function useTakeoverViewport() {
|
|
837
862
|
var media = React.useMemo(function () {
|
|
838
863
|
try {
|
|
@@ -1761,17 +1786,19 @@ window.__ModuleLoader__.load({
|
|
|
1761
1786
|
}
|
|
1762
1787
|
|
|
1763
1788
|
/**
|
|
1764
|
-
* Escape hatch in the composer tool row (
|
|
1765
|
-
* swaps the native input back to the stock editor and remembers it,
|
|
1766
|
-
* device the takeover misbehaves on is never a dead end.
|
|
1789
|
+
* Escape hatch in the composer tool row (every viewport, not just phones):
|
|
1790
|
+
* one tap swaps the native input back to the stock editor and remembers it,
|
|
1791
|
+
* so a device the takeover misbehaves on is never a dead end. It is also the
|
|
1792
|
+
* only way to ask for the takeover on a device this build does not detect —
|
|
1793
|
+
* a large tablet reports touch, but a device that lies needs the switch.
|
|
1767
1794
|
* @returns the slot entry component.
|
|
1768
1795
|
*/
|
|
1769
1796
|
function createNativeInputToggle(diagnostics) {
|
|
1770
1797
|
return function NativeInputToggle() {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
var
|
|
1798
|
+
/* The chip reports WHAT THE TAKEOVER IS DOING on this device, not the raw
|
|
1799
|
+
preference: on a wide mouse-only desktop the default is "off" and the
|
|
1800
|
+
chip must say so (and offer to turn it on). */
|
|
1801
|
+
var on = useTakeoverViewport();
|
|
1775
1802
|
var revealState = React.useState(function () { return readReveal(); });
|
|
1776
1803
|
var revealed = revealState[0];
|
|
1777
1804
|
var setRevealed = revealState[1];
|
|
@@ -1797,7 +1824,6 @@ window.__ModuleLoader__.load({
|
|
|
1797
1824
|
};
|
|
1798
1825
|
React.useEffect(function () {
|
|
1799
1826
|
var update = function () {
|
|
1800
|
-
setPreference(readOverride());
|
|
1801
1827
|
setRevealed(readReveal());
|
|
1802
1828
|
};
|
|
1803
1829
|
window.addEventListener(PREFERENCE_EVENT, update);
|
|
@@ -1810,8 +1836,6 @@ window.__ModuleLoader__.load({
|
|
|
1810
1836
|
endPress();
|
|
1811
1837
|
};
|
|
1812
1838
|
}, []);
|
|
1813
|
-
if (!narrow) return null;
|
|
1814
|
-
var on = preference !== "off";
|
|
1815
1839
|
var label = (on ? "原生输入框:已开启(点击改用官方输入框)" : "原生输入框:已关闭(点击启用)")
|
|
1816
1840
|
+ ";长按显示/隐藏诊断按钮";
|
|
1817
1841
|
var diagnosing = diagnosticsModes().length !== 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mobile-flow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Mobile fixes for the DeepSeek Harness Web UI: on narrow (≤720px) screens the input bar and AI confirmation cards scroll with the page instead of pinning to the viewport floor, edges slim down,
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Mobile fixes for the DeepSeek Harness Web UI: on narrow (≤720px) screens the input bar and AI confirmation cards scroll with the page instead of pinning to the viewport floor, edges slim down, and touch devices (any width, a tablet included) no longer auto-focus the input or hide the workspace row actions; the draft surface becomes a native textarea on narrow screens and on any touch device (ArkWeb/Android-IME hardened: zero DOM writes while typing, resizing only at commit points, with a tool-row escape hatch and an on-device diagnostics bench) so mobile IMEs stop clearing typed text and stop dropping the keyboard; the send button follows the field's content (live as soon as there is text, one tap sends), not the deliberately stale machine draft.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|