--- phase: 02-flexible-sets plan: "02" type: execute wave: 2 depends_on: ["02-01"] files_modified: - backend/src/index.js - frontend/src/App.jsx autonomous: true must_haves: truths: - "Deleting a set row that was previously logged removes it from the database" - "Adding and logging sets beyond the original program count persists to the database" - "After saving/refreshing, the dynamic set count reflects what was logged (no phantom sets, no missing sets)" - "The backend DELETE endpoint returns 200 on success and 404 if the log row did not exist" artifacts: - path: "backend/src/index.js" provides: "DELETE /api/logs endpoint accepting user_id, program_exercise_id, date, set_number" contains: "DELETE.*workout_logs" - path: "frontend/src/App.jsx" provides: "deleteLog function that calls DELETE /api/logs; passed to WorkoutPage as onDeleteSet" contains: "deleteLog" key_links: - from: "ExerciseCard handleDeleteSet" to: "App.jsx deleteLog" via: "onDeleteSet prop through WorkoutPage" pattern: "onDeleteSet" - from: "App.jsx deleteLog" to: "DELETE /api/logs" via: "fetch with method DELETE" pattern: "method.*DELETE" - from: "DELETE /api/logs" to: "workout_logs table" via: "DELETE FROM workout_logs WHERE user_id=$1 AND program_exercise_id=$2 AND date=$3 AND set_number=$4" pattern: "DELETE FROM workout_logs" --- Add a DELETE /api/logs backend endpoint and wire it through App.jsx so that when a user removes a set row that was already logged, the database record is deleted. Purpose: Without this, deleted set rows leave orphan rows in workout_logs, causing ghost sets to reappear on next load. The frontend dynamic state from plan 01 is correct per-session, but only persists with proper backend deletion. Output: Backend DELETE endpoint + App.jsx deleteLog function + WorkoutPage onDeleteSet prop wired to ExerciseCard. @/home/intense/.claude/get-shit-done/workflows/execute-plan.md @/home/intense/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/02-flexible-sets/02-CONTEXT.md @.planning/phases/02-flexible-sets/02-01-SUMMARY.md @backend/src/index.js @frontend/src/App.jsx @frontend/src/pages/WorkoutPage.jsx Task 1: Add DELETE /api/logs endpoint to backend backend/src/index.js Add a DELETE endpoint that removes a specific set log row. Place it directly after the existing POST /api/logs route (around line 329). ```js // Delete a specific set log app.delete('/api/logs', async (req, res) => { try { const { user_id, program_exercise_id, date, set_number } = req.body; const result = await pool.query( 'DELETE FROM workout_logs WHERE user_id = $1 AND program_exercise_id = $2 AND date = $3 AND set_number = $4 RETURNING id', [user_id, program_exercise_id, date, set_number] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Log not found' }); } res.json({ deleted: result.rows[0].id }); } catch (err) { console.error('Error deleting log:', err); res.status(500).json({ error: 'Database error' }); } }); ``` No auth middleware on this endpoint — consistent with the existing POST /api/logs which also has no auth middleware (user_id comes from the request body). Do not add authMiddleware unless the existing POST /api/logs has it (it does not). Start backend (`npm start` in backend/) and run: ``` curl -X DELETE http://localhost:3001/api/logs \ -H "Content-Type: application/json" \ -d '{"user_id":1,"program_exercise_id":1,"date":"2026-02-21","set_number":1}' ``` Returns `{"deleted": N}` if row existed, or `{"error":"Log not found"}` with 404 if not. Server does not crash on missing body fields (returns 404 or 500 gracefully). DELETE /api/logs endpoint exists, deletes the matching workout_logs row by composite key (user_id, program_exercise_id, date, set_number), returns 200+id on success, 404 if not found. Task 2: Wire deleteLog through App.jsx and WorkoutPage to ExerciseCard frontend/src/App.jsx, frontend/src/pages/WorkoutPage.jsx **In App.jsx:** Add a `deleteLog` function alongside the existing `logSet` function: ```js const deleteLog = async (programExerciseId, setNumber) => { try { await fetch(`${API_URL}/logs`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userId, program_exercise_id: programExerciseId, date: today, set_number: setNumber }) }) // Remove from local logs state setLogs(prev => ({ ...prev, [programExerciseId]: (prev[programExerciseId] || []).filter(l => l.set_number !== setNumber) })) } catch (err) { console.error('Failed to delete log:', err) } } ``` Pass `deleteLog` to `WorkoutPage` as `onDeleteSet`: ```jsx setView('dashboard')} fetchProgression={fetchProgression} /> ``` **In WorkoutPage.jsx:** Update the `WorkoutPage` function signature to accept `onDeleteSet`: ```js function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProgression }) { ``` Pass `onDeleteSet` through to each `ExerciseCard`: ```jsx setExpandedExercise( expandedExercise === exercise.id ? null : exercise.id )} onLogSet={onLogSet} onDeleteSet={onDeleteSet} /> ``` The ExerciseCard already has `onDeleteSet` in its prop signature and calls it in `handleDeleteSet` from plan 01. No changes needed inside ExerciseCard itself — just confirm `onDeleteSet` is being passed through correctly. **Behavior when delete is called:** - If the set being deleted has `completed: true` in setList (was logged to DB), `deleteLog` fires and removes the DB row - If the set is new and not yet logged (e.g. user added a set but didn't complete it), `onDeleteSet` is still called but the DELETE request will return 404 — the catch block silently ignores this (the row didn't exist; no harm done) This means the `handleDeleteSet` in ExerciseCard should always call `onDeleteSet` regardless of whether the set was completed — the backend handles the "not found" case gracefully. In the dev server: 1. Start a workout, complete set 1 of an exercise (logs it to DB) 2. Verify it's logged: `curl "http://localhost:3001/api/logs?user_id=1&program_exercise_id=1&date=TODAY"` 3. Delete set 1 row using the trash icon 4. Verify it's gone from DB: repeat the curl — set_number=1 should no longer appear 5. Add a new set (Vanligt set), complete it — verify it appears in DB as the next set_number 6. Reload the workout — no ghost sets, count matches what was logged deleteLog in App.jsx calls DELETE /api/logs. WorkoutPage passes onDeleteSet to ExerciseCard. Deleting a completed set removes it from the database and from local logs state. Non-logged sets delete silently without error. Run `npm run build` in frontend/ — must pass with no errors. Full flow test: 1. Open a workout 2. Add 2 extra sets to the first exercise (Vanligt set) 3. Complete all sets — verify they all persist in DB 4. Delete the middle set — verify DB row removed, UI renumbers 5. Save workout (navigate back to dashboard) 6. Re-open same workout — set count matches what was logged, no ghost rows DELETE /api/logs endpoint exists in backend. App.jsx has deleteLog function. WorkoutPage passes onDeleteSet to ExerciseCard. Deleting a logged set removes it from DB. Adding and completing new sets saves beyond original program set count. Frontend build passes. After completion, create `.planning/phases/02-flexible-sets/02-02-SUMMARY.md`