# Client Rules

适用于 `src/client/` 全域。本文件是 Client 半区**跨模块通用规则**的唯一承接点。
测试规则见 `tests/AGENTS.md`；跨层依赖与全仓库规范见根 `AGENTS.md`。

## 本层承担什么

Client 是**单页工作台**，只负责交互与可视化，承担三类职责：

1. **界面投影与交互编排**：把 Host 的事实投影为可读界面，把用户意图翻译成对 Host 的语义化调用；
2. **自有的界面与编辑状态**：只持有「界面如何呈现、用户正在编辑什么」这类状态，不拥有 Host Runtime 的事实；
3. **宿主装配与协调**：插件入口只负责注册、装配、样式与生命周期汇总，**不承载业务语义**。

判断标准：一段代码若回答「界面如何呈现、如何交互」，属 Client；若回答「事实是什么、何时成立、如何持久化」，属 Host。

## 状态所有权

- **单一状态机拥有业务事实**：同一事实只允许一处所有权与一处写入边界；状态变化只经该状态机的语义化 action（reducer 分支）完成；
- **渲染态与业务态分离**：悬停、拖拽中、展开/折叠、临时校验提示等瞬时渲染态归组件本地状态，**不得**写入业务状态；
- **派生数据由选择器计算**，不得存储、双写或与源数据各自维护；选择器必须是纯函数；
- **组件不得直接修改不属于自己的状态**：只允许 dispatch 自己的语义化 action，禁止直接改状态对象字段、禁止跨模块改写他人内部状态；
- 无对应 action 时，**增加明确 action**，而不是在组件内维护第二份副本或绕过状态机直改。

## 数据流与数据访问边界

- **单向数据流**：事实流向固定为「Host → 统一网络边界 → 状态机 → 选择器 → 组件 → 语义化 action → 统一网络边界 → Host」，不得出现反向捷径或旁路；
- **网络访问只经统一网络边界与注入面**：组件与 hook 不得自建第二条数据访问路径（不得直接发起请求、不得绕过注入面自取宿主服务）；
- 端点名、协议常量等一律引用共享契约，不得在调用点硬编码字面量；
- **稳定错误码由调用方按语义处理**（如乐观锁冲突按冲突语义刷新/提示），不得只显示通用文案后静默继续；
- 网络边界的失败语义必须显式：区分「业务失败」与「传输/解析失败」，并保留可判定的错误码。

## 副作用与异步生命周期

- 订阅、定时器、监听、Observer、长驻 DOM 与独立渲染根，**必须可追踪责任者与清理方式**；谁创建谁释放，释放路径与创建路径成对出现；
- **卸载后不得写状态**：异步回调进入前先校验所属生命周期仍然有效；
- **异步结果必须防竞态**：按归属校验（结果是否属于当前会话/当前文档）或主动取消（AbortController）；两者都没有的异步写入视为缺陷；
- 常驻资源的释放必须**幂等**，且与宿主卸载顺序一致；不得依赖「不会发生」的时序假设；
- 副作用不得藏在名称看似纯计算的函数中。

## 层级边界

- **components（表现与交互）**：负责渲染与用户交互，只消费 props、选择器结果与注入的动作面；不感知网络、持久化与宿主服务细节；
- **hooks（状态组合、生命周期、交互编排）**：负责把状态、派生数据与动作组合成界面可用的输入；一个 hook 只承担一个职责，**不得**把无关的多重职责（如同时管轮询、导入导出与布局）捆在一起；
- **lib（与 UI 生命周期无关的逻辑）**：纯计算与转换、网络访问、持久化；**不得依赖任何 UI 模块**（组件、hook、宿主接入），也不得读取界面状态；被 lib 与组件共用的纯常量应下沉到 lib 或共享契约，而不是让 lib 反向引用组件；
- **依赖方向单向**：components / hooks 可依赖 lib；lib 不得反向依赖，也不得出现循环依赖；
- 纯计算与状态无关的转换优先实现为纯函数：输入即全部事实，不得隐式读取全局或环境。

## 共享契约

- 与 Host 共享的类型与纯常量**一律引用共享契约**（`src/host/shared/`），禁止在 Client 复制形状、枚举、常量或默认值清单；
- 同一语义只允许一处本体：Client 侧只做投影与消费，不得定义第二套映射；
- Client 不得引入 Host 运行时模块（依赖方向约束见根 `AGENTS.md`），只允许引用共享契约中的纯类型与纯常量；
- 修改共享契约必须同步检查 Client 调用方、Host 侧消费者与测试。

## 宿主接入

- 与官方插槽、侧边栏、会话、词典等宿主能力的交互**集中在宿主接入层**；业务组件与 hooks 不得直接触达官方服务对象；
- 官方形状按**最小消费面**收窄声明并做运行时守卫：缺失即降级，不因未知字段抛错，不伪造官方不存在的契约；
- 模块级注册表与 DOM 搬运必须有**明确生命周期与释放路径**（登记、迁移、归还、释放各自有唯一责任者），并在宿主卸载时清空登记；
- 宿主能力不可用时插件其余部分必须照常生效，不得整体失效。

## 文案与样式

- 面向用户的文案**必须走词典**，`zh` / `en` 同步新增，禁止在组件内硬编码用户可见字符串；
- **样式集中在统一样式模块**，以语义化类名与设计 token 表达；**禁止随状态拼接样式**（状态只切换类名/token，不拼接样式值）；
- 不得在 Client 内复制设计 token 的具体取值。
- 以上规则由 `tests/contract/` 下的客户端门禁机械锁定。

## 修改前确认

1. 这个事实的所有者是谁？是否在 Client 内产生了第二份事实源或第二处写入路径？
2. 是否新开了绕过统一网络边界或注入面的数据访问路径？
3. 是否引入无法追踪责任者、无法清理或会在卸载后写入的副作用？异步结果有没有归属校验或取消路径？
4. 这段代码属于 components / hooks / lib / 宿主接入中的哪一层？依赖方向是否仍然单向、无循环？
5. 是否复制了共享契约的形状或常量、绕过了词典、或随状态拼接了样式？

## 核心原则

单一状态机拥有业务事实；界面只投影不拥有 Host 状态；数据只经统一网络边界单向流动；副作用责任者可追踪、卸载即清理、异步防竞态；层次分明、依赖单向、契约共享、文案与样式集中。
