# Mettle — Changelog

All notable changes to this project are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

---

## [0.2.0] — Phase 1: Accounts and Safety

### Added
- AUTH-1: Registration with email, password (≥10 chars, common-password list check), display name, DOB (18+ enforced), timezone (auto-detected in browser)
- AUTH-2: Email verification (24h token) and password reset (1h token); tokens stored as SHA-256 hashes
- AUTH-3: Login with remember-me rotating cookie (30 days); rate limiting middleware (5 attempts/15 min per IP+action)
- AUTH-4: Resumable onboarding wizard (9 steps); progress stored in `onboarding_progress` table
- AUTH-5: Account settings page — change password, export data (JSON), pause account, delete account (7-day grace period)
- SAFE-1: Pre-exercise screening questionnaire (11 questions, seed file flagged for professional review)
- SAFE-2: GP clearance lock/unlock; `see_gp` outcome blocks HIIT and fitness tests
- SAFE-3: Nutrition floor constants in `config/game.php` (1200/1500/1400 kcal)
- SAFE-4: `risk_flags` table and `ScreeningRepository::createRiskFlag()`; nightly evaluation wired as cron stub
- SAFE-5: Diet-style warnings for keto, carnivore, fasting in `ScreeningService`
- SAFE-6: Disclaimer in base layout footer and registration page
- NOTIF-3: `email_queue` table, `EmailQueueRepository`, `EmailService` (queue + SMTP send + cron processor, max 50/run)
- Domain enums: `ScreeningOutcome`, `TokenType`, `OnboardingStep`
- Repositories: `UserRepository`, `AuthTokenRepository`, `ScreeningRepository`, `EmailQueueRepository`
- Controllers: `AuthController`, `OnboardingController`, `AccountController`, `ScreeningController`
- Email templates: `verify_email.php`, `reset_password.php`
- All Phase 1 routes registered
- DI container updated with all Phase 1 bindings
- Tests: 26 total (16 new) — `AuthServiceTest`, `ScreeningServiceTest`


### Added
- Project skeleton: full directory structure per spec section 4
- `composer.json` with all required dependencies
- `.env.example` with all configuration keys documented
- `bootstrap.php` — app entry point, loads .env from outside web root
- `config/settings.php` — all config values from .env
- `config/container.php` — PHP-DI container: PDO, Monolog, Twig
- `config/app.php` — Slim 4 app builder with middleware stack
- `config/routes.php` — route definitions (Phase 0 stub)
- `config/game.php` — all tunable game numbers (XP, levels, streaks, etc.)
- `public/index.php` — web entry point
- `public/.htaccess` — mod_rewrite, security blocks, compression, caching
- `public/manifest.webmanifest` — PWA manifest
- `public/sw.js` — service worker stub (full implementation Phase 5)
- `public/assets/css/app.css` — compiled CSS placeholder
- `public/assets/js/app.js` — app bootstrap JS
- `resources/css/app.css` — Tailwind CSS source with design tokens
- `tailwind.config.js` — Tailwind config with Mettle design tokens
- Middleware: SecurityHeaders, Maintenance, Session, CSRF, Auth, RateLimit
- Controllers: HomeController (Village stub), AdminController (migrate endpoint)
- `app/Support/Clock.php` — testable time abstraction
- Twig templates: base layout, home page, maintenance page, error page, admin dashboard
- `bin/cron.php` — 5-minute cron runner with task registry and `cron_runs` logging
- `phinx.php` — Phinx migration configuration
- Migration: `20260101000000_create_foundation_tables` — cron_runs, login_attempts, feature_flags, audit_log
- `docs/DEPLOY.md` — deployment guide for both hosting layouts
- `docs/DECISIONS.md` — architecture decisions (DEC-001 through DEC-007)
- `docs/LICENSES.md` — all third-party library licences
- `checklist.md` — build progress tracker

### Notes
- Phase 0 acceptance criterion: "hello" page deploys from zip, migrations run via admin endpoint, cron writes to cron_runs
- Tailwind must be compiled locally before deployment: `./tailwindcss -i resources/css/app.css -o public/assets/css/app.css --minify`
- Vendor JS libraries (htmx, Alpine.js) must be downloaded into `public/assets/vendor/` before deployment

