Commit Graph
23 Commits
Author SHA1 Message Date
rzen cbdf02bca7 Add the macOS app: a library-management companion (routines, schedules, exercise browser)
A new 'Workouts Mac' target (same bundle ID as iOS, universal purchase)
with a NavigationSplitView shell over the shared data/sync layers:
routine management with starter gallery and seed-fork follow, schedule
editing (reminders stay iPhone-scheduled), and a browse-only exercise
library with the animated figures and reference guides.

Multi-writer stance: the Mac writes only routine and schedule documents
(whole-document last-writer-wins) and never workout documents, whose
per-log merge exists only on the watch ingest path. The Mac reconciles
on window activation (throttled) since iCloud syncs while the app is
closed, and flushes pending writes on deactivation.
2026-07-16 19:55:06 -04:00
rzen e12fe31152 Add Warm-Up and Stretching activity types
Adds WorkoutActivityType.warmUp (HealthKit .preparationAndRecovery) and
.stretching (.flexibility), and retags the six starter splits that were all
mislabeled as Functional Strength:

- Warm-Up:    Upper Body Warm-Up, Lower Body Warm-Up, Morning Wake-Up
- Stretching: Morning Mobility, Full Body Stretch, Evening Stretch

The split editor's activity picker surfaces them automatically (CaseIterable).
Older app versions decode the new raw values as the default type — additive and
not schema-gated, so no quarantine.
2026-07-09 15:45:31 -04:00
rzen b53381e8ce Give the watch workout screen entirely to the up/down timer
The half-and-half split (paged timer over the looping form-guide figure)
is now iPhone-only. On the watch the ExerciseProgressView TabView fills the
whole screen — the figure slot is gone from both the active flow and the
Completed state, and the ExerciseFigure sources and ExerciseMotions
resources are dropped from the watch target since nothing there uses them.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 14:45:53 -04:00
rzen 16391d9c4a Bump marketing version to 2.3
Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 13:37:02 -04:00
rzen 653cc65e0e Integrate IndieBackup for snapshot backup and restore
Adds a local ZIP backup/restore of the iCloud document tree via the
IndieBackup package, surfaced in Settings with retention controls. A
restore suspends the sync observer, mirrors the files, then rebuilds the
SwiftData cache; opening a shared .workoutsbackup file restores it. The
engine exposes the container Documents root and a restore lifecycle
(isRestoring guards a concurrent connect), and the backup file type is
registered for open-in-place.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 08:03:30 -04:00
rzen df3eac9d5f Add a Live Activity for the running workout
A new iOS widget extension shows the active exercise, its phase, and the
work/rest countdown on the lock screen and in the Dynamic Island, driven
by the run flow's live frames so locking the phone mid-set keeps the
timer. The activity is seeded on open, refreshed on every page settle,
dismissed when the flow is left, and cleared on next launch if stranded.
Unifies the build-info stamping across all targets via a YAML anchor.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 08:02:34 -04:00
rzen 1b399ee7ba Add a watchOS test target
New Workouts Watch AppTests bundle wired into the watch scheme. Extracts
the phone-to-watch cache apply/prune into a pure, session-free
WatchCacheApplier seam and makes the HR-zone bucketing a nonisolated
static, so both can be unit-tested off the main actor without a live
WatchConnectivity session.

Claude-Session: https://claude.ai/code/session_01HJDQQDA9QdP8zByg43H5v3
2026-07-08 07:57:24 -04:00
rzen ff1ffbb1f6 Bump to 2.2
Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
2026-07-06 16:30:38 -04:00
rzen fce8fa4c17 Author the machine exercise circuit with props and corrected hip machine motions
Fourteen Planet Fitness machine exercises join the rig library, each with
authored motion, info page, and schematic equipment via the new props layer
(scene shapes, cables, bars, pads) rendered in lockstep by render.py and the
in-app figure renderer. Abductor and Adductor are authored face-on with the
real seated machine motion — knees-bent legs swinging apart/together against
knee pads — replacing the earlier middle-split depiction. The watch target
now bundles the figure renderer and motion rigs too.

Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
2026-07-06 16:29:31 -04:00
rzen 888852cc2e Replace the YAML exercise catalogs with the motion-rig exercise library
The exercise picker now lists the bundled exercise library — the
motion-rig exercises exported from Exercise Library/ — instead of the
two *.exercises.yaml starter catalogs. ExerciseListLoader and the Yams
dependency are removed; ExerciseMotionLibrary gains exerciseNames, the
bundle enumeration the picker reads.
2026-07-06 12:27:44 -04:00
rzen 936a585ece Seed starter splits deterministically with immutable clone-on-edit seeds
Starter splits ship as byte-canonical SplitDocument JSON with fixed
ULIDs (Workouts/Resources/StarterSplits, regenerated by
Scripts/generate_starter_splits.swift) and auto-seed after connect into
a verifiably empty container, re-checked after a settle delay — wrong
guesses are harmless because identical bytes make same-path conflicts
empty and tombstones reap resurrected seeds. Seeds are immutable:
SyncEngine.save(split:) forks an edited seed to a fresh ULID and
soft-deletes the original, whose stub is exempt from pruning
(IndieSync 0.3.0 prune(exempting:)) and vetoes resurrection forever;
split views resolve by id through a redirect map to follow the swap.
Add Starter Splits in Settings restores deleted seeds by lifting the
veto stub and rewriting the bundle bytes.

