从零构建 Afilmory:一个现代化照片画廊的 213 次 AI 编程会话实录

前言

最近梳理了 CNB 平台上 Afilmory 项目仓库的 .specstory/history/ 目录——里面完整记录了从 2025 年 5 月 24 日到 7 月 21 日期间,共计 213 次 opencode AI 编程会话的全文转录。

Afilmory 是一个面向摄影师的现代化照片画廊平台(SaaS + 自部署),核心卖点是高性能 WebGL 渲染器、响应式瀑布流布局、增量同步和国际化支持。截至 2026 年,已有多位摄影师在使用,包括 afilmory.innei.in 等公开案例。

这篇文章是对这 213 次会话的宏观总结——看看一个 1.2 万行级别的项目是如何通过 AI 会话逐步演进的。


第一阶段:项目启动与核心基建(May 24-25, ~40 sessions)

主题:从零开始搭建照片展示网站

Session 001:项目初始化

第一场会话的 prompt 极其简单:

“我要做一个网站。用来展示我用相机拍摄的照片。我希望整个页面是黑色背景的。整体是一个瀑布流的形态。现代化美学风格。图片托管在 Flickr 中,build 阶段生成 manifest,manifest 中需要有原图的链接、blurhash string、缩略图的链接。”

AI 助手在已有 React + Vite + TypeScript + Tailwind 项目骨架的基础上,完成了:

  • 瀑布流布局实现
  • 黑色主题设计系统
  • Flickr API 构建脚本(build 阶段拉取照片列表)
  • build-manifest.ts——核心构建管道,生成包含原图 URL、blurhash、缩略图的 manifest JSON
  • Sharp 缩略图生成

PhotoMasonryItem — 图片懒加载(Session 7)

单条图片项的详细实现:读取 manifest、计算宽高比、懒加载、加载前显示 blurhash、加载后 fade 过渡到缩略图。

动态 OG 图片生成(Session ~14)

每次 build 自动生成动态 OG image,在 HTML 中引用 metadata og 标签——这是一项后来被广泛使用的功能。

启动屏幕(Session ~18)

在应用完全加载前显示 splash screen,提升首屏感知体验。


第二阶段:图像查看器革命(May 26-27, ~60 sessions)

这是整个项目中 会话密度最高 的阶段。近 60 场会话围绕一个核心问题:如何在浏览器中流畅渲染 40MP 级别的照片

HEIC/HEIF 地狱(Session ~10-15)

苹果生态的 HEIC 格式给构建管道带来了巨大挑战。Sharp 库对 HEIC 的支持有限,团队尝试了:

  • Sharp 原生解析 → 报错
  • heic-convert 库 → 可行但慢
  • 最终采用 heic-convert + 并行处理

这个阶段的试错过程非常典型——heic-convert 的 API 文档不完善,团队通过阅读源码、在浏览器端测试、最终才确定了正确的调用方式。

WebGL 图像查看器(Session ~15-40,最关键的部分)

最初的图片查看器使用 react-zoom-pan-pinch(TransformWrapper),但 40MP 图片的缩放和拖拽存在严重的性能问题。团队决定编写自定义 WebGL 图像查看器

关键里程碑:

Session内容
替换 TransformWrapper 为 WebGLImageViewer核心引擎搭建
Chrome Shader 黑色边框修复跨浏览器兼容
Blurhash 无缝过渡加载体验优化
双击缩放中心设置交互细节
iPhone Safari 黑屏问题iOS 兼容修复
移除 Swiper,使用 WebGL 实现滑动彻底甩开第三方依赖

WebGLImageViewer 的实现包括:

  • WebGLImageViewerEngine 核心类:使用 WebGL2 纹理渲染,支持缩放、平移、双击
  • Worker 化计算:将图像解码和纹理处理迁移到 Web Worker
  • 双缓冲渲染:低分辨率 blurhash → 高分辨率原图的无缝过渡
  • 节流/防抖:在连续手势中控制渲染频率

移除 Swiper(Session ~40)

一个激进的决策:移除所有 Swiper 依赖,完全使用 WebGL 实现图片查看器的滑动切换。这消除了 Swiper 与 WebGL 手势之间的冲突,但工作量巨大——需要实现方向锁定、惯性滑动、边缘回弹等机制。


第三阶段:Live Photos 与视频支持(June 22, ~5 sessions)

主题:让画廊支持 Apple Live Photos

Live Photos 的核心是 .mov(视频)+ .heic(图片)配对。需要实现:

  1. 使用 mov-demuxer 提取视频的 matrix 变换信息(旋转、翻转)
  2. 使用 mp4box 完成视频转封装(transmux):将 MOV 容器转为 MP4 格式
  3. 视频的画面缩放和位置必须与 DOM 图片完全对齐

最终采用 transmux 方案:mov-demuxer 解封装 → mp4box 重封装,期间保存并传递视频的 transform matrix。


第四阶段:图像预处理策略模式重构(June 25, ~3 sessions)

主题:从硬编码到可扩展

之前的 processImageBlob 方法硬编码了 HEIC 格式转换。为了支持更多格式(WebP、AVIF 等),重构为策略模式:

ImageConverterStrategy (interface)
├── HEICStrategy → heic-convert
├── WebPStrategy → sharp (native)
├── AVIFStrategy → sharp (native)
└── (任意扩展)

ImageConverterManager
  ├── registerStrategy()
  ├── detectStrategy()
  └── execute()

