is_trainer was captured at signup and stored, but nothing in the app
used it - no client roster, no way to assign anything, a dead flag that
misled users who checked it during registration.
Adds a trainer/client link (a trainer adds a client by the email they
registered with) and lets a trainer create workouts directly on a
client's account (assigned_by_trainer_id on workouts). Deliberately
scoped for privacy: a trainer only ever sees/manages the workouts they
authored and assigned - not a client's other private data (their own
workouts, weight log, health notes, session/completion history). That
boundary needs a consent or notification system to relax safely, which
doesn't exist yet, so it's left for a follow-up rather than building it
half-safe.
WorkoutController's authorization now distinguishes "manage" (owner or
assigning trainer - view, add/edit exercises) from "own" (owner only -
delete), so a trainer can build out an assigned workout's exercises
after creating it, but can't delete a client's workout out from under
them.
Found a real bug via live testing before committing: the client roster
page eager-loaded `assignedWorkouts` (keyed by assigned_by_trainer_id)
on each client record, which is the trainer's own perspective, not the
client's - so it silently showed "no workouts assigned" even after
assigning one. Fixed to eager-load `workouts` filtered by
assigned_by_trainer_id instead, with a regression test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A Workout was a single flat session with no way to schedule several
across weeks/days as a structured plan (e.g. Push/Pull/Legs over 4
weeks) - every user started from a blank builder each time, and there
was no template concept at all.
Adds a Program (title, description, week count) that Workouts can
optionally belong to via nullable program_id/week_number/day_number
columns, reusing all existing workout and session-tracking
infrastructure rather than building a parallel system. The program show
page lays out the schedule week by week and derives progress (completed
vs total) from the user's existing WorkoutSession completions - no new
enrollment/progress-tracking concept needed. Deleting a program
unschedules its workouts (nullOnDelete) rather than destroying them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Session tracking only ever stored a done boolean - no record of what
weight or reps were actually performed, so there was no way to derive
real progress data even though the dashboard implied "progress
tracking" was a thing. Workout templates only hold a static planned
value, not a history of performance.
Adds actual_weight/actual_reps to workout_session_items, captured
inline when checking off a set in a session (defaults to the workout's
planned weight/reps, editable before confirming). The exercise detail
page now shows the user's personal best (heaviest completed set, tie
broken by reps) computed from their completed session history across
all workouts containing that exercise.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No way existed to log body weight over time - only the single
self-reported value from the signup profile - and the dashboard showed
session activity but nothing about physical progress, which is usually
the main reason people keep using a fitness app.
Adds a WeightLog model (one entry per user per day, re-logging the same
day updates it rather than duplicating), a quick-add form and trend
summary on the dashboard, and delete support.
Found and fixed a real bug along the way: the first implementation used
WeightLog::updateOrCreate() with a raw ['logged_at' => $date] match
array, which silently failed to find the same-day entry because the
`date` cast stores full datetime strings ('2026-01-01 00:00:00') while
the match array holds a plain 'Y-m-d' string - so re-logging the same
day hit a unique-constraint violation instead of updating. Fixed by
using a whereDate() lookup instead, which is comparison-format-safe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Exercises and workouts both have a favorite toggle; yoga poses had an
otherwise identical browse/filter UX but no way to favorite them - an
inconsistency rather than a deliberate omission. Mirrors the existing
exercise/workout favorite pattern exactly: a pivot table, a
belongsToMany on User, and a toggle button on the pose detail page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The workout builder's "add exercise" dropdown was Exercise::orderBy
('name')->limit(50) with no search. Harmless with a handful of seeded
exercises, but once the library is populated with real data (1000+),
users could only ever add exercises starting around A-D - the core
"build a workout" flow would be effectively broken.
Replaces it with a debounced Alpine.js combobox backed by a new
GET /workouts/{workout}/exercises/search endpoint (owner-only, JSON,
capped at 20 results, respects each user's custom exercise aliases).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Registration collects health-adjacent data (age, weight, height, health
notes, goals) with no privacy policy or ToS anywhere in the app. Adds
both as real pages at /privacy and /terms, a site-wide footer linking to
them (the app had no footer at all outside the homepage), and a consent
line on the registration form. Both pages are marked as a starting draft
with bracketed placeholders for operator/jurisdiction details rather
than inventing legal claims the app can't back up.
Also removed the homepage's own footer, which duplicated the new global
one and whose Privacy/Terms/About/Contact links were all dead `#`
placeholders, and fixed APP_NAME (was "Laravel", site is branded
FitAtlas everywhere else including the browser tab title).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only Breeze's default auth/profile tests existed - nothing covered
Workouts, Sessions, Exercises, Yoga, Favorites, Blog, or the Admin
panel, which is the actual product. Adds factories for the domain
models and 45 new feature tests covering the main flows plus
authorization boundaries (owner-only access on workouts/sessions,
admin-only access on /admin, drafts hidden from the public blog).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FavoriteController's constructor called $this->middleware('auth'),
which no longer exists on Laravel 12's base Controller (removed in the
framework's slimmer controller base). Every favorite-toggle request was
500ing. The routes already sit inside a Route::middleware('auth') group
in web.php, so the constructor was both redundant and broken - removed it.
Separately, exercises.exercise_db_id was NOT NULL with a unique
constraint, but the admin "create exercise" form never set it (that
column only gets populated by the fetch:exercise importer). Creating an
exercise by hand through the admin UI has been broken since it was
built. Made the column nullable.
Both found by adding feature test coverage, not by manual testing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BlogController was an empty stub with no view, so /blog 500'd on every
visit. Adds a real Post model, public listing/detail pages, and an admin
CRUD screen mirroring the existing Exercise admin pattern (image upload
or URL, auto-slug, publish toggle). Seeds 5 real posts so it's not empty
on launch.
Also fixes two things that were silently broken: phpunit.xml pointed
DB_DATABASE at a non-existent "testing" file with no DB_CONNECTION set,
and the stock ExampleTest had RefreshDatabase commented out - both meant
the test suite never actually ran successfully.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>