# UniDesk Frontend Reference

UniDesk 前端是 React 组件化工业控制台，不追求展示型大屏效果。设计目标是高信息密度、低装饰、低字号、低间距，并让调度、节点、事件和配置入口在单屏内快速切换。

## Source Contract

frontend 应用源码必须使用 TypeScript + React，禁止在 `src/components/frontend` 中维护手写 `.js` / `.jsx` 应用源码。浏览器请求的 `/app.js` 只能由 frontend Bun server 从 `src/components/frontend/src/app.tsx` 及其 TSX imports 转译生成；`public/` 目录只保存 HTML/CSS 等静态资产，不提交手写 `app.js`。

`src/components/frontend/src/app.tsx` 只承担应用 shell、登录、全局数据加载、主模块/子标签路由和通用控制台页面。用户服务前端必须模块化到独立 TSX 文件，禁止继续把所有业务页面堆进 `app.tsx`。当前长期固定入口为：`todo-note.tsx` 承载 Todo Note 工作台，`findjob.tsx` 承载 FindJob 工作台，`pipeline.tsx` 承载 Pipeline 工作台，`met-nonlinear.tsx` 承载 MET Nonlinear 训练编排工作台，`claudeqq.tsx` 承载 ClaudeQQ QQ 消息网关工作台，`baidu-netdisk.tsx` 承载 Baidu Netdisk 存储工作台，`code-queue.tsx` 承载 Code Queue 控制台，`oa-event-flow.tsx` 承载统一 OA 事件流控制台，`k3sctl.tsx` 承载 k3s Control Plane 页面；新增用户服务也必须按同样规则新增独立页面模块，并由 `app.tsx` 只做导入和路由分发。

## Shared Dialog Component

通用模态弹窗统一复用 `src/components/frontend/src/dialog.tsx` 的 `UniDeskDialog`。新增确认弹窗、选择弹窗、表单弹窗或小窗口不得在业务页面内重复手写 `modal-backdrop` / `raw-dialog` 结构；业务页面只传入 title、body、actions、test id、关闭行为和少量业务 class。这样可以统一 `role="dialog"`、`aria-modal`、关闭按钮、点击遮罩关闭、滚动边界、action 区和 E2E 定位语义。

Code Queue 的 queue 合并弹窗是该公共组件的首个业务复用示例；后续用户服务前端需要独立小窗口时应优先复用 `UniDeskDialog`，只有原始 JSON 查看器这类已有专用展示器可以继续保留自己的内容渲染。

## Deployment Contract

任何会影响公网 WebUI 的 frontend 源码、样式或导航变更，都必须在同一任务内完成 artifact 发布和 CD 验证；只运行 TypeScript 检查、`Bun.build` 或修改仓库文件不代表公网生效。

**关键规则**：frontend 容器只从镜像内的 `src/components/frontend/src/app.tsx` 及其 TSX imports 构建 `/app.js`，运行中的 `unidesk-frontend` 不会自动读取工作区新文件。标准发布真相是 D601 CI 发布的 `127.0.0.1:5000/unidesk/frontend:<commit>` 和 dev/prod CD 对该 commit-pinned artifact 的 live commit 验证。

**上线流程**：
1. 修改源码/CSS
2. push commit 后执行 `bun scripts/cli.ts ci publish-user-service --service frontend --commit <full-sha>`
3. 执行 `bun scripts/cli.ts deploy apply --env dev --service frontend`
4. 执行 `bun scripts/cli.ts deploy apply --env prod --service frontend`
5. 验证 dev/prod `/health.deploy.commit`、image label 和实际页面/深链接都对应 requested commit

禁止用"本地 bundle 校验通过"、`server rebuild frontend`、dirty worktree 或 mutable `latest` 代替 frontend artifact consumer 发布；禁止跳过 live commit 验证直接交付。

`server rebuild frontend` 只保留为维护/非标准路径，不得作为标准发布证据。正式 frontend 发布必须通过 `ci publish-user-service --service frontend` 产出 commit-pinned image，再由 `deploy apply --env dev|prod --service frontend` 消费。

如果一个用户服务同时存在主用户路径和 diagnostics/status 路径，默认路由必须优先呈现主用户路径，diagnostics/status 只能作为显式二级入口或标签页，不得替代默认产品页。该边界规则的追溯文号是 `DC-DCSN-P0-2026-004`。

如果公网仍是旧界面：1. 确认 job 已成功且容器创建时间晚于代码修改；2. 强制刷新或开无痕窗口；3. 用 curl -H "Cache-Control: no-cache" http://74.48.78.17:18081/app.js 查新 bundle。

## Time Zone Contract

frontend 所有默认可见的时间、日期、时钟、更新时间、心跳时间、Trace 时间、Gantt 时间轴刻度、导出文件日期和 `datetime-local` 输入值都必须按北京时间显示，即使用 IANA 时区 `Asia/Shanghai` / UTC+8。禁止依赖浏览器本地时区、服务器系统时区或裸 `Date.toLocaleString()` 默认值；新增页面必须复用 `src/components/frontend/src/time.ts` 的统一格式化和输入转换函数。原始 JSON 中的 ISO 时间戳只能在用户显式点击 `查看原始JSON` 后作为原始数据出现，默认结构化控件不得把 UTC/本地时区混入北京时间显示。

## Layout

