案例研究

每一次动作,都记录在案。

一款快速、私密的力量训练日志,已在 App Store 上架,支持 iPhone、iPad 和 Apple Watch。

2026年7月12日发布。我做 Reps,是因为自己训练时正需要它:一本安静得不会打扰举铁的日志、练完就能锁上的训练日,以及一个让 Claude 读我的历史、规划下一次训练、却碰不到数据库的连接器。36,000 行 Swift 代码和 185 个测试之后,它以 17 种语言上架。下面讲讲它是怎么做出来的。

941 Reps 训练进行中:两组已用绿色勾选完成,带手绘风格的删除线
产品

点一下圆圈,锁上这一天。

训练日志只有一件事要做:趁你还没忘,把这一组记下来。Reps 就是我想给自己用的那本日志。打开就是今天,显示我排好的训练分化,点一下圆圈就记下一组。没有信息流,也看不到别人的数字,因为在组间休息时,这两样我根本不会用。

一组就是一个重量加一个次数。RPE、组间计时和备注留给需要它们的人,对其他人则完全不碍事。个人纪录由已完成的组自动算出:最大重量,外加按 Brzycki 公式估算的单次最大重量。

如果只能留下一个功能,我会留锁定训练日。练完一天,锁上它,应用就会拒绝任何修改,直到你亲自解锁。解锁必须在应用里明确操作,而代表你行事的自动化程序完全无法修改已锁定的一天。当你回顾一个月的训练时,锁定的那一天读起来和你当初记录的一模一样。

商业模式遵循同一条规则。日志永久免费,你的记录永远不会被关在付费墙后面。Reps Pro 订阅只卖一样东西:托管式 AI 规划,给那些想用规划器又不想自带 API 密钥的人。

941 Reps 已锁定的训练:完成的组已被划掉,当天的锁已合上
锁定的训练日,读起来和你当初记录的一模一样。
设计

黑板上的手写字。

纸质训练日志有一点做对了:一个日期、一个动作、下面潦草写着几个数字。Reps 把这个整体原样保留。日期和重量用 Bradley Hand 和 Noteworthy 渲染,配色始终是近黑底上的墨色,外加唯一一抹绿色,分隔线也不是贴图素材。它们之所以歪歪扭扭,是因为一个 16 行代码的 Shape 把每一段都从中线上轻轻推开:

Swift · Theme.swift:143-158
struct RepsHandDrawnLine: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        let segments = max(Int(rect.width / 12), 8)
        let segmentWidth = rect.width / CGFloat(segments)
        path.move(to: CGPoint(x: 0, y: rect.midY))

        for index in 1...segments {
            let x = CGFloat(index) * segmentWidth
            let y = rect.midY + (index.isMultiple(of: 2) ? -0.8 : 0.9)
            path.addLine(to: CGPoint(x: x, y: y))
        }

        return path
    }
}

试试下面这一行。它为这个页面重建了应用的核心交互:点一下圆圈完成一组,然后锁定这一天,看看它怎么拒绝你。

6月12日 星期五

胸 / 背

反向划船

点点圆圈。锁上这一天。再试一次。

手写字的边界。

首发的 17 种语言中,有 10 种使用手写字体。日语、韩语、中文、阿拉伯语、印地语和泰语则回退到 SF Rounded,因为 Bradley Hand 没有这些文字的字形,而伪手写风格的 CJK 字体看上去就像戏服。颜色遵循同样的纪律:品牌绿偏浅,所以浅色表面上的强调文字会自动加深,以保住 5.4:1 的对比度,tvOS 的焦点状态也有自己专属的墨色。每个点击目标都不低于 Apple 的 44-point 最低标准,因为你是在组间、双手带着疲劳去按这些控件的,而不是坐在书桌前。

那些你永远看不到的部分来自 Kit941,941 系列应用共享的 SwiftUI 设计系统:间距、动效和触感反馈令牌,经过快照测试的组件,以及被封装在同一个样式后面的 Apple Liquid Glass。Reps 从不直接调用 .glassEffect()。笔记本的外观属于 Reps,骨架则是共享的。

平台

iPhone、iPad 和 Apple Watch,17 种语言。

Reps 用同一套 SwiftUI 代码库运行在 iPhone、iPad 和 Apple Watch 上。手表应用刻意保持精简:显示当天的组,在手腕上直接标记完成,WatchConnectivity 会在几秒内把每一次改动送到手机。在健身房里,清单胜过键盘。

Siri 和快捷指令通过 App Intents 接入。说一句“记一组”,不用打开应用这组就记好了,因为意图走的是与界面相同的、经过校验的写入路径。

941 Reps 在 iPad 上:今天的胸背训练,已完成的组被划掉
iPad
941 Reps 在 Apple Watch 上:当天的组以清单呈现,杠铃卧推 260 lb 做 6 次
Apple Watch
941 Reps 在 Mac 上:桌面窗口中的手写笔记本
Mac 版,开发中。
941 Reps 在 Apple TV 上:当天的训练,其中一行组处于焦点状态
Apple TV 版,开发中。
  • English
  • Deutsch
  • Español
  • Français
  • Italiano
  • Nederlands
  • Português
  • Svenska
  • Bahasa Indonesia
  • Tiếng Việt
  • 日本語
  • 한국어
  • 简体中文
  • 繁體中文
  • العربية
  • हिन्दी
  • ไทย