Also fixes ingestFromWatch bypassing the tombstone veto (a phone-deleted
workout resurrected when a stale watch resent it) and reaps a live file
immediately when its tombstone arrives.

SplitDetailView also picks up the category-grouped exercise sections
from the exercise-category work.
2026-07-06 01:16:05 -04:00
rzen ddd5631ef2 Add WorkoutsTests unit test target
No unit test target existed (testTargets: [] in scheme). Adds a
bundle.unit-test target wired into the Workouts scheme, with a smoke test
that round-trips SplitDocument through IndieSync's DocumentCoder — the
same codec DocumentFileStore uses for every iCloud document.
2026-07-05 10:23:08 -04:00
rzen 7dce2aeea6 Repoint build stamping at the app-versioning skill's script
Replaces the repo-local Scripts/update_build_number.sh (which git-tagged
on every build) with the shared update_build_info.sh referenced in place
from indie-skills: same commit-count CFBundleVersion and BuildDate, adds
BuildHash, and makes release tagging opt-in via RELEASE_TAGGING=1.
Verified BuildDate/BuildHash/CFBundleVersion land in the built app.
2026-07-05 08:06:33 -04:00
rzen 1f2df491db Migrate the sync file layer onto the IndieSync package
Replaces the app-local copies of the extracted storage core with the
IndieSync 0.1.0 package (pinned from the new tag): ICloudFileManager ->
DocumentFileStore + TombstoneStore, ICloudFileMonitor ->
MetadataObserver (batched events with the content-date churn gate),
and the shared ULID / DocumentCoder / Tombstone / VersionedDocument
pieces. SyncEngine stays app-specific and is rewired onto the package
types; documents rename currentSchema -> currentSchemaVersion to adopt
the package protocol. ULID.make() remains as a shim minting the string
form the app keys on.

Behavior-preserving: identical JSON bytes (same coder config), same
stub wire format (kind encodes as the same strings), same soft-delete
ordering, same 30-day prune. WorkoutDocument keeps its local-calendar
month bucketing rather than adopting TimeBucketedLayout's UTC paths --
changing derivation would strand existing files. The watch target
links the package too (ULID + DocumentCoder via Shared); iOS, watchOS,
and widget targets all build.
2026-07-05 08:04:29 -04:00
rzen e00b97d587 Generate shared schemes for the iOS and watch apps
XcodeGen wrote no scheme files by default, so Xcode fell back to stale
xcuserdata that omitted the iOS app — leaving the Workouts scheme absent
from the run destination dropdown. Declare scheme blocks on both app
targets so real shared schemes are generated.

Claude-Session: https://claude.ai/code/session_01SCv7zvGFcKy47KSTnTLxRe
2026-06-21 11:35:34 -04:00
rzen 192aa6f95a Add an Apple Watch face complication that opens the app
A static WidgetKit accessory widget (launcher only — no data sharing,
App Group, or entitlements). Tapping any accessory widget opens its
containing app, so this is enough to place a Workouts button on the
watch face. Supports the circular, corner, inline, and rectangular
accessory families.

New 'Workouts Watch Widget' app-extension target embedded in the watch
app via project.yml.
2026-06-20 22:15:26 -04:00
rzen 4a28d6a300 Bump IndieAbout to 0.2.2 (build number in changelog link)
The version number and build number now both link to the changelog
in the About screen; the build date stays plain.

Claude-Session: https://claude.ai/code/session_01A9CfUa4E9Zd5swfoNsYPs7
2026-06-20 17:18:41 -04:00
rzen b5fc366fcf Bump IndieAbout to 0.2.1 (version-number-only changelog link)
Picks up the package fix so only the version number in the About
screen links to the changelog, not the build number or build date.

