Commit Graph

295 Commits

Author SHA1 Message Date
浅念 80405cfcc1 chore(api): 公开广告端点补 enabled 投影不变量注释(Task 9 评审建议)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 00:50:46 +08:00
浅念 373f0fc533 feat(api): GET /v1/public/albums/{token}/ads 公开广告(全局+定向/现签)
ov 公开端点(ads-carousel Task 9,设计 §5.2):相册校验复用
findPublicAlbumOv(不存在/offline/紧急停用统一 404 ALBUM_NOT_PUBLIC);
可见集 = 全局投放(albumIds=null)∨ 绑定本相册,sortOrder 升序、并列
createdAt 升序;imageUrl 逐条现签 GET(TTL=SIGNED_URL_TTL_SECONDS)。
匿名无 guard、不加独立限速档(观众页单次加载一次,与公开相册读同量级,
量级假设 ≤ 百条:全表捞 + 应用层 JSON 过滤)。

public-ov.e2e 追加 ⑧:prismaOv 直种 albums_ov/ads_ov——过滤/排序/现签
(可取回真实字节)/404 同码同 path/空 items/匿名 200;9/9 通过,
ads.e2e 16/16 回归通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 00:44:23 +08:00
浅念 9ec8226577 fix(sync): reconciler 清扫环补 ad——错过的 ad.* 推送恢复自愈通道
SWEEP_ENTITY_TYPES 追加 'ad':生产对账循环阶段一逐类拉 cn reconcile
清单时纳入广告当前态(清单恒全量、确定性排序),重放走既有
content-batches 幂等管道。此前 ad 只有 cn 端点而无调用方——ad.* outbox
事件「投后即失」时 ads_ov 无自愈路径(digest 是相册维度计数,覆盖不了
无相册归属的广告;含 enabled=false 下线与硬删墓碑后的终态收敛)。

sweep() 对各类型均一处理(无 per-type 簿记),'ad' 与 'asset' 同构接入;
文件头/Stats 注释同步(四类→五类)。

测试:reconcile.e2e ⑤ 追加 runOnce 生产环断言——sweptEvents=5
(album+photo×2+ad×2)、replayApplied=2(仅 ad 新 eventId)、
ads_ov 两行投影就位(全局 albumIds NULL/定向数组)、digest 稳态不受
影响。sync 全量 e2e 4 suites/28 tests、api ads 16/16、typecheck/lint 净。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 00:26:21 +08:00
浅念 60b675a908 feat(sync): 对账清单 ad 实体当前态清扫
Task 8(ads-carousel):cn reconcile 增 entityType='ad' 分支——纯 SELECT
当前态映射(sortOrder/createdAt 确定性排序,albumId 恒空串信封约定),
载荷复用 ads/ad-payload.ts 单一出处(与业务事务内即时事件逐字节同形,
纯函数模块无依赖环);DTO SYNC_ENTITY_TYPES 追加 'ad'(对齐契约 enum)。

随附质量审查两项:
- ads.service list() 行→AdminAdItem 映射复用 toItem(删 ~14 行内联重复,
  ⑮ 补 admin list 200 形状断言覆盖重构路径);
- 新增 test/ads-extensions.spec.ts 守护 AD_IMAGE_EXTENSIONS ===
  UPLOAD_CONTENT_TYPES 派生归一扩展名集合(jpeg→jpg),防三处白名单漂移。

测试基建:reset.ts RESET_TABLES 补 'ads'(Task 6 建表遗漏,固定 id 直种
依赖跨 spec 清场);reconcile.e2e-spec 追加 ⑤(红→绿,5/5)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 00:22:13 +08:00
浅念 4979091d2c feat(api): 广告位 CRUD——outbox ad.* 事件/audit/停用高优先级/版本化换图
Task 7(ads-carousel):/v1/admin/ads list/create/patch/delete 四端点。
- 事务三写:ads 行 + outbox(ad.updated 全量快照/ad.deleted 墓碑)+ audit
  (ad.create/update/delete);ad.* 信封 albumId 恒空串;停用迁移 true→false
  与删除均 priority='high';
- 创建/换图:staging head 三核(存在/bytes/contentType,assets complete 同
  口径)→ 事务内 copyIfAbsent 先于行写入(失败=孤儿终键对象,OSS 生命周期
  兜底)→ 版本化终键 adObjectKey(adId, v, ext);
