Vibing a Non-Trivial Ghostty Feature — Mitchell Hashimoto, October 11, 2025.
《Vibe Coding 一个不简单的 Ghostty 特性》——Mitchell Hashimoto(HashiCorp 联创、Ghostty 作者),2025 年 10 月 11 日。
I recently shipped a [non-trivial Ghostty feature (unobtrusive macOS automatic updates)](https://github.com/ghostty-org/ghostty/pull/9116) that was largely developed with AI.
我最近交付了一个[不简单的 Ghostty 特性(无干扰的 macOS 自动更新)](https://github.com/ghostty-org/ghostty/pull/9116),它主要由 AI 开发完成。
I'm regularly asked to share non-trivial examples of how I use AI and agentic coding tools and this felt like a golden opportunity to walk through my process with a well-scoped, non-trivial, real-world, shipping feature¹.
常有人请我分享一些「不简单」的例子,讲讲我怎么用 AI 与 agentic 编码工具;这次正是个黄金机会——围绕一个范围清晰、不简单、真实世界、真正上线了的特性¹,完整走一遍我的流程。
This post will share every single agentic coding session I had on the path to shipping this feature, unedited and in full. Alongside it, I'll provide some additional context about my process and reasoning. And yes, I'll also share the token cost for those curious about that, too.
这篇文章会分享我交付这个特性路上的每一个 agentic 编码会话,未剪裁、全文公开。同时我会补充一些关于我的流程与思考的背景。是的,我也会公布 token 花费,给好奇的人。
Important: there is a lot of human coding, too. I almost always go in after an AI does work and iterate myself for awhile, too. Rather than say that at every turn, I'm just saying it once here. Therefore, you may see some discrepancies between what the AI produced and what ended up in the final code. This is intentional and I believe good AI drivers are experts in their domains and utilize AI as an assistant, not a replacement.
重要前提:其中也有大量人工编码。AI 干完活之后,我几乎总会接着进去自己迭代一阵子。这话不在每一步重复了,这里一次说清。因此你会看到 AI 产出的东西与最终代码之间存在一些出入——这是有意的。我认为好的「AI 驾驶员」是自己领域的专家,把 AI 当助手,而不是替身。
The Feature · 特性本身
The finished feature this blog post is about is the macOS unobtrusive update notification feature. This feature shows update status within the terminal window without interrupting work by creating windows, grabbing focus, etc.
本文讲完了的特性,就是 macOS 的无干扰更新通知:在终端窗口内部显示更新状态,不靠弹窗口、抢焦点这类方式打断工作。
Let's set the stage for what lead to this feature (pun intended, as you'll see shortly). During a high-profile OpenAI keynote, a demo was rudely interrupted by a Ghostty update prompt:
说说这个特性的由来(双关,你马上会看到)。一场高调的 OpenAI 主题演讲上,一个演示被 Ghostty 的更新弹窗粗暴打断:
I wanted to ensure that never happened again². The path I decided to take was to make update notifications unobtrusive. Instead of popping up a window, the app would instead show a small, non-modal GUI element somewhere that wouldn't interrupt the user.
我想确保这种事永远不再发生²。我选的路线是把更新通知做成无干扰的:不弹窗口,而是让应用在某处显示一个小的、非模态的 GUI 元素,不打断用户。
Pre-AI Planning · 动手前的规划
So I pulled out my AI tooling. Absolutely not. I began by coming up with a rough plan of how I wanted this to work. Ghostty uses [Sparkle](https://sparkle-project.org/), an extremely popular macOS update framework. I poked around their docs and found that they support [custom UI through Obj-C protocols](https://sparkle-project.github.io/documentation/api-reference/). You have to reimplement a ton of stuff from scratch, but it is possible.
于是我掏出了 AI 工具。不,完全没有。我先自己想了一个粗略方案:Ghostty 用的是 [Sparkle](https://sparkle-project.org/),一个极流行的 macOS 更新框架。翻了它的文档,发现它[通过 Obj-C protocol 支持自定义 UI](https://sparkle-project.github.io/documentation/api-reference/)——大量东西要从零重写,但可行。
Okay, so I had a rough idea of backend. For frontend, I wasn't really sure (and it isn't my area of expertise). I had this really vague idea that it should be a little button embedded in the title bar, and I know macOS supports custom UI in the titlebar via [titlebar accessory controllers](https://developer.apple.com/documentation/appkit/nswindow/addtitlebaraccessoryviewcontroller(_:)), but beyond that I didn't have much of a sense of how it should look or feel.
好,后端有了粗略思路。前端我其实没底(那本来也不是我的专长)。只有一个非常模糊的想法:应该是嵌在标题栏里的小按钮;我知道 macOS 通过 [titlebar accessory controller](https://developer.apple.com/documentation/appkit/nswindow/addtitlebaraccessoryviewcontroller(_:)) 支持在标题栏放自定义 UI,但除此之外,它该长什么样、手感如何,我没有多少概念。
But that's enough to get started. AI is a very good prototyper, so even knowing what you don't know is useful enough to get started. I had a strong enough sense of the big picture.
但这已足够开工。AI 是非常好的原型师,所以「知道自己不知道什么」就足以起步。我对全局已经有足够强的感觉。
First Session: Prototyping the UI · 第一会话:UI 原型
Here is my [first agentic coding session](https://ampcode.com/threads/T-9fc3eb88-5aa2-45e4-8f6d-03697f53102d), with the starting prompt:
这是我的[第一个 agentic 编码会话](https://ampcode.com/threads/T-9fc3eb88-5aa2-45e4-8f6d-03697f53102d),开场提示词如下:
I want to enable custom, unobtrusive update notifications and installs by customizing SPUUserDriver. Let's start by planning the custom UI we'll need. We'll ONLY work on UI. Create a plan for creating SwiftUI views that can show the various states that are required by SPUUserDriver. I think the best place for these to show up is in the macOS window titlebars on the top-right. Create a plan to put it there. Consult the oracle.
我想通过定制 SPUUserDriver 实现自定义、无干扰的更新通知与安装。先从规划我们需要的自定义 UI 开始。我们只做 UI。请制定一个计划,创建 SwiftUI 视图来展示 SPUUserDriver 要求的各个状态。我认为这些 UI 最好的位置是 macOS 窗口标题栏的右上角。请出一个放到那里的计划。咨询一下 oracle。
Frequent question: "What is the oracle?" It is an [Amp-specific read-only subagent](https://ampcode.com/news/oracle) that uses a slower, higher-cost model that is generally better at thinking about things. I consult the oracle for all planning.
常见问题:「oracle 是什么?」它是 Amp 特有的[只读子代理](https://ampcode.com/news/oracle),用一个更慢、更贵、但一般更擅长思考的模型。所有规划我都咨询 oracle。
To start, I decided to prototype the UI first.
作为起点,我决定先做 UI 原型。
Notice here that I don't send the agent off to build the full feature. There are a couple reasons for this. First and foremost, I still don't even know what I want the UI/UX to be like, so I can't possibly coherently expect an AI to do that for me amongst other changes. Second, smaller chunks of work are easier to review, understand, and iterate on.
注意,我没有让 agent 去实现整个特性。有两个原因。首先也是最重要的:我自己都还不知道想要的 UI/UX 是什么样,不可能连贯地指望 AI 替我决定这个、还顺带其他改动。其次,更小的工作块更容易审查、理解和迭代。
Another thing to notice is that I ask it to only create a plan and not to write any code. Since this is a relatively vague request, it's important that I review a plan before it goes off and does a ton of work (and spends a ton of tokens doing it).
另一个值得注意的点:我只让它出计划,不写任何代码。这是个相对模糊的需求,所以在它闷头干一大堆活(并烧掉一大堆 token)之前,先由我审一遍计划很重要。
Tip: Creating a comprehensive plan interactively with an agent is a really important first-step for anything non-trivial. I usually also save it out (via the agent) to something like spec.md and in future sessions I can say "Consult the @spec.md and work on some task".
技巧:对任何不简单的任务,与 agent 交互式地打磨出一份全面计划都是极重要的第一步。我通常还会(让 agent)把它存成 spec.md 之类的文件,之后的会话里就可以说「参考 @spec.md,做某任务」。
The agent came up with an agreeable enough plan that I let it go ahead and implement it. You can see the rest of my conversations as I iterate on that.
agent 给出的计划足够靠谱,我让它放手去实现。你可以在会话记录里看到我迭代其余部分的对话。
The UI it made was directionally very good. It had a ton of polish issues (spacing, colors, etc.) but seeing the UI gave me the spark of inspiration I needed to know what I wanted.
它做的 UI 方向上非常好。有一堆打磨问题(间距、颜色等),但亲眼看到这个 UI,给了我那颗灵感的火花——我终于知道自己想要什么了。
Tip: I very often use AI for inspiration. In this case, I ended up keeping a lot (not all) of the UI code it made, but I will very often prompt an agent, throw away everything it did, and redo it myself (manually!). I find the "zero to one" stage of creation very difficult and time consuming and AI is excellent at being my muse.
技巧:我经常用 AI 找灵感。这次我最终保留了它做的(一部分,不是全部)UI 代码;但我也经常是给 agent 下个提示词,然后把它做的全部扔掉,自己(纯手工!)重做一遍。「从零到一」的创作阶段我觉得又难又耗时,而 AI 做我的缪斯非常出色。
Hitting a Wall · 撞墙
You can see in chats 11 to 14 that we're entering the slop zone. The code the agent created has a critical bug, and it's absolutely failing to fix it. And I have no idea how to fix it, either.
从第 11 到 14 个对话可以看到,我们正在进入 slop 区。agent 写的代码有个关键 bug,而它完全修不好。我自己也不知道怎么修。
I'll often make these few hail mary attempts to fix a bug. If the agent can figure it out, I can study it and learn myself. If it doesn't, it costs me very little. If the agent figures it out and I don't understand it, I back it out. I'm not shipping code I don't understand. While it's failing, I'm also tabbed out searching the issue and trying to figure it out myself.
遇到 bug 我常会做几次这样的孤注一掷式尝试。agent 要是搞明白了,我可以研究它的做法自己学;搞不明白,我的损失也很小。如果 agent 修好了而我看不懂,我就回滚——不理解的代码我不上线。它在失败的同时,我也切到别的标签页搜这个问题,试着靠自己搞明白。
It's at this point that I know I need to step back, review what it did, and come up with my own plans. It's time to educate myself and think critically. AI is no longer the solution; it is a liability.
到这一步我就知道:该退一步,审查它做了什么,拿出自己的方案了。该自学、该批判性思考了。AI 不再是解决方案,而是负债。
Cleanup Sessions · 清理会话
I spend the next few sessions guiding the agent to clean up the code.
接下来几个会话,我引导 agent 清理代码。
The [second session](https://ampcode.com/threads/T-cad10729-a348-4dc6-9297-e1130a12f5da) focused on moving some methods around into better places I recognized:
[第二会话](https://ampcode.com/threads/T-cad10729-a348-4dc6-9297-e1130a12f5da)聚焦把我认出来的几个方法挪到更合适的位置:
Let's move the pill background, foreground, and badge functions from @macos/Sources/Features/Update/UpdateAccessoryView.swift to @macos/Sources/Features/Update/UpdateViewModel.swift and make them more generic (background, foreground, badge)
把 pill 背景、前景、徽章三个函数从 @macos/Sources/Features/Update/UpdateAccessoryView.swift 挪到 @macos/Sources/Features/Update/UpdateViewModel.swift,并让它们更通用(background、foreground、badge)
The [third session](https://ampcode.com/threads/T-ae7a7144-6293-4bf6-af24-982b8ff09984) added documentation to the code:
[第三会话](https://ampcode.com/threads/T-ae7a7144-6293-4bf6-af24-982b8ff09984)给代码补文档:
Update the docs for @UpdateBadge.swift
更新 @UpdateBadge.swift 的文档
Tip: Adding documentation is a really important step because it helps reaffirm your own understanding of the code as well as educate future agents that may read and modify this code. I find agents do much better when they have both natural language descriptions as well as the code itself.
技巧:补文档是极重要的一步——它既帮你巩固自己对代码的理解,也教育了未来可能读到并修改这些代码的 agent。我发现让 agent 同时拿到自然语言描述和代码本身,它的表现会好得多。
The [fourth session](https://ampcode.com/threads/T-af16fe85-bdc5-4fcb-b7e7-dbc425ca5da7) moves the view model to an app-global location, since the original work put it in a window scope and update information is app scoped.
[第四会话](https://ampcode.com/threads/T-af16fe85-bdc5-4fcb-b7e7-dbc425ca5da7)把 view model 挪到应用全局的位置——最初的实现把它放在了窗口作用域,而更新信息是应用级的。
Move the update view model data to AppDelegate since the update information will be app-global.
把更新 view model 的数据挪到 AppDelegate,因为更新信息是应用全局的。
Throughout these, I am usually dropping in and making minor manual changes as well.
整个过程中,我也时常自己动手做些小修小改。
The cleanup step is really important. To cleanup effectively you have to have a pretty good understanding of the code, so this forces me to not blindly accept AI-written code. Subsequently, better organized and documented code helps future agentic sessions perform better.
清理这一步非常重要。要有效清理,你必须对代码有相当好的理解,所以它强迫我不能盲目接受 AI 写的代码。而组织得更好、文档更全的代码,又能帮助后续的 agentic 会话表现更好。
I sometimes tongue-in-cheek refer to this as the "anti-slop session".
我有时半开玩笑地管这叫「anti-slop 会话」(反 AI 垃圾会话)。
Facing "The Bug" · 直面「那个 bug」
Time to get back to that bug that I found in that initial session. I once again spent a few sessions trying to get the agent to figure this out. I start vague, and slowly get more specific with how I'd approach it.
该回头处理第一会话发现的那个 bug 了。我又花了好几个会话试图让 agent 搞定它。我从模糊开始,再慢慢给出越来越具体的思路。
First, the [vague session](https://ampcode.com/threads/T-5b64618e-3884-4bf9-87cb-e041f755279f):
先是[模糊版](https://ampcode.com/threads/T-5b64618e-3884-4bf9-87cb-e041f755279f):
For standard, native tabs, the update accesory view is not visible. It should remain visible in the titlebar of the window.
标准原生标签页下,更新 accessory view 不可见。它应当始终显示在窗口标题栏里。
Failure. Then, I [get more specific](https://ampcode.com/threads/T-0230ecf6-4d77-48da-99e8-ffeb35d3163b):
失败。接着,我[说得更具体](https://ampcode.com/threads/T-0230ecf6-4d77-48da-99e8-ffeb35d3163b):
We need to update @macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebarTahoe.swift tab bar constraints to align the right edge of the tab bar with the left edge of the update accessory view so it remains visible.
我们需要更新 @macos/Sources/Features/Terminal/Window Styles/TerminalTabsTitlebarTahoe.swift 的标签栏约束,让标签栏右缘对齐更新 accessory view 的左缘,使它保持可见。
Failure. Then, I [try a different, specific approach](https://ampcode.com/threads/T-2b5aecbe-7a48-4a4e-b5ad-c355b751042b):
失败。然后,我[换了另一个具体思路](https://ampcode.com/threads/T-2b5aecbe-7a48-4a4e-b5ad-c355b751042b):
What if we changed the @macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift to make the tab bar a top accessory view rather than a bottom one to make our tabs go into the titlebar?
如果我们改 @macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift,把标签栏从底部 accessory view 改成顶部 accessory view,让标签页进到标题栏里,会怎样?
Failure. [One last attempt](https://ampcode.com/threads/T-47dbc7c1-796a-45b4-ba21-3385cc89411a):
失败。[最后一次尝试](https://ampcode.com/threads/T-47dbc7c1-796a-45b4-ba21-3385cc89411a):
The "right accessory view" and layout of @macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift is conflictig with the update accessory view setup in @macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift Can we constrain the tab bar to always appear left of the update notice?
@macos/Sources/Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift 的「右侧 accessory view」及其布局,与 @macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift 里的更新 accessory view 设置相冲突。我们能不能约束标签栏永远出现在更新通知的左边?
Failure.
失败。
This whole time, I've also spent time trying to solve this myself through manual research and human effort. My more specific prompts are based on things I've learned through that process. But overall, it was clearly not working.
这整个过程中,我也一直在靠人工研究和动手尝试自己解这个问题。那些越来越具体的提示词,正是基于我在这个过程中学到的东西。但总体而言,很明显此路不通。
I don't think I can figure this out on my own, so I decide to pivot. I decided that for these problematic titlebar styles, let's put the update notices in the bottom-right corner of the window overlaid on top of the content view rather than in the titlebar.
我判断靠自己一时半会也解决不了,于是决定转向:对这些有问题的标题栏样式,把更新通知放到窗口右下角、覆盖在内容视图之上,而不是放进标题栏。
I need to support this anyways because Ghostty has a configuration to hide the titlebar entirely. So, even if I can solve the titlebar styling issue later, I still need to support this other mode.
反正这个模式我也得支持,因为 Ghostty 有一个把标题栏整个藏掉的配置项。所以就算标题栏样式问题以后能解,这个另外的模式我还是得做。
My [next session](https://ampcode.com/threads/T-69675325-c5e3-497a-b692-0176744069e9) moves forward with this plan with a very specific prompt:
[下一个会话](https://ampcode.com/threads/T-69675325-c5e3-497a-b692-0176744069e9)用一个很具体的提示词推进这个方案:
Augment the @macos/Sources/Features/Update system to also support an overlay approach in @macos/Sources/Features/Terminal/TerminalView.swift The update notice should appear at the bottom of the window. It should go over the text (so it doesn't resize the terminal view). All clicking behaviors should otherwise be the same as the accessory view.
扩展 @macos/Sources/Features/Update 系统,在 @macos/Sources/Features/Terminal/TerminalView.swift 里增加 overlay 方式的支持。更新通知应出现在窗口底部,覆盖在文字上方(这样就不会改变终端视图的尺寸)。其余点击行为与 accessory view 保持一致。
It did a really good job with this. I did a lot of manual polish after (moving stuff around, renaming things, etc.) but the core work was solid.
这一次它做得非常好。之后我做了大量手工打磨(挪东西、重命名等),但核心工作是扎实的。
Here is a video of the feature shortly after this session, showcasing how the update notice appears in the bottom-right corner of the window for certain titlebar styles or when the titlebar is hidden:
下面是这个会话后不久的特性视频,展示在某些标题栏样式下或标题栏隐藏时,更新通知如何出现在窗口右下角:
Starting the Backend · 后端开工
The UI was feeling good enough. I had noted a bunch of polish issues I wanted to tackle later but I wanted to move on to the backend work primarily to see if I'd discover any unknown unknowns that threw a wrench in my plans.
UI 手感已经够好了。我记下一堆以后再处理的打磨问题,然后想转去后端——主要是想看看会不会冒出什么「未知的未知」把我的计划打乱。
I manually created a file with a scaffold of incomplete functions and various TODO comments. I then started [a session to complete that work for me](https://ampcode.com/threads/T-d812d8a2-b7af-47ca-a91b-5ce3a18068b2):
我手工建了一个文件,里面是一堆未完成的函数骨架和各种 TODO 注释。然后开[一个会话让它替我补完](https://ampcode.com/threads/T-d812d8a2-b7af-47ca-a91b-5ce3a18068b2):
Complete the @macos/Sources/Features/Update/UpdateDriver.swift Read the Sparkle documentaiton as necessary to understand the functionality. https://sparkle-project.org/documentation/api-reference/Protocols.html
补完 @macos/Sources/Features/Update/UpdateDriver.swift。按需阅读 Sparkle 文档来理解相关功能。https://sparkle-project.org/documentation/api-reference/Protocols.html
Tip: AI is very good at fill-in-the-blank or draw-the-rest-of-the-owl. My pattern here of creating scaffolding with descriptive function names, parameters, todo comments, etc. is a really common one for me and it works very well.
技巧:AI 非常擅长「填空」或「把猫头鹰的另一半画完」。我这种用带描述性的函数名、参数、TODO 注释搭脚手架的模式,是我极常用的招数,效果很好。
It actually did a really bad job here and I ended up throwing all of this code away. The code it produced worked, but it was obviously the wrong approach. It conflated a lot of different concerns and the way it was storing state in the driver was clearly wrong.
结果它这次干得相当糟,我最后把这段代码全扔了。代码能跑,但方向明显不对:它把很多不同的关注点搅在一起,driver 里存状态的方式明显是错的。
When I studied what it did, I realized it was because the view model was structured in a suboptimal way, so I switched gears into cleanup mode to give AI (and humans, if I chose to write it myself) the better framework to work with.
我研究了它的做法后意识到,根源是 view model 的结构不理想。于是我切换到清理模式,给 AI(以及人类——如果我选择自己写)一个更好的框架去工作。
Cleanup Again, Big Time · 再次大清理
Experience has taught me that the cleanliness of a UI frontend and business logic backend is often dictated by the quality of the view model in between. So I spent some time manually restructured the view model. This involved switching to a tagged union rather than the struct with a bunch of optionals. I renamed some types, moved stuff around.
经验教会我:UI 前端和业务逻辑后端的整洁程度,往往由中间那层 view model 的质量决定。所以我花了些时间手工重构 view model:把「一堆 optional 的 struct」换成 tagged union(标签联合),重命名了几个类型,挪动了些东西。
I knew from experience that this small bit of manual work in the middle would set the agents up for success in future sessions for both the frontend and backend. After completing it, I continued with a marathon set of cleanup sessions.
经验还告诉我,中间这层的小块手工活,会为后续前端和后端的 agent 会话铺平成功之路。做完之后,我继续来了一轮马拉松式清理会话。
After doing the restructuring, the first thing I did was [ask the agent to once again complete the owl](https://ampcode.com/threads/T-29c43cfa-5b00-420d-9490-ce16cea29103), this time inspecting my changes and updating dependent code to the new style and removing the old:
重构完成后,我做的第一件事是[再让 agent 画完那只猫头鹰](https://ampcode.com/threads/T-29c43cfa-5b00-420d-9490-ce16cea29103):这次是审视我的改动,把依赖代码更新到新风格、删掉旧的:
Update @macos/Sources/Features/Update/UpdateViewModel.swift to use the new UpdateState exclusively. Rename state2 to state (remove the old state).
让 @macos/Sources/Features/Update/UpdateViewModel.swift 只使用新的 UpdateState。把 state2 改名为 state(删掉旧的 state)。
Then I asked it [remove additional dead code](https://ampcode.com/threads/T-5c8f7208-fcad-4f62-a212-65c7c2d61346):
接着让它[删除更多死代码](https://ampcode.com/threads/T-5c8f7208-fcad-4f62-a212-65c7c2d61346):
I think we can get rid of UpdateUIActions. They're not used anymore since our UpdateState has callbacks.
我觉得可以干掉 UpdateUIActions 了。我们的 UpdateState 已经有回调,它们不再被使用。
Then I broke the build myself cleaning up some things. I had a meeting to hop onto so I decided to [let the agent fix it while I was busy](https://ampcode.com/threads/T-214e0715-3fbb-42ee-9ad7-bc3ded364f36):
然后我在清理时亲手把构建搞坏了。正好要赶一个会,我决定[让我忙的时候 agent 去修](https://ampcode.com/threads/T-214e0715-3fbb-42ee-9ad7-bc3ded364f36):
Run the build and fix the errors
跑构建,修掉报错
Tip: "I broke a bunch of things, please fix my mess." is another frequent use case I have for agents. I'd say generally this fits within the same fill-in-the-blank pattern as before.
技巧:「我搞坏了一堆东西,请帮我收拾烂摊子」是我对 agent 的另一类高频用法。总体上这仍属于前面那个填空模式。
Later, I started doing [some refactors on some of the views again](https://ampcode.com/threads/T-3ff3649d-4610-4bbe-8c2a-7875b03e9a5d):
后来,我又开始[对一些视图做重构](https://ampcode.com/threads/T-3ff3649d-4610-4bbe-8c2a-7875b03e9a5d):
Turn each @macos/Sources/Features/Update/UpdatePopoverView.swift case into a dedicated fileprivate Swift view that takes the typed value as its parameter so that we can remove the guards.
把 @macos/Sources/Features/Update/UpdatePopoverView.swift 的每个 case 各自变成一个专用的 fileprivate Swift 视图,接受带类型的值作为参数,这样我们就能去掉那些 guard。
[More cleanup](https://ampcode.com/threads/T-0477a9aa-e1ff-4df0-bf16-eed5131b6c14):
[继续清理](https://ampcode.com/threads/T-0477a9aa-e1ff-4df0-bf16-eed5131b6c14):
Change iconName in @macos/Sources/Features/Update/UpdateViewModel.swift to be an optional, return nil for blank. Update usage.
把 @macos/Sources/Features/Update/UpdateViewModel.swift 的 iconName 改成 optional,空白时返回 nil。同步更新所有用到的位置。
Simulation · 模拟
In my first UI session, I had the agent create some demo code so I could see the UI in action without real update checks. But update flows have a number of scenarios and up to this point I only tested the happy path.
在第一个 UI 会话里,我让 agent 写了一些演示代码,不用真的检查更新也能看到 UI 运行。但更新流程有一堆场景,到这里为止我只测过 happy path(一切顺利的主路径)。
In my [next session](https://ampcode.com/threads/T-a2faf4af-c67b-47a5-99d1-b1a0f957cafb) I extracted the simulation code into a dedicated file and asked the agent to create more scenarios:
[下一个会话](https://ampcode.com/threads/T-a2faf4af-c67b-47a5-99d1-b1a0f957cafb)里,我把模拟代码抽到专门文件,并让 agent 创造更多场景:
Extract the update simulation code in @macos/Sources/App/macOS/AppDelegate.swift into a dedicated file in @macos/Sources/Features/Update . This should contain multiple simulation scenarios (happy path, not found, errors, etc.) so that we can easily try different demos.
把 @macos/Sources/App/macOS/AppDelegate.swift 里的更新模拟代码抽到 @macos/Sources/Features/Update 下的专门文件。里面应有多个模拟场景(happy path、无更新、错误等),方便我们尝试不同演示。
Tip: Agents are great at generating tests and simulations. The simulation code specifically that it generated here is honestly pretty gross but it works and it's not part of the release binary so the quality doesn't matter to me. I didn't even bother cleaning it up beyond the basics you can see in the session.
技巧:agent 特别擅长生成测试与模拟。它这里生成的模拟代码说实话挺丑,但能用,而且不进发布二进制,质量如何我不在乎。除了会话里可见的基础清理,我连多收拾一下都懒得。
I then ran the various simulations and found a number of UX improvements.
然后我跑了各种模拟,发现了一批 UX 改进点。
- [Cleaning up the "No Update Found" state](https://ampcode.com/threads/T-01583a65-e9c8-457d-bf27-603b0297bb8d)
- [清理「未发现更新」状态](https://ampcode.com/threads/T-01583a65-e9c8-457d-bf27-603b0297bb8d)
- [Reset state on confirming error](https://ampcode.com/threads/T-e5a5cd11-c024-4562-8afc-8d1574b32a87)
- [确认错误后重置状态](https://ampcode.com/threads/T-e5a5cd11-c024-4562-8afc-8d1574b32a87)
- [Copy and style changes for permission request state](https://ampcode.com/threads/T-f41a9020-e665-4d67-aa7a-d2e96124ad8c)
- [权限请求状态的文案与样式调整](https://ampcode.com/threads/T-f41a9020-e665-4d67-aa7a-d2e96124ad8c)
Last Mile · 最后一公里
At this point, I had a working backend and frontend and I needed to hook it all up.
到这一步,能用的前后端都有了,需要把它们接起来。
My [next session](https://ampcode.com/threads/T-6968a022-f1a2-4011-806a-1060e8e8fd81) prompted the agent to do this:
[下一个会话](https://ampcode.com/threads/T-6968a022-f1a2-4011-806a-1060e8e8fd81)让 agent 来干这个:
Make an UpdateController class the same as https://github.com/sparkle-project/Sparkle/blob/2.x/Sparkle/SPUStandardUpdaterController.m but for our updater types.
仿照 https://github.com/sparkle-project/Sparkle/blob/2.x/Sparkle/SPUStandardUpdaterController.m 做一个 UpdateController 类,但用我们自己的 updater 类型。
This required some back and forth and manual polish, but it got there.
这中间有些来回和手工打磨,但最终到位了。
I then made [some minor improvements](https://ampcode.com/threads/T-fce4f9e0-7bb4-4930-b2b1-34fd600bc242):
然后我做了[一些小改进](https://ampcode.com/threads/T-fce4f9e0-7bb4-4930-b2b1-34fd600bc242):
For our update available state with the appcast, look at https://sparkle-project.org/documentation/api-reference/Classes/SUAppcastItem.html and show other relevant metadata if its set. For example, content length for size.
对于 appcast 的「有可用更新」状态,参考 https://sparkle-project.org/documentation/api-reference/Classes/SUAppcastItem.html ,把其他已设置的元数据也展示出来。比如用 content length 显示大小。
Anything Else? · 还有遗漏吗?
My [last prompt to an agent](https://ampcode.com/threads/T-e4cc70f0-d222-4c40-a1e7-745025e3dc9c) is always to ask what else I might be missing. I do this regardless of if I manually wrote the code myself or not.
我对 agent 的[最后一条提示词](https://ampcode.com/threads/T-e4cc70f0-d222-4c40-a1e7-745025e3dc9c)永远是问:还有什么我可能漏掉的。无论代码是不是我手写的,我都会这么做。
Are there any other improvements you can see to be made with the @macos/Sources/Features/Update feature? Don't write any code. Consult the oracle. Consider parts of the code that can also get more unit tests added.
你觉得 @macos/Sources/Features/Update 特性还有哪些可以改进的地方?不要写任何代码。咨询 oracle。考虑一下哪些部分还可以补更多单元测试。
This highlighted some real issues, so I went ahead and asked it to implement them. I find its easier to tell the agent "okay just do it all" rather than ask it specific things to do, since I can always easily clean it up in selective commits later.
它指出了一些真问题,于是我让它直接实现。我发现与其一条条点名,不如直接告诉 agent「好,全做了」更省事——反正之后随时可以用选择性提交轻松收拾。
An amusing thing in this session is that the agent started going down a really crazy rabbit hole and so I stepped in to stop it:
这个会话里有趣的一幕:agent 钻进了一条离谱的兔子洞,我只好出手叫停:
Stop stop stop. Undo all the main actor stuff.
停停停。把所有 main actor 相关的改动全部撤销。
I also noticed that it did something rather poorly when there is a better way:
我还发现它有个地方做得很差,而明明有更好的做法:
For the error message, instead of truncating, isn't there a SwiftUI standard way to do this? We should add an additional UI element they can use to view the whole message.
错误信息这里,与其截断,SwiftUI 不是有标准做法吗?我们应该加一个额外的 UI 元素让用户能查看完整信息。
Cost and Time · 成本与时间
The work took a total of 16 separate sessions totalling $15.98 in token spend on Amp³. I won't try to speculate whether this is expensive or cheap in general, but I will say for me personally I spent more than that in coffee shops in the two calendar days I spent on this feature.
这项工作总共 16 个独立会话,在 Amp 上花了 $15.98 的 token 费³。我不想猜测这总体上是贵还是便宜,但可以说:就在做这个特性的那两个自然日里,我个人在咖啡馆花的钱都不止这个数。
The total "wall clock" time I spent on this feature I estimate around 8 hours. I only spend around 4 hours a day on the computer. The first and last commit span 3 days. But I also didn't spend all of my time on this feature. For example, I [shipped a Ghostty update](https://ghostty.org/docs/install/release-notes/1-2-2), was a guest on [ThePrimeagen](https://www.twitch.tv/theprimeagen) for an hour, and [gave a guest presentation at Zoo's annual all-hands](https://x.com/adam_chal/status/1976527358099337325), all during the hours I have available for "computer work" during the same days I was working on this feature⁴. So, I think the 8 hour estimate is generous.
我估算了下,花在这个特性上的总「墙钟」时间约 8 小时。我每天只有约 4 小时在电脑上,首次提交到最后一次提交跨了 3 天。而且这段时间我也没全花在这个特性上:比如我还[发了一个 Ghostty 更新](https://ghostty.org/docs/install/release-notes/1-2-2)、上 [ThePrimeagen](https://www.twitch.tv/theprimeagen) 的节目当了一小时嘉宾、[在 Zoo 的年度全员会上做了嘉宾演讲](https://x.com/adam_chal/status/1976527358099337325)——这些都发生在我做这个特性那几天的「电脑时间」里⁴。所以我觉得 8 小时这个估计已经算宽松了。
Many people on the internet argue whether AI enables you to work faster or not. In this case, I think I shipped this faster than I would have if I had done it all myself, in particular because iterating on minor SwiftUI styling is so tedious and time consuming for me personally and AI does it so well.
网上很多人争论 AI 到底能不能让你更快。就这件事而言,我觉得比全靠自己动手要快——尤其 SwiftUI 样式的细碎迭代对我个人来说极其繁琐耗时,而 AI 做这个特别在行。
I think the faster/slower argument for me personally is missing the thing I like the most: the AI can work for me while I step away to do other things. Here is a picture I took of one of my cleanup sessions while I was making breakfast for my family:
而且我觉得「更快还是更慢」的争论,漏掉了我最喜欢的一点:我走开去做别的事时,AI 能替我干活。下面这张照片,是我在给家人做早餐时拍的——当时一个清理会话正在跑:
There's all sorts of dismissals about this, such as "I don't want to be coding while I'm cooking" or "Be more present" or whatever. If that's how you want to be, that's fine. In my case, in this specific example, I'm the first person awake in my household and I prep breakfast while everyone else is still asleep. I don't do this every waking moment.
对此有很多不以为然的说法,比如「我不想一边做饭一边写代码」「活在当下一点」之类。你想那样活,没问题。就我这个具体例子而言:我是全家最早醒的人,趁大家都还没起床我把早餐备好。我并不是每个清醒时刻都这么干。
All this is to say: it works for me. I'm really, really not trying to convince you. I'm not associated financially with any AI company. But as someone who has a lot of success with AI tooling and likes to talk about it, people ask me to share examples all the time. That's what I'm doing here.
说这些是想说:这套东西对我有效。我真的、真的不想说服你。我和任何 AI 公司都没有经济关联。但作为一个用 AI 工具用得很顺、也乐于聊这个的人,总有人请我分享实例。这就是我在做的事。
End · 结语
I think the feature is beautiful, works great, and after a final manual review I merged it⁵. For Ghostty users who are on the tip release, it is available now. For Ghostty users who are on tagged releases, this feature will be available in Ghostty 1.3.
我认为这个特性很漂亮、运行良好,经过最后一轮人工终审后我把它合并了⁵。用 tip 版(跟踪主干)的 Ghostty 用户现在就能用上;用正式标签版的用户,这个特性将随 Ghostty 1.3 发布。
I'm an outspoken advocate about the importance of sharing agentic coding sessions publicly⁶, with one of the reasons being that it's an incredibly powerful way to educate others about how to use these tools effectively. I hope this post helps demonstrate that.
我一直高声倡导公开分享 agentic 编码会话的重要性⁶,原因之一是:这是教别人有效使用这些工具的极有力方式。希望这篇文章有助于证明这一点。
Footnotes · 脚注
¹ It is currently only shipped to nightly beta testers ("tip" testers) but that is a group of thousands of people. It is merged and will be in the next stable Ghostty release.
¹ 它目前只推送给 nightly 公测用户(「tip」测试者),但那也是数千人的群体。它已合并,将进入下一个 Ghostty 稳定版。
² Yes, it was good marketing. No, it was not on purpose. And no, I'm not celebrating it, because you don't want users to fear a tool will take advantage of them. You want to trust that it's there to help. I want presenters (or anyone, really) to want to use Ghostty, and caring about stuff like this is part of that.
² 是的,这是很好的营销。不,这不是故意的。也不,我不为此庆祝——你不能让用户担心工具会乘人之危,你要让他们相信它是来帮忙的。我希望演讲者(其实任何人都是)愿意用 Ghostty,在乎这类事情正是其中一部分。
³ The most poetic though would've been to use OpenAI Codex for this. I'm sure it would've done great! This post isn't an advertisement for Amp, it just happens to be the agentic coding tool I use the most currently.
³ 最有诗意的其实是用 OpenAI Codex 来做这个。我确信它会干得很棒!本文不是 Amp 的广告,它只是碰巧是我当前用得最多的 agentic 编码工具。
⁴ I have a toddler at home so my "computer time" is very scheduled and very limited. 😁
⁴ 我家里有个刚会走路的娃,所以我的「电脑时间」非常规律也非常有限。😁
⁵ "Final manual review" is super super super important. This probably shouldn't be a footnote, but I couldn't find a better place to emphasize it. Please don't ever ship AI-written code without a thorough manual review.
⁵ 「最终人工审查」超级超级超级重要。这本不该是条脚注,只是我找不到更好的位置来强调。请千万不要在没有彻底人工审查的情况下上线 AI 写的代码。
⁶ My reasoning for this could also be its own blog post, so I'm going to avoid explaining myself further here beyond what I already have.
⁶ 我这么想的理由本身都够再写一篇博客了,所以在已经说过的之外,这里就不再展开。