手机界面(界面半边详解)

August 28, 2026 · View on GitHub

dsh-zen-remote 的界面半边完整文档:信息架构、断点策略、安全区体系、调试徽章、兼容插件。 安装与快速上手见根 README;通道侧细节见 remote-access.md。 界面基座衍生自 MIT 的 mexiaosqwq/dsh-web-mobile

v2.0:从「窄屏适配」到「app 化外壳」

v1.0.0(fork 自 mexiaosqwq/dsh-web-mobile)做的是「桌面布局压成窄屏」——保留桌面那套抽屉/侧栏,靠 CSS 把它们塞进手机宽度。v2.0 把手机端重写成「两级页面栈」的 app 信息架构:启动直接落会话列表主屏,点进去是独立的会话页,两页之间横向推入/推出,不再是「桌面页面的缩小版」。改造范围只到手机断点(<768px);平板保持 v1.0.0 抽屉行为不倒退,桌面逐像素不变。

这次重写建立在上游 v1.0.0 打下的底子上(分主题 CSS 结构、构建脚本、平板兼容段大量沿用),在此向 mexiaosqwq 的原始工作表示感谢。

效果

会话列表主屏会话页会话信息卡composer 权限 sheet
会话列表主屏会话页会话信息卡composer 权限 sheet

特性

会话列表主屏

启动永远落在这一屏(推送深链直达会话除外),不再是桌面抽屉的窄屏版本:

  • 会话按圆角卡片排列(左侧头像色块 + 标题/状态两行 + 右侧时间),运行中的会话带状态点;
  • 顶部就是当前 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,社区已有记录(参考文章),本插件只做了两层缓解,不是根治:

  1. 视口下沉检测——检测到系统在底部裁掉一截可视区域时,把底部安全区补偿归零,避免在已经空白的系统条上再叠一层空白;
  2. 主动摘窗重排——在输入框失焦、冷启动 1 秒/3 秒、切回前台后各触发一次「强制页面重排」,逼 WebKit 重新测量视口;能不能真正治好缩水,取决于具体机型和 iOS 版本,失败几次后会自动停手不再重试。

苹果修好这个系统级问题之前,两层缓解只能减少影响面,不能保证每次都完全复原。

断点策略

宽度行为
< 768px(手机)本文档描述的 app 化布局全量生效
768–1023px(平板)保持 v1.0.0 现状:抽屉 + 设置/预览浮层限宽居中,不倒退
≥ 1024px(桌面)严格 no-op,逐像素与未安装时一致

更新日志

v2.0.0 及更早的小节标题用的是手机端界面的代次,与 npm 包版本号不是一条线; 两套编号并行容易看混,从 1.1.0 起本表统一按 npm 包版本记录。

未发布

修复

  • 适配 dsh-vision-router(2.0.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(移动端全宽抽屉,本插件在会话页头部给它留入口位;手机上文件树里点 @ 引用后代点开关自动收起面板——面板全屏盖住会话页,草稿变化看不见,收起本身就是点击反馈)——本机实测 0.15.0
  • @nanmicoder/dsh-agent-teams(AgentTeams 活动浮层:手机端挪到会话头部下方、避开安全区,会话列表页隐藏;子代理会话头部保留可点的父会话面包屑用于切回)——本机实测 0.1.9
  • @ychris12138/dsh-usage-stats(用量与余额,主屏 chips 行可直接打开;旧包名 dsh-usage-stats 已废弃,两个名字同时留在 profile 里会因重复的 usage-stats 行让 DSH 起不来)——本机实测 0.2.9
  • @opendsh/dsh-plugin-scheduled-tasks(定时任务,主屏 chips 行自动收割入口)——本机实测 0.2.3
  • dsh-at-file(@文件引用;它和本插件的附件 chip 读的是同一份草稿 token,手机端会为一个上传文件同时画出缩略图和文件名两条,故隐藏它 .dsh-uploads/ 下那几行,其余 @ 引用不动;桌面端不隐藏——那边本插件的 chip 是关掉的,它是唯一的呈现)——本机实测 0.6.7
  • @ace-zone/dsh-market(插件市场;它的弹窗顶栏在手机上放不下,关闭的 × 被挤出面板外,故隐藏标语/版本号/官网链接三个装饰位并让标题省略号收缩,语言切换和 × 保留;认插件自己渲染的 .dshm-ver[title="@ace-zone/dsh-market"] 版本片,同名的其他"市场"插件不会被误伤)——本机实测 0.1.66
  • dsh-vision-router(视觉桥,composer 行内的识图开关胶囊;手机端压成 28px 圆形图标钮——原胶囊 👁+文字+激活 ✓ 约 90px 定宽,而手机 composer 行不换行、模型座位是唯一可收缩项,会被它挤没;三条文字 span 全部隐藏,改由 CSS mask 画一只 currentColor 眼睛,开合状态仍走插件自己的品牌描边/着色与 aria-pressed,可访问名走按钮自带的 aria-label)——本机实测 2.0.1
  • dsh-web-ui 全家桶(文件树 / 预览 / 任务看板 / SSH / 宠物 / 会话统计 / 远程配对 / 设置)——沿用上游兼容规则,本次未扩展

安装

dsh plugin add dsh-zen-remote

装完重启 dsh web。手动写法与本地开发装法见根 README

构建

pnpm install
pnpm build   # 产物 lib/ 与源码同步入库,改动源码后重新构建再提交

验证

  • pnpm verify 类型检查、pnpm test 全量测试;dsh --profile web --dump-config 应出现插件层;
  • 自检脚本:node scripts/check-sunk-viewport.mjsnode scripts/check-attach-upload.mjsnode 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