- 更新:embed 乐观重试模式(条件 updateMany WHERE entityVersion),保证换图
  终键版本段与行版本严格对齐;换图三字段同时性校验(缺一 400);albumIds
  去重+相册存在性 count 校验([]/null 归一全局 null);
- 删除:条件 deleteMany 重跑保墓碑 entityVersion=末+1;重复删除 404;
- 载荷构建器独立 ad-payload.ts(Task 8 对账共用,逐字节同形约定);
- dto:AdCreateRequestDto.albumIds 收窄为 string[] | null(JSON null 在
  @IsOptional 下可达,embed settings 先例);
- e2e:describe 更名 ads domain (e2e),追加 ⑨-⑯ 八用例(真图直传/定向/
  非法 linkUrl/字段级 PATCH/换图/DELETE/权限/同步贯通),16/16 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 23:57:43 +08:00
浅念 121afadf1e feat(api): /admin/ads/uploads 广告图 staging 预签名(2MB/白名单/admin-only)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 23:34:11 +08:00
浅念 4db90b0e9e feat(sync): ov inbox ad 分支与 sender adId 推导(disabled/墓碑=删投影行)
- dto.ts SYNC_ENTITY_TYPES 追加 'ad'(对齐契约 SyncEntityType)。
- decideAndApply ③依赖检查豁免 ad:albumId 恒空串,不豁免则事件被永久
  暂存且无解锁路径(slot-state 同款空 albumId 危害)。
- project() 新增 ad 分支:全量快照直投 ads_ov;墓碑或 enabled=false 均
  deleteMany(disabled=公开不可见=无投影);albumIds 整体替换;createdAt
  取 cn 原值、updatedAt 为 ov 应用时刻(载荷无该字段)。
- sender.toWireEvent 新增 ad 分支:entityId 取载荷 adId(outbox 表无列),
  泛化 categoryId/assetId 兜底之前,缺料 fail-fast。
- e2e ads.e2e-spec:手工构造 outbox 行(绕过 CRUD,Task 7 才有)走真实
  sender→HTTP→inbox 链路,5 用例覆盖快照落位/disable 删行/复活 upsert/
  迟到旧版本不回滚/墓碑删行;reset-ov 增清 ads_ov。
- db-ov schema 附带注释修:ads_ov updatedAt 语义纠正(迁移头过度声明,
  迁移文件不动防 checksum 漂移)+ sync_inbox.entityType 枚举补 ad。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 23:18:37 +08:00
浅念 d86f452bed docs: 广告位设计 404 码 ALBUM_NOT_PUBLIC 纠正入档 + 实施计划 Task 1-4 勾选
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 23:03:32 +08:00
浅念 539597e9f0 feat(storage): 广告对象键布局(staging + 版本化终键)
adsStagingObjectKey(两步上传 presignPut 直传位)+ adObjectKey
(版本段=entityVersion:换图必然 bump 版本→新键,copyIfAbsent
禁止覆盖语义下天然防重复/防串图);index.ts 按名再导出供 Task 6/7
ads.service 消费。设计方案 §2.1/§3 终键布局同步回填(偏差登记)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 23:01:09 +08:00
浅念 318f69dae7 fix(db): photos 查重索引补 schema 声明——消除 migrate dev 误提议 DROP 漂移
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 22:49:43 +08:00
浅念 726f6a1be8 feat(db): ads 写权威表与 ads_ov 投影表(含迁移)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 22:43:25 +08:00
浅念 001d3aaf73 fix(contracts): 广告位 404 码对齐 NOT_FOUND/title 必填/空 PATCH 语义/上限漂移守护
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 22:29:45 +08:00
浅念 ec55d5718b feat(contracts): 广告位 openapi 端点/schema 与 AD_UPLOAD_MAX_BYTES,类型再生
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 22:16:12 +08:00
浅念 af8f3b78dd feat(contracts): ad.updated/ad.deleted 事件类型与 ad 实体映射
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 21:58:32 +08:00
浅念 ac37794d43 fix(app): path-precise duplicate matching; shared file stat; union test
Review follow-ups on 0fa23e0 (Task 2), applied as one commit:

