技术速递|上下文窗口、Plan Agent 与 TDD:我在使用 GitHub Copilot 构建倒计时应用时的经验总结
了解我是如何管理上下文以保持 Copilot 的专注度,如何借助 Plan Agent 将模糊需求梳理清晰,以及如何通过强制采用测试驱动开发(TDD)实践,在问题到达用户之前捕获缺陷。
作者:Chris Reddington
排版:Alan Wang
了解我是如何管理上下文以保持 Copilot 的专注度,如何借助 Plan Agent 将模糊需求梳理清晰,以及如何通过强制采用测试驱动开发(TDD)实践,在问题到达用户之前捕获缺陷。
在 2025 年最后一期 Rubber Duck Thursdays 直播中,我想做点更有庆祝意味的东西——一个能够体现 Rubber Duck Thursdays 精神的项目:一起构建、从错误中学习,并庆祝来自世界各地收看直播的每一位朋友。
在这个过程中,我也总结了一些与 AI 协作的实用模式,无论你是在做一个倒计时应用,还是完全不同的项目,都可以直接借鉴。从管理上下文窗口、避免对话变得杂乱,到使用 Plan Agent 来梳理和澄清需求,再到借助 Copilot 的测试驱动开发捕捉各种边界情况。当然还有一件事是我意料之外的——世界地图远比看起来复杂得多 👀。
从简单开始:基础倒计时
倒计时器本身是一个非常直观的概念:天数倒计到小时,小时倒计到分钟,分钟再倒计到秒。但有时候,正是这些简单的想法,反而能激发我们最大的创造力。我决定以此为契机,尝试用 Copilot + 规格/需求驱动的方式 来构建一个倒计时应用——一个能唤起期待感、并在跨年时刻绽放烟花的应用。
💡 什么是规格驱动开发?
与“先写代码、再补文档”不同,规格驱动开发,顾名思义,是从规格开始。规格就像一份契约,定义了代码应该如何表现,并成为工具和 AI Agent 在生成、测试和验证代码时所依赖的唯一事实来源。这样做的结果是:更少的猜测、更少的意外、更高质量的代码。
幸运的是,软件开发本身就是一个迭代过程,而这次直播也完全拥抱了这一点。有些需求从一开始就很清晰,而另一些则是在直播过程中,随着观众的建议不断演进。像 Plan agent 这样的自定义 agent,帮助我弥合了这个差距——把模糊的想法转化为我可以直接执行的结构化计划。所以,让我们从最开始做起:先把项目搭起来。
我使用 GitHub Copilot 生成了一个新的工作区,并且给了它一个非常具体的 prompt。这个 prompt 说明了我们要构建一个倒计时应用,并且会使用 Vite、TypeScript 和 Tailwind CSS v4。同时还描述了一些需求,比如:深色主题、居中布局、带有细微动画的大号粗体数字,默认目标时间是 2026 年 1 月的午夜,并且预留了一些可定制空间。
#new
1. Create a new workspace for a New Year countdown app using Vite, TypeScript, and Tailwind CSS v4.
**Setup requirements:**
- Use the @tailwindcss/vite plugin (Tailwind v4 style)
- Dark theme by default (zinc-900 background)
- Centered layout with the countdown as the hero element
**Countdown functionality:**
Create a `countdown.ts` module with:
- A `CountdownTarget` type that has `{ name: string, date: Date }` so we can later customize what we're counting down to
- A `getTimeRemaining(target: Date)` function returning `{ days, hours, minutes, seconds, total }`
- A `formatTimeUnit(n: number)` helper that zero-pads to 2 digits
- Default target: midnight on January 1st of NEXT year (calculate dynamically from current date)
**Display:**
- Large, bold countdown digits (use tabular-nums for stable width)
- Labels under each unit (Days, Hours, Minutes, Seconds)
- Subtle animation when digits change (CSS transition)
- Below the countdown, show: "until [target.name]" (e.g., "until 2026")
**Architecture:**
- `src/countdown.ts` - pure logic, no DOM
- `src/main.ts` - sets up the interval and updates the DOM
- Use `requestAnimationFrame` or `setInterval` at 1 second intervals
- Export types so they're reusable
Keep it simple and clean—this is the foundation we'll build themes on top of.
我非常喜欢 “generate new workspace” 这个功能的一点是:Copilot 自动为我生成了自定义指令文件,把我的需求完整记录下来——包括倒计时应用、Vite、TypeScript 和深色主题。在写下第一行代码之前,一切就已经被文档化了。
几分钟之内,我就得到了一个可用的倒计时。天、小时、分钟、秒,一起向 2026 倒数。功能是对的,但视觉上并不惊艳。公平地说,这也怪我——我在最初的 prompt 里并没有指定任何设计或主题偏好。所以,是时候进入下一轮迭代了。
来自社区的一条建议改变了方向
在直播过程中,观众来自印度、尼日利亚、意大利、美国……世界各地的开发者聚在一起学习。这时,聊天室里有人提出了一个建议,直接改变了我们接下来要做的事:那时区怎么办?
这并不是我一开始计划在直播中实现的需求,所以我也没有一个清晰的方案。也许可以有一个可以旋转的地球来选择时区?或者一个带“时间旅行”主题的世界地图?可能性太多了,而我的需求又非常模糊。于是,我把这个问题交给了 Plan agent。
Plan agent:那些我没想到要问的问题
最近我在有意识地更多使用 Plan agent,尤其是在我感觉需求还不够明确的时候。Plan agent 并不是直接基于初始 prompt 给出一个方案,而是会通过澄清性问题,暴露你可能忽略的边界情况。
我把我的想法告诉它:交互式时区选择器、时间旅行主题、切换时区时有动画、也许用世界地图。Plan agent 的问题让我开始真正思考:
| 问题 | 为什么重要 |
|---|---|
| 圆形拨盘是主元素,世界地图是辅助,还是反过来? | 我还没决定视觉层级 |
| 移动端怎么处理?下拉框兜底,还是可触摸滚动? | 我当时只考虑了桌面端 |
| 当某个时区已经过了午夜,是显示“已经在庆祝”,还是显示距离午夜过去了多久? | 我想要的是庆祝,而不是反向倒计时 |
| 旋转拨盘时是否有音效反馈,还是仅视觉效果? | 引入音效会扩大范围,但可能是未来需求 |
这正是以这种方式与 AI 协作的魅力所在。Plan agent 会提出 A 或 B 的选项,但当你真正思考时,往往会发现答案在中间。
比如,在第二轮需求中,Plan agent 问烟花应该是持续播放、只爆一次,还是轻微循环。我回答说这里可能涉及性能考量,应该取一个中间值。我们还让直播观众投票,决定是用拨盘还是地图——地图胜出,于是我们转向以世界地图作为主选择器,并突出 8 个重点城市。
上下文窗口管理:只保留你真正需要的内容
在开始实现之前,我刻意新开了一个对话。
之前的上下文(工作区创建、基础倒计时逻辑)已经不再需要了,而真正有用的内容已经写进了自定义指令文件。在使用 AI 工具时,上下文窗口是非常宝贵的资源。无关的历史信息只会让对话变得混乱、削弱焦点。所以我清空了上下文,只带入三样东西:
-
新的需求
-
Plan agent 的输出(我让 Copilot 写成了单独的 Markdown 文件)
-
对“时区”的全新关注点
我还复用了自己另一个项目中的一些自定义指令文件、自定义 agent 和 prompt 文件,用来引导 Copilot,并引入更专业的 agent,例如 UI Performance Specialist agent。
💡 你知道吗?GitHub Copilot 的自定义 agent 允许你为不同的开发任务创建专门的角色。我在直播中构建的 UI Performance Specialist agent 只是一个例子。你也可以创建用于安全审查、架构规划,或任何角色化工作流的 agent。awesome-copilot 仓库里已经有不少示例。
实现阶段:模块化、TDD,以及那张地图
Plan agent 完成工作后,我切换到了 UI Performance Specialist agent,让它基于专业经验审查方案,并给出更深入的实现建议。
这一次我没有新开对话,因为上下文很重要。它给出了一组非常具体的考虑点:
-
动画的帧时间预算
-
地图 SVG 的体积优化策略
-
庆祝粒子数量的限制(DOM 元素数量)与清理策略
-
动画属性建议(只使用 transform / opacity)
-
减少动画支持
我又补充了几个关键要求:
-
实现必须模块化
-
先写测试,再写实现
-
测试失败后,再补实现
TDD 循环
Copilot 先创建了测试文件:
-
时区工具
-
城市状态管理
-
倒计时逻辑
所有测试一开始都是红色失败状态——这正是我们想看到的状态。

