549 lines
19 KiB
Markdown
549 lines
19 KiB
Markdown
# 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: PASS(4 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 或 pending,wechat_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 1–2 |
|
||
| 本地路由 + 优先级 + 固定失败文案 | Task 1, 5 |
|
||
| 五端 × 多系统种子与外部 A/B | Task 2 |
|
||
| 消息中心 UI + 端模拟器 + 去处理 | Task 4 |
|
||
| 铃铛快捷 + 查看全部 | Task 7 |
|
||
| 独立中枢架构 | Task 1–4 |
|
||
| .spec + AutoPRD + 标注 | Task 5 |
|
||
| 导航登记 | Task 6 |
|
||
| 不做真推送/真唤端 | Task 4 模态示意 |
|
||
|
||
无 TBD 占位;`resolveMessageTarget` / `HubMessage` / `ROUTE_RULES` 命名全计划一致。
|