Files
trade-message-center/.trellis/tasks/09-15-onetalk-rendered-card-readers/design.md
T

5.9 KiB
Raw Blame History

统一 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、商品列表、orderStatusTextshippingAddress;列表项 title allowlist 增加真实 productNameimage allowlist 保持 productImage 等现有路径。
inquiry msgType=10010, originalData.cardType=6 data.inquiryCardDTOdisplayProducts/productsinquiryContentinquiryID;首个 displayed product 映射 name/image/MOQ/unit;动作按 detailStr/detailActionquoteStr/quoteActionactionName/inquiryAction 的固定顺序产生 { label, available },不穿透动作对象。
product msgType=101 且同一有界 Fiber 子树存在 renderer cardType=54 data.titleproductImagepricemoqmoqUnitproductActionproductAction.actionParams.url 经 canonical product URL normalizer 得到 product reference;固定顺序收集非空 benefitFirstTextbenefitSecondTextpromotionTag 为 badges。

只有上述三种分类可产生 observation。文件 cardType=12、未知 cardType、缺失 template、非 cardType=54msgType=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.tsrendered-cards.ts 的 contract-owned module。避免 content.ts 已依赖 rendered-card union 时产生循环依赖。
  • 基础消息 kind: "product" 与 rendered-card product reader 共用该 normalizerrendered-card validator 要求 sourceUrl 是 canonical product URL 且从路径导出的 product ID 与 payload productId 精确相等,不能复用图片 URL validator。
  • product.imageUrl 仍单独使用 card image URL validator。商品没有独立店铺图的运行时证据,contract 中不保留任何替代字段。

Safety and compatibility

  • 不桥接 raw contentoriginalDatainquiryCardDTO、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 listPending returns only target-account pending records and does not call object-store getAll().
  • 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.