每一下,都記錄在案。
一款快速、私密的阻力訓練日誌,已在 App Store 上架,支援 iPhone、iPad 與 Apple Watch。
2026年7月12日上架。我做 Reps,是因為自己訓練時就想要這樣的東西:一本安靜到不干擾訓練的日誌、練完就能鎖上的日子,以及一個讓 Claude 不必碰資料庫就能讀取我的歷史、規劃下一次課表的連接器。36,000 行 Swift 與 185 個測試之後,它以十七種語言上架。以下是它成形的過程。
點一下圓圈,鎖上這一天。
訓練日誌只有一個任務:在你忘記之前,把那一組記下來。Reps 是我為自己想要的日誌。打開就是今天,顯示我排好的課表,點一下圓圈就記錄一組。沒有動態消息,也沒有別人的數字,因為在組間休息時,這兩樣我根本不會用。
一組就是一個重量加一個次數。RPE、組間計時器和筆記留給需要的人,對其他人則完全不礙事。個人紀錄會從完成的組數自動計算:最大重量,加上以 Brzycki 公式估算的單次最大重量。
如果其他功能都得砍掉,我唯一會留下的是鎖定日。練完一天、鎖上,App 就會拒絕任何後續修改,直到你親自解鎖。解鎖是在 App 裡的明確動作;代替你行事的自動化流程完全無法編輯已鎖定的日子。回顧一個月的訓練時,鎖定的一天讀起來就和你當初記下的一模一樣。
商業模式也遵循同一條規則。日誌永久免費,你的紀錄永遠不會被鎖在付費牆後面。Reps Pro 訂閱只賣一樣東西:託管的 AI 規劃,給想用規劃器、又不想自備 API 金鑰的人。
黑板上的手寫字。
紙本訓練日誌有一件事做對了:一個日期、一個動作,下面潦草寫著數字。Reps 保留了這整個物件。日期與重量以 Bradley Hand 和 Noteworthy 呈現,配色維持在近黑底上的墨色,只添一種綠;分隔線也不是材質貼圖。它們之所以歪歪扭扭,是因為一個十六段的 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 } }
試試下面這一列。它為這個頁面重建了 App 的核心互動:點一下圓圈完成一組,然後鎖上這一天,看它怎麼拒絕你。
6月12日 星期五
反向划船
點點圓圈。鎖上這一天。再試一次。
手寫字到哪裡為止。
十七種上架語言中,有十種使用手寫字體。日文、韓文、中文、阿拉伯文、印地文與泰文則改用 SF Rounded,因為 Bradley Hand 沒有這些文字的字符,而假手寫風的 CJK 字型看起來就像戲服。色彩遵循同樣的紀律:品牌綠偏淺,所以淺色底上的強調文字會自動加深,維持 5.4:1 的對比度;tvOS 的焦點狀態也有自己的墨色。每個點擊目標都維持 Apple 規定的 44-point 下限,因為你是在組間、雙手帶著疲勞按這些控制項,不是坐在書桌前。
你看不到的部分來自 Kit941,941 系列 App 共用的 SwiftUI 設計系統:間距、動態與觸覺回饋的 token、經快照測試的元件,以及包在單一樣式後面的 Apple Liquid Glass。Reps 從不直接呼叫 .glassEffect()。筆記本的外貌屬於 Reps;骨架是共用的。
iPhone、iPad 與 Apple Watch,十七種語言。
Reps 以同一套 SwiftUI 程式碼在 iPhone、iPad 與 Apple Watch 上執行。Watch App 刻意保持精簡:顯示當天的組數、在手腕上打勾完成,WatchConnectivity 幾秒內就把每筆變更帶回手機。在健身房裡,勾選清單勝過鍵盤。
Siri 與捷徑透過 App Intents 接入。說一句「記錄一組」,不用打開 App,那一組就記進去了,因為意圖走的是與 UI 相同、經過驗證的寫入路徑。
- English
- Deutsch
- Español
- Français
- Italiano
- Nederlands
- Português
- Svenska
- Bahasa Indonesia
- Tiếng Việt
- 日本語
- 한국어
- 简体中文
- 繁體中文
- العربية
- हिन्दी
- ไทย
十七種語言在上架第一天就到位,App Store 中繼資料涵蓋 22 個地區。每次建置都由驗證指令碼把關,735 個字串必須零缺漏才放行。
Mac 與 Apple TV 版本已經存在、可以執行。等它們準備好了,就會上架。
只有一扇正門。
Reps 內建一個連接器:一組 CLI 和一個 MCP 伺服器,讓 AI 代理真正存取你的日誌。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?
讀與寫走不同的路。讀取開啟的是你資料的同步本機副本,唯讀,所以查詢不可能弄壞任何東西,離線也能用。寫入永遠不碰那個儲存區。每個變更都以指令送進 App;App 用與 UI 相同的規則驗證、套用、同步,然後回傳一張回條。鎖定的日子拒絕代理的編輯,就像拒絕指尖一樣。
連接器接受的每個指令,都會經過與點擊完全相同的驗證;遇到鎖定的日子,會在任何東西碰到儲存區之前就擲出錯誤:
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
直接讀取同步副本,唯讀。每筆寫入都以指令送出,從不直接碰資料庫。
App 本體
以與 UI 相同的規則驗證每個指令。鎖定的日子拒絕編輯。每筆寫入都回傳回條。
AI 規劃器也受到同樣的待遇。模型(託管或你自己的金鑰)只負責解析意圖:今天練哪一種、要加強什麼、要避開什麼。實際排課表的是手機上的一個確定性引擎:從收錄 42 個動作的動作庫裡挑選、套用符合你目標的次數配置、依你記錄的歷史遞增負荷,並在結果進入日誌之前先行檢查。AI 從不直接寫入任何一次訓練。
一路走來,有兩個能運作的架構被砍掉:先是以檔案為基礎的 Claude Desktop 擴充功能,接著是配對式的區域網路伺服器,帶 TLS 憑證綁定和自己的 RPC 匯流排。它們都替資料多開了一扇門,所以都被拿掉了。上架的 App 只有一扇。
連接器對外提供 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 免費上架。我為自己的訓練做了它,也是我每天打開的日誌;如果你想要同樣的東西,它就在那裡。如果你在為代理打造軟體,寫入路徑是最值得偷走的部分。(數字為上架當日統計。)