新增 AutoRDO 需求清洗工作台与消息中枢,迭代 OneOS V2 设计规范及租赁合同/工作台/车辆等原型,同步云效技能与导航注册;并归档一批 legacy 原型快照。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-07-28 15:50:35 +08:00
parent b14425da5a
commit 5d51a6bf7a
1393 changed files with 488042 additions and 10046 deletions

View File

@@ -0,0 +1,548 @@
# Message Hub 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 落地统一消息中枢(模型 + 本地路由表 + Web 消息中心原型),并让工作台/外壳铃铛读同一中枢。
**Architecture:** `src/common/message-hub/` 持有 Message、ChannelDelivery、RouteRule 与 `resolve()`;新原型 `message-center` 提供筛选/列表/详情/端模拟器/去处理;工作台与 `notice-bridge` 通过适配层映射为铃铛摘要。真推送与真唤端不做。
**Tech Stack:** React + TypeScript现有 Axhub Make 原型、Vitest`resolve` 单测、localStorage可选已读、现有 `oneos-app-shell` 铃铛 UI。
**Spec:** [docs/superpowers/specs/2026-07-22-message-hub-design.md](../specs/2026-07-22-message-hub-design.md)
---
## 文件结构(锁定)
| 路径 | 职责 |
|------|------|
| `src/common/message-hub/types.ts` | Message / Channel / RouteRule 类型 |
| `src/common/message-hub/routes.ts` | 五端 × 多系统路由表常量 |
| `src/common/message-hub/resolve.ts` | `resolveMessageTarget()` |
| `src/common/message-hub/resolve.test.ts` | 路由优先级与失败态单测 |
| `src/common/message-hub/seed.ts` | 宽首期种子消息(含旧 notices 映射) |
| `src/common/message-hub/store.ts` | 读列表、标已读、未读数(内存 + localStorage |
| `src/common/message-hub/shell-adapter.ts` | Message → `ShellNoticeItem` |
| `src/common/message-hub/index.ts` | 公共导出 |
| `src/prototypes/message-center/index.tsx` | 原型入口 |
| `src/prototypes/message-center/MessageCenterApp.tsx` | 主 UI |
| `src/prototypes/message-center/styles/index.css` | 样式 |
| `src/prototypes/message-center/annotation-source.json` | 标注目录 |
| `src/prototypes/message-center/.spec/route-resolve.md` | 路由判定全文 |
| `src/prototypes/message-center/.spec/requirements-prd.md` | AutoPRD |
| `src/resources/prd/message-center-autoprd.md` | PRD 副本 |
| 修改 `oneos-app-shell/ShellNoticeCenter.tsx` | 「查看全部」→ 消息中心;详情优先于行内直跳 |
| 修改 `oneos-web-workbench-new` | 铃铛数据改读 message-hub |
| 修改 `nav-menu.json` | 增加「消息中心」菜单项 |
---
### Task 1: message-hub 类型 + resolve + 单测
**Files:**
- Create: `src/common/message-hub/types.ts`
- Create: `src/common/message-hub/resolve.ts`
- Create: `src/common/message-hub/resolve.test.ts`
- Create: `src/common/message-hub/index.ts`(先导出类型与 resolve
- [ ] **Step 1: 写入类型**
```ts
// src/common/message-hub/types.ts
export type SourceSystem = 'oneos' | 'vehicle-mid' | 'other-system' | 'external';
export type MessageClient = 'web' | 'ios' | 'android' | 'harmony' | 'wechat_oa';
export type ChannelStatus = 'pending' | 'sent' | 'failed' | 'skipped';
export type MessagePriority = 'normal' | 'high';
export type ExternalAppId = 'external-app-a' | 'external-app-b';
export type ChannelDelivery = {
client: MessageClient;
status: ChannelStatus;
updatedAt?: string;
};
export type HubMessage = {
id: string;
sourceSystem: SourceSystem;
bizType: string;
bizId: string;
title: string;
summary: string;
detail: string;
priority: MessagePriority;
createdAt: string;
readAt?: string;
/** 角色占位;空数组表示全员可见 */
audienceRoleIds: string[];
channels: ChannelDelivery[];
/** 展示用业务标签,如「交车任务」 */
bizTag: string;
};
export type RouteRule = {
sourceSystem: SourceSystem;
bizType: string;
client: MessageClient;
targetTemplate: string;
label: string;
externalApp?: ExternalAppId;
/** 精确未命中时,再试该 client如 harmony → android */
fallbackClient?: MessageClient;
};
export type ResolveOk = {
ok: true;
uri: string;
label: string;
externalApp?: ExternalAppId;
rule: RouteRule;
};
export type ResolveFail = {
ok: false;
reason: 'no_rule';
message: '当前端暂无可跳转目标';
};
export type ResolveResult = ResolveOk | ResolveFail;
```
- [ ] **Step 2: 写失败单测(先不实现 resolve 逻辑体)**
```ts
// src/common/message-hub/resolve.test.ts
import { describe, expect, it } from 'vitest';
import { resolveMessageTarget } from './resolve';
import type { HubMessage, RouteRule } from './types';
const baseMsg: HubMessage = {
id: 'm1',
sourceSystem: 'oneos',
bizType: 'approval.arrive',
bizId: 'AP-1',
title: '待审',
summary: '',
detail: '详情',
priority: 'normal',
createdAt: '2026-07-22T10:00:00+08:00',
audienceRoleIds: [],
channels: [],
bizTag: '租赁合同',
};
describe('resolveMessageTarget', () => {
it('精确匹配 web 规则', () => {
const rules: RouteRule[] = [
{
sourceSystem: 'oneos',
bizType: 'approval.arrive',
client: 'web',
targetTemplate: '/prototypes/oneos-web-approval-todo?id={bizId}',
label: '审批待办',
},
];
const r = resolveMessageTarget(baseMsg, 'web', rules);
expect(r.ok).toBe(true);
if (r.ok) expect(r.uri).toBe('/prototypes/oneos-web-approval-todo?id=AP-1');
});
it('harmony 可 fallback 到 android', () => {
const rules: RouteRule[] = [
{
sourceSystem: 'oneos',
bizType: 'approval.arrive',
client: 'android',
targetTemplate: 'oneos://approval/{bizId}',
label: '安卓审批',
fallbackClient: undefined,
},
{
sourceSystem: 'oneos',
bizType: 'approval.arrive',
client: 'harmony',
targetTemplate: '',
label: '鸿蒙走安卓模板',
fallbackClient: 'android',
},
];
// 实现约定harmony 行若 targetTemplate 为空且带 fallbackClient则用 fallback 的模板
const r = resolveMessageTarget(baseMsg, 'harmony', rules);
expect(r.ok).toBe(true);
if (r.ok) expect(r.uri).toBe('oneos://approval/AP-1');
});
it('无规则返回固定文案', () => {
const r = resolveMessageTarget(baseMsg, 'wechat_oa', []);
expect(r).toEqual({
ok: false,
reason: 'no_rule',
message: '当前端暂无可跳转目标',
});
});
it('带 externalApp', () => {
const msg = { ...baseMsg, sourceSystem: 'external' as const, bizType: 'order.action', bizId: 'O-9' };
const rules: RouteRule[] = [
{
sourceSystem: 'external',
bizType: 'order.action',
client: 'web',
targetTemplate: 'ext-app-a://order/{bizId}',
label: '外部 App A 订单',
externalApp: 'external-app-a',
},
];
const r = resolveMessageTarget(msg, 'web', rules);
expect(r.ok).toBe(true);
if (r.ok) {
expect(r.uri).toBe('ext-app-a://order/O-9');
expect(r.externalApp).toBe('external-app-a');
}
});
});
```
- [ ] **Step 3: 跑测确认失败**
Run: `npx vitest --run src/common/message-hub/resolve.test.ts`
Expected: FAIL模块不存在或函数未导出
- [ ] **Step 4: 实现 resolve**
```ts
// src/common/message-hub/resolve.ts
import type { HubMessage, MessageClient, ResolveResult, RouteRule } from './types';
function fillTemplate(template: string, msg: HubMessage): string {
return template
.replaceAll('{bizId}', encodeURIComponent(msg.bizId))
.replaceAll('{id}', encodeURIComponent(msg.id));
}
function findRule(
rules: RouteRule[],
sourceSystem: HubMessage['sourceSystem'],
bizType: string,
client: MessageClient,
): RouteRule | undefined {
return rules.find(
(r) => r.sourceSystem === sourceSystem && r.bizType === bizType && r.client === client,
);
}
export function resolveMessageTarget(
msg: HubMessage,
client: MessageClient,
rules: RouteRule[],
): ResolveResult {
const primary = findRule(rules, msg.sourceSystem, msg.bizType, client);
if (primary) {
const useFallback =
(!primary.targetTemplate || primary.targetTemplate.trim() === '') && primary.fallbackClient;
if (useFallback && primary.fallbackClient) {
const fb = findRule(rules, msg.sourceSystem, msg.bizType, primary.fallbackClient);
if (fb?.targetTemplate) {
return {
ok: true,
uri: fillTemplate(fb.targetTemplate, msg),
label: primary.label || fb.label,
externalApp: fb.externalApp ?? primary.externalApp,
rule: fb,
};
}
}
if (primary.targetTemplate?.trim()) {
return {
ok: true,
uri: fillTemplate(primary.targetTemplate, msg),
label: primary.label,
externalApp: primary.externalApp,
rule: primary,
};
}
}
return { ok: false, reason: 'no_rule', message: '当前端暂无可跳转目标' };
}
```
- [ ] **Step 5: 跑测通过**
Run: `npx vitest --run src/common/message-hub/resolve.test.ts`
Expected: PASS4 tests
- [ ] **Step 6: Commit**(仅当用户要求提交时执行)
```bash
git add src/common/message-hub/
git commit -m "$(cat <<'EOF'
feat(message-hub): add types and resolve() with vitest coverage
EOF
)"
```
---
### Task 2: 路由表 + 种子消息
**Files:**
- Create: `src/common/message-hub/routes.ts`
- Create: `src/common/message-hub/seed.ts`
- Modify: `src/common/message-hub/index.ts`
- [ ] **Step 1: 写入 `ROUTE_RULES`**
至少覆盖(每个 listed bizType 需含 web/ios/android/harmony/wechat_oa 五行wechat 无办理页可用空 template + 不配 fallback使 resolve 失败并在 UI 显示 skipped 通道态):
| sourceSystem | bizType | web 目标示例 |
|--------------|---------|--------------|
| oneos | approval.arrive | `/prototypes/oneos-web-approval-todo?id={bizId}` |
| oneos | urge.remind | `/prototypes/oneos-web-ops`(可带 query |
| oneos | contract.expire | `/prototypes/customer-management` |
| oneos | license.expire | `/prototypes/customer-management` |
| oneos | bill.ready | `/prototypes/lease-business-ledger` |
| oneos | h2.balance | `/prototypes/payment-records` |
| oneos | system.release | (无跳转或打开版本说明占位 path |
| vehicle-mid | fault.overdue | `/prototypes/vehicle-fault-handling#page=detail&id={bizId}` |
| vehicle-mid | vehicle.status | `/prototypes/vehicle-management` |
| other-system | generic.notice | `/prototypes/message-center`(仅详情,无外跳也可失败) |
| external | order.action | `ext-app-a://order/{bizId}` + `externalApp: external-app-a` |
| external | settle.action | `ext-app-b://settle/{bizId}` + `externalApp: external-app-b` |
harmony 行:`targetTemplate: ''`, `fallbackClient: 'android'`
ios/android`oneos://…` / `oneos-android://…` 占位 scheme。
wechat_oa对可 H5 办理的给 `https://oa.example.com/...` 占位;纯 PC 办理给空 template。
- [ ] **Step 2: 写入 `SEED_MESSAGES`**
映射旧 `SEED_NOTICES`(保留原 id 如 `n-1``RELEASE_NOTICE_ID` 便于对照)+ 新增 vehicle-mid / other-system / external 各至少规格要求条数。
`audienceRoleIds` 对齐原 `roleIds`;无角色限制用 `[]`
`channels`web=sent移动三端=sent 或 pendingwechat_oa 按样例 sent/skipped。
- [ ] **Step 3: 导出**
```ts
// index.ts
export * from './types';
export * from './resolve';
export { ROUTE_RULES } from './routes';
export { SEED_MESSAGES } from './seed';
```
- [ ] **Step 4: 用真实 ROUTE_RULES 补一条集成断言(可选放 resolve.test**
```ts
import { ROUTE_RULES } from './routes';
import { SEED_MESSAGES } from './seed';
it('种子 approval 在 web 可解析', () => {
const msg = SEED_MESSAGES.find((m) => m.bizType === 'approval.arrive');
expect(msg).toBeTruthy();
const r = resolveMessageTarget(msg!, 'web', ROUTE_RULES);
expect(r.ok).toBe(true);
});
```
Run: `npx vitest --run src/common/message-hub/resolve.test.ts` → PASS
- [ ] **Step 5: Commit**(用户要求时)
```bash
git add src/common/message-hub/
git commit -m "$(cat <<'EOF'
feat(message-hub): add route table and wide-scope seed messages
EOF
)"
```
---
### Task 3: store + shell 适配
**Files:**
- Create: `src/common/message-hub/store.ts`
- Create: `src/common/message-hub/shell-adapter.ts`
- Modify: `src/common/message-hub/index.ts`
- [ ] **Step 1: store**
```ts
// 要点
const STORAGE_KEY = 'oneos.message-hub.readAt.v1';
// loadSeed(): HubMessage[] — 合并 SEED 与 localStorage 中的 readAt
// markRead(id): void
// unreadCount(messages, roleId?): number
// listForRole(messages, roleId?): HubMessage[]
// sort: 未读优先 → priority high → createdAt desc
```
- [ ] **Step 2: shell-adapter**
```ts
import type { ShellNoticeItem } from '../oneos-app-shell/notice-bridge';
import type { HubMessage } from './types';
import { resolveMessageTarget } from './resolve';
import { ROUTE_RULES } from './routes';
export function toShellNoticeItem(msg: HubMessage): ShellNoticeItem {
const web = resolveMessageTarget(msg, 'web', ROUTE_RULES);
return {
id: msg.id,
type: msg.priority === 'high' && msg.bizType.startsWith('urge') ? '催办提醒' : msg.bizTag,
bizTag: msg.bizTag,
title: msg.title,
summary: msg.summary,
detail: msg.detail,
time: msg.createdAt.replace('T', ' ').slice(0, 16),
read: !!msg.readAt,
href: web.ok && web.uri.startsWith('/') ? web.uri : undefined,
taskId: msg.bizId,
};
}
```
注意:铃铛点击按规格**只打开详情、不在行上直跳**`href` 仍可供详情内「去处理」使用。`type === '催办提醒'` 用于外壳排序/标签adapter 对 `urge.remind` 必须输出 `'催办提醒'`
- [ ] **Step 3: 导出 store / adapter**
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 4: message-center 原型 UI
**Files:**
- Create: `src/prototypes/message-center/index.tsx`
- Create: `src/prototypes/message-center/MessageCenterApp.tsx`
- Create: `src/prototypes/message-center/styles/index.css`
- 实现前 Read: `~/.agents/skills/ui-ux-pro-max/SKILL.md`,并跑
`python3 ~/.agents/skills/ui-ux-pro-max/scripts/search.py "admin dashboard notification inbox" --design-system -p "OneOS message-center"`
视觉对齐现有工作台,不用营销风。
- [ ] **Step 1: 入口**
```tsx
/**
* @name 消息中心
*/
import React from 'react';
import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host';
import annotationSource from './annotation-source.json';
import MessageCenterApp from './MessageCenterApp';
import './styles/index.css';
export default function MessageCenterPage() {
return (
<>
<MessageCenterApp />
<PrototypeAnnotationHost source={annotationSource as any} />
</>
);
}
```
(若项目 annotation 挂载方式与 fault-handling 不同,以同目录近期原型为准。)
- [ ] **Step 2: MessageCenterApp 行为清单(必须全部有)**
1. 左栏:全部/未读/已读;系统源多选或下拉;业务类型下拉;**端模拟器**五端
2. 右栏列表未读点、bizTag、title、时间
3. 选中详情detail、channels 触达态表、`resolve` 预览(随端模拟器变)、「去处理」
4. 去处理:`markRead` → resolve →
- `uri``/` 开头:`window.location.href` 或外壳 `postMessage({ type: 'ONEOS_SHELL_NAV', href })`(与工作台 `protoNav` 一致)
- 否则:模态「将打开 {externalApp} / {uri}」(不 `window.open` scheme
- fail页面内固定文案「当前端暂无可跳转目标」
5. 触控/对比度遵循 ui-ux-pro-max表格数字可用 tabular-nums
- [ ] **Step 3: 本地预览**
打开 `/prototypes/message-center`手测切换端模拟器预览变化external 消息出示意模态;无 wechat 规则时提示不可跳。
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 5: 业务规格 + AutoPRD + 标注
**Files:**
- Create: `src/prototypes/message-center/.spec/route-resolve.md`(判定优先级表、前置条件、数据源、用户可见结果、种子 vs 未接 API
- Create: `src/prototypes/message-center/.spec/requirements-prd.md`(用户故事:起点→运作→闭环;摘要链到 route-resolve
- Create: `src/resources/prd/message-center-autoprd.md`(与 PRD 同步)
- Create/Update: `annotation-source.json` 顶层 `directory.nodes` 含「产品需求说明PRD」全文
- [ ] **Step 1: 按 oneos-autoprd 写 PRD**(产品语言,不写文件路径堆砌)
- [ ] **Step 2: route-resolve.md 必须含**
| 优先级 | 条件 | 结果 |
|--------|------|------|
| 1 | 精确 source+bizType+client 且 template 非空 | 填充 URI |
| 2 | template 空且 fallbackClient 有对应规则 | 用 fallback URI |
| 3 | 否则 | 不可跳转文案 |
- [ ] **Step 3: 若存在脚本则运行**
`node scripts/sync-annotation-directory.mjs`(或项目内同等命令)
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 6: 导航登记
**Files:**
- Modify: `src/prototypes/oneos-prototype-nav/nav-menu.json`(在「工作台」旁或系统能力区增加「消息中心」→ `prototypes/message-center`
- Run: `npm run nav:sync -- --prototype message-center --note "新增统一消息中心原型"`
- [ ] **Step 1: 加菜单项**
- [ ] **Step 2: nav:sync**
- [ ] **Step 3: 刷新原型导航页确认可见**
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 7: 工作台铃铛 + 外壳对接中枢
**Files:**
- Modify: `src/prototypes/oneos-web-workbench-new/components/WorkbenchPage.tsx`(及 NoticePanel 数据源)
- Modify: `src/prototypes/oneos-web-workbench-new/data/notices.ts` — 改为从 hub re-export 适配函数,或标记 deprecated 并改调用方
- Modify: `src/common/oneos-app-shell/ShellNoticeCenter.tsx`
- 列表项点击:`onOpen` 打开详情(勿直接 `onHandle`
- 「查看全部」主按钮导航到 `/prototypes/message-center`
- Modify: `.spec/notice-center.md` 注明数据源迁移至 message-hub
- Update workbench AutoPRD 中通知章节(行为变更需 sync
- [ ] **Step 1: Workbench 使用 `loadMessages` + `toShellNoticeItem` / 自有列表组件读 hub**
- [ ] **Step 2: postNoticesSync 仍发 `ShellNoticeItem[]`adapter 输出)**
- [ ] **Step 3: 外壳「查看全部」→ message-center**
- [ ] **Step 4: 手测:原型演示嵌入工作台时铃铛未读与中枢一致;查看全部进入消息中心**
- [ ] **Step 5: Commit**(用户要求时)
---
### Task 8: 验收对照 + 收尾
- [ ] **Step 1: 对照规格 §10 验收清单逐条勾选**
- [ ] **Step 2: `npx vitest --run src/common/message-hub/resolve.test.ts`**
- [ ] **Step 3: 更新设计文档状态旁注明「已实现原型」(若本轮做完)**
- [ ] **Step 4: 停止 brainstorm companion可选**
`/Users/sylvawong/.agents/skills/brainstorming/scripts/stop-server.sh /Users/sylvawong/oneos1.2/.superpowers/brainstorm/82675-1784712939`
---
## Spec 覆盖自检
| 规格要求 | 任务 |
|----------|------|
| Message / Channel / RouteRule 模型 | Task 12 |
| 本地路由 + 优先级 + 固定失败文案 | Task 1, 5 |
| 五端 × 多系统种子与外部 A/B | Task 2 |
| 消息中心 UI + 端模拟器 + 去处理 | Task 4 |
| 铃铛快捷 + 查看全部 | Task 7 |
| 独立中枢架构 | Task 14 |
| .spec + AutoPRD + 标注 | Task 5 |
| 导航登记 | Task 6 |
| 不做真推送/真唤端 | Task 4 模态示意 |
无 TBD 占位;`resolveMessageTarget` / `HubMessage` / `ROUTE_RULES` 命名全计划一致。

View File

@@ -0,0 +1,62 @@
# 故障处置 Implementation Plan
> **For agentic workers:** Use inline execution in this session (user requested 执行). Steps use checkbox syntax.
**Goal:** 交付 `vehicle-fault-handling` 原型(列表+独立详情),共享故障数据驱动工作台预警与运维看板「故障登记」,并挂到 OneOS → 车辆运维 → 故障处置。
**Architecture:** `src/common/vehicle-fault/` 持有类型、种子、localStorage、时限/归档校验与统计聚合;原型页读写该模块;工作台 KPI / OpsCockpit 改读同一聚合。AI 上报无 UI仅种子模拟待处理原始记录。
**Tech Stack:** React + Ant Design + TypeScript`useHashPage`list / detail / stats-placeholder / proof-placeholder`PrototypeAnnotationHost`localStorage。
**Spec:** `docs/superpowers/specs/2026-07-22-vehicle-fault-handling-design.md`
---
## File map
| Path | Responsibility |
|------|----------------|
| `src/common/vehicle-fault/types.ts` | 状态、附件、通知、FaultRecord |
| `src/common/vehicle-fault/sla.ts` | 截止日、剩余天数、临期/逾期判定、归档硬门槛 |
| `src/common/vehicle-fault/seed.ts` | 演示种子(含临期、逾期、待处理、处理中、挂起、已归档) |
| `src/common/vehicle-fault/storage.ts` | load/save localStorage |
| `src/common/vehicle-fault/stats.ts` | KPI 明细 + 看板 faultStats |
| `src/common/vehicle-fault/notify.ts` | 短信/邮件模板渲染与演示发送记录 |
| `src/common/vehicle-fault/index.ts` | 导出 |
| `src/prototypes/vehicle-fault-handling/*` | UI列表、详情、占位页、样式、PRD、标注 |
| `src/prototypes/oneos-web-workbench-new/data/kpi.ts` 等 | 接入故障 KPI |
| `src/prototypes/oneos-web-workbench-new/data/ops-cockpit.ts` + OpsCockpit | 故障登记同源统计 |
| `src/prototypes/oneos-prototype-nav/nav-menu.json` | 车辆运维菜单 |
---
### Task 1: 共享数据模块
- [x] Create types, sla, seed, storage, stats, notify, index under `src/common/vehicle-fault/`
### Task 2: 故障处置原型 UI
- [x] list / detail / placeholders + PRD + annotation
### Task 3: 工作台接入
- [x] KPI + OpsCockpit 同源
### Task 4: 导航与收尾
- [x] sidebar + nav-menu 车辆运维 → 故障处置
---
## Spec coverage
| Spec 节 | Task |
|---------|------|
| §2 菜单/新原型 | 2, 4 |
| §3 状态机 | 1, 2 |
| §4 时限与模板 | 1 notify, 2 UI |
| §5 列表+独立详情 | 2 |
| §5.3 硬门槛 | 1 sla, 2 |
| §6 工作台 | 3 |
| 二期占位 | 2, 4 |

View File

@@ -0,0 +1,103 @@
# 车辆采购合同 + 验车入库 Linear UI Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 按规格将采购合同、验车入库、外勤三端对齐 Linear 产品浅色(`--ln-*` / `#32a06e`)与 vm-page 壳层,一波交付,不改业务规则。
**Architecture:** PC 两原型继续引用 `vehicle-management/style.css`;列表改为筛选卡 + KPI + `OperationActions` + `TablePagination`;表单/详情用顶栏返回 + 分区卡片;外勤三端共享一份 `field-theme.css``--ln-*` 子集)并改各 `index.html` 内联色。业务仍走 `src/common/vehicle-purchase/*`
**Tech Stack:** React + Ant Design + Lucidevm-/ln- CSS静态 HTML/JS 外勤页。
**Spec:** `docs/superpowers/specs/2026-07-22-vehicle-purchase-inspection-linear-ui-design.md`
---
## File map
| 文件 | 职责 |
|------|------|
| `vehicle-purchase-contract/VehiclePurchaseContractApp.tsx` | 列表壳、KPI、表单分区、主题色 |
| `vehicle-purchase-contract/styles/index.css` | vpc 收敛到 vm/ln |
| `vehicle-purchase-contract/DESIGN.md` | 声明设计基底 |
| `vehicle-inspection/VehicleInspectionApp.tsx` | 同上(验车) |
| `vehicle-inspection/styles/index.css` | vi 收敛 |
| `vehicle-inspection/DESIGN.md` | 声明设计基底 |
| `vehicle-inspection-*/field-theme.css`(新建,三端各一份或同源复制) | 外勤 Linear 浅色 token |
| `vehicle-inspection-*/index.html` | 引用 theme、触控与对比度 |
---
### Task 1: 采购合同列表壳 + 主题色
**Files:**
- Modify: `src/prototypes/vehicle-purchase-contract/VehiclePurchaseContractApp.tsx`
- Modify: `src/prototypes/vehicle-purchase-contract/styles/index.css`
- Create: `src/prototypes/vehicle-purchase-contract/DESIGN.md`
- [ ] **Step 1:** `vmTheme.token.colorPrimary` 改为 `#32a06e``colorLink` 同步。
- [ ] **Step 2:** 列表去掉 `vpc-header` h1/副标题;结构改为:
- `vm-page vpc-page`
- 筛选:`vm-filter-card` / 或 Card `title="筛选条件"` + 关键词/状态/审批 + 查询重置
- KPI 四卡(草稿、审批中合集、已通过、已生成验车),可点驱动 filter
- `vm-table-section`:右对齐「新建合同」;表格;`OperationActions` 替换散落链接(若尚未用)
- `vm-table-footer` + `TablePagination`
- [ ] **Step 3:** CSS 用 `var(--ln-*)` / `var(--vm-*)` 替换 `#10b981``#f8fafc` 等硬编码;保留审批 banner、表单 grid 类名但换 token。
- [ ] **Step 4:** 写入 `DESIGN.md`:基底 = linear 产品浅色 + vm-shared。
- [ ] **Step 5:** 浏览器打开 `/prototypes/vehicle-purchase-contract/` 目视:无大标题、主色绿、分页在底。
### Task 2: 采购合同创建/编辑/查看分区
**Files:**
- Modify: `VehiclePurchaseContractApp.tsx`create/edit/view 分支)
- Modify: `styles/index.css`
- [ ] **Step 1:** 顶栏:返回 + 标题 + 主操作(保存/提交/关闭)。
- [ ] **Step 2:** 表单/详情字段按规格五分区包进 `vpc-section` 卡片(主体 / 车型价格 / 分期 / 交付 / 附件质保)。
- [ ] **Step 3:** 审批提示条用 `--ln-primary-soft` 背景;总价 `tabular-nums`
- [ ] **Step 4:** 目视创建页分区与返回可用。
### Task 3: 验车入库列表 + 详情
**Files:**
- Modify: `src/prototypes/vehicle-inspection/VehicleInspectionApp.tsx`
- Modify: `src/prototypes/vehicle-inspection/styles/index.css`
- Create: `src/prototypes/vehicle-inspection/DESIGN.md`
- [ ] **Step 1:** 同 Task1主题色、去大标题、筛选卡、KPI待验/验车中/已完成)、表格+分页、`OperationActions`
- [ ] **Step 2:** 详情:返回顶栏;摘要;外勤按钮(绑定 + open车辆表主操作右对齐。
- [ ] **Step 3:** CSS 换 `--ln-*``DESIGN.md` 声明基底。
- [ ] **Step 4:** 目视列表与详情;点小程序 URL 200。
### Task 4: 外勤三端 Linear 浅色
**Files:**
- Create: `field-theme.css` 于 web内容含 `:root` `--ln-primary` 等),复制到 miniprogram、app
- Modify: 各 `index.html` link stylesheet调整内联 style 引用 var
- Modify: 如有冲突的 `app.js` 内联色则改为 class
- [ ] **Step 1:** 定义 `--ln-primary: #32a06e`、canvas、ink、muted、hairline、radius、error。
- [ ] **Step 2:** 卡片/按钮/底栏用 token触控按钮 min-height 44pxbody ≥16px。
- [ ] **Step 3:** 三端同步curl 三个 index.html 为 200目视一页。
### Task 5: 文档与验收
- [ ] **Step 1:** 若仅样式PRD 可跳过全量;若分区标题有语义变化,轻量更新 `.spec/requirements-prd.md` 一句「UI 对齐 Linear 产品浅色」。
- [ ] **Step 2:** 对照规格 §7 验收清单自检。
- [ ] **Step 3:** 不自动 commit除非用户要求
---
## Spec coverage
| 规格节 | Task |
|--------|------|
| §2 Linear 浅色 | 14 |
| §3.1 合同 | 12 |
| §3.2 验车 | 3 |
| §3.3 外勤 | 4 |
| §5 业务冻结 | 全程不改 common |
| §7 验收 | 5 |
## Placeholder scan
无 TBD提交步骤尊重用户「未要求不 commit」。

View File

@@ -0,0 +1,716 @@
# AutoRDO Web 工作台 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 落地 Axhub 原型 `autorodo-web`:粘贴素材 → 复制 AutoRDO 清洗提示词 → 粘回初稿 → 按需求分页确认待确认并回填描述 → 填写优先级/标签/提交部门/提交人 → 一键复制 YunxiaoPMapp 多条记录需求提示词。
**Architecture:** 纯函数放在原型内 `lib/`(解析初稿、出题回填、拼提示词、识别部门/提交人UI 为三步工作台React + OneOS V2 Token。MVP 不连云效、不嵌 OCR/ASRCursor 往返靠剪贴板。预留 `agentAdapter` 接口形状供组织 Agent。
**Tech Stack:** React + TypeScriptAxhub Make 原型、Vitest、OneOS V2`oneos-ds-tokens.css` + `UIComponents` 按需)、`PrototypeAnnotationHost`
**Spec:** [docs/superpowers/specs/2026-07-25-autorodo-web-design.md](../specs/2026-07-25-autorodo-web-design.md)
---
## 文件结构(锁定)
| 路径 | 职责 |
|------|------|
| `src/prototypes/autorodo-web/lib/types.ts` | 需求稿、待确认点、元数据、步骤状态类型 |
| `src/prototypes/autorodo-web/lib/parseDraft.ts` | 解析粘回的 AutoRDO Markdown → `RequirementDraft[]` |
| `src/prototypes/autorodo-web/lib/parseDraft.test.ts` | 解析单测 |
| `src/prototypes/autorodo-web/lib/applyAnswer.ts` | 选择题/补充 → 回填描述、消待确认 |
| `src/prototypes/autorodo-web/lib/applyAnswer.test.ts` | 回填单测 |
| `src/prototypes/autorodo-web/lib/buildCleanPrompt.ts` | 步骤 1 清洗提示词 |
| `src/prototypes/autorodo-web/lib/buildRecordPrompt.ts` | 步骤 3 YunxiaoPMapp 多条口令 |
| `src/prototypes/autorodo-web/lib/buildRecordPrompt.test.ts` | 上报词单测 |
| `src/prototypes/autorodo-web/lib/inferMeta.ts` | 从原文推断提交部门/提交人 |
| `src/prototypes/autorodo-web/lib/inferMeta.test.ts` | 推断单测 |
| `src/prototypes/autorodo-web/lib/agentAdapter.ts` | 预留 `clean` / `record` 接口类型MVP 空实现) |
| `src/prototypes/autorodo-web/lib/clipboard.ts` | `navigator.clipboard.writeText` 封装 |
| `src/prototypes/autorodo-web/lib/pendingQuestions.ts` | 从「待确认」文案生成选择题选项(词典摘要常量) |
| `src/prototypes/autorodo-web/components/StepPaste.tsx` | 步骤 1 UI |
| `src/prototypes/autorodo-web/components/StepConfirm.tsx` | 步骤 2 UI |
| `src/prototypes/autorodo-web/components/StepExport.tsx` | 步骤 3 UI |
| `src/prototypes/autorodo-web/AutorodoWebApp.tsx` | 三步状态机主壳 |
| `src/prototypes/autorodo-web/index.tsx` | 入口 `@name` + AnnotationHost |
| `src/prototypes/autorodo-web/style.css` | 布局与触控样式 |
| `src/prototypes/autorodo-web/annotation-source.json` | 标注目录 |
| `src/prototypes/autorodo-web/.spec/requirements-prd.md` | AutoPRD |
| `src/prototypes/autorodo-web/.spec/confirm-flow.md` | 确认与回填规则全文 |
| `src/resources/prd/autorodo-web-autoprd.md` | PRD 副本(若项目惯例需要) |
| 修改 `.axhub/make/project.json` | 注册原型(若 Make 未自动发现) |
| 修改 `~/.cursor/skills/YunxiaoPMapp` + 项目内副本 | 记录需求解析四字段Task 8可同迭代或紧随 |
**不放入 `src/common/`**:逻辑仅本原型使用,避免污染公共层。
---
### Task 1: 类型 + parseDraft + 单测
**Files:**
- Create: `src/prototypes/autorodo-web/lib/types.ts`
- Create: `src/prototypes/autorodo-web/lib/parseDraft.ts`
- Create: `src/prototypes/autorodo-web/lib/parseDraft.test.ts`
- [ ] **Step 1: 写入类型**
```ts
// src/prototypes/autorodo-web/lib/types.ts
export type Priority = '紧急' | '高' | '中' | '低';
export type RequirementMeta = {
priority: Priority | '';
tags: string[];
submitDept: string;
submitter: string;
titleKind: '新增' | '优化' | '';
};
export type PendingItem = {
id: string;
raw: string;
question: string;
options: string[];
resolved: boolean;
answer?: string;
};
export type RequirementDraft = {
index: number;
title: string;
description: string;
pendings: PendingItem[];
meta: RequirementMeta;
confirmed: boolean;
};
export type WizardStep = 1 | 2 | 3;
```
- [ ] **Step 2: 写失败单测(多条 + 待确认)**
```ts
// src/prototypes/autorodo-web/lib/parseDraft.test.ts
import { describe, it, expect } from 'vitest';
import { parseAutorodoDraft } from './parseDraft';
const SAMPLE = `共 2 条
### 1
## 原始诉求AutoRDO
**标题**:合同台账批量导出
**描述**
在租赁合同台账支持批量导出
待确认:
- 导出范围未说明
### 2
## 原始诉求AutoRDO
**标题**:证照到期提醒
**描述**
调整证照到期提醒口径
待确认:
`;
describe('parseAutorodoDraft', () => {
it('splits multiple requirements and pending items', () => {
const list = parseAutorodoDraft(SAMPLE);
expect(list).toHaveLength(2);
expect(list[0].title).toContain('合同台账');
expect(list[0].pendings).toHaveLength(1);
expect(list[0].pendings[0].raw).toContain('导出范围');
expect(list[1].pendings).toHaveLength(0);
expect(list[1].confirmed).toBe(true);
});
});
```
- [ ] **Step 3: 跑测确认失败**
Run: `npx vitest run src/prototypes/autorodo-web/lib/parseDraft.test.ts`
Expected: FAIL模块不存在或 `parseAutorodoDraft` 未定义)
- [ ] **Step 4: 实现解析**
```ts
// src/prototypes/autorodo-web/lib/parseDraft.ts
import type { RequirementDraft, PendingItem, RequirementMeta } from './types';
const emptyMeta = (): RequirementMeta => ({
priority: '',
tags: [],
submitDept: '',
submitter: '',
titleKind: '',
});
function parsePendings(block: string, reqIndex: number): PendingItem[] {
const m = block.match(/待确认[:]\s*([\s\S]*?)(?=\n###|\n##\s*原始诉求|$)/);
if (!m) return [];
const body = m[1].trim();
if (!body || body === '无') return [];
const lines = body
.split('\n')
.map((l) => l.replace(/^[-*•\d.、)\s]+/, '').trim())
.filter(Boolean);
return lines.map((raw, i) => ({
id: `r${reqIndex}-p${i}`,
raw,
question: raw,
options: [],
resolved: false,
}));
}
export function parseAutorodoDraft(md: string): RequirementDraft[] {
const parts = md.split(/(?=^###\s*\d+)/m).filter((p) => /原始诉求AutoRDO/.test(p) || /\*\*标题\*\*/.test(p));
const chunks =
parts.length > 0
? parts
: md.includes('**标题**')
? [md]
: [];
return chunks.map((chunk, index) => {
const title = (chunk.match(/\*\*标题\*\*[:]\s*(.+)/)?.[1] ?? '').trim();
const descMatch = chunk.match(/\*\*描述\*\*[:]\s*\n?([\s\S]*?)(?=\n待确认|$)/);
const description = (descMatch?.[1] ?? '').trim();
const pendings = parsePendings(chunk, index);
return {
index,
title,
description,
pendings,
meta: emptyMeta(),
confirmed: pendings.length === 0,
};
});
}
```
- [ ] **Step 5: 跑测确认通过**
Run: `npx vitest run src/prototypes/autorodo-web/lib/parseDraft.test.ts`
Expected: PASS
- [ ] **Step 6: Commit**(仅当用户要求提交时执行;否则跳过所有 Commit 步骤)
```bash
git add src/prototypes/autorodo-web/lib/types.ts src/prototypes/autorodo-web/lib/parseDraft.ts src/prototypes/autorodo-web/lib/parseDraft.test.ts
git commit -m "$(cat <<'EOF'
feat(autorodo-web): add AutoRDO draft parser and types
EOF
)"
```
---
### Task 2: applyAnswer + pendingQuestions
**Files:**
- Create: `src/prototypes/autorodo-web/lib/applyAnswer.ts`
- Create: `src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
- Create: `src/prototypes/autorodo-web/lib/pendingQuestions.ts`
- [ ] **Step 1: 写回填单测**
```ts
import { describe, it, expect } from 'vitest';
import { applyPendingAnswer } from './applyAnswer';
import type { RequirementDraft } from './types';
const base: RequirementDraft = {
index: 0,
title: '合同台账批量导出',
description: '在租赁合同台账支持批量导出\n导出范围待确认',
pendings: [
{
id: 'r0-p0',
raw: '导出范围未说明',
question: '导出范围指什么?',
options: ['当前筛选结果', '全部合同', '仅勾选行'],
resolved: false,
},
],
meta: { priority: '', tags: [], submitDept: '', submitter: '', titleKind: '' },
confirmed: false,
};
describe('applyPendingAnswer', () => {
it('merges answer into description and marks pending resolved', () => {
const next = applyPendingAnswer(base, 'r0-p0', '当前筛选结果');
expect(next.pendings[0].resolved).toBe(true);
expect(next.pendings[0].answer).toBe('当前筛选结果');
expect(next.description).toContain('当前筛选结果');
expect(next.description).not.toMatch(/导出范围:待确认/);
expect(next.confirmed).toBe(true);
});
});
```
- [ ] **Step 2: 跑测确认失败**
Run: `npx vitest run src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
Expected: FAIL
- [ ] **Step 3: 实现 applyAnswer + 简易出题**
```ts
// applyAnswer.ts
import type { RequirementDraft } from './types';
export function applyPendingAnswer(
draft: RequirementDraft,
pendingId: string,
answer: string,
): RequirementDraft {
const pendings = draft.pendings.map((p) =>
p.id === pendingId ? { ...p, resolved: true, answer: answer.trim() } : p,
);
const hit = draft.pendings.find((p) => p.id === pendingId);
let description = draft.description;
if (hit) {
const label = hit.raw.replace(/未说明|待确认|不清楚/g, '').trim() || hit.raw;
if (/待确认/.test(description)) {
description = description.replace(/待确认/g, answer.trim());
} else {
description = `${description.replace(/\s+$/, '')}\n${label}${answer.trim()}`;
}
}
const confirmed = pendings.every((p) => p.resolved);
return { ...draft, description, pendings, confirmed };
}
// pendingQuestions.ts — MVP关键词启发式无匹配则「是 / 否 / 需补充」
export function buildOptionsForPending(raw: string): { question: string; options: string[] } {
if (/导出范围|范围/.test(raw)) {
return {
question: '「导出范围」指什么?',
options: ['当前筛选结果', '全部数据', '仅勾选行'],
};
}
if (/模块|归属/.test(raw)) {
return {
question: '归属哪个业务模块?',
options: ['租赁合同管理', '车辆管理', '证照管理', '还车应结款'],
};
}
return {
question: raw.endsWith('') || raw.endsWith('?') ? raw : `${raw}`,
options: ['确认按原文理解', '暂不纳入本需求', '需文字补充'],
};
}
export function enrichPendingsWithQuestions(draft: RequirementDraft): RequirementDraft {
return {
...draft,
pendings: draft.pendings.map((p) => {
const q = buildOptionsForPending(p.raw);
return { ...p, question: q.question, options: q.options };
}),
};
}
```
`parseAutorodoDraft` 返回前对每条调用 `enrichPendingsWithQuestions`,或在 UI 粘回后统一 enrich二选一推荐 UI 粘回后调用,保持 parse 纯净)。
- [ ] **Step 4: 跑测确认通过**
Run: `npx vitest run src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**(用户要求时)
---
### Task 3: inferMeta + buildCleanPrompt + buildRecordPrompt
**Files:**
- Create: `src/prototypes/autorodo-web/lib/inferMeta.ts`
- Create: `src/prototypes/autorodo-web/lib/inferMeta.test.ts`
- Create: `src/prototypes/autorodo-web/lib/buildCleanPrompt.ts`
- Create: `src/prototypes/autorodo-web/lib/buildRecordPrompt.ts`
- Create: `src/prototypes/autorodo-web/lib/buildRecordPrompt.test.ts`
- Create: `src/prototypes/autorodo-web/lib/clipboard.ts`
- Create: `src/prototypes/autorodo-web/lib/agentAdapter.ts`
- [ ] **Step 1: inferMeta 单测**
```ts
import { describe, it, expect } from 'vitest';
import { inferSubmitMeta } from './inferMeta';
describe('inferSubmitMeta', () => {
it('detects dept and person from free text', () => {
const r = inferSubmitMeta('业务管理组张三反馈:合同导出不好用');
expect(r.submitDept).toMatch(/业务/);
expect(r.submitter).toContain('张三');
});
});
```
- [ ] **Step 2: 实现 inferMeta**
```ts
const DEPT_PATTERNS = [
/(?:提交部门|部门)[:]\s*([^\s,;]+)/,
/(业务管理组|产品部|安全部|运营部|财务部|技术部)/,
];
const PERSON_PATTERNS = [
/(?:提交人|反馈人|提出人)[:]\s*([^\s,;]+)/,
/@([\u4e00-\u9fa5]{2,4})/,
/([\u4e00-\u9fa5]{2,4})反馈/,
];
export function inferSubmitMeta(text: string): { submitDept: string; submitter: string } {
let submitDept = '';
let submitter = '';
for (const re of DEPT_PATTERNS) {
const m = text.match(re);
if (m?.[1]) {
submitDept = m[1];
break;
}
}
for (const re of PERSON_PATTERNS) {
const m = text.match(re);
if (m?.[1]) {
submitter = m[1];
break;
}
}
return { submitDept, submitter };
}
```
- [ ] **Step 3: buildRecordPrompt 单测 + 实现**
```ts
// buildRecordPrompt.test.ts
import { describe, it, expect } from 'vitest';
import { buildYunxiaoRecordPrompt } from './buildRecordPrompt';
import type { RequirementDraft } from './types';
const one: RequirementDraft = {
index: 0,
title: '合同台账批量导出',
description: '在租赁合同台账支持按筛选结果批量导出 Excel',
pendings: [],
meta: {
priority: '中',
tags: ['租赁合同'],
submitDept: '产品部',
submitter: '王冕',
titleKind: '新增',
},
confirmed: true,
};
describe('buildYunxiaoRecordPrompt', () => {
it('includes priority tags dept submitter', () => {
const text = buildYunxiaoRecordPrompt([one]);
expect(text).toContain('YunxiaoPMapp');
expect(text).toContain('记录需求:【新增】合同台账批量导出');
expect(text).toContain('优先级=中');
expect(text).toContain('标签=租赁合同');
expect(text).toContain('提交部门=产品部');
expect(text).toContain('提交人=王冕');
expect(text).toContain('推进至=暂不推进');
});
it('throws when meta incomplete', () => {
expect(() =>
buildYunxiaoRecordPrompt([{ ...one, meta: { ...one.meta, submitter: '' } }]),
).toThrow(/提交人/);
});
});
```
```ts
// buildRecordPrompt.ts
import type { RequirementDraft } from './types';
export function metaComplete(d: RequirementDraft): boolean {
const m = d.meta;
return Boolean(
m.priority && m.tags.length > 0 && m.submitDept.trim() && m.submitter.trim() && m.titleKind,
);
}
export function buildYunxiaoRecordPrompt(drafts: RequirementDraft[]): string {
for (const d of drafts) {
if (!d.confirmed) throw new Error(`需求 ${d.index + 1} 仍有未确认项`);
if (!metaComplete(d)) throw new Error(`需求 ${d.index + 1} 缺少优先级/标签/提交部门/提交人/类型`);
}
const lines = drafts.map((d) => {
const title = `${d.meta.titleKind}${d.title.replace(/^【(?:新增|优化)】/, '')}`;
const tags = d.meta.tags.join('、');
return `记录需求:${title};描述=${d.description.replace(/\n/g, ' ')};优先级=${d.meta.priority};标签=${tags};提交部门=${d.meta.submitDept};提交人=${d.meta.submitter};推进至=暂不推进`;
});
return [
'YunxiaoPMapp',
'请按下列条目依次记录需求;每条保持独立,不要合并。',
'',
...lines,
].join('\n');
}
```
```ts
// buildCleanPrompt.ts
export function buildCleanPrompt(payload: {
text: string;
attachmentNames: string[];
}): string {
const files =
payload.attachmentNames.length > 0
? `附件:${payload.attachmentNames.join('、')}(请用多模态识读图/语音内容)`
: '附件:无';
return [
'AutoRDO',
'请按 AutoRDO 规则清洗下列素材:多条独立诉求拆成多份;提炼标题与书面描述;不确定处标「待确认」;描述无结尾句号。',
files,
'',
'—— 素材开始 ——',
payload.text.trim() || '(仅附件,无正文)',
'—— 素材结束 ——',
].join('\n');
}
```
```ts
// clipboard.ts
export async function copyText(text: string): Promise<void> {
await navigator.clipboard.writeText(text);
}
// agentAdapter.ts
export type CleanRequest = { text: string; attachmentNames: string[] };
export type RecordRequest = { drafts: import('./types').RequirementDraft[] };
/** MVP未接组织 Agent后续替换实现即可 */
export const agentAdapter = {
async clean(_req: CleanRequest): Promise<null> {
return null;
},
async record(_req: RecordRequest): Promise<null> {
return null;
},
};
```
- [ ] **Step 4: 跑全部 lib 单测**
Run: `npx vitest run src/prototypes/autorodo-web/lib`
Expected: 全部 PASS
- [ ] **Step 5: Commit**(用户要求时)
---
### Task 4: 原型壳 + 步骤 1 UI
**Files:**
- Create: `src/prototypes/autorodo-web/index.tsx`
- Create: `src/prototypes/autorodo-web/AutorodoWebApp.tsx`
- Create: `src/prototypes/autorodo-web/style.css`
- Create: `src/prototypes/autorodo-web/components/StepPaste.tsx`
- Create: `src/prototypes/autorodo-web/annotation-source.json`(先最小目录)
- Modify: `.axhub/make/project.json`(若启动后未出现在列表则补注册)
前置Read `src/resources/design-system/DESIGN.md`Token、触控 ≥44px、禁止原生 select
- [ ] **Step 1: 入口与主壳骨架**
```tsx
// index.tsx
/**
* @name AutoRDO 需求清洗工作台
*/
import './style.css';
import '../../../resources/design-system/oneos-ds-tokens.css';
import React from 'react';
import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host';
import type { AnnotationSourceDocument } from '@axhub/annotation';
import annotationSourceDocument from './annotation-source.json';
import { AutorodoWebApp } from './AutorodoWebApp';
export default function AutorodoWebEntry() {
return (
<>
<AutorodoWebApp />
<PrototypeAnnotationHost
source={annotationSourceDocument as AnnotationSourceDocument}
options={{}}
/>
</>
);
}
```
`AutorodoWebApp`:左侧步骤 1/2/3`step` state步骤 1 渲染 `StepPaste`
- [ ] **Step 2: StepPaste**
- contentEditable 或 textarea 接收粘贴;`onPaste` 收集 `clipboardData.files`image/*、audio/*)为附件名列表(原型阶段可用 File.name不上传
- 按钮「确认并复制清洗提示词」→ `buildCleanPrompt` + `copyText`toast「已复制请粘到 Cursor 跑 AutoRDO」
- 可选 checkbox「粘贴后自动复制」默认关
- 控件:优先 `V2` 封装;按钮 min-height 44px
- [ ] **Step 3: 本地预览**
Run: 打开 Make 预览 `/prototypes/autorodo-web`
Expected: 能粘贴、点确认后剪贴板为 AutoRDO 提示词
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 5: 步骤 2 确认 UI
**Files:**
- Create: `src/prototypes/autorodo-web/components/StepConfirm.tsx`
- Modify: `src/prototypes/autorodo-web/AutorodoWebApp.tsx`
- [ ] **Step 1: 粘回区 + 解析**
- textarea「粘回 AutoRDO 初稿」
- 按钮「解析初稿」→ `parseAutorodoDraft``enrichPendingsWithQuestions` → 对每条 `inferSubmitMeta(description + title)` 预填 meta
- Pill 切换需求 index
- [ ] **Step 2: 左右分栏确认**
- 左:标题(可编辑)+ 描述(只读展示实时稿)
- 右:当前未解决 pending单选 options补充 input「确认并下一题」调用 `applyPendingAnswer`
- 元数据:优先级四选一;标签用可增删 chip自由输入 MVP提交部门/提交人 input类型 新增/优化
- 全部 confirmed 且 `metaComplete` 后可进步骤 3
- [ ] **Step 3: 手动验收路径**
1. 粘贴 Task1 SAMPLE 初稿
2. 答完题后描述含答案且无「待确认」
3. 填齐元数据
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 6: 步骤 3 导出 + 样式打磨
**Files:**
- Create: `src/prototypes/autorodo-web/components/StepExport.tsx`
- Modify: `style.css``AutorodoWebApp.tsx`
- [ ] **Step 1: StepExport**
- 预览 `buildYunxiaoRecordPrompt(drafts)`
- 「一键复制上报提示词」
- 「复制定稿 Markdown」输出多份已确认 `## 原始诉求AutoRDO`,待确认:无)
- 错误态:捕获 throw页面内提示缺哪条哪字段
- [ ] **Step 2: 响应式**
- ≤767px步骤条顶置确认区改为上下堆叠触控 ≥44px正文 ≥14px
- [ ] **Step 3: 预览验收**
全路径走通:粘贴 → 复制清洗词 →(模拟)粘回 SAMPLE → 确认 → 复制上报词,粘贴到文本编辑器检查字段齐全。
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 7: AutoPRD + 标注 + 导航同步
**Files:**
- Create: `src/prototypes/autorodo-web/.spec/confirm-flow.md`
- Create: `src/prototypes/autorodo-web/.spec/requirements-prd.md`
- Create: `src/resources/prd/autorodo-web-autoprd.md`(与 PRD 同步)
- Modify: `annotation-source.json`(产品需求说明节点)
- [ ] **Step 1: 写 confirm-flow.md**
含:判定顺序表、前置条件、数据源(粘回 Markdown / 本地启发式选项)、用户可见结果、标明未接真 API。
- [ ] **Step 2: 写 requirements-prd.md**
按 oneos-autoprd 结构:总览/目标/边界/角色/用户故事(起点→运作→闭环)/验收;链到 `confirm-flow.md`
- [ ] **Step 3: 同步标注与导航**
```bash
npm run nav:sync -- --prototype autorodo-web --note "新增 AutoRDO 网页清洗与云效上报提示词工作台"
```
- [ ] **Step 4: Commit**(用户要求时)
---
### Task 8: YunxiaoPMapp 消费四字段Skill 扩展)
**Files:**
- Modify: `~/.claude/skills/YunxiaoPMapp/references/commands.md`(及项目 `.cursor` / `.claude` 同步副本)
- Modify: YunxiaoPMapp `SKILL.md` 记录需求参数说明
- 可选:`references/record-meta-fields.md` 新文件
- [ ] **Step 1: 文档约定口令字段**
在口令面增加:
```text
记录需求:…;优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=…
```
说明:网页上报词已带齐时,**禁止**再 Plan 重复问这四项(除非缺省);缺省则仍问。
- [ ] **Step 2: 实现侧**
若现有 live 脚本已支持 priority/tag把提交部门/提交人映射到云效字段(查 `runtime-ids.json`;未验证字段则先写入描述尾部 `提交部门/提交人`,并在文档标明「字段 ID 待验证」)。
- [ ] **Step 3: 与网页联调**
用步骤 3 复制出的提示词在 Cursor 跑一次「记录需求」(测试项目或 dry-run 策略按团队惯例)。
- [ ] **Step 4: Commit**(用户要求时)
---
## 规格覆盖自检
| Spec 要求 | Task |
|-----------|------|
| 粘贴文/图/语音 + 复制清洗词 | 4 |
| 粘回初稿 + 分页确认 + 回填 | 1,2,5 |
| 优先级/标签/部门/提交人 | 3,5,6 |
| 一键 Yunxiao 多条口令 | 3,6 |
| 预留组织 Agent | 3 `agentAdapter` |
| AutoPRD / 业务规则文档 | 7 |
| Yunxiao 消费四字段 | 8 |
| 不直连云效 / 无 OCR | 全程遵守 |
---
## 执行说明
- 所有 **Commit 步骤默认跳过**,除非用户明确要求提交。
- Task 8 可与 Task 47 并行由另一会话做;网页 MVP 以 Task 17 为可演示闭环(上报词已含四字段,即使 Skill 暂未写入云效自定义字段,口令仍可用于人工/后续 Skill
---
Plan complete and saved to `docs/superpowers/plans/2026-07-25-autorodo-web.md`.
**两种执行方式:**
1. **Subagent-Driven推荐** — 每任务开新子代理,任务间复核,迭代快
2. **Inline Execution** — 本会话按 executing-plans 连续执行,设检查点
要哪一种?