然后它实现了:
-
基于 Intl.DateTimeFormat 的时区工具
-
带精选城市(纽约、伦敦、东京、悉尼等)的状态管理
-
使用 localStorage 持久化所选时区
-
应用状态管理
在可以访问工具的情况下,自定义 agent 还直接在终端里运行了测试。有两个测试失败了:涉及跨年切换时,庆祝逻辑是否被正确触发,以及庆祝开始后的持续时间。

因为 Copilot 能看到测试输出,它捕捉到了失败原因,修正了时区实现,再次运行测试——全部通过(绿色)。
💡 思考:这正是 TDD 和代码质量重要性的体现。就像我们人类开发者一样,AI 辅助开发也会犯错。测试能帮助我们在问题到达用户之前把 bug 揪出来。如果跨年这个边界情况是在 12 月 31 日才被发现,那就太尴尬了——毕竟这是这个应用的核心能力。
但有些 Bug 会变成功能。我发现有一个 Bug 实在太好笑了,以至于我都没急着修它。我们来聊聊这个世界地图吧。
世界地图?也许吧。
当我打开应用时,倒计时正常运行。时区选择器也能正常工作。时间计算是准确的,从纽约切换到东京时也能正确显示时差。
但是世界地图呢?它并没有按预期渲染。屏幕上出现的与其说是地理地图,不如说更像是一幅抽象艺术作品。这让我在直播中直接笑出声。