- Path-precise dupPaths (#2, user-visible): DuplicateMatch gains
  sourcePath (local match fills it from the matched pick; unified
  server branch from picks[hit.index].path). Both pages' duplicate
  dialogs derive dupPaths from duplicates' sourcePath instead of
  basename matching, so same-basename siblings (different dir/bytes)
  are no longer mis-flagged — "跳过重复" no longer drops the
  non-duplicate sibling. Stale basename-derivation comment removed.
- Single shared stat (#3): files are stat'd once in
  checkDuplicatesUnified (_statPicks) and the picks feed both the
  local half (extracted _matchLocalHistory; public checkDuplicates
  folded — it had no remaining callers) and the server request.
  Halves stat IO for large batches (was ~2x).
- Union test (#1): new test/features/uploads/enqueue_test.dart pins
  that a file hit by BOTH local and server history yields ONE
  DuplicateMatch with the local uploadedAt (server's echoed time does
  not override). Merge dedup is now keyed by sourcePath
  (putIfAbsent, local first).

Tests: +1 enqueue union test (real-async chain inside one runAsync
window; sync file/dir setup — fake-async body must not await real
IO), +1 album_detail same-basename sibling test (skip-duplicates
enqueues exactly the non-hit sibling, distinguished by bytes since
sourceFilePath becomes the preparer's staging copy).

Gates: flutter analyze clean; full flutter test 429/429 green.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 20:22:37 +08:00
浅念 0fa23e015e feat(app): server-side duplicate pre-check at enqueue moment
Task 2 of docs/plans/2026-09-28-上传查重前置到入队前-实施计划.md:
surface server-side duplicate history at the "click upload" moment,
merged with the local check into the EXISTING dialog.

- ApiClient.checkUploadDuplicates: POST /v1/uploads/duplicates/check
  (commit 805f77b endpoint); 500/chunk sequential requests; chunk-local
  index translated to global; empty list short-circuits (no request);
  errors normalized to AppException via _send.
- enqueue.checkDuplicatesUnified: local TaskStore history ∪ server
  history; server hits map back to filename via global index into the
  stat'd picks; merged, de-duplicated (filename+bytes) list; server
  failure (network/permission/any) silently degrades to local-only —
  init 409 remains the backstop.
- capture_page + album_detail_page _pickFromGallery now call the
  unified entry; dialog/skip/force logic untouched.

Tests: api_client chunking (1200 → 500/500/200) + index translation +
merge + 404 normalization + empty no-request; page-level three states
(hit/no-hit/network failure) incl. fresh-install server-only-hit dialog
on both pages; pre-existing Task 8 dialog tests keep passing via the
degrade path.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 19:17:35 +08:00
浅念 64e1be09d3 test(api): pin earliest-uploadedAt + uploadEnabled-agnostic pre-check; fix comment
评审硬化三项(checkUploadDuplicates 后续,不 amend):
- e2e 补「同 bytes 多条可见命中取最早 uploadedAt」:两条同 bytes 不同
  uploadedAt 的种子行,断言回显更早值。
- e2e 补「uploadEnabled=false 预检仍 200 且照常命中」:钉住只读预览刻意
  不校验开关的偏差——防止未来一致性重构把检查加回来,破坏 App 关册时的
  降级体验(init 入队时仍有 409 兜底)。
- 修正 uploads.service checkDuplicates 的 null-uploadedAt 注释:原「谓词
  命中与否不受影响」不准确,改为「仅直改库等理论形状(bytes>0 且
  uploadedAt=null)下预检可能漏报而 init 仍会 409」。

uploads e2e 30/30 ✅ · api typecheck ✅

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 19:06:47 +08:00
浅念 805f77be76 feat(api): upload duplicate pre-check endpoint (checkUploadDuplicates)
「上传查重前置到入队前」Task 1:POST /v1/uploads/duplicates/check 只读批量
查重预检——用户点上传(确认入队)时刻预判服务端历史重复(跨设备/重装/改名
均可查到),不再等任务在上传中心以 PHOTO_DUPLICATE 失败才暴露。

- 契约纯增量:/uploads/duplicates/check + UploadDuplicateCheck(Item|Request|
  Duplicate|Response) schemas;regen openapi.d.ts。
- 判定谓词与 init ③.5 服务端查重逐字一致:同 albumId + uploaderId=当前用户
  + bytes 相等 + visibility != 'deleted'(filename 刻意不参与),保证「预检
  即真相」;命中条目按请求下标逐项返回(同 bytes 多请求条目各一条)。
- 权限链同 init(canView 404 防枚举 / canUpload 403 / 相册空值兜底 404),
  刻意不校验 uploadEnabled:只读预览,init 真正入队时仍按开关 409 兜底。
- 查询走 idx_photos_dedup:请求条目去重字节集一次 findMany,内存建
  bytes→最早 uploadedAt 映射后按请求下标回射。
- items 1..500(minItems/maxItems + 嵌套逐项校验,越界 400)。
- e2e 追加 describe 7 用例:命中含双条目同 bytes/未命中/uploaderId 隔离/
  deleted 不命中/非成员 404/items 边界 400/未认证 401。

契约 lint ✅ · contracts+api typecheck ✅ · api 单测 56/56 ✅ ·
uploads e2e 28/28 ✅

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:56:07 +08:00
浅念 7d744da56e docs: 广告位实施计划——评审修订(inbox 依赖暂存豁免 ad 关键补丁/常量指针纠正/组件测试补齐/compose 全路径)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:41:54 +08:00
浅念 4b5940c0d8 test(api): 修复 uploads e2e 与服务端查重(90dcc58)的字节数冲突
90dcc58 引入 init ③.5 服务端查重(同 albumId+uploaderId+bytes 非 deleted
→ 409 PHOTO_DUPLICATE)时未同步更新本 spec:renew/complete describe 内多个
init 沿用 bytes=1000,C-bytes complete 落定 1000 字节照片后,C-type/C-happy/
DEV-1/DEV-2 的 init 即被查重 409 拦截(该提交仅跑了 app 397 测试,api e2e
漏跑)。为各冲突 init 改用互异字节数(1002/1003/1004/1005),并同步
putStaging 与 bytes/expectedAt 断言;各用例语义不变。

本地库补apply 20260928120000_photos_dedup_index(idx_photos_dedup)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:38:36 +08:00
浅念 3b7d37110a docs: 广告位设计补轮播图左上角「广告」可见小标签(合规可识别性)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:25:49 +08:00
浅念 678225e8b5 docs(plan): upload duplicate pre-check at enqueue moment — incremental plan
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:25:24 +08:00
浅念 8453167b39 docs: 广告位设计补部署顺序硬约束(ov 先 cn 后,DTO IsIn 整批拒收已核实)、广告图 2MB 上限、量级假设登记
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:24:00 +08:00
浅念 ab412a3630 docs: 广告位设计补 enabled 翻转的 sync inbox 直测用例(评审建议)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:16:15 +08:00
浅念 0220a9cc05 docs: 广告位设计评审修订——createdAt 进投影/载荷、inbox disabled=删行、albumId 恒空串、head 校验补 contentType
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:14:00 +08:00
浅念 573a559e4a docs: 广告位轮播设计方案(全局+定向投放/两步上传/outbox 同步投影/sessionStorage 关闭)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 18:09:35 +08:00
浅念 46f9d93fd3 fix(app): guard force-column migration against downgrade-then-reupgrade
Review hardening for Task 8 (test-server workflow: testers install older
APKs over a newer DB):

- Old APK opening a v2 database hits drift's downgrade path, which leaves
  the force column in place but writes user_version back to 1. Reinstalling
  the new APK then re-runs onUpgrade(1,2) and addColumn crashes with
  "duplicate column name: force", failing the DB open.
- onUpgrade now checks PRAGMA table_info(upload_tasks) and only adds the
  force column when absent; the normal v1 -> v2 path is unchanged.
- Migration test simulates the downgrade-reupgrade cycle on a file-backed
  database (v2 open -> user_version forced back to 1 -> re-upgrade),
  asserting the open succeeds, legacy rows survive, and the column stays
  writable. RED before the guard (SqliteException: duplicate column name).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 17:27:52 +08:00
浅念 29f3bde9c2 feat(app): wire server dedup - force passthrough + PHOTO_DUPLICATE classification & l10n
Task 8: close the gap between server-side duplicate detection (90dcc58)
and the app.

- ApiClient.mapDioException: normalize 409 wire body reusing
  VALIDATION_FAILED with message=PHOTO_DUPLICATE to
  ErrorCodes.photoDuplicate (message-based; auto-compat if the contract
  later adds a dedicated code)
- l10n zh/en errorPhotoDuplicate; error_l10n maps the code (queue page
  inherits via fallback)
- UploadEngine._classifyFailure: PHOTO_DUPLICATE joins the no-auto-retry
  bucket (retrying the same init body would 409 again); force to skip
  dedup travels only through the enqueue path
- UploadTasks drift table +force column (schema v2, addColumn migration;
  legacy rows default false); UploadTaskDraft/UploadTask/prepare carry it
- enqueueForUpload +forcePaths; duplicate dialog "全部上传" (capture page
  + album detail page) now returns DuplicateDialogChoice so confirmed
  duplicates enqueue with force=true (server adjudicates owner/admin)
- engine init body carries force only when true (absent otherwise)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 17:06:19 +08:00
浅念 4f89c25bad fix(app): lock capture page portrait; gate zoom badge on 2 pointers; drop dead getter
- MainActivity locked to portrait: the capture page top bar / bottom
  controls and the preview aspect swap all assume portrait; landscape
  misaligns the preview ratio and control layers.
- Zoom badge now requires details.pointerCount >= 2 on onScaleStart so a
  single stationary tap no longer pops a 1.0x badge for 1s; base-zoom
  rebasing stays unconditional (single-finger-into-pinch still resumes
  from the current level).
- Removed unused CameraPluginSource.currentZoom getter.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 16:33:15 +08:00
浅念 ac602acf47 feat(app): camera preview WYSIWYG aspect + pinch zoom with double-tap reset
Preview now centers at the sensor aspect ratio (letterboxed on the dark
backdrop) instead of FittedBox cover, which cropped ~38% horizontally and
made the preview differ from the full-frame takePicture output.

Zoom capability hangs off CaptureSource as default implementations
(minZoom/maxZoom = 1, setZoom no-op) so existing fakes stay untouched;
CameraPluginSource probes getMin/MaxZoomLevel after initialize (failure
keeps 1/1 and disables the gesture). The page attaches a scale
GestureDetector only when maxZoom > 1: pinch drives setZoom (throttled at
0.05 delta, serialized to prevent out-of-order platform calls), a badge
shows the current level and hides after 1s, double-tap resets to 1.0x,
and camera flip/reopen resets zoom state. _StaticCaptureSource in
album_detail_page_test.dart switches implements -> extends to inherit the
new defaults.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 16:17:07 +08:00
浅念 65cd6849cc docs(plan): Task 9 checklist — GIF/truncated source cases for crop
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 16:02:57 +08:00
浅念 683e19bdc1 feat(app): crop cover & banner before upload (crop_your_image, 16:9 / 3:1)
review fixes: drop GIF from pre-sniff (package maps gif→png decode → never-ready hang); PopScope blocks back during processing

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 16:01:24 +08:00
浅念 97db504e25 docs(plan): fix manifest snippet (VISUAL_USER_SELECTED has no maxSdkVersion) + expand Task 9 checklist
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 15:04:42 +08:00
浅念 2e5fc40636 feat(app): in-app asset picker (wechat_assets_picker) - unlimited multi-select with range selection
Review fixes: per-asset originFile error isolation (failed asset skipped, not whole batch); drop maxSdkVersion from READ_MEDIA_VISUAL_USER_SELECTED (API 34+, no upper bound); originFile doc accuracy (API 30+ direct path vs Android 10 ScopedCache copy).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 15:03:44 +08:00
浅念 aea797c7c7 docs+test(app): correct okhttp version comments; harden throttle test assertions
- UploadPutExecutor.kt / build.gradle.kts:OkHttp 版本注释改为实情——声明 4.12.0,Gradle 冲突仲裁实际解析 5.2.1(fluwx 传递依赖胜出)
- UploadPutExecutorTest.kt:节流测试补 isNotEmpty 守护与 size>=2 断言(堵住过度节流只报终值的盲区)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 14:22:24 +08:00
浅念 45dbde3719 perf(app): throttle upload progress callbacks to 256KB steps
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 14:10:29 +08:00
浅念 203f62e7cb test(app): align mockwebserver with resolved okhttp 5.2.1
fluwx 6.0.2 传递 okhttp 5.x,冲突调解覆盖本模块 4.12 声明(debug
运行时同为 5.2.1);mockwebserver 4.12 在 okhttp 5.x 运行时缺
okhttp3.internal.Util → 单测 setUp 即 NoClassDefFoundError(本改动
前 main 上已复现)。对齐后 11/11 通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 14:10:14 +08:00
浅念 93e7046332 feat(app): enqueue gallery batch concurrently via Future.wait
Parallelize the enqueueForUpload collect loop (preparer's internal
bounded semaphore from the previous task keeps IO in check). Also two
test hardenings from that task's code review: pin that failed and
thrown prepare operations release their concurrency slot (cap=1,
subsequent prepare still completes), and pin the maxConcurrentPrepares
<1 -> 1 clamp (peak 1, no deadlock).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 13:53:42 +08:00
浅念 b79edf447b feat(app): bounded-parallel prepare (default 3) replaces serial queue
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 13:43:07 +08:00
浅念 dc5ae4a24a feat(app): raise default upload parallelism 2 -> 4
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 13:34:48 +08:00
浅念 cc47278f50 docs: upload speedup plan v2 — 9 tasks after full re-review
- Task 7 camera WYSIWYG aspect + pinch zoom (two-pointer test, throttled setZoom)
- Task 8 server-dedup app integration (force passthrough, PHOTO_DUPLICATE
  classification, l10n) — closes the 409 dead-end from 90dcc58
- Task 6 crop: crop_your_image 2.0.0 callback API + Isolate.run pixel pipeline
- Stale claims fixed (album_detail dedup already wired in 638dd9b), pre-flight
  parallel-session check added

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 13:34:03 +08:00
浅念 441a24a54c docs: upload speed optimization plan draft
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 11:33:04 +08:00
浅念 90dcc58c33 feat(uploads): P0-1 batch 4 — server-side duplicate photo detection (409 + force + index)
契约 UploadInitRequest 加 force(bool,仅 owner/admin 跳过查重)。
服务端 init 事务前置查重:同 albumId + uploaderId + bytes 的非 deleted
照片已存在 → 409 PHOTO_DUPLICATE。photos 表加 idx_photos_dedup 索引
(迁移 20260928120000)。
App UploadInitRequest 加 force 字段 + toJson 序列化。
本地查重(层 1)+ 服务端查重(层 2)双层防线到位。

API typecheck ✅ · app analyze ✅ · 397/397 ✅ · contract lint ✅

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 11:29:29 +08:00
浅念 33f347cdea feat(app): P0 batch 3 — offline detection with engine pause/resume + UI banner
P0-4 离线感知:
  - connectivity_plus → NetworkAvailability(online/offline/unknown)
  - engine_scope 三重绑定:有账号+有网 → start;任一失去 → stop(在途不中断)
  - 网络恢复 → start + onNetworkRestored(waitingNetwork 即时重试)
  - HomePage 顶部离线黄条(wifi_off + i18n 双语)

P0-5 更正:EXIF 方向已由服务端 sharp .rotate() 自动转正——v2 分析误报

397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 11:23:51 +08:00
浅念 638dd9b45d feat(app): P0 batch 2+2.5 — local upload dedup with batch dialog, album switch check, file size guard
P0-1 层 1 本地查重:
  - checkDuplicates:同账号+同相册+state=completed+filename+bytes 匹配
  - EnqueueResult 返回 enqueued+duplicates+skipped
  - capture_page + album_detail_page:批量单次弹窗(非逐张)三选一
    [跳过重复上传非重复] [全部上传] [取消]

P0-8 相册上传开关预检:album_detail_page 入队前检查 uploadEnabled,
  关闭时提示「该相册已关闭上传」+ 不入队

P1-8 文件大小预检:preparer 限单文件 ≤100MB(磁盘满由 on Object 兜底)

P0-7 更正:上传并发限制已有(kMaxParallelUploads=2),v2 分析误报

397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 11:06:40 +08:00
浅念 9edcb3f2b1 feat(app): P0 batch 1 — photo viewer zoom, camera lifecycle, image memory management
P0-2 查看器缩放:InteractiveViewer(5x max,放大后平移,1x 时穿透到
  PageView 滑动切页)
P0-3 相机后台释放:WidgetsBindingObserver——paused 时 dispose(省电+
  隐私指示灯),resumed 时重开(沿用前后摄/闪光灯偏好)
P0-6 图片内存管理:PhotoThumbnail 加 maxCacheWidth 参数——网格/列表
  缩略图限制解码宽度(600/400px),查看器大图不限

397/397 全绿、analyze 0

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 10:35:14 +08:00
浅念 98e073d8c8 docs: App potential issues v2 — full analysis with server-side dedup (confirmed, ready to execute)
v2 补齐 v1 自查发现的 8 个漏洞:三重指纹(filename+bytes+CRC32)、批量弹窗
(单次确认非逐张)、photos 表 dedup 索引、force 权限约束(owner/admin)、
上传并发限制(3)、相册开关预检、上传进度百分比、磁盘空间预检。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 10:24:46 +08:00
浅念 984fe8981c docs(viewer): prod verification — share sheet copy-only (native share button removed)
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-28 09:15:30 +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
浅念 dc8d3ff967 feat(app): launcher icon — brand logo 1024px + adaptive (blue #1A73E8 background)
flutter_launcher_icons 自动生成全尺寸 mipmap(48-192px + 自适应前景/背景)。
正式签名生产环境 APK 已打好。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-24 17:07:14 +08:00