## [0.6.0] — Phase 5: Game Layer

### Added
- GAME-2: Streak tracking — daily increment, missed-day reset, longest streak, `streaks` table
- GAME-3: Quest system — 3–5 daily + 2 weekly quests generated by cron; progress incremented by other services; XP awards per quest + all-daily bonus (50 XP)
- GAME-4: Comeback mode — detected after 7-day inactivity gap; bonus XP award; coach message prioritised
- COACH-1: Coach tone stored in `user_settings.coach_tone` (motivational/calm/direct/humorous)
- COACH-2: `CoachService::getTip()` — context priority: comeback > low_readiness > streak_milestone > weak_spot > default; all 4 tones covered
- COACH-3: `StreakService::isComeback()` + comeback message in CoachService
- NOTIF-1: Push subscription save/delete via `GameRepository` + `PushService::subscribe/unsubscribe`
- NOTIF-2: `PushService::sendToUser()` + `sendReminders()` (bulk, active users only); expired subscriptions auto-removed
- PWA: Full service worker (`public/sw.js`) — cache-first for static assets, network-first for HTML pages, offline fallback page, push notification handler with action buttons
- Streaks: rest tokens (earn 1 per 7-day streak, max 3, covers 1 missed day); sick mode (freeze streak up to 7 days)
- Cron: `task_generate_quests` (daily + weekly), `task_close_weekly_quests`, `task_send_reminders` (push), `task_risk_flags` (kcal floor check) all fully implemented
- Migration: `20260101000005_create_game_layer_tables` (streaks, quests, push_subscriptions)
- `GameRepository`: streaks upsert, quests CRUD + progress + expire, push subscriptions
- `StreakService`, `QuestService`, `CoachService`, `PushService`, `GameController`
- Routes: GET /quests, POST /push/subscribe, POST /push/unsubscribe, POST /streak/sick, GET /offline
- Templates: `game/quests.twig` (streak card, daily/weekly progress bars, sick mode form), `pages/offline.twig`

### Tests
- 128 total, 970 assertions, 0 failures, 0 warnings
- New: StreakServiceTest (13 tests), QuestCoachTest (14 tests)


### Added
- NUT-1: TDEE via Mifflin-St Jeor BMR × PAL; macro targets from diet profile ratios; kcal floor enforced (SAFE-3: 1200/1500/1400 kcal)
- NUT-2: Meal logging — recipe-linked or custom entry, servings multiplier, htmx fragment swap
- NUT-3: 7-day rolling kcal floor breach check (`isBelowFloor`)
- NUT-4: Meal-plan generator (7.6) — 7-day plan per week, diet-tag filtered recipe selection, closest-kcal matching, servings scaled to target
- NUT-5: XP award on meal log (5 XP, limit 4/day via XpEngine)
- NUT-6: Delete meal log entry with htmx fragment refresh
- `config/diets.php`: 10 diet profiles (balanced, high_protein, low_carb, keto, carnivore, vegetarian, vegan, mediterranean, IF 16:8, IF 5:2) with macro %, tags, warnings, fasting flags
- Recipe seed: 150 recipes split across 4 sub-files (breakfast 22, lunch 27, dinner 60, snack 41), all diet tags covered
- `NutritionRepository`: recipes CRUD + seed, meal log CRUD + day totals, meal plan + items
- `NutritionService`: TDEE, kcalTarget, macroTargets, logMeal, getDayLogs, getDayTotals, isBelowFloor, getOrGeneratePlan, deleteLog
- `NutritionController`: GET /eat, GET /eat/plan, POST /eat/log, POST /eat/log/{id}/delete
- Migration: `20260101000004_create_nutrition_tables` (recipes, meal_logs, meal_plans, meal_plan_items)
- Templates: `eat/index.twig` (daily log + macro summary + inline log form), `eat/plan.twig` (7-day plan grid), `partials/meal_log_row.twig` (htmx target)

### Tests
- 101 total, 920 assertions, 0 failures, 0 warnings
- New: NutritionServiceTest (20 tests: TDEE, macro targets, kcal floors, diet profiles, recipe seed validation)


