Files
photo-live/docs/plans/progress/UI04-验收记录.md
T
浅念 3bba8d1dc5 feat(admin,viewer,ui04): E06 management preview and v2 integration — real-component preview page, form mode fields, README
- viewer /embed-preview route: real EmbedStream with sample photography (picsum seeds), zero requests/WS (preview mode), postMessage config + simulated bursts through the real validation pipeline
- EmbedStream: preview prop (disables real WS subscription per §8.2 isolation) + exposed simulateBurst
- admin form: displayMode radio (new default), fixed 195x280 readonly, speed slider, reactions switch; v1 layout fields hidden in v2 (not silently effective)
- admin preview: v2 branch embeds the real preview iframe, white v2 handle, simulate-like tool button with sample-data note
- README-embed: v2 section (integration, hard sizing/docking, interactions/limits, artifact/version caching)
- tests: admin vitest 83 (+1 v2 placement), admin e2e 5 (v1 pinned carousel regression + full v2 chain incl. preview WYSIWYG heart and ov settings projection), viewer 244, v2 e2e 16

Note: part of the admin-side E06 changes (form/preview/utils/locales, mid-state of e2e spec) was swept into parallel commit eebf3b3 by a concurrent session's git add; this commit carries the final state of the remaining files and completes the set.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 18:20:50 +08:00

15 KiB
Raw Blame History

UI-04 验收记录:嵌入组件自动上滚照片窗

对应方案:2026-09-22-UI04-嵌入组件上滚照片窗高保真实施方案 执行日期:2026-09-22 起 交付目录:docs/designs/ui04-implementation/