每个策略独立实现转换逻辑,管理器负责格式检测和策略路由。这是项目中为数不多的有意识架构决策的会话之一。


第五阶段:互动功能与数据库(June 26-28, ~10 sessions)

Reaction Button(Session ~150)

实现了一个 FAB 风格的 Reaction 按钮——点击后迸发出多个 emoji 到四周。使用了 Framer Motion 的 spring 动画,与项目现有的磨砂玻璃(backdrop-blur)UI 风格保持一致。

Neon Database 迁移(Session ~155)

将 PostgreSQL 数据库从之前的选择切换到 Neon(Serverless PostgreSQL)。主要改动:

  • 连接方式从 pg 切换到 @neondatabase/serverlessneon HTTP 驱动
  • 实现了原子性 upsert 操作处理并发点赞
  • 使用 Drizzle ORM 做类型安全的查询

第六阶段:文档站点建设(July 14-21, ~30 sessions)

主题:为 builder 包编写完整的文档站点

这是项目的第二个高强度开发期。团队用约定式路由 + MDX 构建了一个完整的文档站点(apps/docs)。

Builder Package 文档(Session ~180)

builder 包是整个项目的构建核心——它负责图像处理、缩略图生成、manifest 管理。团队编写了多篇 MDX 文档来描述:

  • Builder 架构总览
  • 存储提供者(S3/Flickr/Local)接口
  • 插件系统
  • 二次开发指南

约定式路由生成器(Session ~190)

编写了一个 Vite 插件,扫描 contents/ 目录自动生成 routes.ts,使用 index 作为首页。支持嵌套目录结构。

主题系统:从 Apple UIKit 到 @pastel/chromatik(Session ~210)

最初使用 Apple UIKit 语义颜色系统(iOS 风格的 text-primary、fill-secondary 等)。在意识到开源社区更熟悉 Tailwind 生态后,果断迁移到 @pastel-palette/tailwindcss

这场会话本身就是一个有趣的故事——从"设计一套 light 和 dark 颜色主题"开始,经历了 Apple 风格的全面应用,到最后"不用苹果得了,切换成 @pastel",体现了 AI 辅助开发中设计决策的快速迭代


数据洞察

会话时间分布

时段会话数密度
May 24-25(基建期)~40
May 26-27(WebGL 期)~60最高
May 28-Jun 21(平稳期)~30
Jun 22(Live Photos)~5
Jun 25-28(重构+互动)~15
Jul 14-21(文档站点)~60很高

会话主题分布(粗略)

  • 前端组件开发 ~35%
  • 性能优化与 WebGL ~25%
  • 构建管道与图像处理 ~15%
  • UI/UX 设计 ~10%
  • 后端/数据库 ~8%
  • 文档与配置 ~7%

失败模式

从会话记录中可以观察到几种典型的 AI 编程失败模式:

  1. API 猜测错误:heic-convert 的导入方式、Neon 的连接字符串格式——AI 经常根据记忆猜测 API,但实际 API 已更新
  2. 类型链断裂:重构一个函数后,关联的类型定义没有同步更新,导致连锁的 TypeScript 错误
  3. “继续迭代"死循环:AI 在解决一个错误后引入新错误,反复 5-6 轮无法收敛,最终人类介入”@agent Pause"
  4. 上下文遗忘:长会话中 AI 忘记之前的约束条件,生成与已存在功能冲突的代码

七个经验教训

  1. WebGL 是性能瓶颈的最后解法:能用 CSS/Canvas 解决的问题,不要上 WebGL。一旦上了,就要做好投入大量会话的心理准备。

  2. 策略模式值得早期引入:HEIC 处理从硬编码到策略模式的迁移,花了 3 场会话。如果一开始就用策略模式,后面添加 WebP/AVIF 支持只需要 1 场。

  3. AI 适合"快写快改",不适合"一次成型":所有成功的 WebGL 实现都不是 AI 一次写对的,而是在 5-10 轮迭代中逐步修正边界条件。

  4. 数据库迁移风险极高:Neon 切换的 5 场会话中,有 3 场在解决连接问题和类型错误。AI 对 Serverless 数据库的认知存在盲区。

  5. 文档站点反而占用最多会话:意想不到的是,文档站点开发(~60 场)的会话数量超过了 WebGL 核心(~50 场)。这说明 AI 编程中"文档即代码"的写法虽然高效,但需要大量 UI 迭代。

  6. 混合语言 prompt 很有效:很多会话的 prompt 是中英混合的(“在这里写一个 contents 的约定式路由生成器插件”),AI 可以准确理解这种混合指令。

  7. Pause 是最重要的指令:当 AI 进入"改错-引入新错"的死循环时,@agent Pause 比任何技术提示都有效。重启后的第一轮回复通常是高质量的。


结语

213 场 opencode 会话,从 2025 年 5 月到 7 月,完整记录了一个照片画廊从 idea 到生产级项目的全过程。这些转录文件不仅是代码变更的历史,更是人类与 AI 协作编程的原始档案

最有意思的是:这些会话没有一个是"一次完成"的。每个功能都经历了多轮迭代——人类提出需求、AI 编码、人类测试反馈、AI 修复。这种 human-in-the-loop 的螺旋式开发,可能就是 AI 编程时代最真实的软件开发范式。

如果想了解 Afilmory 项目本身,可以访问 GitHub 仓库 或官方 文档站点


本文基于 CNB Gallery workspace 中 .specstory/history/ 目录的 213 个 markdown 文件整理而成。