Files
lixi f5457c2f4c
CI / docs-build (push) Successful in 2m2s
docs: M3 第三波收口——报告 21~27 入档挂导航
- 21~26 Flutter 社区接入五单 + 字典 v3 白名单(flutter 286→502、api 325→334)
- 27 收口总表:社区 demo 三页消亡、M3 四条验收标准逐条取证、
  乐观更新与媒体链路端到端、实现期修正记录
- device-verification.md 的 M3 四项真机步骤已由各单收口补全

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 14:48:33 +08:00

13 KiB
Raw Permalink Blame History

25 M3 第三波:帖子详情页替换 + 互动接线(T3-15/T3-16

执行日期2026-09-09 工单:T3-15 帖子详情页整页替换(demo 数据层退役)+ T3-16 互动接线(ToggleSync UI 层),同域合并交付 依赖21 号(T3-12 数据层,ToggleSync/CommunityController 就位)、24 号(T3-14 组件与遗留交接)、05 号 UI 规范 §2.2/§3.4/§3.5/§4、22 号事件白名单 v3、17 号后端评论/互动语义 提交patbond-flutter dev 92524daT3-16 基建)+ f873acf(T3-15/16 页面与接线),基线 8aac8c5,已推送 origin/dev


0. 概要

post_detail_page.dart 整页重写为真实数据:四态首屏、媒体全量渲染(真九宫格 + 全屏大图)、作者卡关注双态、评论区(游标列表 / 输入条创建 / 仅本人可删)全部 落地;点赞/收藏经共享 ToggleSync 接入 Feed 卡片与详情页(同一 controller 实例, 互动状态跨页一致),按 05 号 §4 三层视觉抑制实现;互动域 8 事件挂接完成。 Feed 整卡点按导航详情接通,T3-14 的占位 SnackBar 与禁用态移除。未动 patbond-apicreate 页 demo 留给 T3-17。

质量门禁flutter test 458/458 全绿(基线 421+37;另 2 个 env 门控 compose 冒烟默认跳过)、flutter analyze 0 问题、dart format --set-exit-if-changed 无 diff、compose 六容器真链路实测通过(§5,含断网 点赞回滚)。