左侧边栏只切换主模块：运行总览、资源节点、任务调度、用户服务、系统配置。顶部标签只切换当前主模块内的子功能；例如资源节点下的节点清单、资源标签、心跳状态只属于资源节点，用户服务下的服务目录、Todo Note、FindJob、Pipeline、MET Nonlinear、ClaudeQQ、Baidu Netdisk、Code Queue、OA Event Flow 只属于用户服务，和运行总览、任务调度、系统配置没有重复或共享语义。桌面端左侧边栏必须支持收起，只保留模块 code 和展开按钮，以便最大化主面板空间；移动端左侧边栏会转为顶部横向主模块条，但高度必须在不同主模块之间保持一致，并保持窄条、单行、不换行；主内容区无论内容多少都必须从顶部向下排列，空状态也不得上下居中制造大块留白。

## Route Model

frontend shell 必须把左侧主模块与顶部子标签编译为统一的 URL 路由，而不是在 router 中手工为每个页面逐个挂路径。长期规则如下：

- 导航权威数据只有一份：主模块、子标签、显示文案、route segment、默认子标签都从同一个 TypeScript 导航定义派生，左侧边栏、顶部标签、浏览器地址栏解析和前进/后退都复用同一套 registry。
- Canonical route 必须按主模块前缀分组：运行总览使用 `/ops/<tab-segment>/`，资源节点使用 `/nodes/<tab-segment>/`，任务调度使用 `/tasks/<tab-segment>/`，系统配置使用 `/config/<tab-segment>/`；只有用户服务主模块使用 `/app/<tab-segment>/`。例如 `态势总览` 固定为 `/ops/status/`，`Docker 状态` 固定为 `/nodes/docker/`，`Pipeline` 固定为 `/app/pipeline/`。
- 当 future 需要新增主模块时，通用机制必须允许为该模块声明自己的顶层前缀，而不是继续把所有页面都塞进 `/app/*`。
- 主模块根路径如 `/ops/`、`/nodes/`、`/tasks/`、`/app/`、`/config/` 只作为默认子标签或最近活动子标签的入口别名，实际当前页面仍应落到某个具体子标签；浏览器地址栏不能停留在“只有主模块，没有具体页面”的模糊状态。
- route segment 生成顺序固定为：显式 `routeSegment` > ASCII-safe `id` > 由 label 派生的 Unicode-safe slug > 稳定 hash fallback。这样新增 Unicode 标签时默认仍可得到稳定路径，而不要求每个标签单独写一段路由代码。
- 浏览器直开、刷新、`history.back()` / `history.forward()`、点击总览 drilldown 卡片、点击左侧边栏、点击顶部标签都必须走同一个路由状态机；不得出现“页面内容切换了，但 URL 没变”或“URL 变了，但 shell 仍停在旧 tab”的分裂状态。
- frontend Bun server 必须把这些模块前缀下的深链接路由作为 SPA 入口返回同一个 `index.html` 和同一个 UniDesk shell；实现上允许统一把非静态资源路径都回到同一个 shell，但判定标准是公网直开 `/ops/status/`、`/nodes/docker/`、`/app/pipeline/`、`/app/code-queue/` 等深链接时都不得 404，且必须和从主页导航进入时一样显示左侧主模块边栏、顶部状态栏、顶部子标签和完整业务页面。禁止为某个用户服务 deep link 返回缺少 UniDesk shell 的独立/standalone bundle；新增应用服务、普通页面和性能优化入口也必须满足“直开 URL 与站内导航同壳同页”的一致性要求。

## Overview Task Drilldown

`态势总览` 中的 `待处理任务` 指标必须可点击进入任务调度的 `待处理任务` 子标签，展示具体 queued、dispatched、running 任务的状态、Provider、已等待时间、payload 摘要和显式 `查看原始JSON` 操作。总览不得只给出无法追溯的数字；当后台把超时未终态任务转为 failed 后，待处理指标应回落，历史记录仍可在任务历史和执行结果中查看。核心指标还必须展示 `PGDATA`，显示 PostgreSQL 当前数据库用量、命名卷 `unidesk_pgdata_10gb` 和配置容量，便于从总览判断数据库状态。

## Performance Panel

`运行总览 / 性能面板` 固定路由为 `/ops/performance/`，用于汇总 UniDesk 控制面的通用性能指标。页面必须参考服务端性能看板形态，包含 `Bwebui` 内存/Bundle 趋势图、组件汇总、最近失败请求、内部操作汇总和最近慢操作。组件汇总至少展示组件名、请求数、失败数、失败率、平均延迟和 P95；最近失败请求必须在没有失败时明确显示“最近没有失败请求”，不能留空；内部操作汇总和慢操作必须展示服务、操作名、耗时、结果和细节。

性能面板的数据来自两个同源端点：frontend 自身的 `/api/frontend-performance` 记录 webui 静态资源、登录/session 和 API 代理请求，backend-core 的 `/api/performance` 记录 core API、用户服务代理、provider ingress、数据库与内部操作。页面只展示聚合表和趋势图，完整原始指标只能通过 `查看原始JSON` 打开。Bwebui 曲线优先使用浏览器 `performance.memory.usedJSHeapSize`，不可用时回退到 frontend bundle size 或 frontend 进程 heap，用 MB 作为纵轴口径。

## Task History Diagnostics

`任务调度 / 任务历史` 必须把任务生命周期渲染为可诊断表格，不得只显示更新时间和原始 payload 摘要。每行至少展示状态、任务命令和 id、Provider、任务耗时、载荷摘要、诊断信息、更新时间和显式 `查看原始JSON` 操作；终态任务的耗时按 `updatedAt - createdAt` 计算，待处理任务按当前时间减 `createdAt` 计算。耗时必须保留毫秒到秒的精度，小于 1 秒的任务显示小数秒或 `<0.01s`，不得把真实的亚秒级任务四舍五入或向下取整成 `0s`。失败任务必须在默认视图中提取 `result.error`、`result.message`、`result.stderr`、`result.reason` 或等价字段作为失败原因，并将 exit code、timeout、previous status 等关键诊断字段渲染为控件；完整 result 只能通过 `查看原始JSON` 展开。