💡 思考:在没有提供足够上下文的情况下,我就雄心勃勃地要求加一个世界地图。没有 SVG 资源,也没有引用现有的地图类库,只是简单地说了句“加一个迷你世界地图”。这再次提醒我们,AI 也会出错。
我能修好吗?当然可以。但当时直播已经进行了一个多小时,我们还有更多功能要实现。所以我把它留在那里了。这个地图正是迭代式开发的一个典型例子——事情第一次并不总是顺利。(你应该已经看出来我们是在现场构建这些东西了吧?)
烟花:为午夜营造期待感
单独一个倒计时只是功能性的,但烟花能增添庆祝氛围,也带来一些视觉上的“火花”(明白我这个双关吗?)。
我切换回 Plan agent,并新建了一个聊天线程(没错,又是上下文窗口管理,用来引导 Copilot 制定一个完整计划):
-
使用 Fireworks.js 实现烟花效果
-
根据剩余时间设置烟花的行为
-
如果倒计时还剩超过 24 小时,不显示烟花,只显示环境星空
-
如果剩余时间在 24 到 12 小时之间,每 30 秒燃放一次烟花
-
在剩余 1 小时到 10 分钟之间,逐步增强烟花的强度
-
最后,在倒计时的最后 10 秒内,持续燃放烟花,以达到最大庆祝效果
我还要求在屏幕底部添加城市天际线剪影、深色夜空渐变背景,以及一个主题控制器。另外,还有一个关键的测试需求:“添加一个查询参数,让我可以手动指定距离午夜还有多少分钟,用于覆盖实际时间进行测试。” 虽然我很享受和社区一起直播,但我不确定大家是否真的愿意一直等到 2026 年到来才能看到最终效果!
Plan agent 还进一步询问了关于星星显示方式的细节(是用 CSS 实现,还是用低强度烟花来模拟),以及一些性能方面的考虑。它还问到了开关按钮的放置位置,这倒让我有点意外。我不记得自己要求过一个开关按钮,也许是在某次计划迭代中提到过。
在仔细回顾计划后,Plan agent 提醒我,最初确实是我提出要增加一个动画开关以提升可访问性。这就是我喜欢 Plan agent 的原因——它就像一个拥有完整上下文的 AI 橡皮鸭,可以帮你检查这些需求是否仍然合理、是否前后一致。
在我和 Copilot 重新梳理并确认需求之后,我们采用了熟悉的测试驱动开发方式。一开始有一个测试失败了,因为缺少 JSDOM 环境配置。Copilot 发现了这个错误,定位到测试配置不正确的问题,并完成了修复。之后,所有测试都顺利通过。
现在,我们拥有了一个功能完整的应用:不同强度级别的烟花效果、使用 CSS 实现的动态星空、城市天际线剪影、对减少动画设置的支持,以及用于测试覆盖的查询参数。
测试强度级别
我在 URL 中添加了 ?minutesToMidnight=1。烟花以中等强度出现,随着天空中颜色和粒子数量的增加,营造出不断增强的兴奋感。到了“午夜”时,“Happy New Year” 字样出现,庆祝效果更加热烈。强度曲线感觉恰到好处,逐步的铺垫带来了期待感,而最终的高潮也十分到位。

