# dsh-deep-sleep

DSH 插件：把官方 Web UI 中任务进行中的状态提示换成 **"Deep Sleeping... (1分20秒)"**。

- **纯 client 插件**：host 半部为空壳（no-op），全部逻辑在浏览器侧 `lib/client.js`
- **零运行时依赖**：client 半部不 import 任何模块；host 半部仅 type-only 引用 `cordis`（构建期擦除）
- **幂等替换**：MutationObserver 拦截插入/改写，React 每次重建标签都会被再次替换
- **零源码 patch**：不碰 DSH checkout 任何文件，纯官方扩展缝（bundle insert 行 + `dsh.client` 声明）

## 官方原版长什么样

`@deepseek-ai/dsh-client-ui-chat` 的 `RunningStatus` 从 locale 字典取标签，并带上**实时用时**：

| 状态 | 官方文案（en / zh） |
|---|---|
| `startTime` 未知 | `Deep diving` / `深度求索中` |
| 已知 | `Deep diving for 12s ···` / `深度求索中，用时 12秒 ···` |

秒表由官方以 `LIVE_RUN_CLOCK_INTERVAL_MS`（1s）驱动，每秒重建一次字符串。该标签既没有 slot，也没有对插件开放的 formatter，因此本插件在 DOM 层改写。

## 原理

两个细节决定了实现方式：

**1. 用时从官方字符串里现读，不自建计时器。**
官方每秒重写一次标签文本，所以观察器每次都用「官方此刻写的那个字符串」重新解析用时。解析是**无状态**的：中途加入的轮次、或在任务进行中刷新的页面，都会立刻显示真实用时，也不会累积漂移。

单位同时覆盖 `1h 2m 3s` 与 `1小时2分3秒`，输出统一为中文单位，排列规则与官方一致——不补前导零、满 60 秒才出分钟、满 3600 秒才出小时：

```
12s        → Deep Sleeping... (12秒)
1m 20s     → Deep Sleeping... (1分20秒)
1h 2m 3s   → Deep Sleeping... (1小时2分3秒)
无用时      → Deep Sleeping...
```

**2. 必须同步 `data-shimmer-text`。**
`TextShimmer` 会把同一个字符串**再画一份**：装饰副本的节点里没有文本，字形来自 CSS `content: attr(data-shimmer-text)`，靠一条移动的 mask 扫过高亮。只改文本节点的话，扫光会继续在真实标签上扫过**旧字符串**的字形，出现重影错位。因此每轮都会把该属性从真实文本重新同步一次。

无障碍那一份（`role="status"` + `aria-live`）保持**不含用时**：官方本来就让它稳定不跳动，以免每秒播报。

## 安装

```sh
# 从 npm 安装（首选）
dsh plugin --profile web add dsh-deep-sleep

# 或从 git 直装（预构建策略，无需 allowBuilds 授权）
dsh plugin --profile web add github:peach0x33a/dsh-deep-sleep

# 或本地目录（开发/验证）
dsh plugin --profile web add /path/to/dsh-deep-sleep
```

安装后重启 `dsh web`，浏览器硬刷新（`Ctrl+Shift+R`），任务进行中即可看到 "Deep Sleeping..."。

## 开发

```sh
pnpm install        # 仅 devDependencies（全部公共包，无 private peer）
pnpm run typecheck  # tsc --noEmit
pnpm test           # vitest（jsdom）：解析/格式化 + DOM 改写 + 构建产物契约
pnpm run build      # tsdown 双 bundle：lib/index.mjs（host ESM）+ lib/client.js（browser CJS）
```

预构建策略：`lib/` 入库（不进 `.gitignore`），无 `prepare` 脚本；改源码后必须 `pnpm run build` 并提交 `lib/`。

## 检查

- [ ] `package.json` 含 `dsh.bundle.patch` + `dsh.client`（`platform: "web"`，`immediately: true`）
- [ ] `exports["./client"]` 指向构建产物 `lib/client.js`
- [ ] `lib/client.js` 以 `window.__ModuleLoader__.load({ id: "dsh-deep-sleep", factory })` 包裹，factory 返回 `{ name, inject, apply }`（`test/bundle-contract.test.js` 断言）
- [ ] `cordis.patch.yml` 含 insert 行（`id` / `name` 齐全）
- [ ] host 半部不导出 default（A6）
- [ ] `peerDependencies` = `cordis`（optional），无 devDependencies 冒充
- [ ] `pnpm test` 全绿

> 测试里的 fixture 按官方 `RunningStatus` + `TextShimmer` 的**真实产物**搭建：真实标签一个文本节点，装饰副本只有 `data-shimmer-text`、没有文本节点。改动选择器时请一并更新 fixture。