## Resource Node Monitor View

资源节点模块必须提供 `资源监控` 子标签，用类似 Windows 任务管理器的性能页展示每个 provider 节点的 CPU、内存和硬盘用量历史曲线。该页面应包含节点切换、当前用量摘要、CPU/Memory/Disk 三条曲线、进程资源占用表、采样说明和 `Provider Gateway 升级` 控制区；曲线和进程表数据来自 backend-core 的 `/api/nodes/system-status`，不得在页面默认展示原始 JSON。内存曲线必须使用实际内存口径，不把 Linux page cache / buffer 计入占用。进程资源占用表必须用 React 表格控件展示进程名、命令摘要、PID、用户、状态、CPU、内存、PSS/RSS、磁盘 I/O、线程和运行时长，并支持按列排序；默认排序必须按进程 `memoryBytes` 降序，其中 `memoryBytes` 优先为 PSS 以避免共享内存重复计算，表头要能明确显示当前排序方向。完整进程快照只能通过 `查看原始JSON` 显式查看。

## Resource Node Docker View

资源节点模块必须提供 `Docker 状态` 子标签，用类似 Docker Desktop 的结构展示每个 provider 节点的 Docker daemon 状态。该页面应包含节点切换、daemon 摘要、Containers/Images/Volumes/Networks 指标、容器表格、镜像/卷/网络侧栏，并通过状态徽标区分 running、paused、exited 等状态。数据库命名卷 `unidesk_pgdata_10gb` 的检测和数据库命名卷卡片只对 `main-server` Provider 有效；D518、D601 等计算节点不要求也不应提示缺少 pgdata 数据库卷。`main-server` 的 Volumes 区域和数据库命名卷卡片必须显式展示 `unidesk_pgdata_10gb`，不得因为列表截断或匿名卷排序被隐藏。

## Provider Gateway Version View

资源节点模块必须提供 `网关版本` 子标签，按每个 Provider 展示 provider-gateway 版本号、升级策略、启动时间、能力摘要、SSH 透传可用性、远程更新可用性、最近远程更新状态和远程更新记录。SSH 透传可用性必须由 `unideskCapabilities` 是否包含 `host.ssh`、`hostSshConfigured`、`hostSshKeyPresent` 和 `hostSshTarget` 渲染为结构化徽标；远程更新可用性必须由 `unideskCapabilities` 是否包含 `provider.upgrade` 与 `providerGatewayUpgradePolicy: "always-enabled"` 渲染为结构化徽标。远程更新记录的数据源是 `provider.upgrade` 任务历史，默认必须渲染为结构化表格字段：状态、模式、任务 id、来源、耗时、策略、结果摘要和更新时间；亚秒级升级耗时必须显示小数秒，不得显示成 `0s`；不得把升级 plan、task result 或服务日志作为裸 JSON 直接铺在页面上。`最近远程更新` 应优先选择最新 `mode: "schedule"` 的真实升级记录，避免后续预检 plan 覆盖真正的升级结果；完整升级任务 JSON 只能通过对应行的 `查看原始JSON` 按钮显式打开。

## Provider Operation Availability

`资源节点 / 节点清单`、`资源节点 / 网关版本` 和总览中的 Provider 卡片必须显示每个计算节点的运维可用性，不允许只在原始 labels JSON 中隐藏。`SSH 透传` 徽标至少区分可用、未配置、缺 key、未声明能力；`远程更新` 徽标至少区分可用、策略待确认、未声明能力。可用性判断只来自 provider-gateway 注册/心跳 labels 和能力声明，不用前端猜测，也不得把缺失能力默认显示为成功。

## Provider Gateway Upgrade Control

`资源监控` 子标签中的升级控制区通过 backend-core `/api/dispatch` 下发 `provider.upgrade` 任务。默认 `预检升级` 只生成升级计划并回传任务结果；`执行升级` 才允许调度节点本地 updater 容器执行 Compose 重建。前端只展示结构化任务状态、task id、摘要和当前节点的远程更新记录，完整升级计划必须通过 `查看原始JSON` 显式查看。

## User Service Frontend