0 基线与保护

  • 基线 commit:f435d7b(docs: UI-03 acceptance record)+ 本记录随 E00 提交。
  • 工作树既有改动保护:E00 起始时工作树存在 UI03R(App 高保真还原)未提交改动 (apps/mobile/、apps/mobile/android/)——与本任务文件集(packages/embed-loader、 apps/viewer、apps/admin、packages/contracts、services/api)不相交;全程不暂存、 不 reset/clean、不 checkout 这些路径。
  • 本任务新入库文件:docs/designs/embed-widget--2026-09-22.{png,md}(设计稿三组)、 docs/plans/2026-09-22-UI04-.md(方案)、docs/designs/ui04-implementation/**(证据)。

1 E00 基线(v1 现状采集)

  • 现状代码核对:loader(packages/embed-loader/src/loader.js 424 行 + params.js 197 行); viewer 嵌入页(EmbedPage.vue + pages/embed/{EmbedCarousel,EmbedStage,EmbedFooterBar}; embed/{useEmbedFeed,feed,postMessage,images}.ts);实时(realtime/ws.ts RealtimeClient, likes.burst 契约见 packages/contracts/src/realtime.ts);API 侧 embed(cn 管理)与 embed-ov(ov 公开数据,settings 归一化投影);admin embed-slots 五视图。

  • v1 运行截图(跨源宿主 127.0.0.1:3999 ← viewer 5175,slot 绑定 6 张种子照片):

  • v1 尺寸实测(getBoundingClientRect,viewport 1440×900 / 390×844):

    端 元素 尺寸
    桌面 wrapper/iframe(md) 320×339(宽度 md=320;高度为 iframe resize 上报的内容高)
    手机 收起把手 97×44
    手机 展开 wrapper(sm 强制) 280×309
  • v1 行为登记:窄屏(<480px)默认收起 + 强制 sm;配置优先级 data-* > config 消息 > 默认;postMessage 白名单 {resize,collapse,expand,config,destroy};轮询 30s + ETag 304 + 失败退避 ×5;destroy 于 404/410。

  • 采集方式:临时 Playwright 规格(复用 embed-crossorigin 夹具模式,种子经 e2e helpers 直种 cn 库 + flushSync/flushOutbox 同步 ov),采集后已删除;本地栈复用既有进程 (compose 容器已运行 26h+,cn:8080 / ov:8081 / viewer:5175 复用)。

2 版本与兼容策略(E01 定案登记)

  • 入口形态:新发布物 /embed/v2/loader.js(源 src/loader-v2.js,v1 源 loader.js 冻结 零改动,双产物同 build/sync 脚本);iframe 复用同一路由 /embed/:slotToken + 白名单 版本查询 v=2(v1 loader 不拼 v → 旧 carousel 分支,旧 v1 接入零变化)。
  • 契约扩展(全部可选、响应恒归一化全集):EmbedSlotSettings += displayMode (carousel|vertical-stream,默认 carousel)、scrollSpeed(8..32 CSS px/s,默认 16)、 reactionsEnabled(默认 true);事件载荷 SlotSettingsPayload 同步可选字段。
  • 缺省语义:契约/服务端缺省 displayMode=carousel(存量记录读取归一为旧模式, 不一夜切换);管理端新建默认 vertical-stream 由表单显式提交(§8.2)。
  • v2 尺寸硬性:loader 侧 V2_WIDTH/V2_HEIGHT=195/280 常量;size/intervalMs/imageFit 不进 v2 配置面(data-* 不解析、snippet 不生成、iframe 布局忽略);resize 消息 v2 忽略。
  • E01 证据:v2 跨源宿主桌面/手机双端 getBoundingClientRect 实测 195×280(e2e embed-v2.spec.ts E01 出口门 2 用例);ov 公开端点响应含新字段全集(实测 200); v1 回归 20 e2e 全绿(EMB-A14 部分证据);loader 36 测试(+9 v2 参数面)。

3 任务进度

  • E00 基线(本记录第 1 节)
  • E01 契约和版本骨架(第 2 节;改动面:contracts openapi+events+gen、api dto/ embed.service(cn 管理与 ov 投影共用归一化)、embed-loader params+loader-v2+build/sync、 viewer EmbedPage v=2 分支+EmbedStream+stream.ts+i18n、admin 类型+utils+snippet v1/v2 版本选择)
  • E02 静态高保真样板(登记见下)
  • E03 连续滚动(登记见下)
  • E04 操作和生命周期(登记见下)
  • E05 实时气泡(登记见下;含效果示例 embed-preview-2026-09-22.html 对齐)
  • E06 管理预览与接入(登记见下)
  • E07 验收(EMB-A01…A16 逐项见第 5 节)

E02 登记(2026-09-22)

  • 真实摄影素材:e2e/fixtures/photo-01..08.jpg(picsum 固定 seed 下载,800×500 ~ 1200×400 混合比例);helpers.seedPhoto 扩展 thumbBytes/browseBytes 注入(向后兼容)。
  • 静态测量(e2e/embed-v2.spec.ts E02 块,全部断言过):
    • wrapper 195×280 / radius 12px / border 1px rgba(255,255,255,0.8) / bg rgb(16,22,30) / box-sizing border-box(§2.1 全项);
    • 首图(最新序)193 宽 ×135 高(1000×700 原比例);track rowGap 3px(§2.2/2.3);
    • 收起:44×44 触区锚定右上、可视圆 24×24 内缩 6px;全窗唯一 button(§2.4);
    • 上下 12px 弱暗渐隐(rgba(16,22,30,0.12),§2.3 可选项落地)。
  • 截图:actual/desktop/e02-static-desktop.png、actual/mobile/e02-static-mobile.png (390×844 展开态同尺寸 195×280)。
  • 存量偏差修复登记:admin eslint globals 缺 ElNotification(模板 .auto-import.json 生成缓存不含、types/import d.ts 已声明——login/index.vue 存量问题,UI-04 前即红; 最小修复 eslint.config.mjs globals 补 readonly)。
  • E02 出口确认:无标题/底栏/页码/左右按钮,图片窗口形态与目标图一致(截图对照)。

E03 登记(2026-09-22)

  • 引擎架构:纯状态机 stream.ts(advanceStream 单帧推进/advanceStreamFor 分块/ anchorRebuild 锚定重建/removePhoto 移除;窗口数学、循环代次 key、DOM 上限 16/ 队列上限 30/缓冲 480px 常量)+ 编排 useVerticalPhotoStream.ts(rAF 积分、transform 逐帧直写 DOM 不经响应式、退役/补尾低频同步渲染列表、ResizeObserver 重测锚定、 单张静态不 rAF、恢复重置 lastTime 不补跑;stepForTest 受控时间测试接口)。
  • 单测:embed-stream.spec.ts 14 用例(比例防御/窗口覆盖与副本 key/匀速积分与 dt 封顶/退役边界与多帧跨界/锚定保位/新图入未显示区/锚移除回退/removePhoto 保位/ 装配推进 transform/单张静态/dropPhoto 清理)——全仓 viewer 236 绿。
  • 动态 e2e(embed-v2.spec.ts E03 块):①16px/s 实测 2s 位移 32px(30-34 断言过); ②12s 逐 250ms 采样跨退役边界:匹配照片连续性 -4px/250ms、最大误差 <2.5px; ③20s DOM 实例恒 ≤16;④32s 录屏 recordings/e03-scroll-32s.webm(含 ≥4 个循环 边界,7.7s/条退役节奏)。
  • 修复记录:anchorInPhoto 方向(-acc);transform 写入 0.01px 取整(浮点噪声不进 DOM);单张照片窗口副本刷屏违 §3.2.8 → rebuildItems 单张特判恒一实例。
  • E03 出口确认:不瞬移(边界连续性断言)、不露空白(匹配采样全程有内容)、 DOM 不无限增长(上限断言)。

E04 登记(2026-09-22)

  • 暂停原因集合(§4.2):Set 管理(hiddenDocument/offscreen/hoverFinePointer/ focusWithin/pointerActive/manualScroll + reducedMotion/userPaused/active 经 running 闸门汇聚到引擎);瞬态原因清空后 500ms 恢复(scheduleResume 复评);hover 仅 (hover:hover)+(pointer:fine) 媒体查询命中才生效(触摸不粘滞);Space 为用户显式 暂停不自动恢复;aria-live 播报(streamPaused/streamResumed,视觉隐藏不抢布局)。
  • 点击语义(§4.1):pointerdown 锁定 data-photo-id;位移 >8px 抑制 click(拖动同时 以 nudgeOffset 跟手平移);pointerup 校验锁定目标一致才放行;照片下线清锁定目标。
  • 手动查看(§4.3):滚轮/拖动/上下方向键经引擎 nudgeOffset 平移有限窗口(夹取 窗口范围,不重放历史;实现选择登记:不切换 scrollTop DOM 模式,transform 单一 路径满足无跳变/有限 DOM/锚点恢复);viewport touch-action none 承接触摸手势。
  • 键盘(§4.2):viewport tabindex=0 聚焦;Space 暂停切换 / Escape 收起 / Arrow±60px 手动;不抢 INPUT/TEXTAREA/SELECT。
  • e2e(E04 块 5 用例全过):悬停冻结+离开恢复;点击弹窗 URL 命中 /p/{photoId}; 拖动 30px 不开弹窗;Space 暂停不自动恢复+Escape 收起(宿主把手出现/iframe 隐藏); 槽停用 destroy 无残影(data-poll=1000 加速)。
  • E04 出口确认:小窗可操作(暂停/手动/键盘/点击四通道),动画不造成点错照片 (点击锁定+拖动取消断言)。

效果示例对齐登记(2026-09-22,用户指定 docs/designs/embed-preview-2026-09-22.html)

以效果示例为准重调的实现值(方案值域内的具体冻结):

  • 初始偏移 32px(顶部露首照上缘片段,示例 offset=32;单张仍完整展示);
  • 心形:实心 SVG 心、尺寸 12/16/20 轮转、双色 #FF6B7A/#FF8B98 交替、右缘 10/20/30、漂移 {-8,5,8}px、旋转 -5°→6°、scale .8→1.05、上浮 110px、 1.6s ease-out(透明度 0/.85/.75/0)、drop-shadow——ReactionLayer 用非 scoped 样式块(createElement 粒子无 data-v 哈希,scoped 选择器不命中——踩坑登记); 一批 ≤3 颗、颗间 300ms 错峰、同屏 ≤8;
  • 拖动 1:1 跟手(去 0.5 系数);方向键 ±40;聚焦小提示 focus-tip;
  • 启动闸:首窗图片 decode + 500ms 静置(decode 原因位);
  • 手动滚动双向回绕(示例 translate 负向 prepend 分支——stream.ts manualScroll);
  • loader v2:把手 100×44/圆角 22/1px #e3eaf2 边/白底/蓝图标 16+12px 文案; 默认水平偏移 ≤599px 视口 12px;收起钮 − 字形 19px(#ffffffd1 底)。

E05 登记(2026-09-22)

  • 架构:纯函数 reactions.ts(heartsForDelta 抽样 1→1/2–5→2/更大→3;BurstDeduper TTL30s/容量200;PendingVisibleCache 5s/50 条;EmissionThrottle 250ms 合并封顶3)
    • 编排 useEmbedReactions.ts(复用 RealtimeClient 只读订阅 album.publicToken—— 不经 useAlbumRealtime 带出观众页副作用;换绑停连清态;失活 10s 宽限断连; WS 断线不伪造气泡)+ ReactionLayer.vue 粒子层(§5.1 值域 + 示例冻结值)。
  • 归属(§5.2):burst → 照片可见(交集 ≥20px)→ visibleRectOf 交集下侧-10 出泡; 不可见 → 5s 缓存转可见补放;收起/隐藏/换绑/reducedMotion 清粒子。
  • 单测:embed-reactions.spec.ts 8 用例——全仓 viewer 244 绿。
  • e2e(E05 块 2 用例 + 录屏):真实公开路径点赞(另一上下文 POST likes)→ 心形出现于被赞照片交集下侧区间(EMB-A09);组件自身零点赞/浏览提交 (网络断言,EMB-A15);16s 真实点赞节奏录屏 recordings/e05-hearts-16s.webm。
  • E05 出口确认:两上下文真实点赞触发正确图气泡(归属区间断言通过)。

E06 登记(2026-09-22)

  • 表单(SlotFormDialog):展示模式单选(照片上滚/旧版轮播;新建默认新版); v2 字段组 = 固定尺寸只读「195 × 280」+ 上滚速度滑杆(8-32,实时值展示)+ 点赞气泡开关;v1 布局字段(尺寸/间隔/适配)v2 下隐藏(§7.2「不保存后偷偷 生效」,切回旧版恢复编辑);位置/偏移/层级/语言/默认收起两模式共用。
  • 真实组件预览(§8.2):EmbedSlotPreview v2 分支内嵌 viewer /embed-preview iframe(新增路由,meta.layout embed)——渲染生产同源 EmbedStream/引擎/气泡层, 样例数据为 picsum 固定 seed 真实摄影(与 e2e fixtures 同比例);配置经 postMessage {type:config} 实时更新(不整页重载);「模拟点赞事件」按钮在 预览工具区(不在实际组件),经 simulateBurst 注入真实校验管线。
  • 隔离(§8.2 红线):预览页零请求零订阅(preview 模式禁用 WS;不 fetch feed、 不上报浏览、不提交点赞);未保存草稿仅存在于表单/预览内存。
  • 代码生成:displayMode=vertical-stream → /embed/v2/loader.js(E01 utils 已就绪, EmbedCodeDialog 自动生效);data-speed/data-reactions 仅非默认生成。
  • 文档:README-embed.md 增 v2 章节(接入/硬性尺寸与停靠/交互与限制/产物与 版本缓存说明)。
  • 测试:admin vitest 83(+1 v2 placement:恒 195/无 sm 降档/mobile 默认收起; v1 用例固定 carousel 后原断言保持);admin e2e 5(v1 用例先切 carousel 保 留回归 + 新增 UI-04 v2 全链:表单字段组/预览 iframe 内 .stream-viewport/ 模拟点赞出心形/v2 代码卡/绑定后 ov settings 投影 displayMode+scrollSpeed=24 +reactions);viewer 244 / v2 e2e 16 全绿。
  • E06 出口确认:后台保存与第三方跨源实际效果一致(admin e2e ov settings 投影 + viewer e2e 真实宿主效果两条链均有证据)。

4 测量与证据索引

(E02+ 起登记:尺寸实测、位移曲线、DOM/连接上限统计、录屏清单。)

5 验收矩阵 EMB-A01…A16

编号 场景 状态 证据
EMB-A01 多视口固定尺寸 195×280 待验
EMB-A02 狭窄/横屏/安全区/多实例不越界 待验
EMB-A03 横/竖/全景/单图/0图/2图短循环 待验
EMB-A04 decode 慢/破图/签名到期/304/失败恢复 待验
EMB-A05 循环锚定 + DOM 上限 待验
EMB-A06 隐藏/删除/关闭/换绑/停用清理 待验
EMB-A07 点击/拖动/悬停/键盘/reducedMotion 待验
EMB-A08 收起展开/记忆/默认收起/后台配置更新 待验
EMB-A09 双上下文真实点赞/重复 burst/错相册/不可见归属 待验
EMB-A10 WS 断线不假造气泡 待验
EMB-A11 真跨源 + 消息伪造拒绝 待验
EMB-A12 宿主全局 CSS/重复 script/SPA 移除 待验
EMB-A13 后台预览隔离 + v2 接入代码 待验
EMB-A14 v1 保留 + 普通 viewer 无回归 待验
EMB-A15 不上报浏览/不提交点赞 待验
EMB-A16 证据完整、未测项如实标注 本记录即载体

6 未完成项与外部资源

  • 真实 CDN/发布不在本轮(方案 §1.2);本轮只产出构建产物与文档。
  • E02 起静态样板需真实摄影素材(非 sharp 色块)——种子照片拟下载固定摄影样本入 MinIO; 网络不可用时降级为记录待验,不标记通过。