上下文窗口、计划代理和TDD:我使用GitHub Copilot构建倒计时应用时学到的经验

了解我是如何管理上下文以保持 Copilot 的专注,使用 Plan 代理来明确模糊的需求,以及采用测试驱动开发实践来在用户之前发现错误。

在2025年最后一期“橡皮鸭星期四”直播节目中,我想制作一些庆祝性质的东西。一些能够体现“橡皮鸭星期四”精髓的东西:共同创作,从错误中学习,并向来自世界各地的每一位收看者致敬。 

一路走来,我总结了一些使用人工智能的实用模式,你可以将它们应用到自己的项目中,无论你是开发倒计时应用还是其他完全不同的项目。从管理上下文窗口避免对话混乱,到使用Plan 代理进行需求发现,再到通过 Copilot 进行测试驱动开发来捕获极端情况。还有……为什么绘制世界地图比看起来要难得多。 

从简单的开始:基本倒计时

倒计时器的概念很简单:天数倒计时到小时数,分钟倒计时到秒数。但有时候,正是这些简单的想法激发了我们最大的创造力。我想借此机会,以需求驱动的方式,运用 Copilot 开发一款倒计时应用,在新年来临之际,用绚丽的烟花营造出令人期待的氛围。 

什么是规范驱动开发?

与先编写代码后编写文档的传统做法不同,规范驱动开发(Spec-drived Development)顾名思义,是从规范开始的。规范是代码行为的契约,也是工具和人工智能代理生成、测试和验证代码的权威依据。其结果是减少猜测、减少意外情况,并生成更高质量的代码。

幸运的是,软件开发是一个迭代过程,而这次直播也充分体现了这一点。虽然有些需求定义明确,但其他需求则是在直播过程中根据观众的建议实时演变而来的。像“计划”代理这样的自定义代理帮助我们弥合了这种差距,将模糊的想法转化为我可以执行的结构化计划。那么,让我们从头开始,从项目设置开始吧。

我使用 GitHub Copilot 创建了一个新的工作区,并根据一个非常具体的提示进行了设置。提示说明我们正在构建一个倒计时应用,并且我希望使用ViteTypeScriptTailwind 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.

我最喜欢“生成新工作区”功能的一点是,Copilot 会自动为我生成自定义指令文件,自动捕捉我的需求,包括倒计时应用、Vite、TypeScript 和深色主题。所有这些都在编写任何一行代码之前就记录下来了。

这是 Visual Studio Code 中与 Copilot Chat 对话的屏幕截图。Copilot 检测到用户想要使用 Vite、TypeScript 和 Tailwind CSS v4 创建一个新的工作区,用于开发新年倒计时应用。它将使用 create_new_workspace 工具来创建新的工作区。

几分钟之内,我就做出了一个能用的倒计时。天、小时、分钟、秒,滴答滴答地倒数着,直到2026年。虽然它能用,但视觉效果却不怎么样。说实话,我最初的设计稿里并没有指定任何设计或主题偏好。所以,是时候迭代一下,让它更有趣了。

社区的建议指引了我们的方向

直播期间,观众来自印度、尼日利亚、意大利、美国(还有很多其他国家!);世界各地的开发者齐聚一堂,共同学习。聊天室里有人提出了一个建议,改变了我们接下来的计划:时区问题怎么办?

这并非我预期在直播期间需要完成的任务,所以我并没有清晰的方案。或许可以做一个可以旋转的地球仪来选择时区。或许可以做一个以时间旅行为主题的世界地图。总之,有很多不确定的选项。我的需求很模糊,所以我才求助于方案规划师。

计划代理人:我之前没想到要问的问题

最近我开始更有意识地使用计划代理,尤其是在我觉得需求不够明确的时候。计划代理不会根据我最初的提示直接生成计划,而是会提出一些澄清性的问题,这些问题可以帮助我发现一些可能没有考虑到的特殊情况。

这是 Visual Studio Code 中 Copilot Chat 输入框的屏幕截图。输入框中包含一些提示信息,用于配置交互式时区选择器,该选择器可以像时光机拨盘或迷你世界地图一样显示。

我大致描述了一下我的想法:交互式时区选择器、时间旅行主题、时区切换动画,或许还可以加一张世界地图。项目专员提出的问题让我陷入了沉思:

问题 为什么这很重要
圆形表盘应该作为主表盘,世界地图作为副表盘,还是反过来? 我还没决定视觉层级结构。
移动端会发生什么:下拉菜单还是触控友好的滚动条? 我最初只考虑了桌面版的实现。移动端功能可能是未来的发展方向。
当某个时区过了午夜时,显示“已经开始庆祝”的彩带,或者显示一个计时器,表明从午夜至今已经过了多久? 我想要的是庆祝活动,而不是倒计时。我之前没把要求说清楚。
旋转旋钮时会有轻微的音频反馈,还是只有视觉反馈? 将音频功能引入应用程序属于范围扩大,但这可能是未来的需求。

这就是以这种方式与人工智能合作的妙处所在。规划代理会引导你思考,可能会提出澄清问题并提供选项 A 或 B。但当你仔细思考后,你会发现答案介于两者之间。 

例如,在我第二次修改需求时,方案中询问烟花应该持续播放、只燃放一次还是循环播放。我回复说,这可能涉及到性能问题,我们应该选择折中的方案。我们还请直播观众投票决定组件应该以拨盘还是地图的形式呈现。地图胜出,所以我们最终决定采用世界地图作为主要选择器,并包含八个特色地点。

上下文窗口管理:只保留你需要的内容

在实施之前,我特意开启了一个新的聊天会话

我们之前对话的背景信息(工作区创建、基本倒计时逻辑)已经不再需要了。任何可能有用的背景信息现在都已包含在我们的自定义指令文件中。在使用人工智能工具时,这个背景信息窗口至关重要。引入无关的历史信息会使对话变得混乱,分散注意力。因此,我清理了它,只保留了重要的内容:新的需求、计划代理的输出(我已要求 Copilot 将其写入单独的 Markdown 文件)以及关于时区的新关注点。

我还复用了另一个个人项目中的一些自定义指令文件、自定义代理提示文件,以帮助 Copilot 朝着正确的方向发展,并集成用于相关任务的专用代理。这其中包括一个 UI 性能专家代理。

你知道吗? GitHub Copilot 的自定义代理功能允许你为不同的开发任务创建专门的角色。我在直播中构建的 UI 性能专家代理就是一个例子。你可以创建用于安全审查、架构规划或任何特定角色工作流程的代理。awesome -copilot 代码库中有很多示例

实现方式:模块化、测试驱动,以及映射

计划代理完成工作后,我切换到 UI 性能专家代理,并要求它审查该计划,根据其专业知识提出更深入的实施细节。

这是 Visual Studio Code 中 Copilot Chat 的屏幕截图。代理选择器显示“UI 性能专家”,并提示代理查看计划并提供实施细节。

上下文很重要,所以我没有另起炉灶,而是继续之前的对话。客服人员随后提供了一系列详细的考虑因素:

  • 动画帧时间预算
  • 地图SVG尺寸优化策略
  • 庆典粒子限制(DOM元素问题)和清理注意事项
  • 动画属性建议(仅限变换/不透明度)
  • 减少运动支撑

看起来不错,但我又加了一些要求。我要求定制代理将实现模块化,先根据预期行为编写测试,等测试失败后再编写实现。

没错:使用 Copilot 进行测试驱动开发

TDD周期

Copilot 为时区实用程序、城市状态管理和倒计时逻辑创建了测试文件。所有失败的测试都显示为红色。很好(这是我们少数希望看到测试失败的情况之一)! 

Visual Studio Code 中 Copilot Chat 的屏幕截图显示 GitHub Copilot 遵循 TDD 周期;先进行失败的测试,然后进行实现。

然后它实施了:

  • 使用Intl.DateTimeFormat API 的时区实用程序
  • 以纽约、伦敦、东京、悉尼等城市为特色的城市州
  • 为选定的时区保留本地存储
  • 应用状态管理

利用工具权限,自定义代理还在终端执行了测试。有两个测试用例失败:一个是判断跨年庆祝活动是否正确触发的逻辑。测试预期庆祝活动会在午夜处理,另一个是庆祝活动开始至今的持续时间。