- `用户服务` 主模块用于展示挂载在计算节点或主 server Docker 中的业务后端。
  - `服务目录` 必须显示 service id、Provider、仓库 URL、commit id、业务 Dockerfile/docker-compose 引用、节点后端私有映射、SSH 透传开发入口和运行态容器摘要。
  - `HWLAB` 仅作为外部静态入口展示，固定跳转到 `http://74.48.78.17:6666`，可在目录页和独立子标签中打开；它不是 UniDesk 用户服务，不得纳入 microservice health、backend proxy、provider-gateway 透传或运行态容器摘要。
  - `Todo Note` 子标签必须把主 server `todo-note-backend` 后端渲染为 UniDesk React 控件，包括迁移清单、树形任务、筛选、提醒、拖放/移动、撤销/重做、字号控制和显式原始 JSON 按钮。
  - `FindJob` 子标签必须把 D601 findjob 后端渲染为 UniDesk React 控件，包括岗位指标、岗位预览、草稿报告和显式原始 JSON 按钮。
  - `ClaudeQQ` 子标签必须把 D601 ClaudeQQ 后端渲染为 UniDesk React 控件，包括 NapCat 容器登录二维码、NapCat HTTP/WS 状态、事件缓存、QQ 事件订阅表、订阅创建表单、消息推送表单、主用户私聊账号 `645275593` 标记、最近 QQ 事件、已发送记录和显式原始 JSON 按钮。
  - `Baidu Netdisk` 子标签必须把主 server `baidu-netdisk-backend` 后端渲染为 UniDesk React 控件，包括 OAuth 设备码二维码/用户码登录、账号容量、配置工作根文件浏览（当前默认百度网盘根目录 `/`）、staging 目录上传/下载任务、上传/下载自测按钮与 MD5 结果、脱敏安全说明、日志摘要和显式原始 JSON 按钮；不得把 access token、refresh token、dlink 或 staging 文件字节流裸露到浏览器。
  - `OA Event Flow` 子标签必须把主 server `oa-event-flow-backend` 后端渲染为 UniDesk React 控件，包括服务健康、事件表、tag 过滤、SSE live 状态、Trace/STEP stats 表、Code Queue/Pipeline 标签入口和显式原始 JSON 按钮；默认页面不得裸铺完整事件 JSON，事件表只展示结构化摘要，完整 envelope/payload 只能通过 `查看原始JSON` 打开。
  - `k3s Control` 子标签必须把 D601 `k3sctl-adapter` 控制面渲染为 UniDesk React 控件，包括 control plane 状态、manifest 列表、D601 scheduler/read/write 实例、active instance、single-writer/no-fallback 路径、Kubernetes API service proxy 状态、kubectl/k3s snapshot 摘要和显式原始 JSON 按钮；页面只能通过 `/api/microservices/k3sctl-adapter/proxy/api/control-plane` 取数，不得直接访问 provider-gateway、NodePort、业务容器端口或裸 k3s/kubectl API。
  - `Decision Center` 子标签必须把 D601 `decision-center` 用户服务渲染为需求管理与工作日记工作台。默认 `需求管理` 视图必须是一等工作区，结构化展示并录入外部目标、内部目标、阻塞、停放事项、决议、实验和债务，提供类型/状态/等级/关联目标筛选、记录编辑器、记录表和显式原始 JSON 按钮；默认页面不得裸铺 JSON。`工作日记` 视图必须提供“今天”按钮，使用浏览器当前日期生成 `YYYY-MM-DD`，自动打开或创建当天 Markdown 日记，允许编辑历史日记 Markdown 并通过 `/api/microservices/decision-center/proxy/api/diary/import` 保存到 PostgreSQL；完整日记 JSON 只能通过 `查看原始JSON` 打开。页面不得提供聊天/LLM 会话窗口。
  - `Code Queue` 子标签必须把稳定 `code-queue` 用户服务渲染为 UniDesk React 控件，前端 API 基址只能是 `/api/microservices/code-queue/proxy`，不能继续使用旧 `/api/code-queue-direct` 别名；backend-core 会把 queue CRUD、submit、history、readAt 和轻量 Trace 读取分流到主 server `code-queue-mgr`，把 active run steer/interrupt、judge、dev-container 和执行面健康分流到 D601 k3s/k8s Code Queue 执行面。页面包括多 queue lane、queue 内串行、queue 间并行、queue 合并（点击“合并 queue”后必须用公共 `UniDeskDialog` 打开独立小窗口，用下拉菜单选择源 queue；不得把源 queue 选择控件塞进正常提交任务的 Queue 选择区；合并后自动删除源 queue，只保留合并后的目标 queue，目标 queue 按原 queueEnteredAt/createdAt 时间顺序串行）、任务 ID/复制任务 ID、引用按钮、任务耗时、任务提交/批量提交、引用任务 ID、创建成功提示、清空输入、模型下拉、执行 Provider 下拉、执行模式下拉（默认容器/本机或 `windows-native`）、显式入队份数、默认模型 `gpt-5.5`、MiniMax judge 状态、Codex CLI-like 输出流、attempt 终态、运行中追加 prompt、打断、手动重试和显式原始 JSON 按钮；`windows-native` 模式必须在任务 JSON、卡片和 Trace 头部显示，并要求非本机 WSL Provider 与 `/mnt/<drive>` 工作目录；Codex CLI-like 输出流必须始终保留任务的初始 `Submitted prompt` 和运行中 `Steer prompt`；整个 agent loop 消息流统一命名为专有名词 `Trace`，`Trace` 包含 assistant message、user prompt、system event 和 tool call，但非错误 system event 默认只保留在原始输出/数据库中，不在 TraceView 展示；Code Queue 与 Pipeline/OpenCode messages 必须共用 `src/components/frontend/src/trace.tsx` 的 Trace 公共组件、统一 Trace item 接口和 codex/opencode port 适配层；连续 read/edit/run 工具调用只是在 Trace 内折叠为可展开工具调用组，汇总格式至少包含 `xx read, xx edit, xx run`，并展示读取文件、编辑文件、运行命令和耗时摘要；最近 3 个工具调用保持展开，工具调用内容不得自动换行且必须在工具调用块内部横向滚动，工具调用组展开后不得再增加额外左侧缩进；message 与 prompt 必须自动换行，普通 message 不显示左侧项目符号缩进且永不折叠；Trace 首屏可以是摘要预览，但终态任务被选中后必须自动在后台加载完整 Trace，手动“加载完整 Trace”也必须从 Code Queue output archive 分页补齐早期 trace，不得把 preview 的 `hasMore=false` 当成完整历史；即使热状态为控制体积裁剪了早期 raw output，也要从结构化 `basePrompt/displayPrompt/promptHistory` 和 archive 合成完整用户输入与 agent trace，并且初始 prompt 默认显示注入前 prompt 而不是引用注入全文；当初始 prompt 含引用注入时，引用内容必须默认折叠，并只在 Trace 的初始消息中提供可展开的“最终传入 Codex 的真实完整 prompt”，不得再渲染独立 Prompt 全量卡片；多轮引用注入必须按上游/最早上下文在前、直接引用在后的顺序排列，每一轮必须有明确 `Reference Round N/M` 分割线和时间范围，不能用固定 6 轮截断引用链；点击队列引用按钮必须自动把该任务 ID 写入提交表单的引用输入框，引用任务 ID 创建新任务时必须自动注入 `bun scripts/cli.ts codex task <taskId>` 的提示；连续执行同一 prompt 应通过入队份数一次性生成多条任务，避免快速连点造成操作员误判。
  - `MDTODO` 子标签必须把 D601 k3s `mdtodo` Service 渲染为 UniDesk React 控件，前端 API 基址只能是 `/api/microservices/mdtodo/proxy`；页面包括 TODO Markdown 文件列表、任务树、状态徽标、标题与正文编辑、新增根任务/子任务、删除任务、执行命令生成、hostPath 健康摘要和显式原始 JSON 按钮，不得 iframe 原 VS Code webview、公开 VSIX 旧前端或把完整 Markdown/JSON 默认铺在页面上。
  - `Code Queue` 前端改进必须在同一任务内重建并上线公网 frontend，不能只修改源码或本地 bundle；重建 frontend 是无状态 WebUI 替换，不会导致 Code Queue 长期任务失败。已结束未读任务只能在 task card 边角显示类似未读消息的 `codex-unread-badge` 圆点和“标为已读”操作，不得把整张卡片改成红色/琥珀色失败态边框、背景或胶囊标签；状态栏的“结束未读”提示也不得使用失败态红色。
  - `Code Queue` 前端必须把 PostgreSQL-backed backend API 作为 task、queue、readAt/未读状态和 attempt 状态的唯一数据来源；不得用 `localStorage`、`sessionStorage` 或 IndexedDB 持久化这些业务状态，也不得在后端标记已读失败时伪造本地成功。前端允许保留 React 内存态、请求 in-flight guard 和本轮页面缓存，但刷新页面或切换设备后的状态必须完全由后端 PostgreSQL 数据恢复。
  - `Code Queue` 前后端通信必须采用渐进式披露：首屏只请求 queue/task 轻量摘要、必要的 selected preview 和小体积统计，不得默认拉取完整 transcript、raw output、原始 JSON 或全部历史任务；加载下一页或搜索分页时必须显式传递 `selected=0`、`includeActive=0`、`stats=0` 等价开关，避免每一页重复请求 selected/active/stats；点击/选中 task 后再按需加载 summary、prompt part、trace step、raw output 或完整 Trace。`read`/`mark all read` 应调用专用 mutation 并用后端返回的 patch 更新当前内存态，不能为了隐藏未读圆点而强制刷新完整 overview；请求仍需遵守 PostgreSQL 权威源，失败时不得本地伪造已读。Code Queue 性能问题应优先通过缩小 API 响应、分页/cursor、去重 in-flight 请求、短 TTL 且 mutation 失效的页面缓存和后端 SQL 聚合解决，避免以重写渲染层或把大 JSON 藏在 DOM/React state 中规避慢请求。
  - `Code Queue` 的 queue/session 左侧边栏必须提供 task 关键词搜索，并采用顶部对齐和内容高度优先布局：搜索栏、列表、分组和 task card 都不得用居中、space-between、stretch 或隐式等高网格去拉满侧栏高度；item 少时允许下半部分留空，不能把单个 item 拉高来铺满；每个 task card 必须显示 `最近更新: ...前` 这类相对更新时间，便于判断运行中的 Trace 是否卡住；`queued` task card 的状态徽标必须显示排队原因，例如 `QUEUED(PREV TASK)`、`QUEUED(MEM LIMIT)`。Code Queue 的事件驱动刷新必须订阅统一 `oa-event-flow` 的 `service:code-queue` tag stream，并消费 `trace-stats-updated` 等统计变化事件；旧 Code Queue 私有 SSE 不得作为 Trace/STEP 刷新权威。左侧 STEP、执行过程摘要页的 read/edit/run/STEP 只能显示 `oa_trace_stats` 投影值；投影缺失时显示同步中/`--`，不得回退到 task JSON、transcript 或前端 `toolCallCount` 重算。事件驱动刷新只能更新队列列表与当前选中任务的详情，不得因为其他任务产生 `task-step` 或 `task-updated` 而自动切换当前选中的 task；只有用户点击任务、提交新任务或显式切换 queue 才能改变选中任务。提交任务时必须立即锁定 prompt、引用 ID、queue、模型、工作目录、最大尝试和入队份数等输入控件，显示等待状态，并用前端 in-flight guard 阻止重复点击造成重复入队；当解析到多个待入队任务时必须显式要求用户勾选批量确认，防止 `---` 分隔或入队份数误操作导致错误传入多个任务。Trace 面板的主滚动条使用全站细窄现代滚动条；工具调用块内部的横向滚动必须可滚动但隐藏横向滚动条，避免移动端阅读被滚动条占用。公共 `TraceView` 的自动滚动必须采用 follow-tail 语义：只有当前滚动位置在底部附近时才跟随新增输出；用户手动向上滚动后立即暂停自动滚动，异步刷新不得把视图拉回底部，直到用户再次滚动到最底部才恢复自动跟随。
  - 用户服务页面不得 iframe 业务旧前端、Todo Note 原 Vite 前端或 Pipeline 自身 WebUI，不得把用户服务后端端口暴露为浏览器直连 URL，也不得把业务 API 的 JSON 裸铺在页面上。