### Added
- TRN-1: Exercise library — `exercises` table, 43-exercise bodyweight/minimal-equipment seed, `bin/import-wger.php` wger API importer
- TRN-2: Weekly plan generator — session count from availability, muscle-group rotation, weak-stat bias
- TRN-3: Session lifecycle — create, start, log sets (reps/weight/duration/RPE), complete, skip
- TRN-4: Readiness-adjusted sessions (7.5) — R≥3.3 as planned, 2.5≤R<3.3 volume −20%, R<2.5 → mobility
- TRN-5: Deload week (7.4) — week 4 of every second 4-week block, 60% volume
- TRN-6: XP awards — base 50 + 2/min (cap 150), HIIT bonus 25, HIIW bonus 15
- TRN-7: Progression tracking — best weight/reps per exercise, suggest +2.5kg upper / +5kg lower
- INT-1–8: HIIT unlock (≥6 sessions/≥3 weeks), weekly cap (3), readiness downgrade chain, protocol selection (tabata/amrap/emom/low_impact), interval metadata, consecutive-easy level-up suggestion
- `TrainingRepository`: exercises, plans, sessions, sets, interval sessions, progression
- `IntervalService`: unlock check, session type resolution, protocol selection, consecutive-easy tracking
- `TrainingService`: plan generation, readiness adjustment, deload, session lifecycle, progression suggestion, XP awards
- `TrainingController`: /train dashboard, /train/session/{id} active session, set logging (htmx), session complete
- Migration: `20260101000003_create_training_tables` (exercises, training_plans, workout_sessions, workout_sets, interval_sessions, exercise_progression)
- Templates: `train/index.twig` (weekly plan + readiness banner), `train/session.twig` (Alpine.js interval timer + set logger), `train/complete.twig` (XP + level-up + sets summary)
- Routes: GET /train, GET/POST /train/session/new, /train/session/{id}, /train/session/{id}/log, /train/session/{id}/complete

### Tests
- 81 total, 878 assertions, 0 failures, 0 warnings
- New: TrainingServiceTest (9 tests), IntervalServiceTest (17 tests)


### Added
- CHK-1–5: Full check-in wizard (questions → tests → body measurements → summary with radar chart)
- CHK-6: Daily micro check-in (sleep/energy/soreness/mood 1–5, pain flag) as htmx fragment
- GAME-1: Character creation (name, class) wired into onboarding step 7; CharacterService, CharacterRepository
- `config/norms.php`: fitness test score bands for all 6 tests × 6 age groups × male/female/unspecified
- `LevelTable`: XP threshold formula `floor(120*(n-1)^1.6)`, levelFromXp(), progress()
- `XpEngine`: award() with xp_ledger unique-key duplicate prevention, daily limits, level-up detection
- `StatCalculator`: getBand(), interpolate() with linear interpolation within bands, weakSpot() with goal-weight tie-breaking, knees push-up variant (×0.6), unspecified sex averaging
- `CheckInService`: orchestrates full check-in and micro check-in, readinessScore()
- `CharacterService`: create/getOrCreate with default avatar
- Repositories: CheckInRepository, CharacterRepository (includes user_diets)
- Controllers: CheckInController, CharacterController; OnboardingController updated to save diet, character, safety screening
- Migration: `20260101000002_create_checkin_and_character_tables` (checkins, checkin_answers, checkin_tests, daily_readiness, characters, character_stats, xp_ledger, user_diets)
- Seed files: checkin_questions.php (8 questions), checkin_tests.php (6 test definitions)
- Templates: check-in wizard (questions, tests with Alpine.js timers, body, summary), character page with radar chart, micro check-in partial
- DEC-008: Level curve actual progression documented

### Fixed
- StatCalculator: isset guards in getBand() and interpolate() eliminate undefined array key warnings
- Knees push-up variant: multiply by 0.6 (not divide) so knees scores lower than full for same rep count

### Tests
- 55 total, 842 assertions, 0 failures, 0 warnings
- New: StatCalculatorTest (15 tests covering all age bands, sexes, edge cases), LevelTableTest (10), CheckInServiceTest (7)
