# 0007. 书单不禁用鼠标 - 状态:已接受 - 日期:2026-09-22 - 修订:ADR-0006 中「鼠标契约全局唯一」一条 ## 背景 ADR-0006 把鼠标契约收敛成一条:`stream` 态下鼠标移动 1px / 点击 / pointerdown 立刻回到 `closed`。这条规则对**阅读**是对的 —— 被撞见时手总会先碰鼠标,那一下必须立刻见效。 但它对**书单**是错的。书单(伪装成「最近的任务」)是一个**只能用鼠标操作**的界面: - 点书名打开一本书; - 点 `✕` 删除一本书; - 把 `.txt` / `.epub` 拖进窗口导入。 鼠标一动就收场,意味着这三件事**一件都做不成** —— 书单实际上退化成了一个只读列表。 实测就是这样:手刚移到书名上,界面已经回到 DSH 真实界面了。 ## 决定 书单在屏幕上时(`listVisible`),鼠标事件**不参与收场**;这个状态下唯一的出口是快捷键。 "在屏幕上"有两种来源,`keys.ts` 的 `isListSurface` 把它们合成一个判断: - 用户按 `L` 把书单打开了(盖在内容流上); - **根本没有打开的书** —— 此时 `StreamView` 直接 `return list`,书单就是覆盖层的全部内容。 只看前者会让空书库下鼠标一动就收场,连 "+ attach file" 都点不到。 | 界面 | 鼠标 | 快捷键 | | --- | --- | --- | | `stream`(读书) | 移动 1px / 点击 / pointerdown → `closed` | → `closed` | | `stream` + 书单在屏幕上 | **原样交给书单自己用** | → `closed` | 要点: - **书单不是第三个状态。** 覆盖层仍然只有 `closed` / `stream` 两态(ADR-0006 的结论不变); `listVisible` 是 `stream` 内部的一个子标记,只影响鼠标怎么裁决。ADR-0006 否掉的是 "多一个**界面状态**",不是"裁决时多一个输入"。 - 这个标记住在 store 而不是组件 state 里:做裁决的是挂在 window 上的**全局**处理器 (`index.tsx` 的 `onPointer`),它读不到 React 组件内部状态。见 `store.ts` 的 `setListVisible`。 - **默认关闭**。不传这个标记时,裁决与 ADR-0006 的原契约逐字相同。 ## 后果 - **换来可用性**:书单终于能点开、能删、能把文件拖进去。 - **代价是书单开着时"手碰鼠标就消失"失效。** 这个取舍是明知的: 1. 书单是**主动打开**的短暂界面,不会像阅读那样长时间挂着; 2. 书单本身就是一层伪装 —— 它长得像「recent tasks」,书名摆在那儿的读法就是任务名; 3. 真要一步收起,快捷键还在,而且它是此时唯一的出口,不需要判断当前状态。 - **但必须说清楚**:书单里的**书名是明文的**,对认识这些书名的人来说,它比正文更容易被认出来。 "开着书单长时间挂在屏幕上"不是这个设计鼓励的用法。 - 实现上多了一处同步:`StreamView` 用 effect 把书单开合发布到 store,卸载时清掉 (覆盖层关闭会卸载它,不清的话下次进内容流会带着上次的书单状态)。