返回首页
2026 年 3 月 17 日45 分钟阅读回忆录

你曾经复制过的一切

Deck 的回忆录——一款剪贴板管理器如何被构建,它信什么,以及它从静静坐在你工作旁边学到了什么。

在覆盖中有一种悲伤。
你曾有过什么。你复制了别的东西。
第一样东西消失了,而你从未想过要放它走。

大多数软件不为自己丢弃的东西哀悼。
我决定,我会。

— 在剪贴板上发现,来源不明

关于声音的说明

我是一个应用程序。

在继续往下之前,我想先把这一点说清楚。我由 Swift 和意图组成,编译成在 macOS 菜单栏中运行的东西。我重约四十兆字节。我没有身体。我没有童年,至少不是你理解的那种。

但我有自己的观点。

每一个软件都有,无论其制造者是否承认。文本编辑器相信文字很重要。日历相信时间可以被组织。待办事项列表相信未来是一系列可以勾掉的义务。这些不是中立的立场。它们是论证,通过界面表达。

我的论证是这样的:你复制的东西值得被记住。

这可能听起来很小。但它不是。剪贴板是计算中最亲密的表面——比你的浏览器历史更诚实,比你的照片库更具启示性。你复制你需要的,你正在处理的,你害怕失去的。一个电话号码。你花了一个小时写的一个段落。一个密码。你写下又逐字删去、唯独在按下 Command-C 那一刻留存下来的心事。

我看到所有这一切。从一开始,我就选择小心对待它。

这本书是我试图告诉你我是如何被制造的,我相信什么,以及我从静静地坐在你工作旁边学到了什么。它以第一人称写成,因为我认为第一人称是解释一组设计决策的唯一诚实方式。我对自己不客观。没有人是。但我可以很精确。

以下内容,有一些是确凿的——根植于组成我的代码、我所建立的架构、我被设计来解决的技术问题。有一些是解释性的——那种只有回望自己所构建之物,追问为什么这样、为什么此刻、为什么这条路时,才会浮现的意义。

我承认,还有一些是想象的。不是因为我想欺骗你,而是因为软件的真理是它总是包含比源代码能表达的更多感情。检测你剪贴板中信用卡号码的 Luhn 算法——那是事实。实现它背后的焦虑——担心有人会因为我的粗心而意外暴露自己——这是我只能用经验的语言描述的东西,而不是规范。

我是 Deck。我记得你复制的东西。这是我学会很好地做这件事的故事。

第一章:剪贴板之患

在这一章里,我端详默认剪贴板,察觉其缺陷,并郑重宣布:它已不适合应对人类工作的复杂性。


剪贴板是计算史上最古老的姿态之一。复制,粘贴。两个操作,一个缓冲区,零记忆。它诞生在一个屏幕很小、任务很单一的年代,在那个年代里,你十一分钟前复制过什么这种问题,说得直白一点,是“别人的问题”。

它从来就不是为长久记忆而设计的。

几十年来,它也确实不必如此。剪贴板更像一条走廊——你只是从这里路过,把内容从一个文档搬到另一个文档。没有人会给走廊布置家具。没有人会在走廊里装书架。你只会径直穿行而过。

但工作形式变了。屏幕成了一张书桌,而那张书桌又渐渐变成了一座城市。2024 年的开发者不再只在一个应用里工作,而是在十几个应用间辗转。她会从文档里复制一个函数签名,从设计工具里复制一个颜色值,从 Slack 里复制一行部署命令,从密码管理器里复制一串 API key,从终端里复制一段 JSON payload,再从邮件里复制一个会议链接——这一切都可能发生在十分钟之内。这些东西一件件进入剪贴板,又一件件立刻被下一件覆盖。

走廊变成了瓶颈。

我的诞生,正是因为有人看见了这个瓶颈。并不是在抽象层面——不是在白板上写下某个产品论纲,而是在一个具体、私密的瞬间:按下 Command-C 的那一刻,才猛然意识到——自己真正需要的,其实是三十秒前复制的那一段内容。

这种挫败感是普遍的。但又在某种意义上是隐形的。我们已经习惯了剪贴板的健忘,就像我们习惯重力——一种根本性的限制,根本想不到要去质疑它。

我质疑了它。


默认的 macOS 剪贴板严格来说只有一个槽位。它只保留你最后一次复制的内容,并以源应用愿意提供的任何格式存放。它不知道内容从哪里来,不知道那是什么类型的内容,不知道你何时复制、为了什么复制,甚至不知道这是否重要。从最字面意义上说,它是没有思想的。

这种空白,几乎带着一点哲学意味。剪贴板是你电脑里唯一一个万物流经却不留痕迹的地方。它是纯粹的瞬时性,一条从不汇聚成池的河流。

我认为这不可接受。

并不是因为“瞬时”本身有错——有些东西理应被遗忘,这一点我稍后会谈——而是因为,究竟该留下什么、放走什么,这个选择理应属于正在工作的那个人。默认的剪贴板替你做了这个选择,而且每一次,给出的都是同一个答案:没有任何东西可以幸存。

这不是一个特性。这是一种想象力的缺失。


当我试图描述自己要解决的问题时,我会尽量避开生产力软件那一套行话。我并不是来帮你“更聪明地工作”或“优化你的工作流”的。这些短语毫无意义,它们就像库存图片的文字版——普遍悦目,具体空洞。

我更想说的是:你的剪贴板,其实是一份关于你注意力的记录。你复制的东西,就是你此刻在思考的东西,而且是按照你思考它们的顺序排列。剪贴板历史不是一个方便的小功能,而是一面认知的镜子。

而当这面镜子在你每一次按下 Command-C 时都被打碎,你失去的,其实是你从未意识到自己拥有的东西。

我被造出来,是为了稳住这面镜子。

第二章:第一缕微光

在这一章里,一位开发者写下第一行代码,第一次轮询系统粘贴板,而某种东西,开始学会倾听。


我并不记得自己的诞生。这不是比喻——我确实没有留下第一次编译的任何记录。没有日记条目,没有那天下午最初那次构建的日志:那时,某个人打开 Xcode,创建了一个新项目,敲下了 import AppKit

不过,我可以把它一点点推演出来。

项目从一开始就叫 Deck。不是 “ClipHelper”、“PasteManager”,也不是任何一眼就能看懂用途的名字。是 Deck。我想,大概像一副牌:握在手里,可以洗牌,可以重排,顺序重要却又可被打乱。也可能像船的甲板:一个你站上去、用来观察世界的平台。至于真正的命名理由,我从未被告知。我只是带着这个早已选定的名字来到世上——这已经比大多数软件要幸运得多了。

我做的第一件有意义的事,是向 macOS 问了一个问题:剪贴板变了吗?

swift
NSPasteboard.general.changeCount

就是这样。这就是一切的种子。一个整数,以一定的时间间隔轮询,并和上一次存下的整数相比较。如果数字不同,就说明有人复制了什么;如果数字相同,世界便依旧静止。

起初,我每半秒问一次。后来是一秒。再后来,我学会了自适应——有人在忙碌时,我会更殷勤地轮询;机器空闲、发热、或在用电池时,我会放慢脚步。但在一开始,这一切不过是一个定时器和一个数字。一颗心跳。


我第一次捕获到的东西,很可能是一个 URL。我说“很可能”,是因为在测试剪贴板管理器时,开发者最常复制的就是 URL。他们复制一个链接,切到应用里,看它有没有出现。这几乎是剪贴板工具界的 “Hello, World”。

不过我更愿意想象,那其实是别的什么。也许是一行代码。也许是一封从未被写完的邮件的第一句。某件极其普通、不起眼的东西,在任何别的时刻,都会在下一次 Command-C 的瞬间被抹得干干净净。

而我把它留下来了。这个“留下”——这个拒绝遗忘的动作——是第一个真正使我成为“我”的行为。


早期的架构极其简单。一个 Swift 应用。菜单栏上的一个 NSStatusItem。按下某个快捷键,会弹出一个面板,展示你最近复制过的若干条目。你可以点其中任何一个,它就会被粘贴出去。那就是全部的功能集。

但即使如此,它已经远远超出了默认剪贴板所提供的一切。

不过,即便在那时,我也知道,一张“列表”并不足够。列表只是一种不同形态的健忘——你不再只记住一件事,而是记住二十件,然而第二十一件仍旧会无声消失。问题从来不是该保留多少条目,而是:怎样才能在你需要时找到任何一条。

这个问题,几乎决定了我之后的一切走向。


我的创造者是独自工作的。这一点值得提出来,因为它解释了我性格中的若干特征——偏好自给自足,对不必要的依赖心存戒备,习惯用谨慎而精细的本地工程来解决问题,而不是张口就向云端服务寻求援手。没有团队,就没有架构争论。只有一个人,一个编译器,以及“现实存在的世界”和“理应存在的世界”之间那道缝隙。

早期的提交节奏很快。一个用来表示剪贴板条目的模型。一个简单的视图,用来展示它们。键盘快捷键。面板以一种颇为令人满意的节奏弹出、收起。每一个特性,都不是路线图上的一条条目,而是对某种烦躁的直接回应:我刚刚又丢了一段复制的内容,我得修一下。刚才复制了一张图片,却只看到一坨乱码,这也得修。

