5.9 KiB
统一 OneTalk React 卡片 reader 设计
Scope and ownership
本任务是一个共享实现单元,不拆 child task:订单、询盘、商品都由同一个 MAIN observer、同一 identity/boundary rule 与同一 rendered-card union 承载;拆分会让 strict classifier、shared product projection 和 contract shape 在不同任务内失配。
仅修改 contract 的 rendered-product shape、Chrome extension 的 MAIN card reader/test,以及恢复 rendered-card ledger 已承诺的 scope index 查询。既有 page bridge、ISOLATED forwarder、Service Worker coordinator、ledger 状态机、Bright flow、server supplement repository 和 Mind publisher 不改行为。
Data flow
mounted .message-item-wrapper
-> common bounded Fiber identity reader
-> strict card classifier
-> order | inquiry | product typed projection
-> exact OneTalkRenderedCardContent validation
-> existing page bridge / ledger / Bright / server supplement
MutationObserver 只发现挂载;Fiber itemData 继续提供 message identity、conversation cross-check、direction 与 sentAt evidence。模板 props.data 只能由各 reader 的直接 predicate 选中,不能递归复制对象。每次读取前后均要求唯一 selected conversation 未变化。
Strict classifiers and projections
| Kind | Required identity/classifier | Template predicate and projection |
|---|---|---|
| order | msgType=10010, originalData.cardType=9 |
data.cardTitle、商品列表、orderStatusText、shippingAddress;列表项 title allowlist 增加真实 productName,image allowlist 保持 productImage 等现有路径。 |
| inquiry | msgType=10010, originalData.cardType=6 |
data.inquiryCardDTO、displayProducts/products、inquiryContent、inquiryID;首个 displayed product 映射 name/image/MOQ/unit;动作按 detailStr/detailAction、quoteStr/quoteAction、actionName/inquiryAction 的固定顺序产生 { label, available },不穿透动作对象。 |
| product | msgType=101 且同一有界 Fiber 子树存在 renderer cardType=54 |
data.title、productImage、price、moq、moqUnit、productAction;productAction.actionParams.url 经 canonical product URL normalizer 得到 product reference;固定顺序收集非空 benefitFirstText、benefitSecondText、promotionTag 为 badges。 |
只有上述三种分类可产生 observation。文件 cardType=12、未知 cardType、缺失 template、非 cardType=54 的 msgType=101、身份或会话不一致均返回 null,不会写 ledger。
Shared contract changes
- 从
OneTalkRenderedProductContent删除storeImageUrl,并从精确键校验、clone/canonical fingerprint 的输入 shape、所有 fixture 与断言中去除。当前仓库不存在 rendered-product producer 或 stored fixture,故不需要 JSONB migration/backfill;新的 strict decoder 不接受该旧键。 - 把 product URL canonicalization/validation 提取为不依赖
content.ts或rendered-cards.ts的 contract-owned module。避免content.ts已依赖 rendered-card union 时产生循环依赖。 - 基础消息
kind: "product"与 rendered-card product reader 共用该 normalizer;rendered-card validator 要求sourceUrl是 canonical product URL 且从路径导出的 product ID 与 payloadproductId精确相等,不能复用图片 URL validator。 product.imageUrl仍单独使用 card image URL validator。商品没有独立店铺图的运行时证据,contract 中不保留任何替代字段。
Safety and compatibility
- 不桥接 raw
content、originalData、inquiryCardDTO、action params、callback、trace、hidden ID、URL query/fragment/credential、DOM/Fiber reference。 - 既有 rendered-card ledger 的 first-content-wins、fingerprint、ACK 与 conflict 语义不变;contract 收窄后 fingerprint 自然只基于允许字段。
- 当前协议的唯一运行 producer 是订单 reader;当前 source tree 中没有 rendered-product producer。因此删除未生产字段不需要服务器 migration 或 wire-version bump;新 extension 与 server 必须仍使用同一 contract build。
- rendered-card ledger 必须由 v9 升级到 v10,且 v10 只在
onetalk_rendered_card_ledger创建[channelAccountId, status]与[channelAccountId, conversationId]indexes。listPending(channelAccountId)通过前者读取[channelAccountId, "pending_ack"];clearConversationHistory通过后者在既有 transaction 中删除精确会话。两者都不恢复readAll(),不清理或修改范围外的 ledger record。 - 实际扩展重载和重新打开 OneTalk 标签才会让 MAIN/ISOLATED/Worker 均加载新 bundle。该用户可见操作不在本 task 的自动验证授权内。
Test design
- Contract: three valid card variants; product reference canonical/ID match; reject
storeImageUrl、image URL as source URL、query/credential/fragment/non-product product URL and mismatched ID. - MAIN observer: fixture each true card; verify exact normalized content and base evidence; reject classifier lookalikes, files, missing data, conversation switch and unsafe URL/text.
- Existing bridge/coordinator regression: accepted observation remains durable-first and waits for confirmed base candidate. No test should reimplement downstream behavior already owned there.
- Storage: a v9 rendered-card record survives the v10 upgrade; scoped
listPendingreturns only target-account pending records and does not call object-storegetAll(). - Storage: the existing history-cleanup regression proves only the matching account/conversation rendered-card record is deleted through the new scope index.
- Runtime: with user authorization to reload/reopen only if needed, use Chrome read-only probes to verify the three classifier/template predicates and only normalized bridge output. Database acceptance remains conditional on an existing confirmed base fact and valid authenticated Bright connection.