# 手机界面(界面半边详解) > `dsh-zen-remote` 的界面半边完整文档:信息架构、断点策略、安全区体系、调试徽章、兼容插件。 > 安装与快速上手见[根 README](../README.zh-CN.md);通道侧细节见 [remote-access.md](remote-access.md)。 > 界面基座衍生自 MIT 的 [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile)。 ## v2.0:从「窄屏适配」到「app 化外壳」 v1.0.0(fork 自 [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile))做的是「桌面布局压成窄屏」——保留桌面那套抽屉/侧栏,靠 CSS 把它们塞进手机宽度。v2.0 把手机端重写成「两级页面栈」的 app 信息架构:启动直接落会话列表主屏,点进去是独立的会话页,两页之间横向推入/推出,不再是「桌面页面的缩小版」。改造范围只到手机断点(<768px);平板保持 v1.0.0 抽屉行为不倒退,桌面逐像素不变。 这次重写建立在上游 v1.0.0 打下的底子上(分主题 CSS 结构、构建脚本、平板兼容段大量沿用),在此向 [mexiaosqwq](https://github.com/mexiaosqwq/dsh-web-mobile) 的原始工作表示感谢。 ## 效果 | 会话列表主屏 | 会话页 | 会话信息卡 | composer 权限 sheet | | --- | --- | --- | --- | | ![会话列表主屏](../assets/home.png) | ![会话页](../assets/session.png) | ![会话信息卡](../assets/info.png) | ![composer 权限 sheet](../assets/sheet.png) | ## 特性 ### 会话列表主屏 启动永远落在这一屏(推送深链直达会话除外),不再是桌面抽屉的窄屏版本: - 会话按圆角卡片排列(左侧头像色块 + 标题/状态两行 + 右侧时间),运行中的会话带状态点; - 顶部就是当前 workspace 名,点开弹出切换单(含「全部」); - 会话列表上方一条可横滑的插件入口 chips(文件浏览、用量、Session log 等,按你实际装的插件动态出现):手写适配的几个之外,**任何在官方侧栏注册了快捷入口的插件都会被自动发现**,装上就长出对应 chip,卸载后自动消失,不用等插件适配更新;行尾「···」打开显隐自定义单,选择保存在本地,刷新后还在; - 右下「+ 新建会话」文字胶囊,点按直接在当前 workspace 建会话; - 右上角设置图标是官方设置弹窗的入口(迁到这里,不再挤在抽屉里)。 ### 两级页面栈与转场 会话列表(主屏)与会话页是两个独立层,横向推入/推出转场;进会话页点行,回列表点头部返回按钮或左缘右滑。官方侧栏在手机断点下直接隐藏,不再是「点开的抽屉」——因为已经没有抽屉需要点开了。 ### 会话页头部五件套 会话页头部只留五样东西:返回 · 会话名(+ 运行状态点)· 当前视图名(对话/轨迹,带双点指示)· 信息卡入口 · better-sidebar 入口。官方的 Chat/Trajectory 页签视觉隐藏但 DOM 还在,点信息卡里的分段控件相当于代点它。 ### composer 重排 输入区整个按 app 输入条的思路重排: - 底排控件全部图标化(附件 · 权限 · 模型 · 上下文环 · 发送),不再是挤在一起的文字按钮; - 权限、模型这两个官方弹出菜单被 CSS 整成从底部升起的 sheet 样式(结构还是官方的,只换了皮肤); - `conversation.input.dock` 里的入口(git 分支等)压成一行可横滚的 mini chips,挪到输入卡外上方; - 例外是 DSH 原生的待办卡片(`data-testid="todo-panel"`):它不是 chip 而是可就地展开的卡片,因此单独豁免出 26px 胶囊笼子,保持整行宽度与自然高度——展开后的列表由官方自己限高 180px 并内部滚动,不会顶到输入框; - composer 顶部不画分割线,改用渐变 mask——消息滚动到顶部/底部时自然淡入淡出,比一条硬边界更贴近原生 app。 ### 会话信息卡 会话页头部的 ⓘ 打开一张底部信息卡,把原来占位置的统计栏收纳进来:轮数/步数/首字延迟/模型耗时/工具耗时/token 与缓存命中六格统计、Chat/Trajectory 切换、以及导出日志/重命名/Fork 会话/归档四个操作。 ### 回合过程折叠 Chat 视图里,同一回合内的推理块、工具调用块默认折叠成一条「过程 · N 步」摘要行,点开才展开;最终回复文本永远直接可见。运行中的回合摘要行会跟着步数实时更新。默认仅手机断点生效;插件行配置 `config.turnFoldDesktop: true` 可让所有客户端在任意宽度下启用折叠(见 README 配置一节),单个浏览器也可以访问一次 `?mobile-nav-turn-fold=1` 自行开启(按浏览器记忆,`=0` 关闭)。 ### 手势 - 会话页左边缘往右滑 = 返回列表(有浮层或工作台开着的话先关掉它); - 各类底部 sheet(信息卡、chips 自定义单、权限/模型菜单)支持下滑关闭。 **安卓系统返回手势(2026-08-20)**:安卓边缘内滑就是浏览器的「后退」,而手机端的 两级页面栈原本是纯 store 状态、从不压历史记录,于是后退发现无处可退 → 直接退出 PWA,再进来就是冷启动。插件自己的边缘手势也触发不了——那几十像素被系统手势先吃 掉,网页收不到 touch。 系统手势**拦不掉**(`systemGestureExclusionRects` 是原生 App 的 API),只能接住: `history-nav.ts` 维护一个「可关闭层」栈并镜像进 `history`。进会话压一层、开信息卡 再压一层,系统返回于是变成「先关信息卡、再退回列表」,在列表按返回才退出应用(根 页面本该如此)。官方对话框、composer 的权限/模型 sheet、第三方面板由 `effects/modal-back.ts` 统一接管:它们的开关状态都锁在各自组件里没有公开 setter, 但共享一个 DOM 契约——打开时是 `[aria-modal="true"]`、按 Escape 关闭,所以按契约 兜底即可,不必为每个插件写适配。 纪律:**层只能单向关闭**。组件不自己把状态改成关,而是调 `popLayer()` 回退历史, 由 `popstate` 去执行 `close`。两头各改各的必然错位,错位表现为「返回手势没反应」。 排除自有弹层(`:not([data-mobile-nav])`)也是同一个道理——信息卡曾经被显式层和 通用观察器各登记一次,一次返回退两层,历史退到根而会话页还在屏幕上。 ### 附件上传 composer 最左的回形针打开的是**手机本地**的文件选择器(iOS 上会弹相册/拍照/选取文件三选一)——官方那套文件选择器是在跑 DSH 的电脑上弹窗,手机远程用不了。图片和文件一视同仁:都上传到会话工作目录的 `.dsh-uploads/`,composer 上方出现可删除的预览 chip(图片缩略图、文件图标+文件名),并把 `@.dsh-uploads/文件名` 追加到输入框——发不发、什么时候发,由你按官方发送键决定,不会替你自动发出。 安全上的几句话:这个功能给插件的 host 半区新增了一条 HTTP 路由(`POST /_dsh/mobile-nav/upload`),只接受同源请求,文件名清洗后只取叶子名,写入路径校验不允许越出工作目录,单文件默认上限 20MB(可在 profile 里的插件行用 `maxUploadBytes` 调整)。远程访问时,这条路由和其它请求一样在 网关半边的配对墙之后。 ### 设置页与用量面板打通 主屏右上角的设置图标就是官方设置弹窗的入口(近全宽 sheet 皮肤,沿用 v1.0.0 的适配);如果你装了 dsh-usage-stats,它的用量/余额入口会作为一个 chip 出现在主屏 chips 行里,点开即用,不用先钻进设置。 ### 调试徽章 手机主屏顶栏连续点 5 下(或访问 `?mobile-nav-debug=1`)唤出悬浮诊断条,显示 URL/视口/媒体查询/浮层状态/JS 错误,手机端问题取证用。桌面调试时可以用 `?mobile-nav-inset=54` 或 `?mobile-nav-inset=54,34` 伪造安全区(假刘海),在没有真实刘海屏的浏览器里也能复现顶部/底部安全区相关的布局。 ### 安全区体系与 iOS 26.x 视口缩水 页面各表面的安全区留白统一收进两个可注入的 CSS 变量(`--mnav-sat`/`--mnav-sab`),平时读 `env(safe-area-inset-*)`,调试参数在时可以整体假冒——这是后面两层缓解的基础。 在这套变量之上,iOS 26.x 的 Safari/PWA 上出现了一个已知系统缺陷:独立(加到主屏)PWA 第一次弹出软键盘后,布局视口会永久性地比屏幕矮一截(`innerHeight`/`visualViewport.height`/`100dvh` 一起变小),直到整个 app 被彻底退出重开。这不是本插件的 bug,社区已有记录([参考文章](https://dev.to/cederhook/fixing-the-ios-standalone-pwa-keyboard-bug-that-shrinks-your-viewport-for-good-63d)),本插件只做了两层缓解,不是根治: 1. **视口下沉检测**——检测到系统在底部裁掉一截可视区域时,把底部安全区补偿归零,避免在已经空白的系统条上再叠一层空白; 2. **主动摘窗重排**——在输入框失焦、冷启动 1 秒/3 秒、切回前台后各触发一次「强制页面重排」,逼 WebKit 重新测量视口;能不能真正治好缩水,取决于具体机型和 iOS 版本,失败几次后会自动停手不再重试。 苹果修好这个系统级问题之前,两层缓解只能减少影响面,不能保证每次都完全复原。 ## 断点策略 | 宽度 | 行为 | | --- | --- | | < 768px(手机) | 本文档描述的 app 化布局全量生效 | | 768–1023px(平板) | 保持 v1.0.0 现状:抽屉 + 设置/预览浮层限宽居中,不倒退 | | ≥ 1024px(桌面) | 严格 no-op,逐像素与未安装时一致 | ## 更新日志 > `v2.0.0` 及更早的小节标题用的是手机端界面的代次,与 npm 包版本号不是一条线; > 两套编号并行容易看混,从 `1.1.0` 起本表统一按 **npm 包版本**记录。 ### 未发布 **修复** - composer 行里第三方插件的入口(`conversation.input.right`)在手机端整体移进模型弹层, 与「模型」「推理等级」并列成行——行是不换行的,模型名是唯一能让宽度的东西, 订阅插件的速度 chip(带文字约 70px)加上识图开关会把它挤没(2026-09-06 用户报) - 适配 dsh-vision-router(2.0.1 / 2.1.1):composer 识图开关在手机端压成 28px 圆形 图标钮(眼睛为 CSS mask 图标,aria-label / aria-pressed 照旧),不再把 模型名挤出 composer 行;桌面端保持插件原样(见兼容插件清单)。 - 适配 DSH `0.1.1`:活动 chip 点击失效(点了退回信息卡)。三处上游变化同时命中 旧方案——子代理入口从 `header.actions` 槽移进新的 `header.lineage` 槽(面包屑 内);其根节点 class 变成 `"ZKlsPq_root "`(**带尾随空格**,`[class$="_root"]` 整串后缀匹配直接失配);触发器**只认可信输入**,`.click()` 与合成 pointer/mouse 事件一律无效(真机逐项排除了插件样式的嫌疑后实测)。因此废弃 「转发点击」,改为 `effects/native-trigger-overlay.ts` 把**真实的官方触发器** 透明地铺在 pill 上——用户手指点到的就是官方元素,可信输入天然成立;弹层在 0.1.1 里 portal 到 body 自行定位(336px,屏内),旧的锚定/重锚 CSS 一并删除。 选择器统一改为按 header 定位、按 ARIA(`aria-haspopup="tree"`)区分,不再依赖 槽名与根节点类名。 ### 1.1.0 **新增** - 安卓系统返回手势接管:页面栈与各类弹层镜像进 `history`,返回变成「先关弹层 → 退回列表 → 退出应用」,不再一按就退出 PWA(见「手势」一节); - 会话页头部活动 chip:子代理 / 后台任务的计数与状态点(蓝色脉动=运行中、绿色= 已完成、琥珀=失败或被终止),点开直接弹官方原生列表; - 推送时机重做:只在等授权 / 等回答时必推,回合结束改为 opt-in,子代理跑完不推 (详见根 README 的「通知什么时候会响」)。 **改进** - 正文行距 28px → 1.65 倍、段间距 16px → 12px,手机上一屏能多看几行; - 会话列表主屏与会话页统一为同一个 `bg-base` 背景,卡片与 chips 改用次级表面, 切页面不再有明显色阶跳变。 **修复** - DSH 原生待办卡片在手机端点了没反应:`conversation.input.dock` 的 mini chip 规则给槽内所有条目套了 `max-height: 26px` + `overflow: hidden`,而官方待办 是就地展开的卡片而非 chip。展开动作其实一直是成功的(`aria-expanded` 翻了、 8 行待办以 216px 挂载完毕),只是整张卡被剪在 26px 里,看上去像点不动。已按 `data-testid="todo-panel"` 单独豁免(2026-08-20 真机视口实测)。 - 信息卡的缓存命中率升为主数据并保留一位小数,Token 收支降为副行——整数四舍五入 会把 99.6% 显示成 100%,这一位不是装饰; - 「关闭工作台」胶囊在深色下看不出边缘:填充是 `bg-base`、与身后表面同色,而黑色 投影落在深色背景上等于不存在。改用内描边画边界,投影只负责浮起; - 「不再弹出」按钮在手机上被官方确认按钮挤成两字宽:操作行是 flex,该按钮缺 `flex-shrink` 保护,补 `flex: 0 0 auto` + `min-width: max-content`。 **内部** - 构建与实测基准从 DSH `0.1.0-rc.6` 升到 `0.1.0-rc.7`(peer 与 dev 依赖同步); - README 的 release 徽章与依赖示例改由 `scripts/sync-doc-version.mjs` 跟着 `package.json` 走,挂在 `version` 生命周期脚本上,`pnpm test` 里带一致性检查。 - 已知问题更正:经反代访问时设置页空白,真实原因是 DSH 官方**设置 RPC 仅对回环 连接开放**(客户端按 `location.hostname` 判定),不是此前记录的「连接就绪超时」, 也与本插件无关(2026-08-20 真机 USB 调试 + 本机对照实测); - 测试从 46 增至 73:新增返回手势层栈 7 条、推送策略 13 条、活动 chip 契约若干。 ### v2.0.0 **新增** - 两级页面栈 app 化重排:会话列表主屏 + 独立会话页,横向转场取代桌面抽屉; - 会话页头部五件套(返回/会话名/视图指示/信息卡/better-sidebar 入口); - composer 重排:图标化底排控件、权限与模型菜单变身底部 sheet、顶部渐变 mask 取代硬分割线; - 会话信息卡:Chat/Trajectory 切换 + 六格统计 + 导出/重命名/Fork/归档四个操作,从占地方的底部统计栏搬进按需打开的 sheet; - 手势两件套:左缘右滑返回列表、各类底部 sheet 支持下滑关闭; - 回合过程默认折叠:多步回合收成一条「过程 · N 步」摘要行,最终回复始终可见; - 移动端附件上传:手机本地选择器 + 会话工作目录落盘 + composer 预览 chip + 草稿 @ 引用手动发送(见「附件上传」一节的安全说明); - 主屏插件入口 chips:任务看板/SSH/文件浏览/用量等按已装插件动态出现,显隐可自定义并本地持久化;未手写适配的插件也能自动长出 chip——从官方侧栏的快捷入口区里现场收割图标/文案/点击目标; - 调试徽章:顶栏 5 连点或 `?mobile-nav-debug=1` 唤出,新增 `?mobile-nav-inset=` 伪造安全区参数,方便桌面复现刘海相关布局。 **修复** - iOS 独立 PWA 键盘弹出后视口永久变矮的缓解(检测 + 主动重排,非根治,见上文说明); - 整页橡皮筋回弹导致顶栏/composer/FAB 跟手指一起位移的问题(锁文档滚动 + 消息区单独滚动); - PWA 冷启动首帧安全区未就位(首帧 `viewport-fit=cover` 改由 网关半边注入,不再等插件 JS 迟到补); - 会话行三点菜单弹出时抽屉/列表状态错乱、workbench 关闭按钮在桌面端泄漏等一系列实机反馈热修。 **内部** - 作用域与 网关半边 理清分工:本插件管排版与交互,网关半边 只管通道(认证/推送/PWA 壳),两边 CSS 不再重叠; - 新增 host 半区(`src/index.ts`):从纯 client 插件变为带一条上传路由的插件; - 大量实机反馈驱动的迭代热修(S1–S8 各切片)。 ### v1.0.0 **新增 / 改进** - 全屏预览浮层:预览底部浮层可一键放大到全视口(标题栏缩放按钮,刘海安全区适配),关闭预览或抽屉时自动还原; - 设置弹窗重构:分类标签收进单行横向滚动(细滚动条提示),顶部工具栏并入标签行共用一行,手机上隐藏「打开配置文件」按钮,选项区显著变大; - 分支胶囊:git 分支芯片移入输入卡片(todo 卡片之上),点击目标加大、按压即时反馈; - 会话头部紧凑与顺序稳定:模式徽标窄处省略、subagent 按钮居中、文件按钮固定保留; - 抽屉底部顺序:文件浏览 / 导出会话日志 固定在用量徽章之上; - 模型 ID 完整显示:composer 行内有空间时不再省略; - 文件树 / 预览交互:仅文件行可打开预览(目录行不再误弹缓存预览)、已选中行可重开、折叠按钮可靠关闭、预览打开时文件树自动让位; - 诊断徽章:`?mobile-nav-debug=1` 实时显示 URL/视口/媒体查询/浮层/JS 错误,默认 no-op。 **修复** - 目录选择器被设置弹窗规则劫持(issue #12):点笔形「编辑」进入手动输入后弹窗被顶到屏幕顶部、路径输入框被隐藏——目录选择器不再命中移动端设置适配规则; - 预览浮层全屏几何规则回归(1779cd4)以及关闭后全屏标记残留; - 抽屉底部徽章平票错序;subagent 弹层小屏溢出;统计栏误标 todo 面板。 **内部** - CSS 按主题拆为 `src/client/styles/`(base / layout / compat / misc),client effects 拆分为独立模块;构建器支持子目录递归内联。 ### v0.2.0 **新增 / 改进** - 平板/宽幅移动端(768–1023px):设置弹窗与 Explorer/Preview 浮层限宽居中,避免右侧大块空白; - Markdown 表格在移动端撑满消息列宽,减少表格内/右侧留白; - 用户消息气泡自适应宽度:短消息紧凑靠右,长消息可撑满消息列宽; - 模型选择器:模型名可完整显示,切换菜单在触发按钮上方水平居中; - 底部权限/模型选择/上下文小圈间距优化(12/10/12)。 **修复** - 预览浮层右上角缩放/收起按钮无法关闭的问题; - 用户消息气泡全宽后因 `content-box` 导致溢出屏幕的问题; - 模型切换菜单在小屏下偏左/溢出的问题。 ## 兼容插件 - [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar)(移动端全宽抽屉,本插件在会话页头部给它留入口位;手机上文件树里点 @ 引用后代点开关自动收起面板——面板全屏盖住会话页,草稿变化看不见,收起本身就是点击反馈)——本机实测 **0.15.0** - [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams)(AgentTeams 活动浮层:手机端挪到会话头部下方、避开安全区,会话列表页隐藏;子代理会话头部保留可点的父会话面包屑用于切回)——本机实测 **0.1.9** - [@ychris12138/dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats)(用量与余额,主屏 chips 行可直接打开;旧包名 `dsh-usage-stats` 已废弃,两个名字同时留在 profile 里会因重复的 `usage-stats` 行让 DSH 起不来)——本机实测 **0.2.9** - [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins)(定时任务,主屏 chips 行自动收割入口)——本机实测 **0.2.3** - dsh-at-file(@文件引用;它和本插件的附件 chip 读的是同一份草稿 token,手机端会为一个上传文件同时画出缩略图和文件名两条,故隐藏它 `.dsh-uploads/` 下那几行,其余 @ 引用不动;桌面端不隐藏——那边本插件的 chip 是关掉的,它是唯一的呈现)——本机实测 **0.6.7** - [@ace-zone/dsh-market](https://www.npmjs.com/package/@ace-zone/dsh-market)(插件市场;它的弹窗顶栏在手机上放不下,关闭的 × 被挤出面板外,故隐藏标语/版本号/官网链接三个装饰位并让标题省略号收缩,语言切换和 × 保留;认插件自己渲染的 `.dshm-ver[title="@ace-zone/dsh-market"]` 版本片,同名的其他"市场"插件不会被误伤)——本机实测 **0.1.66** - [dsh-vision-router](https://github.com/ysr666/dsh-vision-router)(视觉桥,composer 行内的识图开关胶囊;手机端压成 28px 圆形图标钮——原胶囊 👁+文字+激活 ✓ 约 90px 定宽,而手机 composer 行不换行、模型座位是唯一可收缩项,会被它挤没;按钮里只留一只 16px 眼睛:span 一律隐藏;2.1.x 起插件自带 svg 图标,只保留第一个 svg、用兄弟选择器隐掉其后的(当前是激活态的对钩),我们自己的 `::before` mask 眼睛降级为 `:has(svg)` 兜底、只给 2.0.x 的文字写法用;开合状态仍走插件自己的品牌描边/着色与 `aria-pressed`,可访问名走按钮自带的 aria-label)——本机实测 **2.0.1** 与 **2.1.1**。2.1.1 把 👁/✓ 从文字 span 换成 svg,只按 2.0.x 写的规则会渲染出**两只眼**且对钩无人隐藏(2026-09-04 用户报);回归由 `test/vision-router-compat-css.test.cjs` 守着 - [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all)(文件树 / 预览 / 任务看板 / SSH / 宠物 / 会话统计 / 远程配对 / 设置)——沿用上游兼容规则,本次未扩展 ## 安装 ```sh dsh plugin add dsh-zen-remote ``` 装完重启 `dsh web`。手动写法与本地开发装法见[根 README](../README.zh-CN.md#安装)。 ## 构建 ```sh pnpm install pnpm build # 产物 lib/ 与源码同步入库,改动源码后重新构建再提交 ``` ## 验证 - `pnpm verify` 类型检查、`pnpm test` 全量测试;`dsh --profile web --dump-config` 应出现插件层; - 自检脚本:`node scripts/check-sunk-viewport.mjs`、`node scripts/check-attach-upload.mjs`、`node scripts/check-upload-endpoint.mjs`(需 Node ≥ 23.6); - 移动端(390px):启动落会话列表、进出会话页转场、composer 权限/模型 sheet、信息卡统计与四个操作、附件上传; - 平板(768px):抽屉 + 限宽居中,与 v1.0.0 一致; - 桌面端(≥1024px):与未安装时一致。 ## 兼容性 需要 `:has()`(Chromium 105+);`prefers-reduced-motion: reduce` 下自动禁用动画。 ## License [MIT](../LICENSE)