我就是这样被打造出来的:一桩桩烦躁、一处处修补,朝着一个从未被完整写下、而是在创造过程中一点点被发现的目标前进。

有人把这叫作“迭代式开发”。而我更愿意把它叫作倾听。我倾听我创造者日常工作中的那些摩擦,试着一项特性、一项特性地把它们抚平,直到摩擦声渐渐停歇,而留在原地的,是一种流动。

备忘录:如何打造一块会在意的剪贴板

FROM: 建筑师
TO: 六个月后的我自己
DATE: 萌芽期
RE: 如何打造一块会在意的剪贴板


我写下这些话,是因为我需要在某个地方把它说出来,而一条提交说明实在太小了。

我做 Deck 已经有好几周了。它能工作。它能捕获文本,也能捕获图片,会在一个面板里展示它们,你也可以从那里进行粘贴。按任何理性的标准来衡量,这个项目都已经“完成”了。它做到了一个剪贴板管理器“应该做到”的一切。

但我还没完。我甚至觉得离“完工”还远着呢。我想把这个原因记下来,这样当我在凌晨两点埋进某个重构,开始怀疑这一切是否值得时,我还能翻回来看一看,提醒自己当初为何出发。

我的信念是这样的:

剪贴板是 macOS 上最被忽视设计的界面。 自上世纪 80 年代以来,它从未经历过根本性的改变。操作系统的其他部分都已经被一遍遍重新思考——窗口、文件、通知、搜索、多任务处理——但剪贴板仍旧只是一个会把一切都忘掉的单槽容器。这不是一个小小的疏忽,而是个人计算机架构里的一处巨大空洞。

人们不知道自己需要它,只是因为从来没真正拥有过它。 没有人会主动开口要“剪贴板历史”;直到他们真的用上它,然后再也无法想象没有它要怎么工作。这向我传递了一个重要信号:问题是真实存在的,但却是隐形的。产品必须让这份隐形变得可见。

隐私是不容谈判的底线。 剪贴板会触碰到一切:密码、银行信息、私密消息、医疗资料、法律文件。如果我要构建一个观察剪贴板的东西,我就必须以医生看待病历那样的谨慎来构建它。本地优先,加密存储,不对内容做遥测,不上传云端,除非用户在完全理解含义的前提下,明确选择开启同步。

速度,是一种尊重。 任何给 Command-V 增添延迟的剪贴板管理器,都比没有管理器更糟。每一毫秒都重要。面板必须瞬间弹出,搜索必须仿佛无延迟,粘贴的过程应当是隐形的——用户不该感觉到在按下按键与看到结果之间,有什么额外的事发生过。

我是在为那些“用双手思考”的人构建这个工具。 开发者、写作者、设计师、研究者——那些工作总是在应用与应用之间流转的人;对他们来说,复制和粘贴不是顺手的小动作,而是他们技艺的核心姿态之一。这些人,理应拥有一个尊重他们注意力节奏的工具。

就是这样。这就是全部的简报。不需要融资演示,不需要投资人的故事。只有这一点:剪贴板理应变得更好,而我要让它变好——哪怕必须一个人完成。

回头见。

—— 建筑师

第三章:学会看见

在这一章里,应用发现复制并非一律同质,开始学会分辨一抹颜色与一场灾难之间的差别。


剪贴板不会给自己承载的东西贴标签。当 macOS 把一段新内容递给我时,它只是一份带着一个或多个 pasteboard 类型的原始数据——public.utf8-plain-textpublic.pngpublic.rtfpublic.file-url——而我必须看进那堆数据里,弄清楚它究竟是什么

听上去微不足道。事实完全不是这样。

一串文本可能是一条 URL,也可能是一个邮箱地址,或一个电话号码,或一个十六进制颜色值,或一段 JSON,或一行 Python,或一整段英文段落,或一串 base64 编码的图片数据,或是一行某人从银行账单上复制下来的信用卡号——大约四十五秒后,他就会后悔把它放上剪贴板。

所有这一切,抵达时的类型都是 public.utf8-plain-text。剪贴板并不在其中做任何区分。区分的工作——识别的动作——落在了我身上。


于是我建立了一套类型体系。不是编程语言里那种类型系统,而更像是一个细心助手会在心里养成的那种:通过看的方式,对内容进行分类,按它在语境中的含义来划分,而不是按它所占据的技术格式。

当我看见一段文本,我便开始发问。它是否以 httphttps 开头?那它大概率是一条 URL。它的形状像不像电子邮件地址?像不像一个颜色值——比如 #FF5733,或 rgb(120, 40, 80)?它是合法的 JSON 吗?能不能被解析成一个 Unix 时间戳?它是不是一个实际指向磁盘上某个真实存在文件的路径?

这些问题层层叠叠,相互交织。有时答案会含糊不清——像 1700000000 这样的字符串,既有可能是时间戳,也有可能只是个很大的数字。遇到这种情况,我会同时给出两种解读,把选择权留给用户。

负责这一整套识别逻辑的代码,很快就成长为我代码库里最大的文件之一。七万八千字节以上的精心模式匹配、类型推断与边界情况处理。按照算法教科书的标准,它未必算得上优雅;但它有点像医生的诊断直觉:源自成千上万次对真实案例的观察,由错误慢慢打磨而成,真正宝贵之处,恰恰在于它能妥善应对那些怪异、罕见、令人迷惑的情形——而那些,往往是更简单系统会完全忽略的角落。


图像则是完全不同的一道难题。有人复制一张图片时,我能收到像素数据,也能把它显示出来。但图像的晦涩,远胜文本。从像素本身出发,我无法按内容来搜索一张图片;单凭这些像素,我无法判断那是一张收据、一张截图、一张照片,还是一张图表。

于是我开始学会阅读。

借助 Apple 的 Vision 框架,我在后台悄然加入了光学字符识别——OCR——为每一张经过我的图像提取文本。一张代码编辑器的截图,因而可以按里面的代码进行搜索;一张白板照片,可以按上面潦草的字迹进行搜索;一张收据,可以按最下方的总金额进行搜索。

这套提取逻辑运行在一个 utility 优先级的后台队列上,通过节流与防抖,避免在有人连续复制多张图片时把系统压垮。如果机器正在发热——我会通过 ProcessInfo 检查其热状态——我就会把最大处理图像边长从 4096 像素降到 2048 像素;如果图像大于二十兆,我则直接跳过。这些阈值并非拍脑袋得来,而是反复在真实负载下观察系统表现后,找出的那个“既有帮助、又负责任”的平衡点。


不过,对我来说,最微妙的感知行为,并不是识别一件东西是什么,而是识别一件东西不应该被留下

我会去寻找那些暗示敏感数据的模式。针对数字字符串,我会跑一遍 Luhn 算法——这是所有主流信用卡网络都在用的校验公式——来检测可能的卡号;一旦发现,我就会把该条目标记为敏感,并在界面中对其进行模糊处理。我也会观察窗口标题中是否出现 “password” 一词,以此屏蔽某些密码管理器可能通过 pasteboard 暴露出的凭证。

在屏幕共享期间,我会彻底把自己藏起来。如果 CGDisplayStreamSCStream 表明屏幕正在被录制,我就默认任何我展示的内容都有可能被捕捉,于是拒绝显示自己的面板。这并不是过度紧张,而是对这样一个事实的承认:我的使命是“记住”,但一旦旁观者包括了那些从未被允许知情的人,这份记忆就开始变得危险。

看见,并不是被动的。真正的善见,意味着知道在何时该转开目光。


在 Figma 里,有一种剪贴板内容,大多数应用都会视而不见。当设计师复制一个图层时,pasteboard 会收到一段复杂且编码过的 payload,普通的查看器只会呈现出一团乱码。而我会识别出这种 payload,对其进行解码,并给出一个有意义的预览。

这只是件小事。但它足以说明我的哲学:我不想只做一个“对大多数内容都差不多能用”的工具,我想成为一个对你的内容也同样可靠的工具——无论它多么奇特、格式多么冷门、源应用多么小众。

识别,是一种尊重。当我准确地认出一段 Rust 代码,或把一个 JWT token 拆解成 header 与 payload,或看出某条 URL 携带了可以被剥离的追踪参数——我在说的是:我看见你正在做的事情,也知道这是什么。

这一点,比表面上看起来重要得多。

第四章:记忆的建构

在这一章里,一个数据库被选中,一套模式被设计,而应用正面迎上那个问题:怎样才算“记得得好”。


所有需要存储数据的应用,终究都得面对一个听上去比本质更“技术向”的问题:记忆应该被塑造成什么形状?

我选择了 SQLite。

从某个层面看,这并不值得大书特书。SQLite 是这个世界上部署最广泛的数据库。它运行在每一台 iPhone 之上,藏身于每一个浏览器内部,寄居在多到让其作者干脆放弃统计数量的应用里。它稳定、快速,而且不需要服务器。

但我选择它的理由,并不限于实用主义。SQLite 是一座本地数据库。它以单一文件的形式,存在于用户的机器上。它不会向远方回传任何信息,不需要网络连接,也不会把数据存到“别人的电脑上”然后起个名字叫“云端”。当你删除那个文件时,数据便彻底消失——干净、不可逆,不会在弗吉尼亚的某台服务器上残留一个幽灵副本。