Visual Studio Code 中 Copilot Chat 的屏幕截图显示了 GitHub Copilot 正在进行实现更改、运行测试并识别出测试失败。Copilot 根据失败情况进行迭代,并重新运行测试以获得一组通过的测试结果。

由于 Copilot 可以访问输出,自定义代理捕获了测试失败,调整了时区实现,测试通过了。

思考:这正是测试驱动开发 (TDD) 和代码质量的重要性所在。就像我们开发者一样,人工智能辅助开发也会出错。测试可以帮助我们在用户发现问题之前就找到 bug。考虑到这是应用的核心功能,如果在 12 月 31 日才发现年份滚动这个特殊情况,那将非常尴尬!

但有些漏洞反而成了特色。我发现一个漏洞太有趣了,一时半会儿没忍住修复。咱们来聊聊世界地图吧。

或许是世界地图?

我打开应用后,倒计时功能正常,时区选择器也能用,计算结果也正确,从纽约切换到东京后,时差也显示正确。

但是世界地图呢?它的渲染效果不太理想。屏幕上显示的与其说是地理图像,不如说是抽象艺术。不过,这真的让我直播的时候笑出了声。

这是倒计时应用程序的屏幕截图。它用几个点表示世界各地的位置,但世界地图被渲染成一系列抽象形状来代表岛屿,而不是真正的世界地图。

想法:我当时雄心勃勃地指定要添加一张世界地图,却没有提供足够的背景信息。既没有SVG素材,也没有引用任何现有的地图库。仅仅是“添加一张迷你世界地图”。这提醒我们,人工智能也会出错。

我本来可以修复它吗?当然可以。但当时直播已经进行了一个多小时,我们还有更多功能要开发。所以我放弃了。这张地图完美地诠释了迭代开发,事情不可能总是一次成功。(看得出来我们现在是在直播中开发东西吗?)

烟花:午夜将至,令人期待

倒计时本身就很实用,但烟花可以增添庆祝气氛,并带来一些视觉上的震撼(明白我的意思吗?)。 

我切换回计划代理并创建了一个新的聊天线程(同样,通过上下文窗口管理,提示 Copilot 构建计划):

  • 使用Fireworks.js实现特效。
  • 根据剩余时间设置烟花表演行为
  • 如果计时器剩余时间超过 24 小时,则不要燃放烟花,只显示环境星空。
  • 如果计时器剩余时间在 24 到 12 小时之间,则每隔 30 秒燃放一次烟花。
  • 在距离活动结束还有1小时10分钟时,烟花的强度应该会逐渐增强。
  • 最后,在最后10秒钟,我们应该燃放连续不断的烟花,以达到最大的庆祝效果。

我还要求在屏幕底部添加城市天际线轮廓、深色夜空渐变效果以及主题控制器。此外,还有一个关键的测试要求:“添加一个查询参数,以便我可以指定距离午夜还有多少分钟,作为手动测试的覆盖设置。” 虽然我很喜欢和我们的社区一起直播,但我不太确定大家是否都愿意等到2026年才能看到结果!

方案代理人要求进一步说明星星的显示方式(是设置为 CSS 样式,还是设置为低强度烟花效果),以​​及一些性能方面的考虑。它还询问了切换按钮的位置,这让我措手不及。我不记得之前要求过添加切换按钮,可能是在方案的某个版本中遗漏了。 

仔细审阅方案后,我发现我最初要求的辅助功能动画切换功能已经实现。这就是我喜欢这个辅助功能的原因。它就像一个人工智能橡皮鸭,能够理解你的对话上下文,并检查这些需求是否仍然合理。

Visual Studio Code 中 Copilot Chat 的屏幕截图,显示了 Copilot 提出澄清问题以确认需求的交互过程。

我和 Copilot 重新协商需求后,便采用了我们熟悉的测试驱动开发方法。最初,由于缺少 JSDOM 环境配置,一个测试失败了。Copilot 发现了这个问题,找到了配置错误的测试环境,并进行了修复。之后,所有测试都顺利通过了。

我们现在有了一个应用程序,它具有不同强度级别的烟花、使用 CSS 的动画星空、城市天际线、减少的运动支持以及查询参数覆盖。

测试强度级别

