每一次动作,都记录在案。
一款快速、私密的力量训练日志,已在 App Store 上架,支持 iPhone、iPad 和 Apple Watch。
2026年7月12日发布。我做 Reps,是因为自己训练时正需要它:一本安静得不会打扰举铁的日志、练完就能锁上的训练日,以及一个让 Claude 读我的历史、规划下一次训练、却碰不到数据库的连接器。36,000 行 Swift 代码和 185 个测试之后,它以 17 种语言上架。下面讲讲它是怎么做出来的。
点一下圆圈,锁上这一天。
训练日志只有一件事要做:趁你还没忘,把这一组记下来。Reps 就是我想给自己用的那本日志。打开就是今天,显示我排好的训练分化,点一下圆圈就记下一组。没有信息流,也看不到别人的数字,因为在组间休息时,这两样我根本不会用。
一组就是一个重量加一个次数。RPE、组间计时和备注留给需要它们的人,对其他人则完全不碍事。个人纪录由已完成的组自动算出:最大重量,外加按 Brzycki 公式估算的单次最大重量。
如果只能留下一个功能,我会留锁定训练日。练完一天,锁上它,应用就会拒绝任何修改,直到你亲自解锁。解锁必须在应用里明确操作,而代表你行事的自动化程序完全无法修改已锁定的一天。当你回顾一个月的训练时,锁定的那一天读起来和你当初记录的一模一样。
商业模式遵循同一条规则。日志永久免费,你的记录永远不会被关在付费墙后面。Reps Pro 订阅只卖一样东西:托管式 AI 规划,给那些想用规划器又不想自带 API 密钥的人。
黑板上的手写字。
纸质训练日志有一点做对了:一个日期、一个动作、下面潦草写着几个数字。Reps 把这个整体原样保留。日期和重量用 Bradley Hand 和 Noteworthy 渲染,配色始终是近黑底上的墨色,外加唯一一抹绿色,分隔线也不是贴图素材。它们之所以歪歪扭扭,是因为一个 16 行代码的 Shape 把每一段都从中线上轻轻推开:
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 接入。说一句“记一组”,不用打开应用这组就记好了,因为意图走的是与界面相同的、经过校验的写入路径。
- 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 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?
读和写走不同的路。读取打开的是你数据的同步本地副本,只读,因此查询不可能破坏任何东西,而且离线也能用。写入永远不会碰那个存储。每一次变更都以命令的形式发给应用;应用用与界面完全相同的规则校验它、应用它、同步它,然后返回一张回执。锁定的训练日拒绝智能体的修改,就像它拒绝指尖一样。
连接器接受的每一条命令都要经过与点按相同的校验,而锁定的训练日会在任何东西碰到存储之前直接抛出异常:
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)") }
Claude / CLI / Siri
直接读取同步副本,只读。每一次写入都以命令发送,从不直接触碰数据库。
应用
用与界面相同的规则校验每条命令。锁定的训练日拒绝修改。每次写入都返回回执。
AI 规划器也一视同仁。无论是托管模型还是你自己的密钥,模型只负责解析意图:练什么类型的一天、侧重什么、避开什么。真正的编排由手机上的确定性引擎完成:从收录 42 个动作的动作库里选动作,套用符合你目标的次数方案,根据你的历史记录递增负荷,并在结果进入日志之前先做一遍校验。AI 从不直接写入训练。
走到这一步的路上,有两套能跑的架构被砍掉了:先是一个基于文件的 Claude Desktop 扩展,然后是一个带 TLS 固定和自有 RPC 总线的局域网配对服务器。它们都给数据开了第二扇门,所以都被拆掉了。最终上架的应用只有一扇。
连接器暴露 23 个 MCP 工具,从 get_exercise_history 到 set_custom_program。这一年我一直在写第一读者是助手的软件这个主题;Reps 就是把这个论点做成了产品。Get Bananas 把 MCP 服务器装进了购物清单。Reps 则把规划器交给智能体,同时守住护栏。
数据住在哪里。
隐私承诺谁都会说,所以这里直接给出存储配置本身。训练数据镜像到你的 iCloud 私人数据库;健康指标住在第二个存储里,永不同步,也永远不进备份:
// 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 提供方。
记录在案。
Reps 在 App Store 免费上架。我为自己的训练而做,它也是我每天都会打开的日志;如果你想要的也是这样一个东西,它就在那里。如果你在为智能体做软件,写入路径是最值得借鉴的部分。(以上均为首发当日的数字。)