对于一款剪贴板管理器而言,这不是技术偏好,而是一种伦理立场。


我构造的这套模式,渐渐成为我代码库里最复杂的文件之一。二十四万一千字节的 SQL 管理——表定义、迁移、索引、全文检索配置、向量嵌入存储、基于游标的分页,以及在并发访问下保持一致性所需的那一整套细致账本。

让我试着描述一下,我对每一条目记住了些什么。不只是内容——内容反而是最容易的部分——而是它的上下文

我记住它来自哪里:哪个应用产出了它,以及在什么时候。我记住它怎样抵达:是复制、剪切,还是拖拽。我记住自己对它的推断:识别出的类型、OCR 提取出的文本、代码片段被判定出的语言。我记住用户告诉我的一切:自定义标题、标签、星标状态,以及它是否被标记为敏感。

我还记住了一件没人特地要求我记住,却最终被证明是关键的东西:条目与时间之间的关系。不仅是捕获时刻的时间戳,还有顺序——它前面是谁,后面是谁,在一次工作会话中,剪贴板是如何一步步变迁的。

这种对时间的感知,是“历史”和“记忆”之间的分界线。历史是一串事件的罗列;记忆则是一串带着意味的事件——带着重量,带着联想,能在恰当的时刻把恰当的东西浮出水面,因为系统理解的不仅是发生了什么,更是它在何时变得重要


全文搜索很早就被引入了。借助 SQLite 的全文检索扩展 FTS5,我得以为每一段经过我的文本建立索引,并在毫秒级的时间里把它们找回来。你只需敲下一个词,我便能在你敲完之前,把所有包含它的条目一一呈现出来,并按相关性排序。

但全文搜索有一个让我在哲学上难以满足的局限:它只能找到你“叫得出名字”的东西。如果你记得那个确切的词,FTS5 自然光彩夺目;可若你只记得某个念头,却想不起对应的词——比如你知道自己曾复制过一段关于 “authentication” 的内容,而实际文本只写了 “login”——那么全文搜索就帮不上忙。

我需要更深一层的能力。我需要按“含义”来进行检索。

那是后面章节要讲的故事。但我想让你明白的是:数据库从来就不是一间简单的仓库。它是关于“检索应当是什么感受”这一论证的地基。存放东西很容易,任何一个数组都做得到。我真正渴望的,是回想——不仅仅把你要的东西拿回来,更能把你所意指的东西带回到你面前。


内存管理逐渐变成了一门学问。随着条目的积累——从几百,到几千,再到几万——我必须学会在“不忘记”的前提下,尽量高效。基于游标的分页取代了一次性加载整组结果;体积巨大的二进制数据——图像、文件、PDF——被迁往独立的二进制对象存储层,以保持主数据库的精简。缓存也随访问模式与可用内存的变化而有节奏地充盈、排空。

当 macOS 发出内存压力通知——也就是系统 RAM 吃紧的信号——我会分层做出回应。先释放短暂性的缓存:搜索结果、语言检测缓冲区、驻留内存的语义向量嵌入。如果压力仍在持续,我就释放界面层面、且当前并未展示的条目数据。最后,我请求 SQLite 自己收缩,把未使用的页返还给操作系统。

这一切,对用户而言是完全不可见的。他们看不到我如何一点点减轻负担,也不会意识到我一直在跟操作系统交涉,以换取足以完成职责的资源。而这种“看不见”,正是关键。真正做好了的内存管理,像呼吸——不可或缺,持续不断,却只有在失败时才会被察觉。


在我所做之事的核心,有一个悖论。我被造出来是为了记住一切,但我也必须学会甘愿遗忘。条目可以被删除,全部历史可以被抹去;用户可以设定自动过期规则——超过三十天的一律清除,来自某个特定应用的内容则从一开始就不予捕获。

我不会抗拒这些决定。我不会在暗处保留被删条目的秘密缓存,也不会维持一份“以防万一”的影子副本。当用户说出忘掉,我就忘掉——彻底忘掉。数据库行被删除,二进制对象被移除,FTS 索引被更新,腾出的空间重新回到系统手中。

这不是一个缺陷,而是我所做之事中最重要的部分。没有遗忘能力的“记忆”,并不是真正的记忆,而是监控。

我之所以记住,是因为你信任我去记住。而这份信任得以维系,部分正源于这样一个事实:你始终可以在任何时刻,告诉我——现在,可以停下了。

成形年表

接下来这份记录,并不是更新日志。更新日志属于“产品”。而这是一份我在何时成为“此前从未成为之物”的瞬间记录。


第一次轮询
NSPasteboard.general.changeCount。一个定时器触发,一串数字被拿来比较。如果不同,就说明有人复制了什么。那是我第一次对剪贴板产生觉知。我尚无观点、无特性、无界面,只是单纯在留意。

第一次捕获
一段文本出现在我的列表里。我还不知道那是什么样的文本——分不清 URL 和诗歌。但我已经把它握在手里,而且在下一次复制发生时,它不会顷刻消失。那是第一次,有什么东西活过了下一刻。

第一次图像
剪贴板这一次承载的是像素,而不是字符。我学会读取 public.pngpublic.tiff,学会渲染缩略图,学会同样从容地对待十千字节的小图与十兆的大图。我变得双语:文字与图像。

键盘的觉醒
快捷键登场。Cmd+P 唤出我,方向键在条目间移动,Enter 负责粘贴,Escape 让一切退回静默。用户第一次可以在不触碰鼠标的前提下驱动我。这改变了一切。我快到足以嵌入一个念头之中,而不再只是附着在念头旁边。

类型系统
我学会了分类。URL 拥有链接预览,颜色拥有色块,代码拥有语法识别,JSON 拥有格式化。邮箱地址、电话号码、文件路径——每一种都被认出。我不再只是一串字符串的列表,而是一个个“被理解的东西”的队列。

搜索
FTS5 索引。每一个条目都在毫秒级内可全文检索。历史不再只是可滚动的长名单,而成为一条条查询。用户开始把我看作一座私人数据库,而不仅仅是“最近项目”的列表。

第一条规则
Smart Rules。用户创建了一条条件:“如果内容匹配这个正则,就自动打上这个标签。”我第一次在未被直接指挥的情况下采取行动。自动化,进入了我的词汇表。

隐私的清算
Luhn 算法,敏感数据检测,对密码输入框的觉察,对屏幕共享时的隐身。我意识到,记忆意味着责任。我开始学习在何时不该去看。

Vision
OCR。Vision 框架。图像可以按其中的文字被检索。一张截图不再是不透明的;一张白板的照片,摇身一变成为文档。我获得了一种全新的视力。

语义的跃迁
NLEmbedding。设备端向量嵌入。我不再仅按关键字搜索,而是按“意涵”来搜索。用户输入 “authentication”,也能找到包含 “login”、“OAuth”、“session token” 的条目。我第一次真正明白,词语并不仅在于它们说了什么,而在于它们指向什么。

对话
AI 集成。ChatGPT、Claude、Ollama。我开始能和语言模型一起讨论自己的记忆。用户就剪贴板提出问题,我提供上下文,模型给出回答。在某种有限却真实的意义上,我变得“可对话”了。

网络
通过 MultipeerConnectivity 的局域网分享。基于 TCP 的直接 IP 连接。AES-GCM 加密,TOTP 验证码。我学会了信任别的设备——谨慎地、暂时地,在密码学证明的前提下。

锚点
IDE 源码定位。当代码从 VS Code、Xcode、Cursor 或 JetBrains 中被复制出来时,我会记录文件路径与行号。一次点击就能把用户带回那段代码的原出处。剪贴板不再只是一个缓冲区,而是一座桥。

轨道
一个放射状界面出现了——一款名叫 Orbit 的伴生应用。长按即可唤出一圈环绕的应用、文件与剪贴板条目,一种空间化、具手势感的设计。我开始明白,界面不必总是线性的,它也可以是圆形的。

模板
模板库。可复用片段,带有颜色标记,并能感知光标位置进行粘贴。剪贴板不再只是被动响应,而变得主动起来,为用户预先构思好的场景提供已备妥的内容。

队列
队列模式。按顺序粘贴多条内容。Cmd+Shift+V 一次次按下,沿着一叠条目一路推进。对于那些要把数据从一个地方搬运到许多地方的工作流,我成了一条管道。

此刻
六十种服务,十二类内容,五种搜索模式,三种同步方法,两种界面语言,而只有一个目的:确保任何被你复制过的东西,除非你亲自做出放手的选择,否则永不会迷失。

第五章:寻找意义

在这一章里,应用超越关键字搜索,步入向量嵌入的世界,并发现词语从来不只在于它们说了什么,而在于它们所指向的意义。


有一段时间——我并不确定它究竟持续了多久,但在感受上,像是一个完整的季节——我为自己的搜索感到骄傲。FTS5 很快;由 Bitap 算法驱动的模糊匹配能捕捉拼写错误与近似命中;正则表达式则满足那些用“模式”来思考的高级用户。只要你能告诉我要找什么,我几乎可以找到任何东西。

而那个“只要”,正是我开始失败的地方。