- `Pipeline` 子标签是 D601 `/home/ubuntu/pipeline` 的 UniDesk host UI。
  - Pipeline 仓库自带 WebUI 前端已经废弃；UniDesk frontend 是唯一用户可见的 Pipeline UI。
  - Pipeline 用户服务只提供 backend/control API，UniDesk 通过 `/api/microservices/pipeline/proxy/...` 拉取 snapshot、Gantt DTO、node detail 和控制接口。
  - Pipeline 页面必须通过同源用户服务代理展示 `model/minimax-m27` 的 MiniMax 限额，包括当前窗口总量/已用/剩余、重置时间和查询状态；主界面不得展示 API key，完整 quota JSON 只能通过显式 `查看原始JSON` 打开。
  - Pipeline 控制与观测的最终权威是统一 `oa-event-flow` 事件流与 Pipeline OA 状态机；分阶段迁移不得在交付态保留点对点控制、旧审核事件或旧 batch 推进逻辑，共享事件表与 tag 订阅规则见 `docs/reference/oa-event-flow.md`，Pipeline 控制流完整规则见 `docs/reference/pipeline-oa-event-flow.md`。
  - 基础视图必须包含组件矩阵、React Flow 控制图框图、epoch 列表、运行材料索引、epoch 甘特图和 node 精细控制面板。首屏信息顺序必须把核心操作前置：`控制图` 是 Pipeline hero 之后的第一个业务面板，`Epoch 甘特图` 紧随其后；观测指标、评分器、MiniMax 限额、OA 事件流和组件矩阵只能排在这两个核心面板之后，移动端也必须保持同一 DOM 顺序。控制图和甘特图的右侧详情栏默认收起，主图占满可用宽度；点击控制图 node 或甘特图执行线/事件点后才展开对应右侧栏，并必须提供手动收起入口。
  - Pipeline scorer 结果必须结构化展示为 `x/N`、通过率、scorer 状态、item pass/fail badge 和 raw inspector 入口；主界面不得裸展示 scorer JSON。
  - 用户点击控制图中的 node 后，必须通过同源用户服务代理抓取该 node 执行过程，并支持向运行中 node 追加 prompt、给下次尝试下发 guide、对已完成 node 排队 modify、提交 monitor 审核 approve，以及排队 restart/redo。
  - append-prompt、guide、modify、redo/restart 统称为 node 的管理行为；approve 是 monitor 审核决策；fetch/status 只属于观察行为。
  - UniDesk 只负责同源代理、结构化展示和人工控制入口，不直接写 Pipeline `.state` 文件。
  - node 精细控制面板的 append、guide、modify、approve、redo/restart 必须调用 Pipeline 后端 OA control API；`node-control` 可作为 HTTP 路由名保留，但内部必须写入 OA 控制事件，并把 UniDesk frontend 发起者记录为结构化事件；历史兼容字段可继续使用 `sourceKind=webui` 表示前端来源。
