Function bodies 262 total
getExerciseSummaries function · typescript · L231-L250 (20 LOC)src/app/(app)/dashboard/page.tsx
function getExerciseSummaries(workout: RecentWorkout) {
return buildExerciseSetSummaries(
(workout.sets || []).map((set) => {
const exercise = Array.isArray(set.exercises) ? set.exercises[0] : set.exercises;
return {
exerciseId: set.exercise_id,
exerciseName: exercise?.name || 'Exercise',
setNumber: set.set_number,
weight: set.weight,
reps: set.reps,
isSplitLR: set.is_split_lr,
leftWeight: set.left_weight,
leftReps: set.left_reps,
rightWeight: set.right_weight,
rightReps: set.right_reps,
};
}),
unitSystem,
);
}handleCreateTemplateWithAI function · typescript · L252-L256 (5 LOC)src/app/(app)/dashboard/page.tsx
function handleCreateTemplateWithAI() {
setShowHistory(false);
setShowPRFeed(false);
router.push('/trainer?intent=create-template');
}openManualTemplateBuilder function · typescript · L258-L277 (20 LOC)src/app/(app)/dashboard/page.tsx
function openManualTemplateBuilder() {
setShowHistory(false);
setShowPRFeed(false);
const suggestedName = `Template ${new Date().toLocaleDateString(undefined, { month: 'short', day: 'numeric' })}`;
const enteredName = window.prompt('Template name', suggestedName);
if (enteredName === null) return;
const templateName = enteredName.trim() || suggestedName;
hydrateWorkoutSession({
workoutName: templateName,
startTime: new Date().toISOString(),
exercises: [],
builderMode: 'template_builder',
});
const state = useActiveWorkoutStore.getState();
if (state.workoutId) {
router.push(`/workout/${state.workoutId}`);
}
}closeManualTemplateBuilder function · typescript · L279-L284 (6 LOC)src/app/(app)/dashboard/page.tsx
function closeManualTemplateBuilder() {
if (savingManualTemplate) return;
setShowManualTemplateModal(false);
setShowTemplateExercisePicker(false);
setManualTemplateError(null);
}addExerciseToManualTemplate function · typescript · L286-L303 (18 LOC)src/app/(app)/dashboard/page.tsx
function addExerciseToManualTemplate(exercise: { id: string; name: string; category: string }) {
setManualTemplateError(null);
setManualTemplateExercises((prev) => {
if (prev.some((item) => item.exerciseId === exercise.id)) {
setManualTemplateError('Exercise already added to this template.');
return prev;
}
return [
...prev,
{
exerciseId: exercise.id,
name: exercise.name,
category: exercise.category,
defaultSets: 3,
},
];
});
}updateManualTemplateSets function · typescript · L305-L316 (12 LOC)src/app/(app)/dashboard/page.tsx
function updateManualTemplateSets(index: number, value: string) {
const parsed = Number.parseInt(value, 10);
setManualTemplateExercises((prev) => {
const next = [...prev];
if (!next[index]) return prev;
next[index] = {
...next[index],
defaultSets: Number.isFinite(parsed) ? Math.max(1, Math.min(12, parsed)) : 1,
};
return next;
});
}removeManualTemplateExercise function · typescript · L318-L320 (3 LOC)src/app/(app)/dashboard/page.tsx
function removeManualTemplateExercise(index: number) {
setManualTemplateExercises((prev) => prev.filter((_, i) => i !== index));
}Repobility · MCP-ready · https://repobility.com
moveManualTemplateExercise function · typescript · L322-L330 (9 LOC)src/app/(app)/dashboard/page.tsx
function moveManualTemplateExercise(index: number, direction: 'up' | 'down') {
setManualTemplateExercises((prev) => {
const target = direction === 'up' ? index - 1 : index + 1;
if (target < 0 || target >= prev.length) return prev;
const next = [...prev];
[next[index], next[target]] = [next[target], next[index]];
return next;
});
}handleSaveManualTemplate function · typescript · L332-L391 (60 LOC)src/app/(app)/dashboard/page.tsx
async function handleSaveManualTemplate() {
const trimmedName = manualTemplateName.trim();
if (!trimmedName) {
setManualTemplateError('Template name is required.');
return;
}
if (manualTemplateExercises.length === 0) {
setManualTemplateError('Add at least one exercise.');
return;
}
setSavingManualTemplate(true);
setManualTemplateError(null);
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
setManualTemplateError('Please sign in again.');
setSavingManualTemplate(false);
return;
}
const { data: template, error: templateError } = await supabase
.from('workout_templates')
.insert({ user_id: user.id, name: trimmedName })
.select('id')
.single();
if (templateError || !template) {
setManualTemplateError(templateError?.message || 'Could not create template.');
setSavingManualTemplate(false);
return;
}
const templateRows = manualhandleStartFromTemplate function · typescript · L393-L406 (14 LOC)src/app/(app)/dashboard/page.tsx
function handleStartFromTemplate(template: TemplateSummary) {
const sortedExercises = [...template.template_exercises].sort(
(a, b) => a.order_index - b.order_index
);
startWorkout(template.name, template.id);
for (const ex of sortedExercises) {
addExerciseWithSets(
{ id: ex.exercise_id, name: ex.exercises.name, category: ex.exercises.category },
ex.default_sets || 3
);
}
const state = useActiveWorkoutStore.getState();
router.push(`/workout/${state.workoutId}`);
}ExercisesPage function · typescript · L21-L27 (7 LOC)src/app/(app)/exercises/page.tsx
export default function ExercisesPage() {
return (
<Suspense fallback={<div className="flex items-center justify-center min-h-dvh text-muted-foreground">Loading...</div>}>
<ExercisesContent />
</Suspense>
);
}handleSelectExercise function · typescript · L92-L97 (6 LOC)src/app/(app)/exercises/page.tsx
function handleSelectExercise(exercise: ExerciseRow) {
if (isSelecting) {
addExercise({ id: exercise.id, name: exercise.name, category: exercise.category });
router.back();
}
}findPotentialDuplicates function · typescript · L99-L114 (16 LOC)src/app/(app)/exercises/page.tsx
async function findPotentialDuplicates(name: string) {
const { data, error } = await supabase
.from('exercises')
.select('id, name, category, primary_muscles, equipment, is_custom, user_id')
.order('name')
.limit(2000);
if (error || !data) {
return { matches: [] as ExerciseRow[], error: 'Could not run duplicate check.' };
}
return {
matches: rankExercisesBySearch(data as ExerciseRow[], name).slice(0, 12),
error: null,
};
}openCreateModal function · typescript · L116-L129 (14 LOC)src/app/(app)/exercises/page.tsx
function openCreateModal(initialName?: string) {
setEditingExercise(null);
const initial = initialName ?? search ?? '';
setExerciseForm({
name: initial,
category: inferExerciseCategoryFromName(initial),
primaryMuscle: inferPrimaryMuscleFromName(initial),
});
setCategoryManuallyChanged(false);
setPrimaryMuscleManuallyChanged(false);
setActionError(null);
setAiDetails(null);
setShowCreateModal(true);
}startCreateFlow function · typescript · L131-L148 (18 LOC)src/app/(app)/exercises/page.tsx
async function startCreateFlow() {
const candidate = search.trim();
if (!candidate) {
openCreateModal('');
return;
}
setDuplicateCheckName(candidate);
setDuplicateResults([]);
setDuplicateError(null);
setShowDuplicateCheckModal(true);
setDuplicateChecking(true);
const { matches, error } = await findPotentialDuplicates(candidate);
setDuplicateResults(matches);
setDuplicateError(error);
setDuplicateChecking(false);
}All rows above produced by Repobility · https://repobility.com
closeDuplicateCheckModal function · typescript · L150-L156 (7 LOC)src/app/(app)/exercises/page.tsx
function closeDuplicateCheckModal() {
if (duplicateChecking) return;
setShowDuplicateCheckModal(false);
setDuplicateCheckName('');
setDuplicateResults([]);
setDuplicateError(null);
}handleUsePotentialDuplicate function · typescript · L158-L165 (8 LOC)src/app/(app)/exercises/page.tsx
function handleUsePotentialDuplicate(exercise: ExerciseRow) {
if (isSelecting) {
handleSelectExercise(exercise);
} else {
setSearch(exercise.name);
}
closeDuplicateCheckModal();
}handleCreateAnyway function · typescript · L167-L171 (5 LOC)src/app/(app)/exercises/page.tsx
function handleCreateAnyway() {
const initial = duplicateCheckName || search;
closeDuplicateCheckModal();
openCreateModal(initial);
}openEditModal function · typescript · L173-L185 (13 LOC)src/app/(app)/exercises/page.tsx
function openEditModal(exercise: ExerciseRow) {
setEditingExercise(exercise);
setExerciseForm({
name: exercise.name,
category: exercise.category,
primaryMuscle: exercise.primary_muscles[0] || 'chest',
});
setCategoryManuallyChanged(true);
setPrimaryMuscleManuallyChanged(true);
setActionError(null);
setAiDetails(null);
setShowCreateModal(true);
}closeCreateModal function · typescript · L187-L193 (7 LOC)src/app/(app)/exercises/page.tsx
function closeCreateModal() {
if (saving || deleting) return;
setShowCreateModal(false);
setCategoryManuallyChanged(false);
setPrimaryMuscleManuallyChanged(false);
setActionError(null);
}openMergeModal function · typescript · L195-L203 (9 LOC)src/app/(app)/exercises/page.tsx
function openMergeModal(exercise: ExerciseRow) {
setShowCreateModal(false);
setMergeSourceExercise(exercise);
setMergeSearch(exercise.name);
setMergeResults([]);
setMergeTarget(null);
setMergeError(null);
setShowMergeModal(true);
}closeMergeModal function · typescript · L205-L213 (9 LOC)src/app/(app)/exercises/page.tsx
function closeMergeModal() {
if (mergeSaving) return;
setShowMergeModal(false);
setMergeSourceExercise(null);
setMergeSearch('');
setMergeResults([]);
setMergeTarget(null);
setMergeError(null);
}handleAiFill function · typescript · L253-L289 (37 LOC)src/app/(app)/exercises/page.tsx
async function handleAiFill() {
const trimmedName = exerciseForm.name.trim();
if (!trimmedName) {
setActionError('Enter an exercise name first.');
return;
}
setAiLoading(true);
setActionError(null);
try {
const res = await fetch('/api/exercise-details', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: trimmedName, category: exerciseForm.category }),
});
if (!res.ok) throw new Error('Failed to generate details');
const details = await res.json();
setAiDetails({
instructions: details.instructions || [],
secondaryMuscles: details.secondaryMuscles || [],
force: details.force || null,
level: details.level || null,
mechanic: details.mechanic || null,
equipment: details.equipment || null,
});
// Update form fields from AI if user hasn't manually changed them
if (!categoryManuallyChanged && dRepobility (the analyzer behind this table) · https://repobility.com
handleSaveExercise function · typescript · L291-L425 (135 LOC)src/app/(app)/exercises/page.tsx
async function handleSaveExercise() {
const trimmedName = exerciseForm.name.trim();
if (!trimmedName) {
setActionError('Exercise name is required.');
return;
}
setSaving(true);
setActionError(null);
const inferredEquipment = inferEquipmentFromCategory(exerciseForm.category);
const isOwnCustom = editingExercise?.is_custom && editingExercise?.user_id === currentUserId;
if (editingExercise && isOwnCustom) {
// Update existing custom exercise (user owns it)
const { error } = await supabase
.from('exercises')
.update({
name: trimmedName,
category: exerciseForm.category,
primary_muscles: [exerciseForm.primaryMuscle],
equipment: aiDetails?.equipment || inferredEquipment,
...(aiDetails && {
secondary_muscles: aiDetails.secondaryMuscles,
instructions: aiDetails.instructions,
force: aiDetails.force,
level: aiDetails.level,
handleDeleteExercise function · typescript · L427-L489 (63 LOC)src/app/(app)/exercises/page.tsx
async function handleDeleteExercise() {
if (!editingExercise) return;
const isOwnCustom = editingExercise.is_custom && editingExercise.user_id === currentUserId;
if (!isOwnCustom) return;
setActionError(null);
const [{ count: setCount }, { count: templateCount }] = await Promise.all([
supabase.from('sets').select('*', { count: 'exact', head: true }).eq('exercise_id', editingExercise.id),
supabase.from('template_exercises').select('*', { count: 'exact', head: true }).eq('exercise_id', editingExercise.id),
]);
const usageWarnings: string[] = [];
if ((setCount || 0) > 0) {
usageWarnings.push(`${setCount} logged set${setCount === 1 ? '' : 's'}`);
}
if ((templateCount || 0) > 0) {
usageWarnings.push(`${templateCount} template entr${templateCount === 1 ? 'y' : 'ies'}`);
}
const warningText = usageWarnings.length
? `This will also delete ${usageWarnings.join(' and ')} tied to this exercise.`
: 'This canhandleMergeExercises function · typescript · L491-L591 (101 LOC)src/app/(app)/exercises/page.tsx
async function handleMergeExercises() {
if (!mergeSourceExercise || !mergeTarget) return;
const confirmed = window.confirm(
`Merge "${mergeSourceExercise.name}" into "${mergeTarget.name}"?\n\nThis will move all logged history and template references, then delete "${mergeSourceExercise.name}".`
);
if (!confirmed) return;
setMergeSaving(true);
setMergeError(null);
const { error: moveSetsError } = await supabase
.from('sets')
.update({ exercise_id: mergeTarget.id })
.eq('exercise_id', mergeSourceExercise.id);
if (moveSetsError) {
setMergeError(moveSetsError.message || 'Could not move exercise history.');
setMergeSaving(false);
return;
}
type TemplateExerciseRef = {
template_id: string;
order_index: number;
default_sets: number;
};
const { data: sourceTemplateRefs, error: sourceTemplateRefsError } = await supabase
.from('template_exercises')
.select('template_id, oAppLayout function · typescript · L9-L24 (16 LOC)src/app/(app)/layout.tsx
export default function AppLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<DesktopMobileFrame>
<div className="relative flex min-h-dvh flex-col bg-background lg:min-h-full">
<WorkoutOutboxSync />
<MainContent>{children}</MainContent>
<ActiveWorkoutRibbon />
<BottomNav />
</div>
</DesktopMobileFrame>
);
}ProgressPage function · typescript · L34-L274 (241 LOC)src/app/(app)/progress/page.tsx
export default function ProgressPage() {
const router = useRouter();
const supabase = useMemo(() => createClient(), []);
const unitSystem = useSettingsStore((s) => s.unitSystem);
const startWorkout = useActiveWorkoutStore((s) => s.startWorkout);
const [exercises, setExercises] = useState<ExerciseOption[]>([]);
const [selectedExercise, setSelectedExercise] = useState<string>('');
const [progressData, setProgressData] = useState<ProgressPoint[]>([]);
const [loading, setLoading] = useState(true);
const unit = weightUnit(unitSystem);
const selectedExerciseName = exercises.find((ex) => ex.id === selectedExercise)?.name ?? 'this exercise';
const chartUnlockThreshold: number = 3;
const chartConfig: ChartConfig = {
maxWeight: {
label: `Max Weight (${unit})`,
color: 'var(--primary)',
},
};
// Load exercises the user has done
useEffect(() => {
async function load() {
const { data: { user } } = await supabase.auth.getUser();
if (load function · typescript · L56-L79 (24 LOC)src/app/(app)/progress/page.tsx
async function load() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
// Get exercises the user has logged sets for
const { data: setData } = await supabase
.from('sets')
.select('exercise_id, exercises(id, name), workouts!inner(user_id)')
.eq('workouts.user_id', user.id);
if (setData) {
const exerciseMap = new Map<string, string>();
for (const s of setData) {
const ex = s.exercises as unknown as { id: string; name: string };
if (ex) exerciseMap.set(ex.id, ex.name);
}
const opts = Array.from(exerciseMap.entries()).map(([id, name]) => ({ id, name }));
opts.sort((a, b) => a.name.localeCompare(b.name));
setExercises(opts);
if (opts.length > 0) setSelectedExercise(opts[0].id);
}
setLoading(false);
}loadProgress function · typescript · L86-L96 (11 LOC)src/app/(app)/progress/page.tsx
async function loadProgress() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data } = await supabase.rpc('get_exercise_progress', {
user_uuid: user.id,
exercise_uuid: selectedExercise,
});
if (data) setProgressData(data as ProgressPoint[]);
else setProgressData([]);
}handleStartWorkout function · typescript · L120-L126 (7 LOC)src/app/(app)/progress/page.tsx
function handleStartWorkout() {
startWorkout();
const state = useActiveWorkoutStore.getState();
if (state.workoutId) {
router.push(`/workout/${state.workoutId}`);
}
}Repobility · code-quality intelligence · https://repobility.com
SettingsPage function · typescript · L21-L137 (117 LOC)src/app/(app)/settings/page.tsx
export default function SettingsPage() {
const router = useRouter();
const supabase = createClient();
const {
unitSystem,
defaultRestTimer,
autoStartRestTimer,
setUnitSystem,
setDefaultRestTimer,
setAutoStartRestTimer,
} = useSettingsStore();
const trainerProfile = useTrainerProfileStore((s) => s.profile);
async function handleSignOut() {
await supabase.auth.signOut();
router.push('/login');
}
return (
<div className="pb-24">
<div className="mx-auto w-full max-w-3xl space-y-4 px-4 pt-4 sm:px-6">
<header className="space-y-1">
<h1 className="text-xl font-semibold tracking-tight">Settings</h1>
<p className="text-sm text-muted-foreground">Manage your app preferences and training defaults.</p>
</header>
<Card>
<CardHeader className="pb-3">
<CardTitle className="text-base">Unit System</CardTitle>
<CardDescription>Choose your preferred weight display formhandleSignOut function · typescript · L34-L37 (4 LOC)src/app/(app)/settings/page.tsx
async function handleSignOut() {
await supabase.auth.signOut();
router.push('/login');
}ensureSuggestionsLine function · typescript · L24-L31 (8 LOC)src/app/(app)/trainer/page.tsx
function ensureSuggestionsLine(content: string, suggestionsLine: string): string {
const trimmed = content.trim();
if (!trimmed) return `Here is a draft template. Want any changes?\n${suggestionsLine}`;
const lines = trimmed.split('\n');
const lastLine = lines[lines.length - 1]?.trim() || '';
if (lastLine.startsWith('suggestions:')) return trimmed;
return `${trimmed}\n${suggestionsLine}`;
}TrainerPage function · typescript · L112-L118 (7 LOC)src/app/(app)/trainer/page.tsx
export default function TrainerPage() {
return (
<Suspense fallback={<div className="flex items-center justify-center min-h-dvh text-muted-foreground">Loading...</div>}>
<TrainerContent />
</Suspense>
);
}loadContext function · typescript · L176-L313 (138 LOC)src/app/(app)/trainer/page.tsx
async function loadContext() {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const ctx: WorkoutContext = {
unitSystem,
recentWorkouts: [],
personalRecords: [],
weeklyStats: null,
};
// Include trainer profile in context if available
if (trainerProfile) {
ctx.trainerProfile = {
experienceLevel: trainerProfile.experienceLevel,
trainingFrequency: trainerProfile.trainingFrequency || undefined,
sessionDuration: trainerProfile.sessionDuration || undefined,
goals: trainerProfile.goals,
gymAccess: trainerProfile.gymAccess || undefined,
availableEquipment: trainerProfile.availableEquipment.length > 0 ? trainerProfile.availableEquipment : undefined,
favoriteExercises: trainerProfile.favoriteExercises.length > 0 ? trainerProfile.favoriteExercises : undefined,
dislikedOrAvoidedExercises: trainerProfile.dislistartProfileSetup function · typescript · L327-L334 (8 LOC)src/app/(app)/trainer/page.tsx
function startProfileSetup() {
setProfileMode(true);
createConversation();
setTimeout(() => {
addMessage('assistant', "Let's get to know your training style so I can personalize things for you! How would you describe your experience level?\nsuggestions:Beginner|Intermediate|Advanced");
}, 0);
inputRef.current?.focus();
}handleFileUpload function · typescript · L336-L338 (3 LOC)src/app/(app)/trainer/page.tsx
function handleFileUpload(content: string, filename: string) {
handleSend(`[file: ${filename}]\n\n${content}`);
}handleSend function · typescript · L340-L482 (143 LOC)src/app/(app)/trainer/page.tsx
async function handleSend(text?: string) {
const messageText = text || input.trim();
if (!messageText || isLoading || !context) return;
// Auto-detect profile update requests
if (!profileMode && /update.*profile|edit.*profile|change.*profile|modify.*profile|redo.*profile|set up.*profile|setup.*profile/i.test(messageText)) {
startProfileSetup();
return;
}
setInput('');
addMessage('user', messageText);
setIsLoading(true);
const assistantId = addMessage('assistant', '');
try {
const chatMessages = [
...messages.map((m) => ({ role: m.role, content: m.content })),
{ role: 'user' as const, content: messageText },
];
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: chatMessages,
context,
...(profileMode && { mode: 'profile-setup' }),
}),
});
Repobility · MCP-ready · https://repobility.com
handleConfirmImport function · typescript · L495-L521 (27 LOC)src/app/(app)/trainer/page.tsx
async function handleConfirmImport(messageId: string, importData: ImportData) {
setImportingMessageId(messageId);
try {
const response = await fetch('/api/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'import',
workouts: importData.workouts,
unitSystem: context?.unitSystem || 'imperial',
}),
});
if (!response.ok) {
throw new Error('Import failed');
}
const result = await response.json();
updateImportStatus(messageId, 'imported');
addMessage('assistant', `Imported ${result.imported} workout${result.imported !== 1 ? 's' : ''} with ${result.summary.totalSets} sets. Check your training log to see them!`);
} catch {
updateImportStatus(messageId, 'pending');
addMessage('assistant', 'Sorry, the import failed. Please try again.');
} finally {
setImportingMessageId(null);
}
}handleCancelImport function · typescript · L523-L526 (4 LOC)src/app/(app)/trainer/page.tsx
function handleCancelImport(messageId: string) {
updateImportStatus(messageId, 'cancelled');
addMessage('assistant', 'Import cancelled. Let me know if you want to try again with different data.');
}handleConfirmTemplate function · typescript · L528-L556 (29 LOC)src/app/(app)/trainer/page.tsx
async function handleConfirmTemplate(messageId: string, templateData: TemplateData) {
setSavingTemplateId(messageId);
try {
const response = await fetch('/api/templates', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: templateData.name,
exercises: templateData.exercises,
}),
});
if (!response.ok) {
throw new Error('Failed to save template');
}
const result = await response.json();
updateTemplateStatus(messageId, 'saved');
const unmatchedMsg = result.unmatched && result.unmatched.length > 0
? ` (${result.unmatched.length} exercise${result.unmatched.length !== 1 ? 's' : ''} couldn't be matched: ${result.unmatched.join(', ')})`
: '';
addMessage('assistant', `Saved "${result.name}" with ${result.exerciseCount} exercises! You can find it in your Saved Templates on the home screen.${unmatchedMsg}`);
} catchandleCancelTemplate function · typescript · L558-L561 (4 LOC)src/app/(app)/trainer/page.tsx
function handleCancelTemplate(messageId: string) {
updateTemplateStatus(messageId, 'cancelled');
addMessage('assistant', 'No problem! Let me know if you want me to create a different template.');
}buildWorkoutUploadSnapshot function · typescript · L66-L91 (26 LOC)src/app/(app)/workout/[id]/page.tsx
function buildWorkoutUploadSnapshot(state: ActiveWorkoutState): WorkoutUploadSnapshot | null {
if (!state.workoutId || !state.startTime) return null;
return {
workoutId: state.workoutId,
workoutName: state.workoutName,
startTime: state.startTime,
templateId: state.templateId,
exercises: state.exercises.map((exercise) => ({
exerciseId: exercise.exerciseId,
logMode: exercise.logMode,
sets: exercise.sets.map((set) => ({
setNumber: set.setNumber,
weight: set.weight,
reps: set.reps,
leftWeight: set.leftWeight,
leftReps: set.leftReps,
rightWeight: set.rightWeight,
rightReps: set.rightReps,
time: set.time,
distance: set.distance,
isWarmup: set.isWarmup,
isCompleted: set.isCompleted,
})),
})),
};
}normalizePreviousPerformanceRow function · typescript · L128-L141 (14 LOC)src/app/(app)/workout/[id]/page.tsx
function normalizePreviousPerformanceRow(row: PreviousPerformanceRow): PerformanceSet | null {
const splitWeight = Math.max(row.left_weight ?? 0, row.right_weight ?? 0);
const splitReps = Math.max(row.left_reps ?? 0, row.right_reps ?? 0);
const weight = row.weight ?? (row.is_split_lr ? splitWeight : 0);
const reps = row.reps ?? (row.is_split_lr ? splitReps : 0);
if (weight <= 0 && reps <= 0) return null;
return {
weight,
reps,
setNumber: row.set_number || undefined,
};
}getPreviousSetForNumber function · typescript · L143-L145 (3 LOC)src/app/(app)/workout/[id]/page.tsx
function getPreviousSetForNumber(sets: PerformanceSet[], setNumber: number) {
return sets.find((s) => s.setNumber === setNumber) ?? sets[setNumber - 1];
}prefillEmptySetsWithPreviousPerformance function · typescript · L147-L178 (32 LOC)src/app/(app)/workout/[id]/page.tsx
function prefillEmptySetsWithPreviousPerformance(
exerciseId: string,
previousSets: PerformanceSet[],
) {
const state = useActiveWorkoutStore.getState();
const exerciseIndex = state.exercises.findIndex((exercise) => exercise.exerciseId === exerciseId);
if (exerciseIndex === -1) return;
const exercise = state.exercises[exerciseIndex];
for (let setIndex = 0; setIndex < exercise.sets.length; setIndex += 1) {
const currentSet = exercise.sets[setIndex];
if (currentSet.isCompleted || currentSet.timestamp) continue;
const previousSet = getPreviousSetForNumber(previousSets, currentSet.setNumber);
if (!previousSet) continue;
const nextSetData: Partial<ActiveSet> = {};
if (currentSet.weight === null) nextSetData.weight = previousSet.weight;
if (currentSet.reps === null) nextSetData.reps = previousSet.reps;
if (exercise.logMode === 'split_lr') {
if (currentSet.leftWeight === null) nextSetData.leftWeight = previousSet.weight;
if (currenAll rows above produced by Repobility · https://repobility.com
WarmupToggle function · typescript · L180-L214 (35 LOC)src/app/(app)/workout/[id]/page.tsx
function WarmupToggle({ isWarmup, setNumber, onToggle }: { isWarmup: boolean; setNumber: number; onToggle: () => void }) {
const pressTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const didLongPress = useRef(false);
function startPress() {
didLongPress.current = false;
pressTimer.current = setTimeout(() => {
didLongPress.current = true;
onToggle();
if (navigator.vibrate) navigator.vibrate(40);
}, 500);
}
function cancelPress() {
if (pressTimer.current) {
clearTimeout(pressTimer.current);
pressTimer.current = null;
}
}
return (
<Button variant="ghost"
className={`w-10 h-10 flex items-center justify-center rounded-lg text-sm font-medium select-none touch-none ${
isWarmup ? 'bg-primary/10 text-primary' : 'text-muted-foreground'
}`}
onPointerDown={startPress}
onPointerUp={cancelPress}
onPointerLeave={cancelPress}
onPointerCancel={cancelPress}
title="Hold tostartPress function · typescript · L184-L191 (8 LOC)src/app/(app)/workout/[id]/page.tsx
function startPress() {
didLongPress.current = false;
pressTimer.current = setTimeout(() => {
didLongPress.current = true;
onToggle();
if (navigator.vibrate) navigator.vibrate(40);
}, 500);
}cancelPress function · typescript · L193-L198 (6 LOC)src/app/(app)/workout/[id]/page.tsx
function cancelPress() {
if (pressTimer.current) {
clearTimeout(pressTimer.current);
pressTimer.current = null;
}
}