1. 详情页四态与结构(T3-15

渲染 widget 测试
loading(无内存副本) 居中转圈;评论区独立骨架 2 个(32 圆 + 圆角 16 块高 7205 §3.7
内存副本先渲染 进入即展示 controller 缓存内容,getPost 后台拉新静默替换(Feed 卡片互动字段一并回写)
error InlineErrorBanner(pets 同款话术映射)+ 「重试」;有副本时后台刷新失败不打断阅读
40403 不存在态 SnackBar「帖子不存在或已被删除」→ 返回 Feed 并触发整体刷新(失效帖剔除);详情 / 评论 / 评论创建三条路径均可触发,单次守卫防重复 pop
ready 媒体区 → 作者卡 → 正文卡 → 操作行 → 评论区,底部固定输入条

结构落点(05 §2.2 对照):

  • 媒体区(本单裁定:真九宫格,D11 轮播方案弃用):单图原比例通栏、高度 钳制 [宽×0.75, 宽×1.33]widthPx/heightPx 缺失回落 4:3);多图走 PostMediaGrid 全量形态(24 号预留的列数规则 2/4→2 列、3/5–9→3 列与 超 9 折叠「+N」直接生效)。点格进全屏大图:黑底 + InteractiveViewer (03 号拍板 E 选①内置方案,零依赖)+ 横滑翻页 + 双击定点 2.5x 缩放 + 右上「n/N」ink 胶囊(13.50:1)与关闭钮。偏差:05 §2.2 的「下滑关闭」 与 InteractiveViewer 平移手势冲突,本版未做(关闭钮 + 返回手势可退出), 留待 photo_view 复评(03 号 E 的升级条件「体验不达标」)。
  • 作者卡PetAvatar md44 + 名字/相对时间;关注双态钮见 §3。
  • 正文卡:标题 titleMedium + 求助帖 TagPill(accent) + 正文 14/1.6 全文 + 「发布于 …」12 inkSoft。契约 Post 无话题字段,TopicChip 不涉本单。
  • 操作行:与 Feed 卡片同一套组件卡外裸排(demo 的 FilledButton.tonalIcon 弃用);评论锚点钮点按聚焦底部输入框(唤起键盘直接开写)。
  • 输入条surface 底 + 顶部 border 1px 分隔线(demo 缺失,已补)+ isDense 输入框 + filled 发送钮(空文本禁用;发送中 18 转圈锁尺寸)。
  • AppBar 分享:占位 SnackBar「分享功能即将上线」(无契约端点)。

2. 评论区(T3-15

  • 游标列表(created_at DESC, id DESC) 服务端序原样渲染,触底(余量 400)携 nextCursor 补页,失败态只走显式重试(Feed 同款守卫);空态 「还没有评论,来抢沙发」(装饰图标 muted 合法、文案 inkSoftDEBT-2 零新增)。
  • 创建:仓库层 Idempotency-Key 每次提交换新键(21 号已测线上语义);成功 插入列表头 + adjustCommentCount(+1) 同源写入(详情副本与 Feed 卡片 commentCount 一并更新)+ 清空输入收起键盘;失败保留输入 + 按类型话术 SnackBar(40000 →「评论内容不合规」等),撞 40403 走不存在态流程。
  • 仅本人可删(UI 呈现)currentUserIdapp.dart 注入 sessionManager.userId 与评论 author.userId 相等才渲染「删除」入口——权限判定只做 UI 自见性, 服务端 40301/40404 仍是裁决者(17 号 §2.3)。删除经确认弹窗 → 软删成功 剔除 + 计数 -1;40404(已在别处删)本地同步剔除;40301 提示无权限。
  • CommentTile 升共享组件lib/core/widgets/comment_tile.dart05 §3.4): PetAvatar sm32 + 气泡(surface/border 1px/圆角 16/padding 12);@ 回复以 「回复 @昵称:」前缀呈现(响应 replyToUser,含降级「宠友」占位);删除 in-flight 转圈锁定。取舍:评论点赞(§3.4 底行右端)无契约端点不渲染; @ 回复的发起 UI 与长按操作 sheet(回复/复制/举报)留待后续工单 (数据层 replyToUserId 已支持,isReply 埋点属性预留)。

3. 互动视觉实现(T3-16,05 §3.5/§4 三层抑制对照)

规范 实现落点
即时反馈 点按即刻翻转 + 激活动画 ToggleSync 乐观写入同帧 notifyLikeButton 升 Stateful——点按驱动的激活播 240ms 弹性缩放(1→1.25→1+ 120ms 图标淡入,取消仅 120ms 颜色渐出无缩放
连点合并 只发最终态 由数据层单飞合并承担(在途链只并入 pendingTarget、完成后按最终意图至多补发一次,连点至多两在途);UI 不再叠加 600ms 计时防抖——ToggleSync 已保证「合并后只发最终态」的语义,双状态机会打架(D8 的跨角色确认以 21 号定稿为准)
回滚静默化 零动画 + 成对恢复 + SnackBar 非点按驱动的状态变化(回滚/对账)直接跳变;计数与展示态成对更新(不出现「心已灭计数未减」中间帧);激活动画未播完等播完再跳(§4.3a);toggleError 一次性消费出 SnackBar「操作失败,请重试」(Feed 页与详情页共用消费口,先消费者清空,同帧恰一条)
对账不打扰 静默替换计数 服务端权威计数与乐观值不同(他人并发)时数字直接替换、无动画(LikeButton 对「状态不变的计数变化」不播任何过渡)

关注钮同策略(§4.5):乐观翻转、失败直接跳回 + SnackBar;取关先确认 「不再关注 TA?」;本人帖不渲染(自关注 42204 不给触发面);关注状态经 getFollowStats.followedByMe 拉取,拉取失败不渲染钮(不阻塞阅读)。 系统「减弱动态效果」开启时全部动画降级瞬变(LikeButton 与 FeedSkeleton 同口径)。

跨页一致:Feed 卡片与详情页共享同一 CommunityController/ToggleSync 实例, 互动写入经 _writeInteraction 同帧更新详情副本与 Feed 卡片(widget 测试从 UI 侧断言「详情点赞、卡片同帧 +1」)。

4. 埋点挂接清单(8 事件 + 口径)

新增 community_interaction_analytics.dart(22 号白名单键集逐一对齐, 编译期锁死):

# 事件 触发点 props 挂接位置
1 post_liked 点赞成功响应后 sourcefeed / post_detail CommunityController send 闭包(触点在 toggle 调用处归因)
2 post_unliked 取消点赞成功响应后 source 同上
3 post_favorited 收藏成功响应后 source 同上
4 post_unfavorited 取消收藏成功响应后 source 同上
5 comment_create_succeeded 评论创建成功响应后 durationMs、isReply、textLengthBucket 详情页提交回调
6 comment_create_failed 评论创建失败 failureReason、errorCode、httpStatus、attemptSeq 详情页提交回调
7 user_followed 关注成功响应后 source=post_detail 详情页关注钮
8 user_unfollowed 取关成功响应后 source=post_detail 详情页关注钮

口径说明(widget/单元测试逐字段断言):

  • 成功才报:乐观翻转与失败回滚不报(06 §1.4「点赞/收藏/关注不埋失败」); 单飞合并链每个实际抵达服务端并成功的状态变更各报一条(快速连点合并后 至多两条、方向相反,与「成功响应后」字典口径一致)。
  • comment_create_started 不发22 号锁死 unknown);durationMs 取 「输入会话首字符 → 成功响应」(评论无 started 事件,时长随成功事件带出); textLengthBucket 分桶 empty/short(≤50)/medium(51500)/long(>500),精确 字数不出端(红线 1);attemptSeq 输入会话内从 1 递增,成功或清空输入重置; httpStatus = code ~/ 100(pet 域同款);会话失效不上报;postId/commentId 等内容 ID 一律不进 props(红线 2)。
  • follow UI 判定:详情页作者卡有关注钮(demo 形态保留升级双态),故 user_followed/unfollowed 本单接通;user_profile / follow_list 触点随 后续页面启用。

5. compose 真链路实测

后端 patbond-api dev@8089c06 六容器 docker compose up -d --build 全部 Up、postgres/minio healthy;测毕 docker compose down 干净退出,patbond-api 零改动。

a)互动一轮(test/smoke/detail_interactions_smoke_test.dartenv 门控 PATBOND_DETAIL_SMOKE=1,生产 ApiClient/Repository/Controller 全真实现): 注册一次性账号 → 发帖(published)→ 点赞({liked:true, likeCount:1})→ 重复 PUT 幂等不重复计数 → 收藏/取消(计数 1→0)→ 评论创建 Idempotency-KeycommentCount 0→1)→ 仅作者删除评论(commentCount 回 0、列表剔除)→ 权威计数逐步对账。一次通过