?minutesToMidnight=1在网址中添加了内容。烟花以中等强度绽放,随着天空中色彩和粒子数量的增加,气氛也逐渐升温。午夜时分,新年快乐字样出现,庆祝活动更加热烈。强度曲线恰到好处,前奏营造了期待感,结尾更是精彩纷呈。

倒计时应用程序的屏幕截图显示,计时器已达到 0,屏幕上正在播放烟花庆祝。

揭晓:我那天早上建造的东西

但我并没有就此止步。在整个直播过程中,我一直在暗示我当天早上还做了一个倒计时应用,主题非常契合。我们的观众猜测是另一个烟花倒计时应用、一个彩带计时器,甚至还有一个“诱导式井字棋”(公平地说,我们以前确实做过)。 

但作为 GitHub 直播,我们只有一种方法可以完成它:那就是制作一个以贡献图为主题的倒计时!

倒计时位于屏幕中央,前方是一个动态的贡献图表。每个方格都闪烁着绿色的贡献值,如同波浪般在网格上出现和消失。正如烟花主题曲所描绘的那样,随着倒计时接近零,越来越多的方格被点亮,气氛也愈发热烈。

这是 GitHub 贡献图主题倒计时应用的截图。图中用绿色方块显示了 2026 年,这些方块叠加在一个贡献图之上。贡献图上有多个不同深浅的绿色方块,分别代表不同的贡献值。

这场直播是一场庆祝活动。它让我们的社区跨越时区聚集在一起,让我们在世界各地的同一角落里共同筹备、倒计时,迎接同一时刻的到来。

直播期间,有人问到哪些编程语言最容易找到工作。我的回答和我做这个项目的理念一样:找到让你快乐的事情,合适的工具和语言自然就会到​​位。我开发这个 GitHub 倒计时主题,是因为它让我快乐。因为我想做一些“GitHub 出品”的东西,也因为我喜欢创造视觉体验。 

自那次直播之后,我一直致力于将这两个项目整合到一个统一的开源倒计时应用 Timestamp 中。它拥有一个集中式的主题编排器,允许开发者接入通用架构并扩展新主题。每个倒计时都是一个 URL,因此可以轻松共享,并且有多种倒计时模式可供选择(本地时间、绝对时间点和计时器)。

您可以查看在线应用查看代码库。欢迎您查看代码仓库、点赞、fork,甚至贡献新的主题。 

我希望这能激励你去做那个一直搁置的项目,花些时间做些能给你带来快乐的事情。

我们学到了什么?

  • 上下文窗口管理是一项技能。当不再需要旧的上下文时,开启新的聊天会话。保持对话聚焦。这关乎上下文工程,而不仅仅是提示工程。
  • 计划代理会提出一些你可能已经忘记的问题。当需求模糊不清时,可以使用它。它可以通过澄清问题来揭示特殊情况。有时,A 或 B 的答案可能“介于两者之间”。
  • 定制代理是专业的助手。我的 UI 性能专家精通帧预算、动画属性和可访问性。他负责提供实现细节,而计划代理则负责提出澄清问题,以确定范围。专业化至关重要。
  • 使用 Copilot 进行 TDD 是行之有效的。先编写测试,让它们失败,然后再实现通过测试的功能。就像我们开发者一样,AI 辅助工具也会产生 bug。我们需要使用与以往相同的质量检查方法(构建、代码检查和测试),以便在用户发现问题之前将其捕获。
  • 事情并非总能一次成功,这没关系。世界地图的渲染效果并不理想,我也没有急于求成,直到我对倒计时应用进行大规模重构和重建后才解决了这个问题。真正的开发过程意味着展现混乱的中间阶段,而不仅仅是最终的完美结果。我们从意料之外的结果中学习到的东西,与从成功中学习到的东西一样多。
  • 目标要远大,实现要循序渐进。我们从基本的倒计时,到时区,再到绚丽的烟火效果,最终发展到以贡献图为主题的独立倒计时。罗马不是一天建成的,你也不需要第一天就把所有东西都做出来。

2026年你会建造什么?欢迎收看英国时间上午10:30和美国东部时间下午2:00的下一期“橡皮鸭星期四”直播,让我们一起建造一些能给我们带来快乐,但还没能排进“总有一天”愿望清单前列的东西!

评论