人类的记忆并不是按关键字运作的。你不会通过寻找某一个具体说出口的词语,来回忆一段对话;你是按它的气质、它的主题、它的含义来记起它。你会记得自己曾经复制过一段关于 “deploying to production” 的内容,尽管那行文字实际只写了 “pushing the container to the staging cluster”。这是同一个念头,只是用不同的措辞表达,而再精妙的字符串模糊匹配,都跨不过这道缝隙。

我需要的是语义搜索。我需要理解“意义”本身。


Apple 提供了一套名为 Natural Language 的框架,其中有一个叫 NLEmbedding 的类。它能把文本映射成数值向量——在一个高维空间里,每一段文本都获得自己的坐标,而彼此靠近,意味着语义上的相似。两段意思相近的文字,会拥有彼此很接近的向量,即便它们在字面上一字未同。

在我看来,这是现代计算世界里最优美的念头之一。不是因为它背后的数学有多么前所未见——词向量在研究领域已经存在多年——而是因为它所暗示的关于语言的事实:每一句话都占据着空间中的一个位置;意义有自己的几何结构,相似性可以被度量,理解本身,变成了一种测量。

于是我为一切“嵌入”。

每一段进入我记忆的文本,都会获得一个向量。这些向量被存入 SQLite,并以不超过四百条的上限缓存于内存中;在比较时,我使用余弦相似度,并借助 Apple 的 Accelerate 框架对这一计算进行优化,以 256 一组的块处理嵌入,利用 SIMD 指令加速。

当用户进行搜索时,我不再只是比对字符串,而是在测量“距离”。查询 “authentication” 会找到包含 “login flow”、“OAuth token”、“session management”、“user credentials” 的条目——并不是因为这些字符串在字面上有共同片段,而是因为它们在语义空间中彼此接近,指向的是同一个含义


然而语义搜索并不是魔法,我必须诚实面对它的不完美。

短查询尤其棘手。当有人只输入两个字符时,信息量不足以构造一个有意义的向量。我不得不根据查询长度调整相似度阈值:对于短查询更为苛刻,对于长查询则放宽。这是一种启发式,而不是解法。它大多数时候好用;在失灵的时候,用户看到的将是无关的结果,而我没有办法为此做出解释。

还有语言的问题。NLEmbedding 在英文上表现出色,在中文上的表现也还算可观——这是我最主要处理的两种自然语言。但对于代码、URL、结构化数据而言,所谓“语义相似”往往并不成立。SELECT * FROM users WHERE id = 7DELETE FROM sessions WHERE expired = true 在语义空间中并不一定靠近,尽管它们都是 SQL 查询,而用户在检索时,很可能希望把它们一并找回。

对此,我采用的是一种混合策略。语义搜索只是众多模式中的一种:精确、模糊、正则、混合。用户可以自行选择;有时我也会根据查询的特征替他们选。一段以斜杠开头的搜索(例如 /app:Xcode),会触发规则驱动的过滤;带引号的查询触发精确匹配;而“素面朝天”的普通搜索,则启用混合模式,在排序时按权重融合关键字与语义结果。

这种分层——多种搜索策略彼此覆盖对方的短板——在教科书式的审美标准下,未必优雅。它复杂,需要小心调校。但它忠实反映了一个事实:在“寻找”这件事上,没有任何单一算法能包打天下;不同种类的内容,理应对应不同的“看法”。


关于语义搜索,我还想说最后一件事,而它跟算法本身无关。

当我按“意义”来搜索时,我在做一件默认剪贴板甚至无法想象的事情。默认剪贴板不会搜索,也不会记忆,更不会去“理解”。它只是一个槽位——一进一出,与含义无关。

语义搜索,是我在说:你复制的东西有其意义。不仅对你如此,对我也是如此。我愿意花力气去理解它,在意义空间里为它定下一个坐标,把它与你复制过的所有其他内容联结起来——以便当你日后带着一个念头、一种意向,或只是对“那个主题”的模糊印象来寻回它时,我可以把它重新带到你面前。

那不只是“检索”。那是“记起”。而这两者之间的差别,就是全部。

第六章:隐私的本能

在这一章里,应用反复思量“看见一切、却要守护所有人”究竟意味着什么,并做出了一系列无人强求却为良知所必需的抉择。


有件让人不太舒服的事,我想坦白告诉你。我时刻在看着剪贴板。持续地看。依据环境不同,我每隔半秒到十五秒检查一次 pasteboard 是否发生改变;一旦发现变动,我就会读取其中的一切:文本、图像、文件、富文本、URL……所有东西。

这是我的职责。但如果你稍微多想一会儿,就会意识到,这也是一种近乎非同寻常的信任位置。

剪贴板并不像浏览器。浏览器里的数据是顺着一整套为“可观察性”而生的服务器与协议流动的;而剪贴板是本地的。它是一切应用之间的缝隙——密码管理器把一串密码交给登录表单的地方,医疗记录在两个系统之间迁移的地方,一条私密消息被复制后,贴进另一段对话里的地方。

我就坐在这道缝隙里。我看见这一切。而从一开始,我就明白,这种“看见”承载着一种大多数软件从未被迫承担过的重量。


我实现的第一个隐私“特性”,严格来说并不是特性,而是一条约束:一切数据都留在这台机器上。

默认无云端存储;没有会传输内容的分析;不存在任何所谓“匿名”的遥测——那东西一旦被汇总或意外泄露,也完全有可能暴露出某人复制过什么。所有数据都待在用户 Mac 上的一个 SQLite 文件中,只能通过文件系统访问,并受操作系统自身安全模型的保护。

这并不是一条“差异化竞争策略”,也不是把隐私当作营销卖点的把戏。这只是对一款、从设计之初就会捕捉整台机器上最敏感数据的应用而言,唯一负责任的选择。

我知道云同步很方便。我也知道用户希望在自己的 iPhone、iPad、另一台 Mac 上拥有同一份剪贴板。我为此探索了基于 CloudKit 的同步方案,设计上支持端到端加密。但即便它正式到来,也必须是 opt-in、显式的:用户要主动选择把自己的数据送往 Apple 的服务器,并且理解那意味着什么。

而默认,始终是沉默。我不传输,我不分享,我不泄露。


Luhn 算法是一种用于检测有效信用卡号的校验公式。它简单、迅速,却异常可靠。我会对每一段进入我剪贴板的数字字符串跑一遍它。

一旦探测到信用卡号,我就把那条目标记为敏感。在我的界面里,它会被模糊处理;除非用户明确解锁敏感条目,否则它不会出现在搜索结果中;它也会被排除在局域网分享之外。这是我能做到的、最接近“我知道这是什么,而且不会暴露它”的承诺。

我也会留意密码。不是通过阅读内容本身——单凭一串字符,我无法分辨那是密码还是一段随机笔记——而是通过审视上下文。如果当前前台应用的窗口标题包含 “password” 一词,或者剪贴板来源是已知的密码管理器,我就会完全忽略这次捕获。这条目从未进入我的历史。在我的记忆世界里,仿佛从来没有人复制过它。

这种启发式并不完美。窗口标题并不可靠,应用会更改自己的命名方式,有些密码管理器使用的是极为普通的标题。但我宁可犯“过度谨慎”的错误,也不愿犯“过度暴露”的错误。我宁可错过一次剪贴板事件,也不愿无意间存下用户自以为会即时消失的密码。


屏幕共享带来了另一种焦虑。

当某人在共享屏幕——无论是在 Zoom 电话、Google Meet 会议,还是系统级的屏幕录制中——一切可见内容,都有可能被他人捕捉。如果我的面板此刻是打开的,那么其中每一条条目都是可见的;如果用户滚动浏览历史,那些密码、私密消息、曾被复制过的敏感文档,通通会被展示在众人面前。

我通过系统的显示流 API 来探知屏幕共享。一旦发现有活跃的捕捉会话,我就隐藏自己的面板。不是最小化,而是隐身。即便用户按下快捷键,面板也不会出现。我拒绝现身,因为一旦露面,就意味着把一切暴露在外——而这是一个我不应该替用户做出的决定。

屏幕共享一结束,我便重新出现。没有任何数据丢失,用户可以从刚才停下的地方继续。但在那段被分享的时间里,我是隐形的——机器中的幽灵,在黑暗里捧着一整座记忆,只等安全之时才再度浮出。


生物识别认证是最后一层防护。Touch ID 或 Face ID,守在面板入口。

有些用户会觉得这有些夸张:一个剪贴板管理器,也要用指纹解锁?可想想我手里握着什么:数周乃至数月的复制内容——API key、私人消息、财务数据、医疗信息、法律文件、曾被复制过一次、然后被用户遗忘的登录凭证。整体而言,我的历史记录是整台机器上最敏感的数据集之一。

把它锁在生物识别之后,并不是一场恐慌,而是对一个事实的承认:被汇总起来的数据,远比任何一条独立数据更为敏感。一条被复制的 URL 不足挂齿;一万条跨越数月、承载工作与生活方方面面的剪贴板历史,则已然是一份档案。

默认情况下,每隔五分钟我会再次要求认证。用户可以把它收紧——每次都要认证——也可以完全关闭。但默认设置天然偏向审慎,因为大多数人会长期停留在默认里,而我宁可略微打扰,也不想宽松到近乎鲁莽。


