Replace capped exercise picker with a real search combobox

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>
This commit is contained in:
2026-08-10 12:02:08 +03:00
co-authored by Claude Sonnet 5
parent 0c6a297198
commit 0a44b97232
4 changed files with 104 additions and 9 deletions
+37
View File
@@ -146,4 +146,41 @@ class WorkoutTest extends TestCase
$response->assertForbidden();
$this->assertDatabaseHas('workouts', ['id' => $workout->id]);
}
public function test_owner_can_search_exercises_for_the_picker(): void
{
$user = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $user->id]);
Exercise::factory()->create(['name' => 'Barbell Bench Press']);
Exercise::factory()->create(['name' => 'Dumbbell Curl']);
$response = $this->actingAs($user)->getJson(route('workouts.exercises.search', $workout) . '?q=Bench');
$response->assertOk();
$response->assertJsonCount(1);
$response->assertJsonFragment(['name' => 'Barbell Bench Press']);
}
public function test_search_caps_results_at_twenty(): void
{
$user = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $user->id]);
Exercise::factory()->count(25)->create();
$response = $this->actingAs($user)->getJson(route('workouts.exercises.search', $workout));
$response->assertOk();
$this->assertCount(20, $response->json());
}
public function test_other_user_cannot_search_exercises_for_someone_elses_workout(): void
{
$owner = User::factory()->create();
$other = User::factory()->create();
$workout = Workout::factory()->create(['user_id' => $owner->id]);
$response = $this->actingAs($other)->getJson(route('workouts.exercises.search', $workout));
$response->assertForbidden();
}
}