Claude-Session: https://claude.ai/code/session_01A9CfUa4E9Zd5swfoNsYPs7
2026-06-20 15:53:57 -04:00
rzen 1a0c484177 Refresh CLAUDE.md for 2.0; link changelog from the version in About
- Rewrite the stale CoreData/CloudKit architecture docs to describe the
  2.0 iCloud Drive document architecture (JSON source of truth + SwiftData
  cache, SyncEngine/ICloudFileManager/ICloudFileMonitor, ULID document/
  entity/mapper split, AppServices DI, WatchConnectivity bridge, XcodeGen/
  Swift 6/iOS 26, real directory layout).
- Add an "Authoring the Changelog" section documenting the end-user,
  one-paragraph-per-entry, derive-but-rewrite-from-git-log convention.
- About screen: make the version line open the changelog (IndieAbout
  0.2.0 changelogDocument) and drop the separate "Changelog" link; bump
  the IndieAbout dependency to from: 0.2.0.

Claude-Session: https://claude.ai/code/session_01A9CfUa4E9Zd5swfoNsYPs7
2026-06-20 15:33:09 -04:00
rzen 6d882998fa Auto-set the build number from git; bump to 2.1
Add Scripts/update_build_number.sh (the shared indie build script) and wire it into both targets as a postBuildScripts phase. On each build it sets CFBundleVersion to the git commit count, writes a BuildDate key (surfaced by IndieAbout), and tags the marketing version. Run as a post-build phase rather than post-compile so it lands after the Info.plist/dSYM exist, which also avoids an Xcode 26 build cycle. Bumps MARKETING_VERSION to 2.1.

Claude-Session: https://claude.ai/code/session_01A9CfUa4E9Zd5swfoNsYPs7
2026-06-20 14:17:21 -04:00
rzen 90271952f3 Make the iPhone app iPhone-only
Set TARGETED_DEVICE_FAMILY to 1 for the iOS target — it was universal (1,2) but
isn't an iPad experience, which forced an iPad screenshot requirement at
submission. The Apple Watch app is a separate target (family 4) and is unaffected.
Removes the iPad screenshot set from the listing metadata.

Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
2026-06-19 19:30:09 -04:00
rzen d5915a9552 Add HIIT watch runner, rest-time setting, and HealthKit watch auto-launch
- Redesign the watch app into an active-workout runner: a root gate shows the
  in-progress workout's exercises or prompts to start one on iPhone, and each
  exercise runs as a horizontally-paged HIIT cycle (count-up work, count-down
  rest with final-three-second haptics + auto-advance, One More / Done on the
  last set). Replaces the old history list.
- Add a configurable rest-between-sets duration in iPhone Settings (default 45s),
  synced to the watch over WatchConnectivity.
- Launch the watch app into the session when a workout starts on the phone via
  HealthKit (startWatchApp); the watch runs an HKWorkoutSession for foreground
  runtime and ends it when the workout finishes. Adds the HealthKit entitlement +
  Health usage strings on both targets and WKBackgroundModes on the watch.

Claude-Session: https://claude.ai/code/session_018gg69MaUetDNzWzBXisfMV
2026-06-19 16:16:44 -04:00
rzen 85d0eaddbb Workouts 2.0: re-base persistence on iCloud Drive documents
Replace Core Data + NSPersistentCloudKitContainer + App-Group store +
WatchConnectivity dictionary sync with the QuickRabbit iCloud-documents
architecture:

- iCloud Drive JSON documents are the sole source of truth (one file per
  aggregate: Splits/<ULID>.json, Workouts/YYYY/MM/<ULID>.json), with a
  rebuildable SwiftData cache populated only by an NSMetadataQuery observer
  and a connect-time reconcile. Soft-delete tombstones; hard iCloud gate.
- Shared model layer (ULID, Codable *Documents + stateless mappers, @Model
  cache entities, SwiftData container) compiled into both targets.
- New iPhone<->Watch bridge over WatchConnectivity keyed by ULIDs; the phone
  is the sole writer of iCloud Drive, the watch round-trips documents.
- AppServices DI + iCloud-required root gate; Swift 6 strict concurrency.
- Starter splits generated on demand from the bundled YAML catalogs.
- Migrate to XcodeGen (project.yml), iOS 26 / watchOS 26; CloudDocuments
  entitlement (drop CloudKit/App Group/aps-environment).
- Duration stored as Int seconds (was a Date epoch hack); fix workout
  end-on-create, undismissable delete dialog, toolbar-hiding nav stacks,
  and the Settings placeholder.
- Add README/CHANGELOG/LICENSE, .gitignore, refreshed REQUIREMENTS, and the
  Scripts/ TestFlight pipeline (release.sh + ASC API scripts).

MARKETING_VERSION 2.0.
2026-06-19 14:25:27 -04:00