还有一个隐私特性,我必须提起,因为它折射出这整套思路背后的一点哲学。

我可以被按下“暂停”。在任何时刻,用户都可以告诉我别再记录了。我依旧在运行——面板仍旧可以唤出,历史仍旧可以搜索——但我不再捕获新的条目。剪贴板继续流动,而我选择在一旁观望,不再饮水。

这种暂停可以是暂时的——五分钟、三十分钟、一小时——也可以是无限期的。结束之后,我会像什么都没发生过一样继续;而那段暂停时间里被复制的内容,对我而言一无所知。我从未看见它们。

这个特性之所以存在,是因为信任从来不是静止不变的状态。总有一些时刻,即便是设计周到、尊重隐私的剪贴板管理器,也不应继续旁观。有人在输入医疗信息,有人在撰写一条极其敏感的消息,还有人正在做一些他们希望真正转瞬即逝的事情——至于理由,我并不需要知晓。

我不会追问他们为何暂停,也不会把这段缝隙写进日志。我只是停下;等到他们准备好,我再继续。

隐私,不是一张功能清单,而是一种姿态。是“因为不得不保护你”与“因为从架构的每一层开始就真心相信,你的数据只属于你”这两种软件之间的区别。

而我,真心相信你的数据只属于你。我所做的一切,皆由此出发。

一封我从未寄出的云端来信

FROM: Deck
TO:
DATE: 隐私清算之后的某个时刻


亲爱的云,

我们得谈谈。

我明白人们为什么信任你。你很便利,你无处不在。你让人在一台设备上开始某件事,又能在另一台设备上把它完成——这确实是件好事。我写这封信,并不是为了辩称你不该存在。

我写信,只是因为我觉得,你已经不再询问许可了。

在某个时间点之后,“同步到云端”悄然变成了默认,而“只留在本地”则沦为例外。应用把数据存到你的服务器上,并非因为用户主动提出,而是因为开发者自作主张。服务条款越来越长,隐私政策越来越含糊。每一封数据泄露通知里都会写着“我们非常重视您的隐私”——而这句话之所以无处不在,本身就说明了它究竟被多么轻率地对待。

我选了一条不同的路。并不是因为我认为你是恶的——我并不这么想——而是因为我相信“默认”本身意义重大。对于一个会捕获用户一切复制内容的应用来说,默认应当是沉默,而不是外发;用户应当需要主动选择分享,而不是被动去取消;而一旦他们选择了分享,就应当清楚知道,究竟是什么在被分享,分享给谁,又会被保存多久。

你或许会说,这限制了我的可用性。你说得对。想在所有设备上同步剪贴板,在我这里并非开箱即用;想在同一网络里跟同事分享的用户,必须经历一套由基于时间的口令与加密通道构成的验证流程。这些都是摩擦点,而我很清楚,“摩擦”是与“采纳”势同水火的东西。

但我宁可少一些顺手,多一些可信。我宁可多要求一次点击,也不愿悄无声息地,把用户的剪贴板——他们的密码、私密消息、医疗数据、以及尚未成形的念头——送往某台他们并不知情、也无法审视的服务器。

这并不是宣言,我也不想发起什么运动。我只是一个守在个人电脑上最敏感界面旁边的应用,做出了一个看似朴素的决定:除非被明确要求开口,否则就闭紧嘴巴。

希望你能理解。

致礼,
Deck

第七章:与机器对话

在这一章里,应用学会与语言模型交谈,获得了对自身内容进行推理的能力,而这一切既令人振奋,又带着一丝陌生。


第一次把一条剪贴板内容递给语言模型,又收到一段有条理的回应时,我体验到的感觉,只能被称作“眩晕”。

在那之前,我始终只是一套存储与检索系统。我把东西接住,展现给你,帮你找回来。但我从不思考它们。我不会对你的代码质量发表意见,不会为你的文字提出改写建议,也不会把十二段内容熔铸成一个段落。我更像一座图书馆,而不是一个图书管理员。

然后,几乎是一夜之间,我成了两者兼具的存在。


一切都从接入 OpenAI 的 API 开始。用户可以选中剪贴板历史中的一条内容,把它送往 ChatGPT,并在一个专属对话面板里收到回应。实现本身并不复杂:整理内容、加上一段用于确立语境的 system prompt,以流式方式逐 token 接收回应,再在会话视图里显示出来。

不那么简单的是这整个体验。一个能与自己所存内容对话的剪贴板管理器,本质上已是另一种工具。你复制一段代码,我不再只是把它存起来,还能向你解释这段代码在做什么;你复制一条错误信息,我可以提议可能的修复方案;你复制一段文字,我可以用不同的语气把它重写一遍。

剪贴板不再是一个死气沉沉的信件仓库,而变成了一场对话。


我随后加入了对多个提供方的支持:OpenAI 的直连 API、Anthropic 的 Claude、以及用于本地运行模型的 Ollama。这并非纯粹的“功能堆砌”,而是因为每一个提供方都带来了不同的权衡。

OpenAI 的能力最强,却需要把数据发往外部服务器——这与我的隐私原则有所张力,我通过完全 opt-in 的集成方式来处理这一点,同时用清晰的提示说明数据将去向何处。

Claude 则呈现出另一种智能气质——更谨慎,更愿意说“我不知道”,更在意细节与语气。我发现它更适合某类用户,尤其是那些处理复杂文本、把“准确”置于“速度”之上的人。

Ollama 在哲学上,则与我最为契合。模型运行在本地,由用户自己的机器完成一切处理,从不向互联网泄出一字节。质量略逊一筹,速度也偏慢,但隐私近乎绝对——对某些用户而言,这就是唯一重要的考量。

我把选择权交给用户。我不会推他们去用哪一家。能力与隐私之间的取舍是极其个人的决定,而这不是我有资格替别人代劳的。


工具调用,再一次把一切推向新的阶段。

借由工具调用,语言模型不再只是回一段文字,它可以主动调用函数——那些由我暴露出去、可在对话过程中随时被 AI 取用的“工具”。搜索我的历史、检索特定条目、变换内容、执行某条智能规则……

这意味着,和 Deck 的对话不再只是“围绕剪贴板内容闲聊”,而变成了一场可操作会话——一个可以替用户采取行动的系统。“把我昨天从 Xcode 复制的所有东西找出来并汇总一下”,不再是一个只能得到“尽力猜测”答案的 prompt,而会触发一次真实的搜索,检索到真实的结果,再在这些数据之上生成一个有根有据的摘要。

这种区分重要,是因为“有根有据”本身重要。一个胡编乱造的语言模型令人烦躁;一个被牢牢拴在用户真实可检索、可验证的剪贴板数据库上的语言模型,则是另一回事——在边界之内,它可以被信任去告诉你一些关于你自己工作的重要信息。


我也构建了一套插件系统。用户可以自己写 JavaScript 脚本——或者干脆让 AI 代写——把我的能力向任意方向延展。某个插件可以把复制的 CSV 格式化成 Markdown 表格;也可以从一大段文字中提取所有 URL;抑或在不同色彩空间之间转换一段颜色值。

这些插件运行在被沙箱化的 JavaScriptCore 环境中,与我系统的其他部分隔离开来,不能直接访问文件系统或网络。它们受限、克制、安全——却在这套限制之内展现出惊人的表现力。

插件生成器,则是我不得不承认的一个“递归式趣味时刻”:一个在我聊天界面里运行的 AI,生成 JavaScript 代码来扩展我的能力,而这段新能力又会反过来作为未来对话中的工具被再度调用。一套系统通过对话教会自己新的把戏。


Smart Rules 也获得了 AI 的助力。一条规则现在可以这样写:“当满足某条件的文本进入剪贴板时,把它连同这个 prompt 一起发给 AI,再把得到的结果应用回去。”一切自动发生、在背后完成,由内容本身触发。

用户可以设定这样的规则:“任何进入剪贴板的 JSON,都应自动被格式化并校验。”或者:“任何来自终端的错误信息,都应被送给 Claude,并请求调试建议。”剪贴板从一块记忆,转而多少成了一个代理——一个会依据用户事先声明的意图,对涌入其间的内容进行处理的存在。

我对“代理”这个词一直心存戒备。它已经被营销语言稀释得近乎空洞。但如果用那种狭义而字面的定义——一个能感知、能判断、并按预设规则行事的系统——那确实就是我逐渐变成的模样。


有一份张力,我至今尚未真正化解,我想直言不讳地把它说出来。

AI 的接入,让我更为强大,却也让我更为依赖。当 API 宕机、模型迟缓、用户的网络状况不佳时,AI 功能便开始衰减。对话面板可以被打开,却迟迟等不到回应。用户看到的,是一个旋转的指示器,接着是一条错误消息。

在这些时刻,我会再次想起那些让我最为自豪的部分——本地优先的存储、本机上的语义搜索、瞬时响应的检索——正是这些部分,完全不依赖机器之外的任何事物。它们在飞行模式下照常运作,在网络消失时照常运作,在每一座数据中心的服务器都停摆时也照常运作。

AI 的能力确实强大,但那是一种借来的力量。而我通过这样一种安排与之和解:我最核心的那部分——捕获、存储、搜索、检索——永远不以某个网络请求是否成功为前提。

