{"id":52014274,"url":"https://github.com/dredfort42/dredfit","last_synced_at":"2026-08-04T23:00:28.968Z","repository":{"id":371773417,"uuid":"1302748179","full_name":"dredfort42/dredfit","owner":"dredfort42","description":null,"archived":false,"fork":false,"pushed_at":"2026-08-01T18:37:46.000Z","size":24670,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-08-01T19:06:13.285Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dredfort42.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2026-07-16T12:17:36.000Z","updated_at":"2026-08-01T18:37:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dredfort42/dredfit","commit_stats":null,"previous_names":["dredfort42/dredfit"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/dredfort42/dredfit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredfort42%2Fdredfit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredfort42%2Fdredfit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredfort42%2Fdredfit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredfort42%2Fdredfit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dredfort42","download_url":"https://codeload.github.com/dredfort42/dredfit/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dredfort42%2Fdredfit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36291062,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-04T02:00:06.901Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-31T21:00:22.846Z","updated_at":"2026-08-04T23:00:28.961Z","avatar_url":"https://github.com/dredfort42.png","language":"Swift","funding_links":[],"categories":["Health"],"sub_categories":["Fitness"],"readme":"# Dredfit\n\n[![CI](https://github.com/dredfort42/dredfit/actions/workflows/ci.yml/badge.svg)](https://github.com/dredfort42/dredfit/actions/workflows/ci.yml)\n[![Lint](https://github.com/dredfort42/dredfit/actions/workflows/lint.yml/badge.svg)](https://github.com/dredfort42/dredfit/actions/workflows/lint.yml)\n[![Localization](https://github.com/dredfort42/dredfit/actions/workflows/localization.yml/badge.svg)](https://github.com/dredfort42/dredfit/actions/workflows/localization.yml)\n[![CodeQL](https://github.com/dredfort42/dredfit/actions/workflows/codeql.yml/badge.svg)](https://github.com/dredfort42/dredfit/actions/workflows/codeql.yml)\n\n[dredfit.com](https://dredfit.com/) · [App Store](https://apps.apple.com/app/id6791739610)\n\n**Adaptive bodyweight training for iOS. Zero setup — open the app, your workout is ready.**\n\nDredfit works like a thermostat. There is no quiz, no goal picker, no timer settings. The app starts you at a conservative minimum and regulates itself: it proposes a plan, you do it, you answer one question — *how did it go?* — and the next workout adjusts. Tell it what you actually managed on the first workout and it lands on your real level right away; answer with a rating alone and it converges over a handful of sessions. Either way it then keeps the load right at the edge of what you can do, which is where progress happens.\n\nNo equipment required. No account. No network. Your entire training state is a session counter plus two small integers per movement pattern — a level and a fail-streak — on your device.\n\n## How it works\n\nThe engine (`DredfitCore`) is built on three mechanisms:\n\n**One integer per movement pattern.** Progress in each movement pattern is a single level `L ∈ [0, 47]`. The level *encodes* the exercise variation, the rep target and the set count all at once:\n\n```text\nband = L / 8                       # 0…5\ntier = min(4, 1 + band)            # which variation: 4 tiers from knee push-up to archer push-up\nsets = 3 + max(0, band - 3)        # 3 sets (L ≤ 31), 4 sets (32…39), 5 sets (40…47)\nreps = repStart[tier] + L % 8      # floors 8/6/5/4 → 8…15 on tier 1, 4…11 on tier 4\nhold = holdStart[tier] + L % 8 * 5 # floors 20/15/15/10 s → 20…55 s on tier 1, 10…45 s on tier 4\n```\n\nDouble progression falls out of the encoding for free: top out a tier's rep range and the next level up switches you to a harder variation, restarting low. The floor drops as the variation gets harder, so a new tier lands softly instead of jumping from an easy fifteen straight into a hard eight. Above tier 4 the same mechanism keeps working by adding a set instead of a variation, so the ceiling is 5 × 11 rather than a dead end. The level history *is* the progress chart.\n\n**Deterministic rotation.** Every session has 6 exercises. One slot is always a pull, for shoulder health — the tested invariant is that weekly pull volume stays at least 70% of combined pushing volume. The other 8 patterns rotate through the remaining 5 slots so that over any 8 consecutive sessions each appears exactly 5 times. No randomness anywhere: the same state always generates the same workout.\n\n**A feedback regulator.** After the workout, one tap: *tough / on plan / easy* → −1 / +1 / +2 levels for the session's patterns. During the workout you can record a per-exercise actual (\"went differently\") that overrides the rating for that pattern — upward moves are capped at +2 per session, downward moves are not. Three consecutive shortfalls on a pattern trigger an automatic deload (−3). A skipped exercise is neutral: its level and streak are left untouched rather than judged. Levels never go below 0. Time is the one input that isn't a tap: a week away eases every pattern down a step without saying anything about it, and past two weeks the app offers to meet you lower still — the longer the break, the further down. That's the whole model.\n\n**The pull-up bar module.** Vertical pulling is the one honest gap of a no-equipment format. Turn the bar on in settings and every other session swaps the floor pull for a vertical one — bar hang, negative pull-up, partial, full pull-up — tracked as its own independent level. Turn it off and the branch freezes without losing progress.\n\nThe 40-exercise library is 10 patterns × 4 tiers: 8 rotating patterns (32), the fixed pull slot (4), and the bar branch (4). Classic calisthenics — squat to shrimp squat, knee push-up to archer push-up — each with reviewed, plain-language technique steps and common mistakes, in English, Russian, Spanish and Brazilian Portuguese.\n\n## The app\n\nSwiftUI, iOS 17+, iPhone, portrait. Three tabs, a settings sheet reachable from all of them, and one flow:\n\n- **Today** — the generated plan and one Start button, with a short version under it for the days there is no time: three of the six exercises, the rest recorded as honest skips. A completed state once you're done, with a card for the next workout. If a session was cut short by iOS reclaiming memory or a swipe-kill, this is where it is offered back.\n- **Workout** — warm-up, then one exercise at a time: a big number, set dots, a date-based rest ring with a 3-2-1 audio countdown, a hold timer for static exercises, in-the-moment actual adjustment, per-exercise skip. Unilateral holds get a counted pause to switch sides. A cool-down closes the session before the rating — six stretches, half of them chosen from what you just trained. Every position of both guided blocks opens with a five-second \"Get ready\" naming what is coming, skippable by a tap for anyone already in place. Every countdown is wall-clock based, so locking the phone mid-rest loses nothing — and the position is snapshotted on every transition, so neither does losing the process. Leaving asks first, and offers to finish early rather than discard.\n- **Rating** — the one question on three equal cards, with an honest summary of anything you adjusted, skipped or left unfinished.\n- **Calendar** — filled days are tappable history (what you did, with actuals and skips); *upcoming* planned days are outlines; today gets an accent ring; rest days a quiet fill; missed days are left as plain dimmed numbers, deliberately unmarked and unshamed.\n- **Progress** — total level, a line chart across sessions with per-pattern projections, a weekly summary, per-pattern level bars.\n- **Settings** — rest days, the pull-up bar, sounds and haptics, a reminder on training days, Apple Health export, backup export/import.\n\nBeyond the app itself: **widgets** in every size — small, medium and large on the home screen, all three lock-screen accessories — showing workout / done / rest day and flipping at midnight without the app running; a **Live Activity** that puts the rest countdown on the lock screen and in the Dynamic Island, **Apple Health** export (write-only — completed workouts become strength-training samples, nothing is ever read), and **local reminders** on training days.\n\nState is one JSON file in Application Support. Old records survive every update — new fields are optional, migrations are decode-level. Backup export/import round-trips the whole thing as plain JSON.\n\n## Architecture\n\n```text\nDredfitCore/            Swift package — the engine, pure functions, no UI imports\n  Engine.swift          state → session; state × session × feedback → state\n  Library.swift         40 exercises, hand-written to mirror the JS reference\n  Resources/            String Catalog (en source; ru, es, pt-BR translations)\n  Tests/\n    EngineTests.swift    invariants: encoding, rotation, balance, deload, caps\n    EngineV23Tests.swift zero-level calibration, comeback, per-tier rep/hold floors\n    EdgeCaseTests.swift  boundary behavior\n    GoldenTests.swift    bit-for-bit match against the reference implementation\n    Fixtures/golden.json\n\nDredfit/                SwiftUI app target\n  AppStore.swift        the only mutable state + JSON persistence; also owns\n                        the Health export flags and the in-progress snapshot\n  HealthStore.swift     write-only HealthKit bridge, stateless\n  LiveActivityController.swift, WidgetBridge.swift\n  Views/                Today, WorkoutFlow, Feedback, Progress, Calendar,\n                        History, Technique, NextWorkout, Settings,\n                        Onboarding, HowItWorks, Milestone, ShareCard\n  Design/Theme.swift    ink scale + one accent (and its soft tint)\n\nDredfitWidgets/         widget extension — TodayStatusWidget, RestLiveActivity\nShared/                 the App Group snapshot contract\n\ndocs/                   the dredfit.com site — GitHub Pages, static, no build\n  index.html            landing + privacy.html, mirrored under ru/ es/ pt-br/\n  CNAME, robots.txt, sitemap.xml, og.png\n```\n\nThe engine was first written and verified as a JavaScript reference (8,009 property checks and scenario simulations), then ported to Swift. `golden.json` is the reference's recorded trace — 143 steps across 10 scenarios — and the Swift port must reproduce it exactly. Changing engine behavior means changing the reference first, re-verifying, regenerating fixtures, then porting. Plausible-but-different is a failing test, not a judgment call. (The JS reference lives outside this repository; the recorded fixture is what ships.)\n\n## Testing\n\nThree layers, 301 automated tests:\n\n| Layer | Count | What it covers |\n| --- | --- | --- |\n| Core invariants + golden | 63 | encoding bijectivity, rotation properties, pull:push balance, deload timing, override caps, skip semantics, bar-branch independence, lenient state decode, feedback replay safety, the silent decay and its non-stacking with the comeback, reference parity |\n| App unit tests | 195 | persistence round-trips, corrupted-file quarantine, a frozen journal and its reload, legacy-record migration, in-progress snapshot validity, rest-day calendar math, Health export ordering and idempotence, the one-shot reminder window, day-anchor rollover, the widget snapshot, its backward compatibility and the per-day timeline words, the variation-debut badge, share-card wording and its level curve, the short workout's picks, cool-down composition, the get-ready transition and the minutes both blocks fit into, the jubilee's then-and-now comparison, the life-benefit override |\n| UI tests | 43 | the full workout flow, in-workout adjustment, hold mis-tap grace, resume after a kill, the three exit paths, the short workout, the cool-down, the side-switch pause, the get-ready transition, position technique sheets, history, relaunch persistence, and the release smoke walked end to end in English and Russian |\n\nPlus [TESTPLAN.md](TESTPLAN.md): a manual QA checklist (locale passes, date rollover, backgrounding during rest, device-only integrations) and a registry of found issues with their status.\n\nCI runs the unit suites on every push — that is the gate for merges and releases. UI tests are slow and occasionally flaky on shared runners, so they run nightly on their own and gate nothing; they are run locally before cutting a release branch instead.\n\n## Building\n\n1. Open the Xcode project (iOS 17+, Xcode 15+).\n2. The `DredfitCore` package is local — add it via *File → Add Package Dependencies → Add Local* if not already linked.\n3. `⌘U` on the package first: golden tests are the gate.\n4. Run on any iPhone simulator. UI tests expect an English locale and drive the app through DEBUG-only launch flags — `--uitest-reset` for a clean slate, `--uitest-fast` to collapse rest countdowns, `--uitest-long-transition` to hold a \"Get ready\" transition open so the tests that tap it are not racing its five seconds, and a few that seed a specific state (`--uitest-session2`, `--uitest-milestone`, `--uitest-comeback`, `--uitest-restday`, `--uitest-onboarding`).\n\n## Localization\n\nEnglish is the source language; Russian, Spanish and Brazilian Portuguese each ship complete — 544 translated keys across four String Catalogs, including all exercise technique. English base strings live inline at each call site; translations live in the catalogs. Every translation is idiomatic rather than literal: Russian avoids anglicisms and calques and uses `е` rather than `ё` throughout; Spanish and Brazilian Portuguese address the reader informally (`tú` / `você`) and take their exercise and pattern vocabulary from the same glossary as the [marketing site](https://dredfit.com/), which ships in the same four languages.\n\n## Design principles\n\nOne accent color. System typography. No gamification, no streaks, no guilt: a missed day stays a quiet outline in the calendar, because the engine adapts anyway. The app asks the user exactly one question per day, and it's answerable with one thumb.\n\nNo third-party dependencies, no network calls, no analytics of any kind — the App Store privacy label \"Data Not Collected\" is literally true.\n\n## Continuous integration\n\nEvery push and PR runs the gate — unit tests (Core + app), SwiftLint, and a\nString Catalog completeness check. UI tests run nightly and gate nothing;\nCodeQL, secret scanning, and PR-title linting run alongside as advisory checks. Releases are tagged `vX.Y.Z`, which publishes a GitHub\nRelease from the matching `CHANGELOG.md` section. Full details, the release\nprocedure, and how to enable branch protection are in\n[`.github/WORKFLOWS.md`](.github/WORKFLOWS.md).\n\n## Status\n\nShipping. Working on device, tested across all four locales.\n\nPlanned work and deliberately-rejected ideas are tracked in the project backlog (`instructions/BACKLOG.md`, kept alongside the engine specification outside this repository).\n\n## Disclaimer\n\nDredfit is a general-fitness tool for healthy adults, not medical advice. Sharp or joint pain means stop; consult a physician before starting if you have cardiovascular or joint conditions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdredfort42%2Fdredfit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdredfort42%2Fdredfit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdredfort42%2Fdredfit/lists"}