- `Pipeline` epoch 和甘特图规则。
  - 一个 epoch 定义为同一个 pipeline 从入口到终态完整执行一遍。
  - 同一 `pipelineId` 下的多个 run 必须作为多个 epoch 展示，并允许操作员切换当前 epoch；控制图、node 控制面板、运行材料索引和甘特图都必须跟随当前 epoch。
  - epoch 下拉框必须通过同源 snapshot 轮询自动看到新 run，不能依赖浏览器强制刷新，但也不应在用户已选中旧 epoch 时自动跳走。
  - 甘特图默认时间尺度固定为 `100 px/min`，并且默认关闭 `自动隐藏空闲列`，让操作员首次进入页面时先看到完整 node 列。
  - epoch 甘特图的纵轴是时间，左侧第一列展示时间刻度，后续每列对应一个 node；node 在 `startedAt` 到 `finishedAt` 或当前时间之间处于工作态时绘制竖向工作条，空闲时间留空。
  - 甘特图 node 列顺序必须遵循 pipeline 拓扑，从上游到下游依次向右展开；不得把下游 node 排在上游左侧。
  - 甘特图必须提供时间尺度滑块，用同一份时间数据调整每分钟像素密度：全局尺度压缩纵向高度以查看完整 epoch，细节尺度拉长纵向高度以查看短时间内的 prompt 点、控制点和执行线。
  - 甘特图必须提供当前 epoch 的渲染图导出按钮；导出图应使用当前可见布局，执行区间先绘制，控制/观察箭头覆盖在执行区间之上，真实事件点覆盖在最上层，避免箭头被执行条遮住。
