Build 1 — Foundation

The first build that reached TestFlight: the functional base of Aski (schema, runner, watch, Strava, library), the Quiet Sport redesign across the whole app, History + pain trend, the Coach → Aski rename, and the marketing site.

← All changes

Foundation

The functional groundwork before any visual redesign — six phases in one intense day:

  • Schema + decoder. Typed workout model with per-field back-compat (decodeIfPresent everywhere). Full test coverage for every optional field.
  • iCloud Drive import. NSMetadataQuery watches Aski/Documents/workouts/ and pulls new files in without an explicit sync trigger. SwiftData persists everything locally.
  • Runner + session logging. Sets, reps, timer, per-set pain slider (later removed in favor of the session-end pain gate).
  • Watch + HealthKit. HKWorkoutSession owned by the phone, never the system Workout app — HealthKit would double-count. HR samples stream over WatchConnectivity.
  • Strava OAuth. PKCE flow, tokens in Keychain, one-way upload after each completed session.
  • Exercise library. AVPlayer for local videos, tag filter, search, cross-references, and a YouTube-search fallback for exercises without an explicit videoUrl (opens in an in-app Safari view).

No third-party dependencies at any point.

Quiet Sport — the design system

  • Tokens. Three type roles (display / body / data), Motion springs, a component library, and a debug gallery.
  • Palette. Quartz canvas, pine and graphite text, chalk hairlines, coral as the single accent.
  • Hero numerics scale up to 128 / 160 / 200pt — the runner’s single active set, the 160pt rest-timer glyph, the set-complete bloom.
  • Nine new atoms. Caps, DSRule, SegmentedProgressBar, TickRuleBar, PainSlider, StatCell, SpecCell, InlinePickerSheet, HeroNumeric.
  • Free font stack bundled. Space Grotesk, Inter, JetBrains Mono.

The default UI wasn’t helping when the phone sat six feet away on a tripod. The redesign codifies what not to build alongside what to ship — bigger numerics for tripod-distance reading, fewer decorative borders, a flatter canvas that keeps coral as the only pull.

Runner + rest timer + watch

  • Inline set rows, a “previous” ghost for 2-tap repeat logging, persistent tempo bar with optional metronome, contraction type on every rep set.
  • Live Activity rest timer on Lock Screen and Dynamic Island, audible tick on the last ten seconds, gong at zero.
  • Watch shows HR + elapsed + rest haptic — nothing else. Protocol versioned for forward-compat. Tempo haptics are distinct per phase (.directionDown for the eccentric, .click for pauses, .directionUp for the concentric). For duration-based exercises the wrist shows a coral progress ring and the remaining seconds; during rest it shows a numeric counter, the last three tick with .directionUp, zero fires .notification.

Rehab features

  • End-of-session pain gate + next-morning reactivity check.
  • Silbernagel flag on the summary when the threshold is crossed.
  • PR-mute toggle so the rehab user isn’t celebrated on every re-tested lift.

First-workout sweep

  • Auto-advance after the last set’s rest.
  • “SET N OF M” hero, drag-to-minimize rest timer, propagate reps / weight / duration edits to remaining sets with Undo.
  • Working duration editor with a Start button and overtime display; single coral Finish on the last set.
  • The phone now owns the canonical HKWorkout (wall-clock start/end), fixing the 16-second-workout bug. New StravaUploadSource setting (Direct / Via Apple Health / Off) avoids double-counting.
  • Watch handoff via startWatchApp; phone audio gated behind a setting when the watch is paired.
  • Optional in-session form video per set (60s cap, flip camera), stored under Documents/Videos/{sessionId}/, surfaced as chips on the summary. Off by default.
  • Five bundled routines (Beginner A/B, GZCLP Press, PPL Push, Bodyweight Full-body). Three-stage onboarding (welcome → path → six-question quiz), every card skippable, ≤90 seconds to first logged set.

History + per-exercise progression

  • TabView shell. Workouts, History, and Settings live in separate tabs with isolated navigation stacks. The tab bar hides during a workout.
  • Session history. Reverse-chronological list of finished sessions.
  • Pain trend chart. 30 / 60 / 90-day view with three series: session-end pain, pain during load, and next-morning pain. A coral flag marks sessions that crossed the Silbernagel threshold.
  • Per-exercise progression. Load-over-time chart (kg for rep-based, longest hold for duration-based) on any exercise in the library.

Rehab loading needs a memory. A quiet, passive view of what the pain did and where the load trended makes week-over-week decisions easier — without streaks, badges, or nudges.

Manage workouts

  • Edit and delete a workout directly from the detail view.
  • Robust iCloud re-scan with more aggressive recovery from missed file-change events.
  • Clipboard paste: copy a JSON you generated in an AI chatbot, open Aski, and import it with one tap without ever touching iCloud.
  • Strava upload mode in Settings: Always (default) / Ask / Never.
  • Structured Weight type (value + unit) across the schema and model, eliminating ambiguous conversions everywhere a weight is read. Shared ±-stepper between the runner and the planner, with haptic feedback on each tap and long-press to scrub.
  • Muscle groups per exercise. Restricted 20-token enum (chest, quads, hamstrings, rear-shoulders, …) surfaced in the runner, the detail view, and the library. Schema change is fully back-compat.
  • Strava description composed from the session — exercise list, total volume, key tempo cues — written at session close.

Polish

  • SetRow horizontal overflow on narrow iPhone widths fixed.
  • Delete-workout confirmation dialog now behaves correctly.
  • Bundled-sample deletions persist across app restarts.
  • App icon ships. Ink canvas + coral mark — same language as the app’s Quiet Sport aesthetic.

Renamed to Aski

  • New bundle IDs across all four targets: com.casvanderhoven.aski, …aski.watchkitapp, …aski.tests, …aski.restwidget. URL scheme is now aski:// (Strava OAuth callback included). The iCloud workout container moves to iCloud~com~casvanderhoven~aski.
  • Website now lives at aski.fit — a custom domain attached to Cloudflare Pages. The auto-generated coach-workout-app.pages.dev URL still resolves as a fallback.
  • The on-icon name stays Aski. The App Store listing is Aski Strength because Aski alone was already taken — search results say one thing, your home screen says another. By design.
  • “Coach notes” / the coachNotes JSON field are deliberately kept — those are generic strength-coaching terminology (the program designer’s notes), not stragglers from the rebrand.

“Coach” was generic on the App Store and ironically required a disclaimer (“Yes, the app is called Coach. No, it doesn’t coach you — that’s the point”). “Aski” is short, distinctive, and lets the product stand on its own without the explainer.

Marketing site + design docs

  • Public site on Cloudflare Pages: landing page with the core philosophy, Use with an AI chatbot (the workout JSON schema is downloadable so anyone can generate a training plan in any chatbot and drop the result into iCloud), App-Store-ready Privacy policy, a Support page with FAQ, and this changelog.
  • DESIGN.md and CLAUDE.md capture the invariants that should never silently change — ≤2-tap mid-set logging, resilient rest timer, watch as HR strap, session-end pain gate only, 90-second onboarding, no gamification, personalization over streaks. A change that crosses any of these should surface itself, not slip in quietly.