# dsh-web-mobile

> 把 DSH Web 在手机浏览器上变成接近 App 的体验：侧栏改抽屉、文件预览改为底部弹层、设置弹窗近全宽，并在桌面端完全无感。

## Metadata

- Author: [@mexiaosqwq](https://github.com/mexiaosqwq)
- Repo: <https://github.com/mexiaosqwq/dsh-web-mobile.git>
- GitHub: [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile)
- Stars: 38
- Language: JavaScript
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `deepseek-harness`, `dsh`, `dsh-plugin`, `mobile`, `mobile-ui`, `plugin`, `responsive`, `web-ui`
- Forks: 9
- Open Issues: 0
- Last push: 2026-08-21T01:37:31.000Z
- Added: 2026-08-17T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:mexiaosqwq/dsh-web-mobile
```

## Wiki

## 一句话定位
把 DSH Web 在 ≤1023px 的视口上重排为接近原生 App 的体验：侧栏变成可滑出的抽屉、文件树与预览变成底部弹层、设置弹窗近全宽并把工具栏并入分类栏；在 ≥1024px 时完全无操作。

## 核心能力
- 侧栏改抽屉：会话头部新增「打开目录」图标按钮，点按抽出/收起抽屉；首页/空白页等没有会话头的阶段，由左上角浮动按钮接管开关
- 文件浏览一键直达：会话头部新增「文件浏览」图标按钮，直接把 dsh-web-ui 的 aionui 资源管理器展开成底部弹层，无需先开抽屉
- 抽屉底部双胶囊：抽屉打开时底部多出「文件浏览」「导出会话日志」两个胶囊按钮；会话日志按钮在没有当前会话时自动禁用
- 状态栏与安全区适配：写入 `viewport-fit=cover` + `theme-color`，按 `env(safe-area-inset-top)` 把所有可见面顶到刘海之下；安卓系统状态栏颜色随明暗主题切换
- 预览浮层全屏：dsh-web-ui 预览浮层标题栏右侧新增一个全屏切换按钮，按下后浮层填满视口（含安全区），再次按下恢复
- 关闭交互全套：抽屉打开时点击遮罩关闭、按 Esc 关闭（弹窗打开时让位）、点击抽屉里的会话行/任务板/搜索结果收起抽屉；会话行的三点按钮被显式排除，点它打开的菜单不会被收起
- 媒体查询限宽：768–1023px（折叠屏、平板竖屏）下，弹窗与浮层改为居中并限宽至 `min(100vw-32px, 720px)`，避免内容挤在屏幕一角
- 诊断浮条：URL 加 `?mobile-nav-debug=1` 后，右上角出现实时显示视口、断点匹配、帧标记、浮层可见性、最近 5 条 JS 错误的悬浮面板

## 技术实现
- **语言**: TypeScript（React 18 + DSH client SDK）；客户端编译为 CommonJS 并由 `scripts/build-client.mjs` 自定义打包器内联成 `lib/client.js`
- **关键依赖**: `@deepseek-ai/cordis`（宿主容器）、`@deepseek-ai/dsh-client-runtime`（cordis fiber + ClientContext）、`@deepseek-ai/dsh-client-ui-slots`（slot 注册）、`react ^18.2.0`（头部 / 抽屉底部 React 组件）
- **架构模式**: 双面插件。Host 端 `src/index.ts:7` 暴露一个空 `apply()`，仅用于在宿主 Loader 里登记 `dsh-mobile-nav` 行；真正的工作全部在 Browser 端 `src/client/index.tsx:27-97`：注入一张移动端 `<style data-plugin>`、注册三个 slot（`conversation.session.header.actions`、`sidebar.footer.action`，以及 README 与 AGENTS.md 提到的 `settings.general.item`，但 `src/client/index.tsx:66-97` 仅注册前两个）、挂一组生命周期受管的 DOM effect
- **入口文件**: 客户端 `src/client/index.tsx:27` 的 `apply(ctx)`；cordis patch 在 `cordis.patch.yml:4-5` 写入一行 `insert: { id: dsh-mobile-nav, name: '@dsh-external/dsh-mobile-nav' }`
- **核心机制**: `src/client/effects/reconciler-core.ts` 维护零导入的脏键注册表，按动画帧合并 MutationObserver 突发，`src/client/effects/phone-chrome.ts:124-165` 把这层 DOM-free 引擎接到单一全树观察者上；任务声明自己的脏键（如 `data-aionui-preview-open`）后，只有真正变化的属性才会唤醒相关任务

## 适用场景
在手机浏览器（包括安卓 Chrome / iOS Safari）上使用 DSH Web、嫌默认三栏布局在窄屏下挤不下的用户；或需要在桌面窗口临时拉窄时仍然可用、又不想失去桌面版体验的开发者。安装一次即可同时改善首页、会话进行中、设置、文件树、预览五个场景。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH 客户端 SDK | `@deepseek-ai/dsh-client-runtime` `^0.1.0-rc.6` 等系列（同版本号） | `package.json:48-57` 声明 peerDependencies，涵盖 locale / runtime / ui-primitives / ui-slots / ui-conversation / ui-layout / ui-settings / ui-sidebar / session-log-export |
| Cordis | `@deepseek-ai/cordis` `^4.0.1` | `package.json:47` 声明，由宿主提供 |
| React | `^18.2.0` | `package.json:52` 声明，仅 peer |
| Node.js | 未声明 | `package.json` 未设置 `engines`；运行 `pnpm verify` 与 `pnpm build` 的最低 Node 由 `@types/react` 与 TS 6.x 间接决定 |
| 平台 | 浏览器 | `package.json:39-44` 声明 `dsh.client.platform: "web"`，无 host 端代码，无原生模块依赖 |
| 宿主注入项 | `slots` / `layout` / `locale` / `sessionLogDownload` | `src/client/index.tsx:19` 的 `inject = [...]` 列表，缺失任意一个 cordis fiber 会拒绝加载 |
| 第三方兼容插件 | dsh-web-ui-all 0.1.14、dshmarket 1.2.2、dsh-usage-stats 0.1.2、dsh-genui 0.8.3 | `README.md:50-55` 明示；UI 兼容补丁写在 `src/client/styles/compat.css.ts` |

## 安装方式
```bash
dsh plugin --profile web add github:mexiaosqwq/dsh-web-mobile
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `?mobile-nav-debug=1` | URL 查询参数 | 在右上角显示诊断浮条（视口、断点、帧标记、预览/资源管理器列可见性、预览/资源管理器是否打开、当前 phase、最近 5 条 JS 错误）。仅用于手机端排查，不影响功能 | 不传 = 不显示 |

本插件没有 Schema / 配置文件 / 环境变量形式的配置项（`src/` 内未读取 `process.env`、`Schema` 字段或 `localStorage` 键，仅调试面板会读取 URL 查询参数）。

## 常见问题

**Q: 安装后需要重启 DSH 吗？**

A: 需要。`README.md:63` 明确写出「装完重启 `dsh web`」。插件在 host 启动时通过 `cordis.patch.yml` 注入 `dsh-mobile-nav` 行，运行中的 `dsh web` 进程不会动态加载。

**Q: 桌面端会被这个插件影响吗？**

A: 不会。源码里 `src/client/styles/misc.css.ts:140-150` 在 `(min-width: 1024px)` 媒体查询里把所有 `[data-mobile-nav=...]` 控件设为 `display:none !important`；同时 `src/client/effects/phone-chrome.ts:23-42` 的 `installMobileEffect` 只在 `matchMedia('(max-width: 1023px)')` 命中时安装副作用。`>=1024px` 时不创建 MutationObserver、不写 DOM、不注册事件。

**Q: 预览浮层全屏按钮在哪儿？怎么用？**

A: 仅在 dsh-web-ui 的预览浮层打开时出现在标题栏右侧（约 `right: 36px; top: 8px`）。点一下预览浮层铺满整个视口（含状态栏安全区），再点一下恢复到底部弹层尺寸。`aria-label` 会随状态在「全屏预览」/「退出全屏」之间切换。

**Q: 点「文件浏览」按钮没反应怎么办？**

A: 这通常不是按钮坏了，而是当前已经打开了预览浮层（`compat.css.ts:104-115` 让预览盖住资源管理器）。先在预览浮层标题栏点收起按钮（chevron），再点「文件浏览」。`AGENTS.md:117` 明确把这点列入「Pitfalls」。

**Q: 升级后界面回到桌面版布局了怎么办？**

A: 拉宽窗口或用桌面浏览器（≥1024px）就会回到桌面版；如果在手机视口下仍然是桌面版布局，先确认 `dsh web` 进程确实重启了，再加 `?mobile-nav-debug=1` 看顶部 W 视口宽度与 `mq≤1023` 是否为 true。

**Q: 怎么卸载？**

A: `dsh plugin --profile web remove dsh-web-mobile`，然后重启 `dsh web`。本插件无 host 端进程、无持久化文件、无外部网络请求，卸载即清空。

## 上手难度
入门 — 不需要任何代码改动，安装重启即可生效；唯一的开关是排查用的 `?mobile-nav-debug=1` URL 参数，普通用户可以完全忽略。

## 已知问题与限制
- CSS 依赖 `:has()` 选择器，要求 Chromium 105+；老旧 WebView 上的 `:has()` 规则会被静默丢弃，可能出现抽屉/弹层异常。源码在 `src/client/styles/layout.css.ts:1-3` 与 `compat.css.ts` 大量使用 `:has()`
- 当用户系统设置启用了「减少动态效果」，`@media (prefers-reduced-motion: reduce)` 会让浮层的滑入动画与变换过渡关闭（`compat.css.ts:210-216`）
- 预览浮层与资源管理器存在互斥关系：同时只能开一个；预览打开时点「文件浏览」按钮看似失效是预期行为。`AGENTS.md:117` 把这点列为排查「按钮看似失效」的优先级检查项
- 抽屉内点击关闭逻辑使用 capture 阶段事件，并显式排除会话行的三点菜单按钮（`phone-chrome.ts:415`）；如果将来第三方插件的会话行按钮选择器形态变化，需要同步更新这里的选择器，否则会误收起抽屉
- 全树 reconciler 的任务在 `src/client/effects/phone-chrome.ts:48-88` 用模块级 `installed` 标志防重复挂载；同一宿主环境下热重载插件会先卸载再重建，期间会有极短的闪烁

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-web-mobile](https://deepseek-plugin.org/plugins/mexiaosqwq/dsh-web-mobile)
Wiki generated by AI (model: `MiniMax-M3`)