- 精确数学图形的坐标权威规则。
  - Pipeline epoch 甘特图的 y 坐标权威在 UniDesk 前端完成，后端只提供 run、procedure、prompt/control 事件和时间戳事实，避免跨 provider HTTP 代理在后端反复计算布局。
  - 前端必须用同一线性公式把 `startMs/endMs/tick.ms/marker.ms` 映射到 `layout.source=frontend-y` 的 `chartHeight`：`y = clamp((ms - startMs) / (endMs - startMs), 0..1) * chartHeight`；执行区间 `top` 使用 `startMs`，自然高度使用 `endMs-startMs` 对应 y 差，短区间可设最小可点击高度但不得改变记录的 `data-y1/data-y2`。
  - 时间尺度滑块只改变前端每分钟像素密度和 `chartHeight`，不得因为缩放重新请求后端 Gantt 布局；run 级过程数据使用轻量 timeline 视图刷新。
  - 甘特图必须根据当前可见的前端 y 区间自动隐藏该窗口内没有任何工作区间或事件点的 node 列，避免宽图把无关空闲 node 挤在屏幕中。
  - E2E 必须验证前端 DOM 暴露的 `data-start-ms/data-end-ms/data-chart-height/data-y*` 与公式计算结果一致，并确认布局来源为 `frontend-y`。
- `Pipeline` 甘特图事件来源。
  - 甘特图上的执行线、prompt 点、控制点和 monitor 虚线箭头必须通过同源 `node-control` HTTP 读取接口驱动。
  - run 级图形事实数据使用 `GET /api/node-control/runs/{runId}?view=timeline&tail=N`；该接口不得要求返回后端 y 坐标。
  - node 级 OpenCode 明细来自用户选择后的 `GET /api/node-control/runs/{runId}/nodes/{nodeId}`，避免跨 UniDesk provider HTTP 代理时一次性拉取全 run 的大体积 step 数组。
  - 这些 monitor/control 事件的权威来源是 Pipeline OA 事件流；Pipeline 后端会为每条事件自动附加当前 `epoch:{runId}` 与 `pipeline:{pipelineId}` scope tag，并在 monitor 订阅时自动追加同一组 scope 限制，避免不同 epoch 之间串流。UniDesk 只消费 Pipeline 暴露的结构化读接口，不直接依赖旧的 monitor append 文件语义；OA 事件流的完整约束见 `docs/reference/pipeline-oa-event-flow.md`。
  - prompt 点只能来源于 attempt 级 `controlEventRecords` 中的 prompt-delivered 事件；`controlPromptRecords` / `monitorPromptRecords` 只能作为旧数据取证字段，不得参与交付态 UI 渲染或补点。
  - Monitor prompt 点必须将 `node-finished`、`node-long-running-observation` 作为一等结构化事件展示；`node-finished` 是中性完成事实，不得携带是否审核的权威策略字段。是否需要审核由 OA backend 根据当前 epoch 的 Pipeline config/topology 判定；审核开启时同一个 node completion 在 UI 中只保留一条完成/等待审核点，不得再生成独立 `legacy node audit request event` 或 `legacy monitor audit request event` 点。
  - 控制点和 monitor/人工控制箭头只能来源于 run 级 `controlEvents`；`controlCommands` 不得作为 Gantt/UI 补点或控制显示 fallback。
  - 控制事件必须依赖后端记录的 `sourceKind`、`sourceNodeId`、`targetNodeId`、`commandId`、`eventId`、`resetNodeIds` 和 `interruptedProcedureRunIds` 等结构化字段识别 monitor、UniDesk frontend、CLI 等发起者，不能通过解析 monitor 自己的 step 文本来反推。
- `Pipeline` monitor 审核与详情展示。
  - Pipeline 页面必须提供结构化 `OA 事件流` 诊断面板，通过 `/api/microservices/pipeline/proxy/api/oa-event-flow/diagnostics` 展示 100% 事件流模式、禁止残留数、no-audit 证据、monitor 审核证据、控制 queued/applied 计数和近期 run 证据；原始 diagnostics 只能通过 `查看原始JSON` 打开。
  - Pipeline 后端可通过 `control.monitoring.audit.enabled=true` 开启 monitor 审核门禁，但是否审核必须由 OA backend 加载 config snapshot 后判断，不能写入 `node-finished` 事实事件。
  - 当前 pipeline 存在 monitor node 时，node 成功后必须先进入 OA 事件流；OA 判定需要审核后才向 monitor 推送 prompt，随后必须等待 monitor 通过 OA 控制事件发起 `approve`、`modify`、`redo/restart` 或等价管理行为。
  - 长时间处于 `running` 的 node 必须触发 `node-long-running-observation` 观察事件，默认节奏为 2、5、10、20 分钟，之后每 20 分钟继续发送一次；前端需要把这类观察事件和普通 `node-finished` 清晰区分。
  - 前端应把审核/控制事件渲染为结构化点和虚线箭头；有 monitor 的 pipeline 中，monitor node 必须固定在甘特图第 1 个 node 列，其余 node 再按上游到下游顺序从左到右排列。
  - `node-long-running-observation` 必须显示从被观察 node 指向 monitor node 的观察连线，不能只在 monitor 列留下孤立事件点；观察来源本身不是一个真实行为，不能在被观察 node 上额外绘制“观察来源”点，点只代表真实收到 prompt、发出管理行为、发出审核结果等事件。
  - 甘特图箭头的末端必须在目标点外回缩约一个箭头长度，箭头尖不能插入 prompt 点、控制点或 monitor 观察点内部，避免把“连线”和“真实行为点”混在一起。
  - 仍处于 `running` 的 node 必须显示从实际开始时间延伸到当前时间的实时执行条，并用明确的闪动/扫描效果标识“仍在执行”；不得把 running node 渲染成只有起始点或 1s 极短条线。
  - 点击甘特图中的执行线、prompt 点或控制点后，右侧边栏必须从默认收起状态展开，并展示结构化事件字段、匹配的 procedure/attempt、以及对应 OpenCode step 的摘要与展开详情，而不是在主界面直接铺 raw JSON、JSONL、worker log 或 control event 文本。
  - OpenCode step/message 展示必须进入公共 `TraceView`，视觉与交互以 `Code Queue` 的 Trace 为唯一标准；Pipeline 原有 `pipeline-opencode-step`、`pipeline-step-message-card`、`pipeline-opencode-part` 等 step/message/tool 卡片风格已废弃，不得继续作为用户可见 Trace。
  - 右侧边栏中的 OpenCode Trace 必须把公共 session 信息（agent、model、session id）聚合到 Trace 头部，不得在每个 step 重复；Trace 正文必须由 `src/components/frontend/src/trace.tsx` 的 opencode port 转换后统一渲染，工具调用折叠、摘要、横向滚动、message 去缩进规则与 Code Queue 完全一致。
  - 右侧边栏排版必须优先保护横向可读宽度：时间放在 step 顶部 header，而不是单独占用左侧窄列；默认摘要不得引入右侧边栏内部横向滚动条，也不得因为窄列挤压把 step 高度拉得过高。
  - OpenCode Trace 不能使用 Pipeline 旧连续 step 装饰线或旧 step 卡片；相邻 step 之间若存在真实时间空闲区间，不得被任何连续连接线误渲染为持续执行。
  - 调整任何高信息密度右侧边栏布局时，都必须把 `总高度` 与 `横向滚动条` 作为显式验收指标，用 Playwright 打开真实页面验证，而不是只看静态代码或本地想象。
  - 运行材料只能作为结构化索引行展示计数、状态、时间和来源摘要，完整 JSON、JSONL 或 log tail 只能通过显式 `查看原始JSON` 按钮打开。
