A local-first, privacy-focused workout tracker and progressive overload manager built with React Native, Expo, TypeScript, and SQLite.
Track workouts, build custom routines, monitor strength progression, and analyze training volume — without user accounts, advertisements, telemetry, or cloud dependencies.
| Screen | File Path | Highlights |
|---|---|---|
| Dashboard & Streak | assets/screenshots/01_dashboard.png |
7-day compliance strip, current day split, interactive streak badge with all-time top-5 streak history modal, one-tap session launch |
| Exercise Library | assets/screenshots/02_exercises-tab.png |
Global movement catalog, muscle group tagging, instant search, CRUD actions |
| Settings & Data Backup | assets/screenshots/03_settings.png |
Rest timer configuration, KG/LBS preference toggle, JSON database export and import |
| Analytics & 1RM Progression | assets/screenshots/04_analytics.png |
Session/week/month progression filters, peak-benchmark aggregation, stacked date axis labels, 30% chart headroom, two-line exercise title wrapping, estimated 1RM calculations |
Every commit and pull request is automatically validated through a GitHub Actions CI pipeline with strict quality gates across 12 test suites and 179 passing automated tests:
Code Commit / Pull Request
│
▼
GitHub Actions
│
├── 1. Clean Dependency Installation (`npm ci --legacy-peer-deps`)
├── 2. TypeScript Compilation Check (`npx tsc --noEmit`)
└── 3. Automated Jest Test Suite (`npm test -- --ci --watchAll=false`)
│
▼
Quality Gate Passed (0 errors / 12 suites / 179 tests passing)
Node requirement: the service test suites execute the real schema on Node's built-in SQLite engine (
node:sqlite), so tests require Node.js 22.13+ (CI runs Node 24).
| Quality Gate | Verification Command | Scope & Verified Logic | Result |
|---|---|---|---|
| TypeScript Strictness | npx tsc --noEmit |
Strict compilation across all screens, hooks, services, components, and types | 0 errors |
| 1RM Epley Formula |
npm test (calculations.test.ts) |
|
Passed |
| Volume Aggregation |
npm test (calculations.test.ts) |
Multi-exercise set summation, malformed set input sanitization | Passed |
| Weight Unit Conversion |
npm test (calculations.test.ts) |
|
Passed |
| Streak & Gap Math |
npm test (dateUtils.test.ts) |
Consecutive days, same-day multi-session deduplication, |
Passed |
| All-Time Streak History |
npm test (dateUtils.test.ts) |
calculateAllStreaks: single runs, multi-streak resets, zero workouts, same-day sessions, descending ranking |
Passed |
| Analytics Aggregation & Headroom |
npm test (analyticsCalculations.test.ts) |
SESSION/WEEK/MONTH bucketing, Monday-Sunday ISO weeks, monthly peaks, stacked axis labels, 30% chart headroom (maxValue = rawMax * 1.30, overflowTop = 30), bucket caps |
Passed |
| Weekly Target Compliance |
npm test (dateUtils.test.ts) |
ISO 8601 week transitions, month and year boundary transitions, target met flags | Passed |
| Relative Date Formatting |
npm test (dateUtils.test.ts) |
"Today", "Yesterday", "X days ago", ISO string sanitization, fallback formats | Passed |
| Schema Versioning & Migrations |
npm test (database.test.ts) |
PRAGMA user_version stamping (0 -> 1), legacy pre-versioning repair, relaunch idempotency |
Passed |
| Referential Integrity & Cascades |
npm test (database.test.ts) |
Foreign-key cascade enforcement on exercise deletion (CASCADE DELETE), orphan prevention, FK-safe split and template writes |
Passed |
| Transactional Writes |
npm test (database.test.ts) |
Routine day insertion/replacement, workout set logging, idempotent re-saves, mid-transaction rollback | Passed |
| SQLite Concurrency & Retries |
npm test (database.test.ts, useAppStore.test.ts) |
WAL journal mode, 5000ms busy timeout, singleton initialization promise with rejection-clearing on boot, writes held back until in-flight store reads settle | Passed |
| Cold-Start Race Prevention |
npm test (DashboardScreen.test.tsx, useAppStore.test.ts) |
Initial screen data fetching gated behind store isLoading state, preventing concurrent query collisions during cold start schema initialization |
Passed |
| Crash-Recovery Drafts |
npm test (workoutDraftService.test.ts) |
Draft save/retrieve round-trip, malformed payload rejection, atomic discard vs. debounced auto-save, concurrent hydration | Passed |
| Backup Portability & Schema Parity |
npm test (backupService.test.ts) |
JSON export/import round-trip parity, strict camelCase schema preservation (id, name, muscleGroup, createdAt), malformed/partial payload rejection, rollback on invalid restore |
Passed |
| Alarm Resilience (Android 13+) |
npm test (notifeeTimerService.test.ts) |
Exact-alarm permission probing, single per-session settings redirect, inexact fallback, unique per-run ids, cancellation sweep | Passed |
| Streak Sheet UI |
npm test (StreakHistoryModal.test.tsx) |
Ranked rows from real logged days, singular/plural day labels, same-day deduplication, dynamic placeholder slots ("More streaks to come"), (X) dismissal |
Passed |
| Rest Timer Picker UI |
npm test (RestTimerModal.test.tsx) |
Saved-default loading, interactive minute (0–60) and second (0–59) scroll/stepper clamping, disabled controls while running, exact seconds handed to scheduler | Passed |
| Dashboard Screen Integration |
npm test (DashboardScreen.test.tsx) |
Header badge renders live streak and triggers all-time streak modal; cold-start data fetch gated behind store isLoading state |
Passed |
| Analytics Screen Integration |
npm test (AnalyticsScreen.test.tsx) |
Default SESSION view, WEEK/MONTH re-aggregation, peak benchmarks, stacked date labels, 30% headroom, responsive two-line title wrapping (numberOfLines={2}) with unshrinkable metric toggle (flexShrink: 0), streak sheet trigger |
Passed |
| Dead-Code & Tree-Shaking | Verified clean build | Pruned 22 files and 26 dead declarations/unused imports via refactor/dead-code-prune, verified with tsc --noUnusedLocals and babel-preset-expo
|
Passed |
| Code Coverage | npm test -- --coverage |
Statements/lines/functions: calculations.ts, dateUtils.ts, analyticsCalculations.ts at 100%; services at 90%+; all 12 suites green (179 tests) |
Passed |
- Embedded Storage: All data (exercises, day templates, split definitions, workout sessions, and set entries) is persisted locally via
expo-sqlite. - Concurrency & WAL Mode: Eliminates
NativeStatement.finalizeAsyncanddatabase is lockedruntime collisions by enforcingPRAGMA journal_mode = WAL;,PRAGMA foreign_keys = ON;, and a 5000msbusy_timeout(PRAGMA busy_timeout = 5000;). - Singleton Connection Handling: Hardens connection handling using a singleton initialization promise in
src/services/database.tswith explicit rejection-clearing to ensure safe retries on interrupted boots without leaving dead lock handles. - Race Condition Prevention: Cold-start query collisions are eliminated by gating screen data hydration (
DashboardScreen.tsx) behind the global store'sisLoadingstate, preventing concurrent query collisions during cold start schema initialization. - Schema & Seeding Stability: Retains a strict camelCase relational schema (
id,name,muscleGroup,createdAt) acrossgym_tracker.dbto protect legacy data parity, migration stability, and backup/restore reliability. - Relational Integrity & Cascades: Foreign-key constraints and cascading rules guarantee safe deletion of exercises without orphaned logs or corrupted routine templates.
- Versioned Schema: Initialization checks
PRAGMA user_version; a fresh database builds the base schema, repairs any pre-versioning legacy state, and stamps version1, skipping redundant DDL operations on subsequent launches. - Offline-First Operation: Zero network calls or remote server dependencies; full functionality is available completely offline.
- Real-Time Session Persistence: Active set inputs, weight, reps, and exercise ordering are continuously persisted to local AsyncStorage draft storage (
@active_workout_draft). - Crash Recovery Banner: When returning to the app or navigating screens during an active session, a persistent "Workout in Progress — [Resume] | [Discard]" banner allows immediate session recovery, preventing data loss across background app terminations.
- Atomic Log Finalization: Completing a session writes all completed sets into SQLite in a single transaction before purging the working draft.
- Interactive Workout Rest Timer: Added interactive scroll/stepper controls for minutes (0–60) and seconds (0–59) directly within the active workout session before timer activation, integrating seamlessly with
notifeeTimerService. - Configurable Countdown: Minute and second rest intervals customizable per session and stored in persistent preferences.
- Robust Background Notifications: Uses
@notifee/react-nativeto reliably schedule and deliver timer completion notifications even when the app is completely backgrounded or closed. - Android 13+ Alarm Resilience: Exact-alarm capability is probed before every schedule; if the
SCHEDULE_EXACT_ALARMspecial-access permission is restricted, the user is routed once per session to the system "Alarms & reminders" screen and the alert automatically falls back to the OS's inexact scheduling path instead of silently failing. - Native Haptic Feedback: Triggers a 3-pulse vibration pattern on Android (
Vibration.vibrate([0, 400, 200, 400])) and success haptics on iOS (expo-haptics) when the countdown reaches zero.
- KG <-> LBS Dynamic Unit Engine: First-launch onboarding prompt and instantaneous toggle in Settings. Converts canonical database kg storage to display units on the fly across workout logging, historical benchmarks, and progression charts.
- Customizable Routine Splits: Multi-muscle group assignment per day of the week (Monday through Sunday) supporting PPL, Upper/Lower, Arnold Split, Bro Split, or fully custom routines.
- Previous Session Benchmarks: Dynamically fetches and displays the exact weight and rep numbers achieved during the previous workout for each exercise.
- Pre-populated Pool: The app ships with a comprehensive pool of standard exercises, fully categorized by muscle groups (Chest, Back, Legs, etc.), allowing users to immediately begin logging without manual data entry.
- Custom Exercises: Users can seamlessly add custom movements directly into the local catalog, which are fully supported across all split assignments, statistics, and workout logs.
- Full Database Export: Serializes the entire relational database into a standardized, human-readable JSON schema and opens the native device share sheet via
expo-sharing. - Validated Restoration: Imports JSON backup files via
expo-document-picker, validates schema structure, and restores exercises, splits, templates, and logs within atomic database operations.
- Top Streak Calculation Engine: Implemented
calculateAllStreaksinsrc/utils/dateUtils.tswith calendar-day gap analysis and same-day session deduplication to compute all historical runs. - Streak History Modal: Dedicated
StreakHistoryModal.tsxpresents the "Top 5 Streaks of All Time", with dynamic placeholder slots ("More streaks to come") for unrecorded ranks and plural-safe day labeling. - Interactive Modal Triggers: Interactive modal triggers on streak badges across both
DashboardScreenandAnalyticsScreenprovide instant access to streak history. - Analytics Header & Flex Wrapping: Resolved UI overflow issues where lengthy exercise titles collided with metric controls by implementing responsive two-line wrapping (
numberOfLines={2}) alongside an unshrinkable toggle container (flexShrink: 0) for[Max Weight | Volume]. - Dynamic Analytics Granularity & Chart Headroom: Reordered and hooked dynamic time grouping to
[ WEEK | SESSION | MONTH ], withSESSIONas default. Dynamic aggregation (analyticsCalculations.ts) plots session points or peak weekly/monthly benchmarks. Resolved chart top-border clipping by applying dynamic upper-bound headroom calculations (maxValue = rawMax * 1.30,overflowTop = 30). - Stacked Date Axis Labels: Added stacked two-line X-axis date labels via
labelComponent(e.g.,21 Sep,Wk 2 Sep,Sepwith'26underneath) to maintain visual clarity on dense progression timelines.
- Tree-Shaking & Cleanup: Pruned 22 files and stripped 26 dead declarations/unused imports across
src/via a verified Git branch (refactor/dead-code-prune), confirmed withtsc --noUnusedLocalsandbabel-preset-expo(dropping unusedReactimports under the modern JSX transform, unreferenced theme tokens, and unused local constants). - ABI Filtering & App Size Reduction: Integrated
expo-build-propertieswithandroid.buildArchs: ["arm64-v8a"]. Configured GradlereactNativeArchitecturesto strip redundant 32-bit and PC emulator ABIs (armeabi-v7a,x86,x86_64), reducing download APK size and lowering physical device storage footprint from universal bloat down to a streamlined native installation.
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Core Framework | React Native / Expo | RN 0.81 / Expo SDK 54 | Cross-platform mobile foundation |
| Language | TypeScript | 5.9 | Static type safety and data models |
| Database | expo-sqlite | 16.0 | Local embedded relational database |
| State Management | Zustand | 5.0 | Global in-memory UI and catalog state |
| Navigation | React Navigation | 7.x | Bottom tabs and native stack routing |
| Data Visualization | react-native-gifted-charts | 1.4 | Progression and volume line charts |
| Vector Graphics | react-native-svg | 15.12 | Chart and visual rendering |
| Background Alerts | @notifee/react-native | 9.1 | Exact alarm scheduling and background timer notifications |
| Local Draft Storage | AsyncStorage | 2.2 | In-progress workout draft persistence and user preferences |
| Build Configuration | expo-build-properties | 1.0 | Native ABI filtering (arm64-v8a) and Gradle architecture flags |
| Haptics & Vibration | expo-haptics | 15.0 | Timer completion feedback |
| Date Calculations | date-fns | 4.1 | ISO 8601 calendar and date arithmetic |
| Testing | Jest / jest-expo | Jest 30 | Automated unit testing |
| Continuous Integration | GitHub Actions | - | Automated linting, typecheck, and unit test CI pipeline |
counterapp/
├── __mocks__/ # Jest manual mocks (real SQLite engine, in-memory FS/storage)
├── assets/
│ ├── appicon.jpg # Application branding and launcher icon
│ └── screenshots/ # Application preview captures
├── src/
│ ├── components/ # Reusable UI elements (RestTimerModal, StreakHistoryModal, WorkoutDraftBanner, etc.)
│ │ └── __tests__/ # Rendered-component behavior suites
│ ├── constants/ # Static movement catalog (exercisePool.ts)
│ ├── hooks/ # Custom React hooks (useTimeSync, useWeightUnit)
│ ├── screens/ # Core application screens
│ │ ├── __tests__/ # Dashboard and analytics screen integration suites
│ │ ├── ActiveWorkoutScreen.tsx
│ │ ├── AnalyticsScreen.tsx
│ │ ├── DashboardScreen.tsx
│ │ ├── ExercisesScreen.tsx
│ │ ├── SettingsScreen.tsx
│ │ ├── SplitSetupScreen.tsx
│ │ └── WorkoutDaysScreen.tsx
│ ├── services/ # Local database, backup, notifications, preferences
│ │ ├── backupService.ts
│ │ ├── database.ts
│ │ ├── notifeeTimerService.ts
│ │ ├── restTimerPrefs.ts
│ │ ├── timerAlertService.ts
│ │ ├── weightUnitPrefs.ts
│ │ ├── workoutDraftService.ts
│ │ └── __tests__/ # Service integration suites (SQLite, drafts, backup, alarms)
│ ├── store/ # Zustand application store
│ │ └── __tests__/ # Store read/write ordering suite
│ ├── theme/ # Color palettes and visual constants
│ ├── types/ # TypeScript interface and type declarations
│ └── utils/ # Domain math, analytics aggregation, and date calculations
│ ├── analyticsCalculations.ts
│ ├── calculations.ts
│ ├── dateUtils.ts
│ └── __tests__/ # Jest unit test suites
├── App.tsx # Main entry point and navigation tree
├── app.json # Expo project configuration
├── eas.json # Expo Application Services build configuration
├── package.json # Project dependencies and test scripts
└── tsconfig.json # TypeScript configuration
- Node.js 22.13+ (Node 24 recommended — the service suites use
node:sqlite) - npm 9+
- Expo Go application or Android/iOS emulator
# Clone the repository
git clone https://github.com/ASSAS-Labs/Omega.git
cd Omega
# Install project dependencies
npm install# Start the Expo development bundler
npm run start- Press
ato open on an Android emulator / connected device. - Press
ito open on an iOS simulator. - Scan the Metro terminal QR code with Expo Go.
# Run TypeScript compilation check
npx tsc --noEmit
# Run Jest unit test suites with coverage report
npm test -- --coverageThe suite is split into three layers:
- Domain unit tests (
src/utils/__tests__/) — pure date arithmetic, streak math, aggregation, and unit conversion. - Service integration tests (
src/services/__tests__/) — exercise the data layer, crash-recovery drafts, backup portability, and notification scheduling. Native modules are replaced by the manual mocks in__mocks__/, and the SQLite suites run against a genuine in-memory SQLite engine, so foreign keys, cascades, and transaction rollbacks are really enforced during the run. - UI render suites (
src/components/__tests__/,src/screens/__tests__/) — render the streak sheet, rest-timer picker, dashboard, and analytics screens withreact-test-renderer(bundled withjest-expo) and assert the text a user actually sees plus the props handed to the chart and notification layers. The progression chart is doubled in tests so its animation timers cannot perturb the run.
# Install EAS CLI
npm install -g eas-cli
# Authenticate with Expo Application Services
eas login
# Trigger cloud build for Android APK preview
eas build -p android --profile previewThis project is licensed under the PolyForm Noncommercial License 1.0.0. You are free to view, clone, fork, and contribute for personal and educational purposes, but commercial use, sales, and monetized distribution are strictly prohibited. See the LICENSE file for details.



