使用Claude Code以自然编码风格快速实现交互式UI,展示AI在前端开发中的效率优势。
这篇文章出乎意料地在 Hacker News 上火了。如果你感兴趣,可以去看看那里的讨论。
我拥有的书,已经多到读不完了。并不是那种听起来很美好、充满理想主义色彩的「藏书太多」,而是一个非常现实的问题:从某个时刻开始,我已经记不清自己到底有哪些书了。藏书数量达到 500 本左右时,记忆就不再是一个可靠的目录。
这些年来,我一直告诉自己,总有一天要解决这个问题。不需要多复杂,也不是什么值得包装成创业点子的东西。一个电子表格就足够了。但我始终没有动手,不是因为它很难,而是因为它太烦琐。
从产生想法到真正执行,中间的距离其实很短,但已经足以让这个项目永远停留在「以后再做」的清单里。
到 2025 年年底,我使用 AI Agent 已经有足够长的时间,这类项目终于开始显得切实可行。并不是因为它们能把东西做得多么惊艳,而是因为它们消除了那个总让我止步不前的环节:执行。
正是在这个书架项目里,我清楚地意识到:当执行不再是瓶颈之后,我的角色会变成什么。
我先试了那些显而易见的工具。ISBN 扫描应用无法识别罗马尼亚语版本,Goodreads 也认不出冷门出版社的书或古旧书。只要稍微偏离标准,返回的数据就会残缺不全,甚至完全错误。对我来说,不完整的数据比没有数据更糟,所以每次尝试的结局都一样:录入几条,然后放弃。
我真正需要的并不是一款更好的应用,而是一种能够容忍不完美、又不会让整个系统因此崩溃的方法。
每个项目都是从糟糕的数据开始的,而这个项目的数据尤其糟糕。一个下午,我把自己拥有的每一本书都拍了下来:书脊、封面、重复的书,以及偶尔误入镜头的模糊拇指。一共 470 张照片。把图片传到笔记本电脑之后,我打开了 Claude。
最初几步都是机械操作:重命名文件,把 HEIC 转换成 JPG。接着,我提出了一个真正的需求:写一个脚本,把每张图片发送给 OpenAI 的 vision API,提取作者、书名和出版社,统一名称格式,缩小图片尺寸以免浪费 token,并把所有数据写入 JSON 文件。
Claude 写好脚本,然后运行了它。它成功了。虽然并不完美,但已经好到足以产生实际价值。
{
"id": "ZfEPBCMZDaCKm6k0NVJ8F",
"title": "Simulacre și simulare",
"author": "Jean Baudrillard",
"publisher": "Colectia Panopticon",
"source": "/dataset/83.jpg",
},
大约 90% 的书都识别正确。失败的情况也都在预料之中:光线太差、封面破损、书脊无法辨认。有一本小说甚至被信心满满地识别成了一本 1987 年出版的苏联农业手册。
剩下的部分,我手动修正了。这个决定与技术无关,而是一次判断。90% 的准确率已经足够了。为了剩下的 10% 继续追求完美,意味着要花上好几天处理各种边缘情况,换来的额外价值却很少。就在那一刻,我第一次看清了自己的角色。
后来,圣诞节时我收到几本新书,于是我们又添加了第二个脚本,让新增书籍也能走同一套 pipeline:输入照片,输出 metadata 和图片。
metadata 整理完成之后,封面仍然缺失。我的照片拍的是书脊,不是封面图,而我希望得到一套干净的视觉素材。Claude 建议使用 Open Library 的 API 获取封面,这个方法大体有效。但有一半封面的质量很差,或者匹配错误,而罗马尼亚语版本在数据库中几乎不存在。
我们继续迭代。Claude 编写了第二轮处理流程:再调用一次 model,对封面质量进行评分,并标记匹配效果不佳的结果。对于被标记的书,它会转而通过 SerpAPI 从 Google Images 获取图片。这解决了大多数情况。最后仍有少数几本无能为力:一些淘来的古旧书,以及冷门的苏联拳击手册——任何数据库都不可能为它们提供干净完整的素材。
我打开 Photoshop,手动修复了 10 张封面。对于一个拥有 460 本书的藏书库来说,只需要手动修改 10 张,我觉得已经算是胜利。
数据和封面准备就绪之后,下一步就是 UI。最显而易见的方案是做一个封面网格。它没错,但毫无生气。我不断回头观察自己的实体书架。真正让它变得有趣的并不是封面,而是书脊:宽度各不相同,彼此挤压得并不均匀,各种颜色融合成一片整体的纹理。
这才是我想重现的东西。
Claude 并没有替我想出这个点子。它负责的是把点子实现出来。它编写了一个脚本,利用颜色量化从每张封面中提取主色,然后计算与背景形成对比的文字颜色,以保证可读性。结果变好了,但依然不对。每本书的宽度都一模一样,而真实的书并不是这样。
Open Library 提供了页数信息。我们把页数映射为书脊宽度,并加入轻微的随机变化,打破整齐划一的感觉。到了这一步,它终于看起来像一个真正的书架了。
{
"id": "ZfEPBCMZDaCKm6k0NVJ8F",
"title": "Simulacre si simulare",
"author": "Jean Baudrillard",
"backgroundColor": "#f0f0ff",
"color": "#1f1f2e",
"paddingLeft": 13,
"paddingRight": 13,
"height": 384,
"cover": "/images/bookshelf/[email protected]",
"source": "/dataset/83.jpg"
},
从视觉上看,这个书架已经成立了,但它仍然显得很静态。真正的书架会对触碰产生反应。当你的手指沿着书脊划过时,书会轻微倾斜。我让 Claude 加上动画,它用 Framer Motion 实现了一个基于滚动的倾斜效果。
已经很接近了,但还是不对。动作会突然跳变,而不是平滑流动。我不知道原因是什么,只知道它的感觉不对。这就足够了。
Claude 立刻解释了问题所在:我们在每次 scroll event 触发时都更新 React state,造成了不必要的 re-render。解决方法是使用 motion value 和 spring,让动画在 React 的 render cycle 之外运行。两分钟后,问题解决了。接下来的几分钟,我只是不停地来回滚动页面,看着它动。正是在这一刻,我放下了戒心。并不是因为这个工具永远正确,而是因为尝试一个想法的成本已经低到几乎可以忽略不计。
这种信心也带来了负面影响。我开始要求一些其实并不需要的功能。Infinite scroll 听起来很合理。为什么要一次性渲染 460 本书?Claude 实现了它,从技术上看也确实有效。内存占用保持平稳,DOM 也能正确更新。
但滚动体验坏了。容器高度失去同步,最后几本书无法访问,每次尝试修复都会引入新的卡顿。功能本身可以运行,但体验不行。于是我们把它删掉了。不是因为它坏了,而是因为它根本没有必要。460 本书还远远谈不上规模问题。什么时候应该删除一段能够正常工作的代码,并不是 AI 可以替你决定的。
书架在桌面端看起来很棒,但在移动端,横向滚动显得非常局促。我想要另一种布局:让书平放并垂直堆叠,这样不用歪着脑袋也能阅读书名。我让 Claude 查看书架的实现,然后要求它制作一个堆叠视图。
它读取了代码,推断出其中的模式,并复用了这些模式:动画时序、颜色提取、基于滚动的透明度,以及相同的数据结构。它构建了新的组件,并接入了一个用于切换两种布局的开关。整个过程不需要解释,就直接运行成功了。这件事比其他任何事情都更让我惊讶。
所有代码都是 Claude 写的。那么,我做了什么?
我决定 90% 的准确率已经足够。
我修复了那 10 张任何 API 都找不到的封面。
我否决了网格布局,因为我想要的是书脊。
我删除了 Infinite scroll,因为我根本不需要它。
我不断滚动查看动画,直到它的感觉终于对了。
Claude 负责实现,我负责品味。
经历了多年的反复开头又放弃之后,我的书架终于真正存在了。460 本书,全部完成编目,并展示在 bookshelf 上。我曾经差点把 Claude Code 当成又一轮炒作。如今,那些所有代码都由我亲手编写的日子,感觉已经十分遥远,甚至有些陌生。
执行正变得越来越便宜,品味却不会。