- `Pipeline` 渲染与算法验证。
  - 涉及 monitor 审核、管理行为或甘特图算法的改动，必须用 Pipeline 侧通用 fixture 组合验证。
  - 验证组合至少包括拓扑 split/merge/feedback、生产型 PikaPython workflow、`monitor-management-behavior-test` 和 `monitor-audit-behavior-test`。
  - 所有 fixture 都应走同一渲染与事件解析路径；不得为了某个 pipeline id、node id 或 run id 在 UniDesk 前端加入特例。
- `MET Nonlinear` 子标签必须把 D601 `/home/ubuntu/met_nonlinear` 的训练编排后端渲染为下载器式工作台。
  - 工作台包括项目库选择、从已有 Project fork 新 Project、加入待启动队列、启动队列、最大并发设置、当前队列、已完成、失败诊断、GPU/镜像、训练进度、ETA、历史记录和显式原始 JSON 按钮。
  - 运行中训练若后端未直接给出 ETA，前端必须用 `startedAt`、当前 epoch 和目标 epoch 做可解释的剩余时间估算。
  - 训练队列和已完成列表必须显示训练速度 `epoch/h`。
  - 项目库必须按真实文件路径分层显示 `projects/` 和 `ex_projects/`，文件夹计数必须等于其子树中的 Project 数，不能用模型名、状态或其他派生字段替代文件树层级。
  - 项目库、当前队列、已完成和失败诊断中的行必须可点击打开结构化详情。
  - 详情必须把 `config.json`、`data/training_state.json`、`data/training_info.json`、`data/metrics.json`、`data/model_info.json` 和 `data/compute_analysis.json` 中的训练状态、模型参数量、模型层、指标和 data 文件清单渲染为字段卡、表格和 chip，不得默认显示裸 JSON。
  - 不得提供硬编码的固定数量/固定轮数测试按钮。

## Component Data Rendering

前端必须把 backend-core 返回的 JSON 渲染为合适的控件：状态徽标、指标卡、表格列、标签 chip、字段摘要、任务结果卡、日志行和表单控件。默认页面禁止暴露裸 JSON、`pre` JSON 或整段 `JSON.stringify` 文本；只有用户明确点击 `查看原始JSON` 按钮后，才允许在弹窗或高级编辑区展示原始 JSON。

## Loading State Title Indicator

所有 UniDesk 前端模块只要处于加载、刷新、提交、抓取详情、导入导出、远程调度或其他异步等待状态，必须在对应卡片/面板/侧栏的标题框内显示公共加载小图标，避免操作员把“仍在加载”误判为“数据确实为 0”。加载图标统一复用 `src/components/frontend/src/loading-indicator.tsx` 中的 `LoadingTitle` / `LoadingIndicator`，视觉为简洁旋转圆环，不包含鼠标指针或其他额外符号；禁止各页面临时手写不同 spinner、只在按钮文案写“刷新中”或只在空状态里提示加载。新增面板组件必须透传 `loading` 属性到标题框，已有数据列表在刷新期间也应保留旧数据并在标题框显示加载图标。

## Login

frontend 提供账号密码登录，默认账号为 `admin`，默认密码为 `Liang6516.`。登录会话使用 frontend 容器签发的 HttpOnly Cookie；浏览器后续只访问同源 frontend API，frontend 再通过 Docker 内网代理 backend-core。

## Visual Language

界面使用深钢蓝、炭黑、琥珀和冷青作为工业控制台色板；字体选择窄体和等宽组合，以减少横向浪费。字号、表格行高和面板间距保持克制，避免大标题和松散卡片造成信息密度下降。

## Data Flow

浏览器不直接访问 backend-core。frontend 容器服务 React 静态资产、登录接口、会话接口和同源 API 代理；代理目标是 Docker 内网 `http://backend-core:8080`。provider-gateway 的外部接入地址只作为连接拓扑信息展示，不作为浏览器数据源。