首发当天就带着 17 种语言上线,App Store 元数据覆盖 22 个地区。一个校验脚本为每次构建把关:735 条字符串,缺一个键都不放行。

Mac 和 Apple TV 版本已经存在并且能跑。等它们准备好了,就会发布。

工程

只有一扇正门。

Reps 自带一个连接器:一个 CLI 和一个 MCP 服务器,让智能体真正接触到你的日志。Claude 读取你的历史,发现哪里停滞了,然后把周五安排好。在终端里:

reps — zsh
$ reps last
Friday, June 26 — Chest / Back  [locked]
  Barbell Bench Press   255 lb × 6, 6, 6, 5
  Weighted Pull-Up      25 lb × 5, 5, 4
  Weighted Dip          60 lb × 10, 10, 10

$ claude "I started BJJ twice a week. What should change?"
Reading your last 8 weeks via the reps connector… pulling
volume is high for a grappling schedule. Suggesting: drop one
row day, add hip hinge + neck work. Plan Friday around this?

读和写走不同的路。读取打开的是你数据的同步本地副本,只读,因此查询不可能破坏任何东西,而且离线也能用。写入永远不会碰那个存储。每一次变更都以命令的形式发给应用;应用用与界面完全相同的规则校验它、应用它、同步它,然后返回一张回执。锁定的训练日拒绝智能体的修改,就像它拒绝指尖一样。

941 Reps 规划器面板:快速命令和一个日常语言输入框
规划器:可以用快速命令,也可以直接用日常语言提问。

连接器接受的每一条命令都要经过与点按相同的校验,而锁定的训练日会在任何东西碰到存储之前直接抛出异常:

Swift · ConnectorCommandHandler.swift:438-448
guard let session = try sessionForDay(date, modelContext: modelContext) else {
    throw CommandError.validationFailed("no workout exists on \(dayLabel)")
}
guard !session.isLocked else {
    throw CommandError.dayIsLocked(dayLabel)
}
guard let target = (session.sets ?? []).first(where: {
    $0.exerciseName.caseInsensitiveCompare(exerciseName) == .orderedSame
        && $0.setNumber == setNumber
}) else {
    throw CommandError.validationFailed("no set \(setNumber) of \(exerciseName)")
}

AI 规划器也一视同仁。无论是托管模型还是你自己的密钥,模型只负责解析意图:练什么类型的一天、侧重什么、避开什么。真正的编排由手机上的确定性引擎完成:从收录 42 个动作的动作库里选动作,套用符合你目标的次数方案,根据你的历史记录递增负荷,并在结果进入日志之前先做一遍校验。AI 从不直接写入训练。

走到这一步的路上,有两套能跑的架构被砍掉了:先是一个基于文件的 Claude Desktop 扩展,然后是一个带 TLS 固定和自有 RPC 总线的局域网配对服务器。它们都给数据开了第二扇门,所以都被拆掉了。最终上架的应用只有一扇。

连接器暴露 23 个 MCP 工具,从 get_exercise_historyset_custom_program。这一年我一直在写第一读者是助手的软件这个主题;Reps 就是把这个论点做成了产品。Get Bananas 把 MCP 服务器装进了购物清单。Reps 则把规划器交给智能体,同时守住护栏。

隐私

数据住在哪里。

隐私承诺谁都会说,所以这里直接给出存储配置本身。训练数据镜像到你的 iCloud 私人数据库;健康指标住在第二个存储里,永不同步,也永远不进备份:

Swift · RepsModelContainer.swift:67-78 (condensed)
// No explicit name: the synced configuration keeps the historical
// default.store path, which the Mac connector reads directly.
let modelConfiguration = ModelConfiguration(
    schema: syncedSchema,
    isStoredInMemoryOnly: false,
    cloudKitDatabase: .private("iCloud.com.941apps.Reps")
)
let healthCacheConfiguration = ModelConfiguration(
    "Reps-healthcache",
    schema: localSchema,
    isStoredInMemoryOnly: false,
    cloudKitDatabase: .none
)

你的训练数据

存放在你的 iCloud 私人数据库里,通过 Apple 在设备之间同步。Reps 没有账号系统,也没有存储训练记录的服务器。我读不到你的深蹲重量。

健康指标

从“健康”读取的心率和卡路里保存在第二个存储里:仅在设备上,排除在备份之外,从不同步。把一条“健康”体能训练关联到锁定的训练日时,同步的只有关联本身。测量数据始终留在手机上。

AI 规划

完全可选。启用后,规划请求(你的资料和近期历史)会发给你选择的提供方:托管服务,或你自己的 API 密钥。展开并校验每份计划的引擎在手机上运行。不用 AI,就不会有任何数据发给任何 AI 提供方。

记录在案。

17 种首发语言
23 个 MCP 工具
42 个动作库收录动作

Reps 在 App Store 免费上架。我为自己的训练而做,它也是我每天都会打开的日志;如果你想要的也是这样一个东西,它就在那里。如果你在为智能体做软件,写入路径是最值得借鉴的部分。(以上均为首发当日的数字。)