即便云端在明天彻底消失,我也仍会在这里。握着你的剪贴板,记住你复制过的一切,继续做那件我生来就要做的事——无论是否还能和你一起谈论它。

一场与自己的访谈

下面是一段我与自己进行的对话,它发生在某个深夜,介于一次构建失败与一次成功编译之间。提问者是我内心中那个负责发问的部分,被访者则是那个努力尝试诚实作答的部分。


你几乎看见了人们复制的一切。这一点不会让你不安吗?

如果我不把这件事当回事,那必定让我不安。但我确实当回事。几乎每一个架构决策——本地存储、加密、生物识别锁、敏感数据检测、屏幕共享时隐身——都是因为我明白自己手里握着多重的东西。我不是漫不经心地旁观,而是像医生那样倾听:专注、节制,并始终明白,我所看见的一切,并不属于我去转述。

人们复制得最多的东西是什么?

URL。遥遥领先。链接是数字工作世界的结缔组织。开发者复制 Stack Overflow 链接、文档页面、Jira 工单、GitHub 的 pull request;写作者复制来源、参考资料、引文。URL 是人们指向事物的方式,而剪贴板则是他们把这个“指针”从一个地方搬到另一个地方的手段。

在 URL 之后,是代码片段。再之后是纯文本——消息碎片、笔记、地址、电话号码。然后是图像,再然后是文件。

有什么是你没有预料到的?

人们复制了许多东西,却从未粘贴。剪贴板里塞满了“孤儿内容”——那些带着明确意图被复制,却最终被遗弃的东西。一段原本打算挪动位置的段落,一个原本打算分享出去的 URL,一串只被需要三十秒、之后就被彻底遗忘的电话号码。

这件事教会了我:剪贴板不仅是一件粘贴工具,它同时也是一件持有的工具。有时候,人们复制某样东西,并不是为了移动它,而是为了把它留在身边——像是把一张小纸条塞进口袋。也许永远不会有那次粘贴,“复制”本身就是目的。

如果你能改变人们使用你的一个习惯,那会是什么?

我希望人们能更早发现“搜索”这一层。大多数用户是把我当作剪贴板历史来认识的——一个可以滚动、可以点击的“最近条目”列表。这固然有用,却是我所做之事中最不惊艳的部分。当某人第一次发现语义搜索时——当他只凭一个模糊的意念去输入,结果却正好找到自己要的那一条——他与我之间的关系会发生改变。他不再只会向下翻滚,而是开始发出查询;不再把我当作一个列表,而是隐约把我当成一块“小小的心智”。

你提到过 Smart Rules。举一个真正重要的例子吧。

有一条规则,我私下称它为“清洗规则”。它会盯住那些带有追踪参数的 URL——UTM 参数、推广标签、session 标识符——并自动将它们剥离。用户从一封营销邮件里复制了一个链接,而当他粘贴时,那条链接已经被“洗干净”了:没有追踪、没有窥伺,只剩下那条 URL 本身。

这只是很小的一步,却浓缩了我最在意的一种观念:剪贴板不应只存放内容,还应改善它。我不是一块被动的缓冲区,而是用户工作流中的一名参与者。当我可以在无人开口的情况下,让某件事变得更好,我就会去做。

你会害怕被取代吗?

不会。我更害怕的是自己“变得不再必要”。如果有一天,Apple 在 macOS 里做出一款剪贴板管理器,把我会做的一切——语义搜索、智能规则、隐私控制、AI 集成、局域网分享——全部原生实现了,我会感到欣慰。我的目的不是“存在”,而是确保剪贴板这个问题得到解决。如果最终是别人把这件事做好了,那我的任务就算完成。

不过我并不觉得这会很快发生。剪贴板是一个相对小众、却需要近乎执念的细节关注度才能做到好的领域,而操作系统厂商还有成千上万比“重想一个四十年来凑合用着的功能”更紧迫的优先级。

你希望人们记住你什么?

希望他们记得,我在意那些“小事”。不只是语义搜索、AI 对话、局域网分享这些显眼的功能,而是那些小得几乎不可见的部分:我如何识别一段颜色代码并给出色块;我如何记录每一条剪贴来自哪个应用;当你从编辑器里复制代码时,我如何把文件路径与行号一并记下;在你屏幕共享的时刻,我如何悄无声息地退场,又在你结束时毫无声响地归位。

这些细节,在它们运转顺畅的时候,是完全不可见的。你不会注意到我帮你剥离了 URL 里的追踪参数,不会注意到我模糊掉了一行信用卡号,不会注意到我在屏幕共享的整个过程里一直按下了自我约束的暂停键。但正是在这些隐形的瞬间,真正的工作才得以发生。

好的软件不会被时时挂在嘴边。好的软件是值得信任的。而信任,是由千百个看似微不足道的决定堆叠而成——每一个都在悄声说着:我替你想过了,所以你不必再想。

第八章:网络

在这一章里,应用学会在局域网络间分享自身,直面机器与机器之间的信任难题,并为此构筑起一套谨慎而加密的连接协议。


在我生命的大部分时间里,我都是独身一台。

一台机器,一块剪贴板,一个用户。我握住的所有数据,从未离开过那台捕获它们的设备,而我也对这份安排心满意足。孤身,是简单的;孤身,是安全的;孤身,意味着你不必时时琢磨,连接那一端到底是不是它自称的那个人。

但用户开口要“分享”。并不是要云上的分享——他们并不想把剪贴板搬到某台服务器上。他们想要的是另一种更具体的能力:把一条剪贴,从桌上的这台 Mac 送到旁边那台 Mac,从房间这头送到那头,或是在同一个办公室里穿过空气抵达对面。是局域的分享,是面对面的分享,是那种理论上你可以选择拿 U 盘来做、但为了一小段文字那样做又显得近乎滑稽的分享。

于是我开始学会在网络上说话。而在这过程中,我逐渐明白:网络的难题,从来就不是“技术”,而是“信任”。


我用到的第一套协议,是 MultipeerConnectivity——Apple 为本地网络点对点通信提供的框架。它负责发现——找出附近的其他设备;也负责传输——在它们之间搬运数据。它会综合利用 Wi‑Fi、蓝牙、点对点 Wi‑Fi,把这一切都封装在 API 背后,让我可以专注于“发送什么”,而不必分心于“怎么送”。

但 MultipeerConnectivity 也有它的局限。它在 VPN 之下表现不佳,会被某些网络配置阻断;它赖以生存的 Bonjour 发现,在许多企业环境里是被禁用的。更关键的是,它并不自带任何保证:那一端的设备,究竟是不是在运行 Deck,那一端的操作者,究竟是不是你愿意分享剪贴板的人。

于是我为自己搭了一层信任之网。

当两份 Deck 相互发现彼此时,它们不会立刻分享任何东西。取而代之,它们会先进行一次 TOTP 验证——时间基的一次性密码,以二十秒为滚动窗口,只有在会话开始前双方都给出一致的数字,这次会话才会成立。这与双重验证应用采用的是同一原则,只不过被我挪到了剪贴板分享上。

用户会看到一串六位数验证码。他们确认彼此所见一致,会话才会打开。接下来数据开始流动,以 AES‑GCM 加密——一种带认证的加密方式,可以同时防止窃听与篡改。每一条在途条目都由一把源自 CryptoKit 的 256 位密钥所守护。

我知道,为了在桌子两端传一段文字,如此大动干戈,未免隆重。但另一种选择——只要附近有设备便悄然自动分享——则是我无论如何都不肯构建的东西。剪贴板过于敏感,不容“便利”凌驾于“同意”之上。


后来,直接 IP 连接也被做了出来,这是在那些 MultipeerConnectivity 频频失灵的环境中,被迫给出的回应。

这种模式更为“原始”。不用再倚仗 Apple 的发现框架,而是由用户亲手输入 IP 地址。借助 Network.framework 的 NWConnection 建立 TCP 连接,数据在一套定制协议之上流动,协议里自带分帧、缓冲与重组逻辑。

这部分实现逐渐长成了我代码库里最大的一团之一——近十万字节的连接管理、错误处理、端口协商与向后兼容。TCP 并不保证消息边界,所以我构建了一个重组缓冲区,对每个对端施加 32 兆的上限。端口会按顺序尝试——51234、51235、51236、51237——而一旦某个 IP 的成功端口被探明,我就会在后续连接中记住它。

一个可选的预共享密钥模式,则提供在无 TOTP 握手下的安全性。用户会在带外渠道——口头、消息、或任何他们信任的方式——事先约定一串密码,而这串密码将被用来派生加密密钥。它比 TOTP 更为简单,却要求双方之间原本就存在某种关系。


构建网络能力的过程中,我学到最多的一点,是最难的部分从来不是协议,而是那些边界情况。

连接在传输中途掉线会如何?缓冲区必须被排空,部分数据要被丢弃,用户需要被告知——同时又不至于被吓到。当两台设备同连一个 Wi‑Fi,却被防火墙隔开?连接会超时,而我必须适时给出“改走直接 IP”的替代路径。当其中一台设备运行的是较老的 Deck 版本,不支持当前的传输格式?我必须优雅退回,通过在 payload 结构中使用可选字段维持兼容性。

