Commit Graph

58 Commits

Author SHA1 Message Date
浅念 744d45cc61 fix(ads): 轮播图裂图修复——广告现签 TTL 24h + 失败节流重拉
根因:广告图现签 900s(照片域 TTL),观众长留直播页超时后重拉 403。
广告为匿名公开内容,短轮换无安全收益——专属 AD_SIGNED_URL_TTL_SECONDS=24h;
另加兜底:img error → 页面层 60s 冷却重拉广告列表换新签名 URL。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 11:27:06 +08:00
浅念 7f8cda1cd3 fix(viewer): 去除头部下方残留的旧广告位实例(位置迁移漏删)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 10:14:55 +08:00
浅念 2261b8aebc style(viewer): 广告轮播移至宣传图与标题之间(after-banner 插槽)
用户定稿:广告位放标题上面——AlbumHeader 增具名插槽注入,
组件不感知广告域;无 banner 时仍居标题上方。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 10:03:15 +08:00
浅念 bfba76b5e6 fix(ads): 终审修复——staging 前缀+运行时上限核验/失效绑定编辑/白名单去 heic/杂项
- verifyStaging 硬化(services/api/src/ads/ads.service.ts):新增键前缀核验
  (仅 OBJECT_KEY_ROOT/ads/staging/ 布局,防照片域任意键被提升为公开广告)
  与运行时 2MB 上限(head.bytes > AD_UPLOAD_MAX_BYTES → 400 报文含限额值
  ——init 申明管不住实际 PUT 体,create/update 是唯一核验点);e2e ⑰ 以
  测试 storage 直接 presignPut 真实对象覆盖两分支
- 失效绑定编辑 400(admin AdsFormDialog):PATCH 仅在 albumIds 实际变化时
  携带(albumIdsChanged 序敏感深比较,纯函数落 adsLogic + vitest 用例);
  preloadOptions 解析失败的绑定 ID 加「已失效,可移除」后缀(zh/en 新增
  adsManage.form.staleAlbum)——软删绑定不再挡住其它字段保存
- 白名单去 heic(原图直出无变体兜底,Chromium/Firefox 不渲染=破图):
  contracts 三处 enum 收窄 [image/jpeg, image/png] 并重生成类型;dto 定义
  广告域独立 AD_IMAGE_CONTENT_TYPES(与照片域 UPLOAD_CONTENT_TYPES 分离),
  AD_IMAGE_EXTENSIONS 由其派生;init 对 .heic 文件名以广告域集合二次核验
  拒绝(共享 extOfFilename 仍认 heic,杜绝 init 放行→create 中段断裂);
  e2e heic 矩阵 400;admin accept/文案/类型同步收窄
- searchAlbums 补 catch(http 层已弹 toast,本地静默保持旧选项)
- ActivityPage 关闭记忆测试补位(activity.spec):dismissed 不拉取不渲染、
  close 写 sessionStorage 并清空轮播两用例(getPublicAds 入替身防真实 fetch)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 02:20:21 +08:00
浅念 7f9789eb46 polish(viewer): 广告轮播 a11y 补全——aria-current/focus-visible/touch-action/键名对齐
评审跟进(Task 10 a11y 两项 + 三个顺手项):
- 圆点当前项程序化状态 :aria-current(ThumbnailStrip:103 同形),active 圆点
  类名对齐 sibling 约定 ad-dot--active → .active(viewer 无 BEM -- 修饰符)
- .ad-slide/.ad-close/.ad-dot 补 :focus-visible 2px 主色描边
  (AlbumHeader/ThumbnailStrip 同规;主色对暗色衬底/蒙层对比 ≈4:1)
- .ad-carousel 补 touch-action: pan-y(ThumbnailStrip pan-x 对偶:
  水平翻页自持、纵向滚动交还页面)
- sessionStorage 键名 viewer:ads-dismissed → viewer.adsDismissed(sibling
  点号命名约定;仅 ActivityPage 两处引用,测试无引用)
- ad-carousel.spec:类名断言随改 + aria-current 前后迁移断言

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 01:08:13 +08:00
浅念 f1c13bff7c feat(viewer): 相册页广告轮播——4s 自动/暂停/滑动/徽标/会话级关闭
Task 10(广告位轮播实施计划):
- ads/carousel.ts 纯逻辑:nextIndex 环形推进(末→首、单条恒 0)+ swipeStep
  滑动判定(≥40px 且水平分量严格大于垂直),carousel.spec 直测 7 例