揭晓:那天早上我构建了什么
但我并没有就此止步。在整个直播过程中,我一直在暗示,自己那天早上还做了另一个倒计时应用,一个主题非常贴切的作品。观众们猜测是另一个烟花倒计时、彩带计时器,甚至还有“由 elicitation 驱动的井字棋”(说实话,我们之前确实做过这个)。
但作为一场 GitHub 直播,收官方式只有一种。我们必须做一个以贡献图为主题的倒计时!
倒计时位于中央,前方是一个动态的贡献图。每个方块在网格中闪烁,绿色的贡献方块以波浪形式出现又消失。就像烟花主题一样,随着倒计时逐渐接近零点,点亮的方块越来越多,强度也随之提升。
这场直播是一场庆祝。它让我们的社区跨越时区聚在一起,我们在世界的不同角落构建项目,并共同倒计时迎接同一个时刻。
直播过程中,有人问到找到工作的最佳编程语言是什么。我的回答和我做这个项目的方式一样:找到能带给你快乐的事情,然后合适的工具和语言自然会到位。我做这个 GitHub 主题的倒计时,是因为它让我快乐。因为我想做一个“很 GitHub 的”东西,也因为我喜欢构建视觉体验。
自那场直播以来,我一直在将这两个项目整合为一个统一的开源倒计时应用 Timestamp。它拥有一个集中的主题编排器,允许开发者基于通用架构扩展新主题。每个倒计时都是一个 URL,因此可以轻松分享,并且提供多种倒计时模式可选(本地时间、绝对时间点以及计时器)。
你可以查看在线应用并浏览代码库。欢迎查看仓库、star、Fork,甚至贡献一个新的主题。
希望这能激励你去完成那个一直躺在待办列表里的项目,花点时间在那些能带给你一点快乐的事情上。
我们学到了什么?
-
上下文窗口管理是一种技能。当旧上下文不再需要时,开启新的聊天会话。保持对话聚焦。这是上下文工程,而不仅仅是提示工程。
-
Plan agent 会提出你可能已经遗忘的问题。当需求模糊时使用它。通过澄清性问题让它揭示边界情况。有时候,A 或 B 的答案是“介于两者之间”。
-
自定义 agent 是专业的助手。我的 UI 性能专家具备帧预算、动画属性和可访问性方面的专业知识。它提供了实现细节,而 Plan agent 则通过提问帮助明确范围。专业化很重要。
-
与 Copilot 结合进行 TDD 是有效的。先写测试。让测试失败。再实现代码使其通过。就像我们开发者一样,AI 辅助工具也会产生 Bug。我们需要使用那些熟悉的质量保障实践(构建、代码检查工具和测试),在问题到达用户之前将其捕获。
-
事情不一定第一次就成功。没关系。世界地图没有按预期渲染,我一直保持那样,直到对倒计时应用进行重大重构和重建。真实的开发意味着展示混乱的中间过程,而不仅仅是打磨好的成果。我们从意外结果中学到的,不亚于从成功中获得的经验。
-
大胆设定范围,迭代式实现。我们从基础倒计时开始,到时区支持,再到高强度烟花,最后到独立的贡献图主题倒计时。罗马不是一天建成的,你也不需要在第一天就拥有一切。
2026 年你会构建什么?让我们一起构建一些能带来快乐、但尚未登上“某一天”清单顶端的项目吧!
更多推荐


所有评论(0)