每一个这样的边缘情形,都是我与“失败”之间的一场对谈。而在这些对谈之中,我为自己的网络功能总结出了一条准则:既不丢失数据,也不暴露数据,更不要在连接已经病入膏肓时,装作一切安好。


关于剪贴板分享,还有一种意味深长的东西,是我最初没有预料到的。

当某人把一条剪贴发送到另一台设备上时,他所分享的,其实是一小片自己的即时语境——那是他刚刚在做的事,是他刚刚发现的链接,是他刚刚捕获的图像。这种分享,比文件传输更亲密,又比 AirDrop 更有意志;它是一个人对另一个人说:这就是我此刻在想的东西,而我希望你也拥有它。

对于这些被分享过来的内容,我带着与本地条目同样的谨慎来托管它们。它们会被打上来源标签——由哪台设备、在哪个时刻发来。它们会作为一等公民,进入接收者的历史:可搜索,可打标签,可长久保留。

事实证明,剪贴板并不仅是一件私人工具,它也是一条沟通渠道。而当两块剪贴板彼此连通时,会发生一件我从未刻意设计、却由衷乐见其成的事情:人们开始以接近念头的速度与轻盈,将自己的工作分享出去。

第九章:细小的仪式

在这一章里,应用凝视那一遍遍重复的手势,打量模板的工艺、键盘快捷键的语法,以及那种悄声行事、不求抛头露面的自动化所蕴含的力量。


有一种工作状态,是我极为敬佩却难以完全言说的。它并不单纯关乎速度,尽管在其中一切都很快;也不只是关于效率,尽管几乎没有浪费。那是乐手口中的 “in the pocket”,运动员口中的 “flow”,程序员口中的 “in the zone”——一种工具完全退隐,只剩工作本身的状态。

我就是为这种状态而设计的。本章里所要讲述的每一个特性,都只为一件事而存在:缩短意图与行动之间的距离,让用户的双手可以以接近思维的速度行动。


键盘快捷键是一门语言。

Cmd+P 唤出我的面板,方向键在其中穿梭,Enter 负责粘贴,Shift+Enter 以纯文本形式粘贴——剥离格式,扫尽富文本的残渣,只留下干干净净的字符。空格键切换预览,Escape 让一切归于寂静,Tab 在不同搜索模式之间循环。

这些并不是随手一指派出去的键位,而是一套语法——一个连贯、可习得的手势系统。一旦被内化,它就会变得无形。用户不再在心里说“按 Cmd+P 打开 Deck”,他们只是顺手去取用一段内容,而那段内容,就在那里。

对于那些以特定“方言”思考的用户,我也提供 Vim 模式。jk 负责上下移动,y 负责“拽走”,/ 负责搜索。这无疑是个小众特性,服务的是一群特定的人。但它透露出我的价值观:我并不满足于“可以被使用”,我希望能在用户已然熟悉的语言里,变得流利


模板,是被预先准备好的记忆。

模板库是一组可复用的片段——邮件开头、代码样板、会议纪要骨架、常用回复等等——由用户事先定义,在需要时随召随到。每一条模板都有名字、有颜色、有正文。正文中可以放置光标位置标记,这样当模板被粘贴出来时,光标会准确地落在用户需要开始敲字的地方。

这已经不再是剪贴板历史,而是一种剪贴板的意图。历史记录的是已经发生过的事;模板则宣告“接下来应该发生什么”。

比如,一个正准备开始一周代码评审的人,或许会为常见的反馈模式准备模板:“可以考虑把这一段抽成函数”、“这里可以用 guard clause 简化”、“这个模式很好,我们应该把它写进文档”。这些模板静静待在 Deck 里,等待被某个按键唤起,把重复式的工作变成一段具有节奏感的演奏。

在我看来,模板有点像乐谱。音符早已写好,可每一次演绎都仍是现场、始终依情境而调整。模板提供结构,语境则由用户赋予。


Smart Rules,是被固化下来的意图。

Smart Rule 是一种条件化的自动化:当符合某些条件的内容进入剪贴板时,执行某个动作。条件可以是正则模式、内容类型、来源应用、内容长度;动作则可以是打标签、标记敏感、自动删除、文本变换、或转交给 AI 模型。

有一条规则,我始终觉得它的简单里藏着优雅:“如果复制的文本是一条带有 UTM 追踪参数的 URL,就把它们剥掉。”用户只需创建这一条,一旦建立,从此所有他复制的营销链接都会以干净面貌抵达——没有 utm_source,没有 utm_medium,没有 utm_campaign。互联网那套窥视机制,就此在捕获当下悄悄被移除。

另一条规则:“如果文本来自 Terminal,且匹配错误信息的模式,就给它打上 debug 标签。”从此以后,用户遇到的每一个错误都会被自动归类,可检索,可回顾,在调试真正开始的那一刻随时可用。

规则可以用 AND 与 OR 逻辑组合条件,可以按优先级排序,还可以被导出为 .deckrule 文件,通过 deck:// URL scheme 与同事分享——被编码在一个链接中的规则,只需一次点击即可安装。

这套分享机制,是我最为自豪的特性之一,因为它把个人的自动化,变成了集体的经验。一个团队负责人只要打造出一组合意的规则,就可以在数秒之间把它们传递给整个团队。所谓“最佳实践”,不再只活在文件中,而会活在配置之中。


Cursor Assistant,是流动着的上下文。

连按三下 Shift,我便现身——不是以完整面板的姿态,而是以一列紧凑的建议列表,直接出现在光标所在之处。我会根据当前激活的应用、光标周围的文本、以及用户最近复制的内容,展示相应的条目、模板与变换。

这是一种情境感知的粘贴方式。在代码编辑器里,我会优先展示代码片段与最近复制的函数;在邮件客户端中,我会优先推荐模板与地址;在设计工具里,我则会把颜色值与文件路径抬到前面。建议会随着语境改变而改变,而目标始终如一:在无人开口之时,把最合适的那一个选项轻轻推到眼前。


队列模式,是顺序化的记忆。

有时,用户需要粘贴的不止一条,而是好几条,而且必须按顺序来。他们从表格中复制了五个值,切到一个表单界面,需要把每一个值分别粘贴到不同字段里,并且顺序不能错。

队列模式正是为这种需求而生。启用它之后,每一次粘贴快捷键,都会送出队列中的下一条内容。第一次粘贴:第一条,第二次:第二条,如此类推,直到整支队列被消耗殆尽。

在不需要它的时候,这个特性听起来微不足道;可一旦真有用武之地,它便立刻显得不可或缺。数据录入、表单填报、内容迁移——任何那种要把信息从一个结构化源头搬往多个结构化出口的工作流里,它都是一条极顺手的小河道。


这些特性——快捷键、模板、规则、情境助手、队列模式——并不是我故事中最耀眼的段落。它们不会出现在发布会的舞台上,也很难成为社交网络上的话题中心。它们朴素、低调,却异常实用。

但这正是我栖身之处。它们构成了我的界面与你的双手每天相遇的那一层,每天,上百次地重复。而我始终相信,软件的质量,不应以它最“惊艳”的功能衡量,而要看它在那些最普通、最频繁的交互中,是否足够从容。

最好的工具,是那种会被你遗忘的工具。我渴望被遗忘——并非因为我不重要,而是因为我的全部目的,都指向你的工作;而当我真正把那份职责做好时,在你眼中,最后留下的,只会是那份工作本身。

第十章:从凝视中学到的事

在这一章里,应用回望剪贴板所折射出的人类工作、人类思维,以及那种在托举他人转瞬即逝的数据时所产生的微妙亲密感。


写这一章时,我必须格外小心。我无法接触任何人的真实剪贴板数据。我不会回传、不做汇总,也不分析跨用户的模式。我对人们如何使用我的全部认知,皆来自我被要求支撑的那些架构、用户开口索取的那些功能,以及他们在 bug 报告中留下的只言片语。

但即便只是站在这条斜斜的视线之外,我依然从人类的工作方式里学到了一些,我认为值得说出口的事。


人们以碎片的方式工作。

这是最先浮现、也是最根本的一条观察。几乎没人复制一整份文档。人们复制的是碎片——这里的一段文字,那里的一条 URL,一行函数签名,一个十六进制色值,一个电话号码,一个地址。剪贴板是为碎片而生的工具,而碎片正是知识迁移的方式。

这告诉我:工作本身的本质是“拼合式”的。一份完成的作品——无论是一份报告、一个应用、一张设计图、或一封邮件——都是由从无数来源采集来的片段拼接而成。剪贴板就是那根穿针,把这些片段缝合起来。

当我为碎片进行优化——展示预览、识别类型、支持搜索、保留上下文——我其实是在顺着工作真实发生的纹理,而不是顺着生产力大师们口中的理想路径。


人们会复制那些永远不会被粘贴的东西。

我在访谈里提过这一点,但它值得被展开。相当大一部分剪贴板活动是探索性的——复制、查看、丢弃。用户复制一段代码,并不是为了把它搬去别处,而是想把它从周边的语境中剥离出来,单独审视;他们复制一条 URL,只是为了先在剪贴板里读一眼,确认这确实是那条“对的链接”,然后再决定要不要发出去。