- AdCarousel.vue:10:3 轮播,4s 自动推进(本地常量 ROTATE_MS);pointerenter/
  pointerdown 暂停、pointerleave 恢复;圆点跳页;touchstart/touchend 滑动翻页
  (cancel 合成 click);单条静态无圆点;左上角常驻「广告」徽标 + 右上角 ≥44px
  ✕ 关闭(emit close,组件无副作用);视觉一律 var(--viewer-*)(半透明层经
  color-mix 自 token 派生)
- client.ts:PublicAd/PublicAdListResponse 契约类型 + getPublicAds(同形
  apiRequest);__tests__/ad-carousel.spec.ts 组件级 6 例(圆点/关闭/window.open
  spy/假时钟自动轮播与暂停/滑动/单条静态)
- ActivityPage:load() 成功分支独立拉取广告(失败静默、会话级关闭记忆
  viewer:ads-dismissed 先行、沿用 sequence 防跨相册竞态);AlbumHeader 与
  PhotoFeed 之间挂 AdCarousel;i18n zh-CN/en 补 ads.* 词条

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 00:58:48 +08:00
浅念 a5d71686ff feat(viewer): remove native share button from share sheet (user request) — copy link only
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 09:08:39 +08:00
浅念 d279cefd71 feat(viewer): hide link text under QR in share sheet (user request) — QR + copy button remain
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 16:51:36 +08:00
浅念 643f837bca feat(viewer): hide view count in album header (user request) — counting/reporting unaffected
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 16:24:05 +08:00
浅念 cf490c7892 feat(app): all album date displays date-only — formatAlbumDateTime drops HH:mm
相册列表卡/详情信息/分享页 meta/照片查看器日期一律纯日期(y/M/d)。
全量测试断言同步;397/397 全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 16:20:47 +08:00
浅念 c199d0630c feat(embed): photo click opens album page in a new tab (user decision 2026-09-24)
Reverts the in-host detail modal interaction: click now window.open's the album page (/a/:token?lang=, no /p/ deep link) with noopener — same target for middle-click/right-click via the real <a> href. The loader's open-photo modal code remains but is no longer triggered.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 15:25:41 +08:00
浅念 bd4db7b458 style(viewer): 相册 banner 限高 320px + cover 裁切(PC 端观感修正)
height:100% + contain 在桌面宽屏下整图 letterbox 压扁留白——改为限高裁切。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 15:00:12 +08:00
浅念 17d510ccd5 fix(embed): scroll freeze final layer — Chrome fires a queued pointerenter after pointercancel when dragging outside, permanently trapping hover pause
Sequence: drag past iframe edge -> pointercancel (clears press state) -> Chrome still delivers one pre-queued pointerenter (re-sets hover) -> no further boundary events ever reach the iframe -> hover pause stuck forever. Fix: ignore pointerenter within 500ms after a pointercancel (and while pressed); re-set hover on genuine in-frame release so post-click hover behavior is preserved.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 18:59:47 +08:00
浅念 5efb89a252 fix(deploy): remove orphaned proxy directives from template cleanup
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 18:41:31 +08:00
浅念 57394387cc fix(deploy): proxy_pass double http:// prefix — API was down
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 18:39:12 +08:00
浅念 222c44c1f7 fix(deploy): disable gzip on /v1/ API paths — Flutter release AOT gzip decompression failure
服务端 200+数据正常、debug/JIT 正常;release/AOT 的 Dart HttpClient 对
Content-Encoding: gzip 解压异常 → JSON 解析失败 → 显示 INTERNAL_ERROR。
nginx /v1/ 路径 gzip off(静态资源 gzip 不受影响);proguard keep 规则
一并保留(dio/flutter_secure_storage/全项目模型)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 18:36:11 +08:00
浅念 65581b7920 fix(android): usesCleartextTraffic=true — release APK blocked HTTP APIs (Android 9+ default)
release 构建默认禁明文 HTTP(debug 由 Flutter 自动放行)——服务暂时不可用
即此因。当前测试服务器 API/观众页均为 http://,需显式允许;上 https 后移除。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 18:10:46 +08:00
浅念 1cad7823e2 fix(embed): watchdog also clears hover + releases capture (third freeze layer — capture suspends pointerleave, lost release never frees it)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 17:13:49 +08:00
浅念 74593d9953 fix(embed): scroll freeze root cause — drag-set 'manual' pause reason never cleared
setReason('manual', true) fired on >8px drag had no clear path anywhere (wheel/keyboard have 500ms timers; pointer path didn't) — one drag left the reason permanently, stopping auto-scroll until refresh. Now cleared on pointerup, watchdog, and window blur.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 17:09:17 +08:00
浅念 b414b3666e fix(embed): pointer pause watchdog — capture does not cross iframe boundary, outside-release now auto-resumes in 2s
The earlier capture+window-listener fix is insufficient: releasing outside the 195x280 window dispatches pointerup in the host document, which no in-iframe listener can observe. Deterministic fallback: 2s watchdog on pointerdown force-clears the pause reason (in-frame releases still cancel it immediately).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 17:04:46 +08:00
浅念 cd8a3696e2 fix(embed): scroll freeze — pointer pause leaked when release happened outside window
Root cause: pointerup/pointercancel bound only on the viewport element; press on a photo then release outside the 195x280 window (or gesture interruption) never cleared the 'pointer' pause reason -> auto-scroll stopped permanently until page refresh.
- pointerdown now setPointerCapture (release guaranteed to return)
- window-level pointerup/pointercancel + blur fallbacks clear the reason
- rAF frame wrapped in try/catch so a single bad frame can no longer break the animation chain

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 16:57:04 +08:00
浅念 5a207b395e feat(app,viewer): WeChat card share + 'Share to...' label + site og tags
- 按钮文字「分享到微信/其他应用」→「分享到...」(用户需求)
- 微信卡片分享(fluwx 6):WECHAT_APPID 经 --dart-define 注入;已配置且
  装微信 → WXWebpage 卡片(标题=相册名、描述=日期·地点、缩略图=封面压缩
  240px PNG ≤32KB、点击进观众链接);未配置/未装 → 回退系统分享面板
- viewer index.html 加站点级 og 标签(微信/QQ 抓取链接预览用;每相册动态
  og 需服务端注入,登记后续增强)
- 397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 16:05:53 +08:00
浅念 2babd364ba feat(viewer): hide like feature — action bar like slot, photo like badges, hot sort tab, others' like bubbles (user request 2026-09-23)
- PhotoFeed: cell like badge not rendered
- AlbumHeader: sort tabs row removed (hot depends on likes; latest-only has no toggle value)
- PhotoActionBar: like slot v-if=false (share/download remain, auto re-center)
- ActivityPage: LikeBubbles hidden
- specs updated to hidden assertions; 244 green

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 14:29:22 +08:00
浅念 85146be7ef fix(viewer): restore lightbox close button styles orphaned by menu removal (transparent bg, white X icon, hover/focus)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 13:48:03 +08:00
浅念 bdad433dae fix(viewer): remove lightbox top-bar settings menu (user request) — close button + counter only; Esc closes directly
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 13:39:53 +08:00
浅念 903962a46b feat(embed-slots): soft-delete slots — admin entry, ov shutdown via enabled=false snapshot
- 契约 deleteEmbedSlot(DELETE /admin/embed-slots/{id} → 204 幂等;admin 专属)
- cn schema:embed_slots.deletedAt(迁移 20260923120000,本地已应用)
- softDelete:乐观锁 claim + outbox slot.updated(payload enabled 强制 false
  ——ov 副本无墓碑,enabled=false 快照即公开端 404 下线;priority=high)+ audit
- 列表/加载/更新过滤 deletedAt → 同码 404
- Admin 列表操作「删除」(确认:已接入网站将停止展示、不可恢复)
- API 56/56、admin 87/87 + build 全绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 13:37:39 +08:00
浅念 f667842e9c style(embed): ambient hearts denser and lower (user feedback — spawn 160-320ms, band 140-244px)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 11:38:55 +08:00
浅念 d11ca16d1d feat(embed): ambient heart bubbles — always floating in the right band (user request 2026-09-23)
- ReactionLayer ambient prop: self-scheduled spawn every 400-800ms (jitter), band 56-232px avoiding the 44px action zone, stops on hidden page/unmount, shares MAX_ACTIVE=8 with real bursts
- EmbedStream mounts layer with ambient = reactions && !reducedMotion — persistent decoration independent of real likes (no burst frames, no count inflation; §5.2 anti-fake semantics unaffected)
- viewer 244 green

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 11:30:53 +08:00
浅念 3d45350711 feat(viewer): share sheet with QR code + link + copy (replaces inline toast, App-style)
- ShareSheet.vue: bottom sheet (mobile) / centered card (desktop) — QR canvas (qrcode dep), selectable deep link, copy with on-button feedback (2.5s), optional navigator.share button, Esc/scrim/close dismiss, focus on open
- AlbumHeader + PhotoLightbox: share buttons open the sheet (album URL / photo deep link); page-level copied strips removed
- tests rewritten for sheet behavior — viewer 244 green

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 11:04:11 +08:00
浅念 63b3e539fd docs: register asset status acceptance — admin branding tab shows current banner/cover/logo
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 10:56:14 +08:00
浅念 2618ad3163 feat(assets): album asset status — show current banner/cover/logo in admin branding tab
- 契约 listAlbumAssetStatus(GET /albums/{albumId}/assets):每 (kind,locale)
  最新 effective 行 + 现签 URL;权限同详情(可见即读);deleted → 404
- 服务端 AssetsService.status(createdAt,id DESC 组内取首;presignGet 现签)
- Admin 品牌资产页签改造:进入即加载现状——三种 kind 卡片(预览图/未设置
  空态/设置时间),banner 分全局/zh/en 槽位;上传成功自动刷新(替换语义)
- API 56/56、admin 87/87 + build 全绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 10:51:53 +08:00
浅念 d678c8d38d style(viewer): banner-wrap drops fixed aspect-ratio and placeholder bg (user edit)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 10:42:03 +08:00
浅念 fd35c712a5 fix(viewer): remove topbar settings menu, keep locale switcher only (user request)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 10:38:23 +08:00
浅念 aa78855657 fix(viewer): remove tap-more like hint from photo action bar (user request)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 09:31:26 +08:00
浅念 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
浅念 91c85e196a feat(viewer,ui04): E05 realtime hearts + effect-demo alignment (initial offset, heart params, decode gate, bidirectional manual scroll)
- reactions.ts pure fns: delta->hearts sampling, burstId dedupe (TTL/capacity), pending-visible cache (5s/50), 250ms emission throttle with merge
- useEmbedReactions: read-only RealtimeClient subscription on album.publicToken (no viewer-page side effects, A15), rebind teardown, 10s grace disconnect, no fake bubbles on WS failure
- ReactionLayer: demo-frozen heart effect (12/16/20 sizes, two colors, 110px rise, 1.6s ease-out, 300ms stagger, max 8, drop-shadow) in unscoped block (created elements lack scoped hash)
- demo alignment: initial offset 32px, decode+500ms startup gate, 1:1 drag, arrows +-40, focus-tip, bidirectional manual scroll (manualScroll), loader handle 100x44 exact styles, <=599px offset default 12
- tests: +8 reactions unit (244 viewer), e2e E05: real cross-context like -> heart on correct photo (EMB-A09), zero likes/visits submission (EMB-A15), 16s hearts recording

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 17:38:02 +08:00
浅念 f193e4f085 fix(viewer): remove extra closing brace in AlbumHeader CSS
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:59:14 +08:00
浅念 b353a3de06 fix(viewer): replace text arrow share icon with SVG share glyph
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:55:37 +08:00
浅念 900ff2a74d feat(viewer): refine visual details to match design reference
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:49:15 +08:00
浅念 3912279889 feat(viewer,ui04): E04 interactions — pause reason set, click lock/drag cancel, manual scrub, keyboard semantics, destroy
- pauseReasons Set (hover/focus/pointer/offscreen/hidden/manual + userPaused via Space, no auto-resume); 500ms transient resume with re-evaluation
- pointerdown target lock; >8px drag suppresses click and scrubs via nudgeOffset (window-clamped); removed photos clear pending target
- keyboard: Space pause toggle, Escape collapse, ArrowUp/Down scrub; input fields untouched
- aria-live status announcements (zh/en); viewport touch-action none
- e2e: hover freeze/resume, popup deep link hit, drag cancel, keyboard, slot disable destroy

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:28:13 +08:00
浅念 998565e439 feat(viewer,ui04): E03 continuous scroll engine — pure state machine, rAF orchestration, boundary continuity
- stream.ts: advanceStream (per-frame, dt cap 50ms), loop generation keys, window math (DOM<=16, queue<=30, 480px buffer), anchorRebuild (photo-position preserving), removePhoto, single-photo static
- useVerticalPhotoStream: rAF integration, direct transform writes (no per-frame Vue reactivity), low-frequency item sync, ResizeObserver re-anchor, stepForTest hook
- EmbedStream wired to engine; cycle copies aria-hidden; img error -> controlled drop
- tests: 14 unit cases (236 viewer total), e2e: 32px/2s speed, boundary continuity <2.5px, DOM<=16 over 20s, 32s recording with loop boundaries

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:19:21 +08:00
浅念 1d1979f540 feat(viewer,ui04): E02 static fidelity — real photo fixtures, measured shell/photo/collapse specs, edge fade
- fixtures: 8 picsum-seeded real photos (mixed ratios); seedPhoto thumbBytes/browseBytes injection
- e2e E02 block: wrapper shell (195x280/radius/border/#10161E), photo strip (193w, ratio-height, 3px gap), collapse (44/44 hit, 24 visual, sole button), desktop+mobile screenshots
- EmbedStream: top/bottom 12px dark fade (0.12, §2.3 optional item)
- fix(admin): pre-existing eslint globals gap for ElNotification (template drift, blocks lint gate)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 13:01:34 +08:00
浅念 66d1eb3e9f feat(embed,ui04): E01 v2 contract skeleton — displayMode/scrollSpeed/reactionsEnabled, v2 loader entry and fixed 195x280 shell
- contracts: EmbedSlotSettings optional v2 fields (carousel default) + SlotSettingsPayload; gen + redocly green
- api: DTO validation + normalization full-set (cn manage + ov public projection share normalizeStoredSettings)
- embed-loader: params v2 parse/merge/url family, loader-v2.js fixed shell (border/radius/#10161E/safe-area, resize ignored, stack gap 12), dual-artifact build+sync; v1 source frozen
- viewer: EmbedPage v=2 branch, EmbedStream (viewport/track/real anchors/collapse/empty), stream.ts pure fns, i18n zh/en
- admin: types + utils (create default vertical-stream, clampScrollSpeed, v1/v2 snippet selection)
- tests: loader 36 (+9 v2), viewer 222, admin 82, api unit 51, v1 embed e2e 20, v2 E01 gate 2 (195x280 desktop+mobile cross-origin, no footer)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-22 12:50:28 +08:00
浅念 2a489aa16a docs: UI-02 acceptance record and visual verification
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 23:10:58 +08:00
浅念 160e7e5f1a feat(viewer): UI-02 heart particles, interaction notices, share and download
Task 5 (V05 §5.4/§6): decorative heart particles on the lightbox stage
(local like clicks + others' WS bursts for the current photo), compact
cross-photo interaction notices above the action bar (click to locate),
navigator.share-first share orchestration with clipboard fallback for
album and photo deep links (lang param), and download failures attributed
to the photo that triggered them. Self-echo dedup stays in the realtime
router (own batches never produce particles/notices); particles cap at
12 with visibility/unmount cleanup and are suppressed under reduced
motion; notices cap at 3 with ~4s auto-dismiss and throttled SR summary.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 22:20:19 +08:00
浅念 89f45896b7 feat(viewer): UI-02 dark immersive photo viewer with thumbnail strip
V04 (方案 §5):PhotoLightbox 模板重构为深色沉浸布局——顶栏(关闭/位置
计数/⋯ 减少动画菜单)+ 舞台(contain 大图 + 边缘 ‹› + 粒子/互动占位层)+
缩略图条 + 底部操作栏;业务逻辑(序列导航/缩放手势/签名刷新/下载/点赞)
原样保留。

- 新增 viewer/position.ts:§5.2 位置计数真实回退(solo→照片预览;无
  total 未到末页→已加载 N 张;全部加载→i/n;不伪造总数)
- 新增 ThumbnailStrip:当前 ±3 窗口(≤7 项)、主色 2px 描边、thumb 变体、
  滚入可见(遵守减少动画)、点击选择不新增历史
- 新增 PhotoActionBar:Like(coral 心形+计数+连续点赞提示)/Share/
  Download 均分大触控;下载关闭整项移除;原图二级菜单(▾ + 长按)
- 模态可用性:body 滚动锁、焦点进入/困住/归还触发元素、Esc 先收菜单、
  菜单内左右键不抢占、dvh + 安全区、chrome 区手势/滚轮隔离
- 分享 V04 最小实现(复制当前深链,support/clipboard 从 AlbumHeader
  提取共用);navigator.share 编排属 Task 5
- zh-CN/en 新词条;vitest 新增 23 例(位置回退/缩略图/操作栏/焦点/滚锁)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 21:58:09 +08:00
浅念 3d89fcdb99 feat(viewer): UI-02 responsive waterfall with like badges
Task 3 (V03 瀑布流重排, 方案 §4.3):
- responsiveColumns 断点:<600px 双列 gap4 / 600-959px 三列 gap6 / ≥960px 四列
  gap8;PhotoFeed 经既有 ResizeObserver 容器测量整体重排
- 照片卡片去卡片化:图片边到边 + 4px 圆角,无白色厚包裹/无常驻文件名;
  右下角点赞角标(半透明暗底渐变、白心 16px、12px tabular-nums 计数),
  @click.stop 独立交互不冒泡开大图;计数经 Intl 分组
- 新照片提示改白色 pill(viewer.newPhotos,接口仅 hasNew 不伪造数量);
  refreshFromHint 不再强制回顶(用户浏览位置保持)
- 首屏骨架按 skeletonSources 竖/横/方比例走真实瀑布流放置(非均匀网格);
  宽高缺失/NaN/Infinity 回退 4:3 安全比例(无 NaN/无限高度)
- 保留懒加载 IO、游标分页、photoId 稳定键、翻页失败底部重试与已到底文案

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 21:34:56 +08:00
浅念 9c3915b387 feat(viewer): UI-02 visual foundation, top bar and album info
V01+V02 (Task 2 of the UI-02 viewer redesign plan):

- viewer-tokens.css: UI-02 §3.1 design tokens scoped to .viewer-shell
  (never :root), so embed routes consume none of them
- App.vue: normal routes get a sticky white top bar (PhotoLive brand +
  compact locale switcher + lightweight "..." menu holding the
  reduce-motion toggle); global 16px content padding removed so banner
  and photos run edge-to-edge; embed shell branch untouched
- LocaleSwitcher: compact "EN/中文 + caret" trigger with a proper menu
  (current language checked), 44px touch target, Escape/outside close
- AlbumHeader (new): banner (2.4:1 mobile, 320px desktop cap,
  object-fit contain, collapses on missing/failed), 2-line expandable
  title + share button (clipboard copy with selectable-text fallback),
  3-line expandable pre-line description, meta row (Intl-formatted
  view count with em-dash fallback for unknown, album-timezone dates
  with full range on title, venue), Latest/Popular 44px tabs and
  category pills from the API with horizontal scroll + overflow fade
- ActivityPage: delegates the info area to AlbumHeader; sequence
  reset/sort-lock/category-by-id semantics unchanged; desktop content
  capped at 1120px
- i18n: viewer.* strings (brand/share/views/readMore/latest/popular/
  allCategories/noPhotos/newPhotos/reduceMotion/settings) in zh-CN+en
- tests: viewer-shell (token source + normal/embed isolation) and
  album-header (8 cases) specs; activity/i18n specs updated for the
  menu switcher and the em-dash unknown view count; e2e selectors
  refreshed

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 21:22:36 +08:00
浅念 ddf26a177f feat(embed,viewer): UI-01 complete carousel, loader config and accessibility
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 18:55:15 +08:00
浅念 7385785504 feat(i18n): T15 full-stack language audit with coverage assertions
- viewer/admin 错误码→文案映射按契约 ErrorCodes 枚举全集登记(机器守护
  spec 遍历枚举断言无缺漏 + 映射键在 zh/en 词条均存在 + locale 键集
  递归相等);mobile 镜像补齐 DOWNLOAD_DISABLED(P4a 增补漏登)
- 漏译修复:viewer.photoRemoved en 侧缺失(审计测试捕获);
  DOWNLOAD_DISABLED 观众侧专属文案 + useDownload 专属提示接线;
  admin apiMsg 增 RATE_LIMITED/ALBUM_NOT_PUBLIC/DOWNLOAD_DISABLED/
  SYNC_AUTH_FAILED 四码词条
- 运行时抽查:admin 主旅程 en 版 e2e(登录→相册管理全英文 + 偏好
  持久);embed iframe lang=en 渲染与深链跟随 e2e(zh 侧既有用例覆盖)
- contracts 新增 ./errors 浏览器安全子路径(errors.ts 纯 TS);
  admin 增 @photo/contracts devDep(仅测试消费)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 23:44:27 +08:00