(b)断网点赞回滚(同测试内,生产 CommunityController + ToggleSync Feed 刷新拿到该帖(likedByMe=true/count=1)→ community 端点整体切至不可达 端口模拟断网(连接拒绝走生产 ApiClient 的真实 ApiNetworkException 链路)→ toggleLike:乐观翻转同帧可见false/0)→ 请求失败后快照成对回滚 true/1)、toggleError 为 ApiNetworkExceptionSnackBar 消费口就位)→ 恢复网络再 toggle → 服务端权威终态收敛(likedByMe=false/likeCount=0)。 一次通过(首轮实测暴露测试自身竞态:以乐观值判收敛会早退,已改为轮询 服务端权威终态)。

(c)互动 8 事件白名单验真(curl,客户端真实 payload 形状) POST /api/v1/eventsuser :8082)一批 8 条(platform=android)→ 202 accepted 8 / rejected 0platform.product_events 落库 props 完整 source / durationMs+isReply+textLengthBucket / failureReason+attemptSeq+ errorCode+httpStatus 逐键核对无剥离)。

真机专属项(乐观更新手感:连点合并请求数、回滚动画帧率、跨页一致、减弱 动态降级)已补全 device-verification.md「M3 预登记」第 2 项的步骤与通过标准。

6. 测试数变化

基线 本单后
flutter test 421+1 门控冒烟跳过) 458+37,门控冒烟跳过 2
flutter analyze 0 0
dart format 无 diff 无 diff

新增分布:post_detail_page widget 测试 17(四态 5 含 40403 弹回刷新与内存 副本先渲染、媒体九宫格与全屏大图 1、评论区 5——游标补页/失败重试/创建成败 与 attemptSeq/删除权限与 40301、互动 4——乐观翻转/失败回滚/收藏事件/跨页 一致、关注 5)、LikeButton 动画 6(点按激活缩放/取消无缩放/外部零动画跳变/ 动画中回滚等播完/对账静默/禁用态)、CommentTile 3、互动埋点单测 6(键集/ 分桶边界/失败原因映射)、home_page 更新 3(导航接通替换 T3-14 占位断言、 点赞接线成功事件、失败回滚 SnackBar)、helpers 扩展(评论/关注假仓钩子)。 另 compose 冒烟 1 条(env 门控,默认跳过不计入套件)。

7. 遗留与交接

  • T3-17 发布页create 页 demo 数据层(AppState.publishPost/updatePost 与 posts 字段本体)随发布页真实化退役;demo 发布流现只回 Feed 不再导航 (demo 详情页已消亡);PostMediaGrid 编辑态 + UploadProgressOverlay 已在。
  • @ 回复发起 UI / 长按操作 sheet(举报):数据层与埋点属性(isReply) 已支持,交互留待范围拍板。
  • 大图浏览下滑关闭:与 InteractiveViewer 平移手势冲突未做,photo_view 复评条件不变(03 号 E)。
  • 真机项device-verification.md M3 预登记第 2 项待真机执行(连点合并 请求数 ≤2 的抓包核对只能在真机/真网完成)。

Frontend DeveloperFlutter 日期2026-09-09