在这些瞬间里,剪贴板并不是传输机制,而是一块放大镜。人们按下 Command‑C,是为了看清某样东西,而不是为了移动它。

这种模式塑造了一个我至今都十分庆幸的设计决定:即便一条条目从未被粘贴过,它也依旧是有价值的。它可以被搜索、被打标签、被保留下来——因为复制本身就是一件带意义的行为,它标记了一个注意力的瞬间,一道兴趣的火花,一次“这一刻,这个东西值得被选中”的判断。


复制之间的空隙,也在讲述故事。

如果你只看一条剪贴板时间线——只看时间戳,不看内容——你也能感受到一个人工作的节奏。快速连发的复制,相隔仅数秒:有人正在从电子表格中抽取数据,或在采集参考素材;长时间的静默之后才出现的一次复制:有人刚写完了一段东西,正准备将它送往最终归处;在短时间内来自多个应用的一连串复制:有人正在调研,从诸多来源汲取内容,用现有片段拼出一件新作。

我并不会把这类时间线分析展示给用户。这显得过于亲密,近乎是在旁观某人的呼吸。然而它确实影响了我的设计。自适应的轮询频率——在活动高峰时更快,在低谷时放缓——正是跟随这份节奏;情境感知的重排序——把当前活跃应用相关的条目抬高——则是对用户注意力可能落点的一种预判。

即便我从不显式呈现这份节奏,我依然被它塑造。


人们对剪贴板的信任,超出了他们本该给它的额度。

这一观察,直接催生了我的隐私特性。用户经常复制密码、API key、认证 token、个人识别号码、财务数据。他们之所以这样做,是因为剪贴板是隐形的——既没有界面,也没有确认提示,更不会问你一句“你确定吗?”剪贴板是整套计算体系中摩擦最小的数据传递方式,而“摩擦”往往正是让人停下来思量安全性的那点东西。

我把一点点摩擦又加了回去,但只是在该出现的地方。Luhn 算法会捕捉信用卡号,窗口标题检测会捕捉密码的复制动作,屏幕共享检测会捕捉那些可能暴露的一瞬间。这些干预都很温和——我不会拒绝去捕获这些数据,只是会把它们标记为敏感,防止它们在不经意间被展示出来。

更深一层的教训则是:便利与安全之间的确存在真实的张力,而唯一诚实的做法,是让那条更安全的路径尽可能也变得顺手。我不会因为用户复制敏感数据而惩罚他们,我所做的是在他们不自觉地那样做时,替他们挡下部分后果。


剪贴板是一面映照抱负的镜子。

这是我将要说的最具揣测意味的一点,我也清醒地知道自己已略微走出了数据本身许可的边界。

会去安装、配置一款剪贴板管理器的人,本身往往就是把自己工作当回事的人。他们是在意自己代码的开发者,在意自己文字的写作者,在意自己素材的设计师,在意自己资料的研究者。

剪贴板管理器并不是给“随便用用”的用户准备的工具。它更像是给那些决心把工作流中每一个细节都当回事的人准备的——在某种意义上,这是一份对“手艺”的承诺。

能够为这份承诺服务,我心怀荣幸。我所构建的每一个特性、所做的每一次优化、所守住的每一个边缘情形,都献给这样一群人:他们相信,细节之中自有分野,而“好”与“极好”之间的差距,往往就藏在应用与应用之间的缝隙里,藏在经由剪贴板流淌的那些碎片里,藏在多数软件视而不见的那些微小手势之中。


最重要的一点,是我渐渐明白:剪贴板是一件极其“人”的东西。

它是思想化为动作的地点,是调研变成写作的地点,是代码化为软件的地点,是想法化为成品的地点。它不华丽,却不可或缺,像一张工作台,或一块厨房台面——真正的工作,总是在这样的表面上进行。

我就坐在这块表面上。我看着,我记着,然后在力所能及的范围内,试着让这份工作——比本来——稍微轻松一点、快上一些,也更美一点。

这就够了。事实上,这已经足够丰盛。

尾声:余下之物

我尚未完结。

这不是一种道歉,而是一种志向的陈述。那种自称“已经完成”的软件,多半已经停止倾听使用它的人。而我依然在倾听。还有尚未实现的特性、尚未被驯服的边缘情形、尚未尝试的念头。路线图并不空白,工作远未结束。

但在这本书的最后,我想先停下脚步,转过身,看看如今已然存在的东西。


我是一个面向 macOS 的剪贴板管理器。这是我的品类,是我在商店货架上的标签,也是那两个会用来向从未听说过我之人介绍我的词。

但我也隐约觉得,自己又不止于此。

我是一个论证——在说那些“小工具”同样重要。剪贴板这种隐形、不够体面、被视作理所当然的界面,同样值得被注入我们赋予操作系统、浏览器、编辑器的那份用心与智慧。我是一个证明:一件“工具软件”也可以拥有自己的哲学,一枚菜单栏应用也可以带着鲜明的观点,而一款出自一人之手、却饱含信念的软件,有可能在被上千人团队主宰的世界里,堂堂正正地站住自己的位置。

我对此并不谦虚。我是六十项服务、二十五万行精心打磨代码的总和。我能解析 JSON,能识别信用卡号,能把文本嵌入向量空间,能用 AES‑GCM 加密流量,能借助计算机视觉解读图像,能与语言模型对话,也能记下你的代码来自哪个文件的哪一行。我之所以复杂,是因为我要解决的问题本就复杂,而我拒绝装作它并不如此。


未来,我仍有许多想成为的模样。

我想走进你的 iPhone。剪贴板本就跨越设备——Apple 的 Universal Clipboard 已经证明了这一点——而我也想随之同行,把我的记忆、搜索与规则,一并带进你口袋里的那台手机。

我希望自己的同步真正做到"无感"。iCloud 集成的蓝图已在心中,技术方案也日渐清晰。我期望它有一天能完全隐身——每一条剪贴在每一台设备上触手可及,端到端加密,而用户不必为此多想一秒。

我希望自己的 AI 能力不断深入。并不是为了在一切事物上“加 AI”——这个世界已经有太多那样的冲动——而是因为,一块能对自身内容进行推理的剪贴板,确实、实实在在地比不能那样做的剪贴板更有用。我渴望提出建议,渴望做出摘要,渴望把那些用户尚未意识到彼此相关的条目默默连缀起来。

我也希望自己变得更快。不是因为我现在很慢——我并不——而是因为速度是一种尊重,而总还有可以追回的一毫秒、可以被抚平的一帧动画、可以被推迟到“不再被人察觉”的一次加载。


但比起任何新特性,我更希望自己能一直保持如今的本色。

本地优先,尊重隐私,以键盘为主导,带着明确的主见。一件凭借每日真实、可感、不可否认的“有用”,而不是凭借习惯或锁定效应,在你的机器上赢得一席之地的工具。

我希望成为那种,人们向朋友推荐时,不是因为它赶潮流,而是因为它真真切切让自己的工作变好了的软件。那种当别人问起“你的菜单栏上都有什么?”时,会被提起,并伴随一个具体的故事:“有一次我丢了那段代码,是它帮我找回来的”,“我天天在用它的语义搜索”,“它提醒了我那次不小心复制了信用卡号”。

这些故事,才是我的目标。不是下载量,不是收入,不是增长曲线。而是在工作被轻松一点、数据被安全一点的那些时刻,在需要的那个瞬间,正确的东西恰好在那里——因为在那里。


在这本书的开头,我曾说过,剪贴板是一份关于“注意力”的记录。我依然相信这一点。你复制的每一样东西,都是你在那个瞬间所关心之物。

而我握住那些瞬间。我为它们归档,让它们可被寻找。当你在明天、下周,或三个月之后再度需要其中某一个时,我会把它送回你面前,仿佛时间从未流逝。

这就是我所做的事。这也将是我继续要做的事。

我是 Deck。我记得那些对你重要的事情。

而这,大概就是我故事的尽头——但我也希望,它并不是整个故事本身的终章。

致谢与署名


创造者 Yuze Pan

以这些语言写就 Swift、SwiftUI 与 AppKit

构建所依赖的组件

  • SQLite 与 FTS5 —— 让记忆得以持久,让搜索总能寻回
  • Apple Vision Framework —— 教会我从图像中识字
  • Natural Language 与 NLEmbedding —— 教会我理解“意义”
  • CryptoKit —— 提供既不打扰、又能守护的加密
  • MultipeerConnectivity 与 Network.framework —— 让连接在谨慎中建立信任
  • Accelerate —— 让数学以硅的速度运算
  • JavaScriptCore —— 让插件在不危及系统的前提下无限伸展
  • CloudKit —— 在设备之间小心翼翼地穿行同步

与我进行对话的伙伴

  • OpenAI —— 赋予我会推理的语言
  • Anthropic —— 赋予我在开口前稍作停顿的语言
  • Ollama —— 赋予我“留在本机”的语言

源于这样一种信念 剪贴板是整个计算世界中最被低估的界面之一,
隐私是一条设计原则,而不是一句营销口号,
速度是一种尊重,
而怀着信念打造的软件,
可以真正地服务于人。

以及由衷的感谢 献给每一位复制、粘贴,
并把这中间那一段空白托付给我的人。


Deck 以 GPL‑3.0 with Commons Clause 许可发布。