# AGENTS.md - timeline-web

## 目录用途

本目录是八字时辰时间轴的独立 React 体验壳：时辰时间沿纵轴连续展开，登记字段沿横轴投影；只消费 timeline 机器契约，不计算命理事实。

## 目录结构

```text
timeline-web/
├── AGENTS.md
├── .node-version
├── index.html
├── package.json
├── package-lock.json
├── playwright.config.ts
├── tsconfig.json
├── vite.config.ts
├── src/
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── WorkbenchToggle.tsx
│   ├── api.ts
│   ├── grid-state.test.ts
│   ├── grid-state.ts
│   ├── main.tsx
│   ├── styles.css
│   ├── types.ts
│   ├── url-state.test.ts
│   ├── url-state.ts
│   ├── vite-env.d.ts
│   ├── window-state.test.ts
│   └── window-state.ts
└── tests/
    ├── performance.spec.ts
    ├── point.spec.ts
    └── window.spec.ts
```

## 职责边界

- `.node-version` 与 `package-lock.json` 固定本地、CI、Docker 的 Node 22.23.2 和完整依赖图；禁止使用浮动 Node 或未锁依赖构建发布资产。
- `api.ts` 只传输 Point/Window/Track Catalog contract，以 15 秒 native abort 上界约束网络等待，并把非 2xx Problem 显式抛出。
- `grid-state.ts` 定义二维键盘移动、结构化复制文本、registry renderer 对应的只读中文值投影，以及内容驱动的列宽、表头高与行高估算；可以解释既有 track value 结构，但不得计算或改写命理事实。
- `url-state.ts` 只保存可分享的时间、地点、口径和轨道；`coordinate` 是每次请求必带的时间行头元数据，不是可切换投影列；natal context 永不进入 URL。
- `App.tsx` 只把现有产品结构装入三层工作台：Top 放独立开关，Middle 放定位/字段/候选控制，Bottom 放全视口时间轴；首屏直接查询纵向时辰网格，任意时间输入负责定位，并用 Point 的精确时点值覆盖锚点行的 Window 代表值。上下滚动自动续接有界 Query 页，横向字段由 registry 驱动并双轴虚拟化，不复制四柱、节气、十神或边界算法。
- `WorkbenchToggle.tsx` 是 Top 唯一 owner：以继承的 `32px` 字号在透明 Canvas 中测量并裁剪 Emoji 可见 Ink，按按钮真实底部加 `4px` 派生内容起点；开合状态只修改 Middle 的可见性与可访问性，不得进入 App 查询状态、改变 Bottom 几何、触发 renderer 重建或重新请求。
- 投影单元格只显示用户可读的具体命理文本，禁止渲染原始 JSON、英文 typed-state 或机器字段名；unknown/conflict/candidate_set/not_applicable 必须转换成明确中文状态，结构化 JSON 仅允许通过键盘复制进入剪贴板。
- 时辰名、区间、帧内断点与公历/命理时由时间行头合并展示；断点中的机器 track ID 必须通过 catalog 转成稳定字段名。`coordinate` 不得重复出现在横向投影网格或字段菜单。四柱、藏干、十二长生按“年/月/日/时柱 = 值”逐行展示，十神按“年/月/日/时干/支 = 值”逐行展示，节气只显示节气名，五行按“元素 = 数量（占比）”逐行展示。
- 时间轴所有用户可见结构化文本统一使用一行一个“字段 = 值”，禁止使用中点分隔符拼接字段。左上角是第一列的语义列头，只显示“时间”；横向投影关系由字段列头和网格 ARIA 表达，不重复显示轴向说明。纵轴时间行头必须使用 `role="rowheader"`；横轴字段表头必须使用 `role="columnheader"` 且只显示稳定字段名。普通投影值继续使用 `role="gridcell"`，不得用样式 class 冒充表头语义。
- `uncertainty` 是候选/未知/冲突的状态语义，不是独立命理投影列；前端必须从默认选择、deep-link 恢复、字段菜单和网格四处排除它，但不得删除 typed value 或候选分辨契约。
- Track Catalog 分组只用于字段控制面的单次逻辑分组，不得进入横轴字段表头或单元格 ARIA 名称。成功态不保留诊断状态栏，时间行头显示地支、紧凑民用时间范围、断点摘要与时间坐标，完整区间通过原生 `title` 保留。
- 网格尺寸必须由 `grid-state.ts` 的同一内容模型派生：列宽读取字段表头和当前有界窗口的格式化值，按 `8px` 步进限制在 `160px..360px`，并在同一候选会话内只增不减；表头高度以 `72px` 为下限按标题换行计算；时辰行高以 `120px` 为下限，按每个单元格在真实列宽下的换行数取最大值。总行宽是时间行头宽加全部投影列宽，总列高是表头高加全部时辰行高。禁止按 track ID 写十神等字段的尺寸特例，禁止单元格内滚动条或内容裁切。
- `window-state.ts` 只负责窗口页去重排序与纵向 prepend/eviction 后的像素 anchor 恢复；尺寸变化导致 anchor 行收缩时必须把像素偏移限制在该行内。轴算法使用 axis-neutral 命名，不解释 cursor。
- `${HOME}/.codex/Design.md` 对本图形探索页部分适用：DOM 必须使用原生标题、表单、链接、段落、列表、`details` 和 ARIA 语义；禁止自定义颜色、品牌字体、视觉间距、装饰边框、圆角、阴影、动画、响应式美化和装饰 class。`styles.css` 除双轴虚拟化必需的尺寸、定位、滚动、裁切、层叠、换行和 sticky 系统 `Canvas` 背景外，只允许三层工作台的精确结构属性：`4px` edge gap、按钮真实 Ink 盒、Middle 独立滚动/系统背景、Bottom 全视口和窄屏 Middle 全宽。动态行内样式另只允许 virtualizer 坐标与按钮测量变量；这些规则只属于独立 `/timeline`，不得泄漏到 `/web`。
- 每个 DOM class 必须一一对应现存的虚拟化结构规则；没有 CSS、脚本、测试或机器解析用途的 class 必须删除。即使完全移除 CSS，原生表单、标题、候选、时辰与命理文本仍须保持可读顺序；无 JavaScript 时 `noscript` 必须提供真实 `/web` 入口。
- `tests/` 以 mock contract 验证三层开合不改变 Bottom 几何或请求计数、窄屏默认收起、首屏纵向时间轴、上下滚动边缘自动续页、纵向锚点保持、sticky 时间行头、字段列表头、精确 Point 锚点覆盖、逐行等式文本、内容驱动双轴尺寸、无行内滚动条、二维交互和 `Design.md` 零美化边界；测试必须扫描整个页面，确保中点分隔符与机器 track ID 不会进入用户可见内容。显式加载按钮不能替代滚动验收，真实计算正确性由 fate-core 回归负责。
- `tests/performance.spec.ts` 用合成 1000 帧/100 轨道和真实 Chromium 采集 10 秒滚动、long-task、DOM、Query 页数与 CDP 内存证据；不得读取真实命主数据。

## 依赖方向

- `.github/apps/timeline-web -> /api/v1/timeline/{point,window,tracks} + .github/contracts/fate/bazi-formalization/timeline`
- 禁止 `.github/domains/*` 反向导入本目录。
