Rebrand to Proof of Work; multi-user 0.4 package with curated library sync

Repo cleanup
- Add top-level .gitignore (was missing; node_modules, .next, *.s9pk,
  image.tar, seed/data/*.db, log files, etc.) and a root README.
- Delete legacy start9/0.3.5/ package (StartOS 0.3.5 wrapper, no longer
  the deploy target).
- Delete start9-example-packaging/ (template from another project).
- Delete planning docs (START9_PACKAGING_LOG.md, VERSIONING.md,
  STARTOS_0.4_UPGRADE_PROMPT.md, ICON_FILES_INDEX.md, etc.) — info now
  lives in the deploy guide and code comments.
- Drop the standalone Dockerfile, docker-compose.yml, ICON_*, and dev
  log/build artifacts from the app dir.
- Drop the v0.1.0:18/19/20 version files (they belonged to the legacy
  workout-log package and don't apply to the new id).

Rename + new package
- Rename app dir workout-planner/ -> proof-of-work/.
- Rename StartOS package id workout-log -> proof-of-work; the new id
  makes this a brand new StartOS service (clean cutover from the old
  one rather than in-place upgrade).
- Reset version graph; v1.0.0:1 is the seeded cutover release. The
  Dockerfile bakes a one-time /data snapshot and docker_entrypoint.sh
  copies it into the new volume on truly-fresh first boot only (both
  /data/app.db missing AND /data/.seeded absent).
- Move start9/0.4-migration/ -> start9/0.4/; the old start9/0.4/ stub
  is gone.

Curated exercise library (multi-user-aware)
- proof-of-work/prisma/exercises.seed.json is the canonical library
  shipped to every install (164 exercises today, dumped from the live
  snapshot).
- proof-of-work/scripts/sync-library.cjs (npm run sync-library) refreshes
  the JSON from start9/0.4/seed/data/app.db after refresh_seed.sh.
- proof-of-work/prisma/seed.ts now reads from the JSON instead of a
  hardcoded 52-exercise array; runs at Docker build time to seed the
  fallback DB and on first boot for fresh installs.
- proof-of-work/prisma/ensureExerciseLibrary.cjs runs on every container
  boot (from docker_entrypoint.sh) and INSERT OR IGNOREs every library
  entry for every user, keyed on (userId, name). Library updates flow
  to existing installs on package upgrade; user-custom exercises
  (isCustom=true) and any colliding names are never overwritten;
  removed exercises stay on existing installs (additive-only).

Deploy guide (start9/0.4/DEPLOY_040.md)
- Rewritten end-to-end for the workout-log -> proof-of-work cutover:
  refresh_seed, sync-library, build, sideload, verify, rotate creds,
  stop the old service, then post-cutover cleanup release v1.0.0:2.
This commit is contained in:
Keysat
2026-05-08 20:12:25 -05:00
parent 1b64c45c52
commit aa407b5f67
184 changed files with 8314 additions and 3286 deletions
Vendored
BIN
View File
Binary file not shown.
+9 -9
View File
@@ -4,15 +4,15 @@ startos-wrapper/docker-images
startos-wrapper/*.s9pk startos-wrapper/*.s9pk
# Keep local app artifacts out of Docker build context # Keep local app artifacts out of Docker build context
workout-planner/node_modules proof-of-work/node_modules
workout-planner/.next proof-of-work/.next
workout-planner/logs proof-of-work/logs
workout-planner/.server.pid proof-of-work/.server.pid
workout-planner/prisma/*.db proof-of-work/prisma/*.db
workout-planner/prisma/data/*.db proof-of-work/prisma/data/*.db
workout-planner/.env proof-of-work/.env
workout-planner/.env.local proof-of-work/.env.local
workout-planner/.env.*.local proof-of-work/.env.*.local
# OS/editor junk # OS/editor junk
.DS_Store .DS_Store
+46
View File
@@ -0,0 +1,46 @@
# OS / editor
.DS_Store
Thumbs.db
.idea/
.vscode/
*.swp
*.swo
# Node / build
node_modules/
*.tsbuildinfo
.next/
out/
dist/
build/
# Logs / runtime
logs/
*.log
*.pid
# Env
.env
.env.local
.env.*.local
# Local DB snapshots that aren't part of the package
proof-of-work-*.db
*.db-journal
*.db-wal
*.db-shm
# Start9 build artifacts
*.s9pk
image.tar
start9/*/javascript/
# App-local dev DB
proof-of-work/prisma/dev.db
proof-of-work/prisma/data/*.db
!proof-of-work/prisma/data/.keep
# Live data snapshot pulled off the running 0.3.5 host — contains real
# workout history. Stays on disk so the maintainer can rebuild the seeded
# cutover image, but MUST never be committed to a public repo.
start9/*/seed/data/*.db
+65
View File
@@ -0,0 +1,65 @@
# Proof of Work
Self-hosted multi-user workout planner and logger. Plan training cycles,
log daily workouts, search your history, and curate a shared exercise
library across everyone on the instance. Distributed as a StartOS 0.4
sideload package.
## Repo layout
```
proof-of-work/ Next.js app (TypeScript, Prisma + SQLite, Tailwind, PWA)
start9/0.4/ StartOS 0.4 package wrapper (manifest, Dockerfile,
entrypoint, version graph, change-credentials action)
```
Everything else is generated at build time.
## Local development
```sh
cd proof-of-work
npm install
npx prisma db push # create the dev DB at prisma/data/app.db
npm run db:seed # admin@local / workout123 + curated exercise library
npm run dev # http://localhost:3000
```
## Building the StartOS package
See **[start9/0.4/DEPLOY_040.md](start9/0.4/DEPLOY_040.md)** for the full
deployment / cutover guide. Short version:
```sh
cd start9/0.4
npm ci
make clean
make x86 # produces proof-of-work_x86_64.s9pk
make install # sideload to the host in ~/.startos/config.yaml
```
## Curated exercise library
`proof-of-work/prisma/exercises.seed.json` is the canonical library
shipped to every install. It seeds fresh installs (via `prisma/seed.ts`)
and is re-applied on every boot to existing installs (via
`docker_entrypoint.sh` + `ensureExerciseLibrary.cjs`) so updates flow to
all users on package upgrade.
Refresh the JSON from the maintainer's live host:
```sh
./start9/0.4/refresh_seed.sh <ssh-target> # pull a fresh /data snapshot
cd proof-of-work && npm run sync-library # extract Exercise table -> JSON
git diff prisma/exercises.seed.json
```
The system is additive only — removing an exercise from the JSON does
not delete it from existing installs (users may have logged sets against
it). Users' own custom exercises (`isCustom = true`) are never touched.
## Privacy
`start9/0.4/seed/data/app.db` is your live `/data` snapshot. It contains
real workout history and a bcrypt'd password hash. The top-level
`.gitignore` keeps it out of git; do NOT commit it to any public repo.
@@ -34,6 +34,7 @@ export async function GET(
exerciseId: params.id, exerciseId: params.id,
workout: { workout: {
userId: user.id, userId: user.id,
deletedAt: null,
}, },
}, },
include: { include: {
@@ -49,7 +50,7 @@ export async function GET(
{ workout: { date: "desc" } }, { workout: { date: "desc" } },
{ setNumber: "asc" }, { setNumber: "asc" },
], ],
take: 100, take: 500,
}); });
// Group by workout // Group by workout
@@ -62,7 +63,7 @@ export async function GET(
workoutMap.get(key)!.sets.push(log); workoutMap.get(key)!.sets.push(log);
} }
const history = Array.from(workoutMap.values()).slice(0, 20); const history = Array.from(workoutMap.values()).slice(0, 50);
return NextResponse.json({ return NextResponse.json({
exercise, exercise,
@@ -0,0 +1,104 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
const SeedExerciseSchema = z.object({
name: z.string().min(1),
type: z.string().min(1).default("other"),
muscleGroups: z.array(z.string()).optional().default([]),
inputFields: z.array(z.string().min(1)).optional().default(["sets", "reps", "weight"]),
defaultWeightUnit: z.string().nullable().optional(),
});
const SeedPayloadSchema = z.object({
exercises: z.array(SeedExerciseSchema).min(1),
});
function normalizeToken(value: string): string {
return value.trim().toLowerCase();
}
export async function POST(request: NextRequest) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const parsed = SeedPayloadSchema.parse(body);
const existingExercises = await prisma.exercise.findMany({
where: { userId: user.id },
select: { name: true },
});
const existingNames = new Set(existingExercises.map((x) => normalizeToken(x.name)));
let created = 0;
let skipped = 0;
const errors: Array<{ name: string; error: string }> = [];
for (const item of parsed.exercises) {
const rawName = item.name.trim();
const nameKey = normalizeToken(rawName);
if (!rawName) continue;
if (existingNames.has(nameKey)) {
skipped += 1;
continue;
}
const muscleGroups = Array.from(
new Set(item.muscleGroups.map((v) => normalizeToken(v)).filter(Boolean))
);
let inputFields = Array.from(
new Set(item.inputFields.map((v) => normalizeToken(v)).filter(Boolean))
);
if (!inputFields.includes("sets")) {
inputFields = ["sets", ...inputFields];
}
try {
await prisma.exercise.create({
data: {
userId: user.id,
name: rawName,
type: normalizeToken(item.type || "other"),
muscleGroups: JSON.stringify(muscleGroups),
inputFields: JSON.stringify(inputFields),
defaultWeightUnit: item.defaultWeightUnit ?? null,
isCustom: true,
} as any,
});
existingNames.add(nameKey);
created += 1;
} catch (error) {
errors.push({
name: rawName,
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
return NextResponse.json({
success: true,
created,
skipped,
errors,
});
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: "Invalid seed payload", details: error.errors },
{ status: 400 }
);
}
console.error("POST /api/import/exercises/seed error:", error);
return NextResponse.json(
{ error: "Failed to process exercise seed" },
{ status: 500 }
);
}
}
@@ -38,12 +38,20 @@ interface ParsedSet {
weight?: number; weight?: number;
weightUnit: string; weightUnit: string;
reps?: number; reps?: number;
durationSeconds?: number;
distance?: number;
distanceUnit?: string;
calories?: number;
rpe?: number;
customMetrics?: Record<string, string>;
notes?: string; notes?: string;
} }
interface ParsedExercise { interface ParsedExercise {
exerciseId: string; exerciseId: string;
exerciseName: string; exerciseName: string;
sourceName?: string;
unmapped?: boolean;
sets: ParsedSet[]; sets: ParsedSet[];
} }
@@ -58,33 +66,86 @@ interface ParseResponse {
} }
function parseCSV(content: string): Array<Record<string, string>> { function parseCSV(content: string): Array<Record<string, string>> {
const lines = content.trim().split("\n"); const text = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
if (lines.length === 0) return []; if (!text.trim()) return [];
// Parse header const parsedRows: string[][] = [];
const header = lines[0].split(",").map((h) => h.trim().toLowerCase()); let row: string[] = [];
const rows = []; let cell = "";
let inQuotes = false;
// Parse data rows for (let i = 0; i < text.length; i++) {
for (let i = 1; i < lines.length; i++) { const ch = text[i];
const line = lines[i].trim(); const next = text[i + 1];
if (!line) continue;
const values = line.split(",").map((v) => v.trim()); if (ch === "\"") {
const row: Record<string, string> = {}; if (inQuotes && next === "\"") {
cell += "\"";
i++;
} else {
inQuotes = !inQuotes;
}
continue;
}
if (ch === "," && !inQuotes) {
row.push(cell);
cell = "";
continue;
}
if (ch === "\n" && !inQuotes) {
row.push(cell);
if (row.some((v) => v.trim() !== "")) {
parsedRows.push(row);
}
row = [];
cell = "";
continue;
}
cell += ch;
}
row.push(cell);
if (row.some((v) => v.trim() !== "")) {
parsedRows.push(row);
}
if (parsedRows.length === 0) return [];
const header = parsedRows[0].map((h) => h.trim().toLowerCase());
const rows: Array<Record<string, string>> = [];
for (let i = 1; i < parsedRows.length; i++) {
const values = parsedRows[i];
const out: Record<string, string> = {};
header.forEach((col, idx) => { header.forEach((col, idx) => {
if (values[idx]) { const value = (values[idx] || "").trim();
row[col] = values[idx]; if (value !== "") {
out[col] = value;
} }
}); });
if (Object.keys(out).length > 0) {
rows.push(row); rows.push(out);
}
} }
return rows; return rows;
} }
function parseFloatMaybe(value?: string): number | undefined {
if (!value) return undefined;
const n = Number.parseFloat(value);
return Number.isFinite(n) ? n : undefined;
}
function parseIntMaybe(value?: string): number | undefined {
if (!value) return undefined;
const n = Number.parseInt(value, 10);
return Number.isFinite(n) ? n : undefined;
}
function getVariationNote(originalName: string): string | null { function getVariationNote(originalName: string): string | null {
if (originalName.includes("Narrow")) return "narrow"; if (originalName.includes("Narrow")) return "narrow";
if (originalName.includes("Negatives")) return "negatives"; if (originalName.includes("Negatives")) return "negatives";
@@ -191,6 +252,33 @@ export async function POST(request: NextRequest) {
// Build parsed workouts // Build parsed workouts
const parsedWorkouts: ParsedWorkout[] = []; const parsedWorkouts: ParsedWorkout[] = [];
const knownColumns = new Set([
"date",
"date_str",
"workout_date",
"exercise",
"exercise_name",
"set",
"set_number",
"weight",
"weight_unit",
"weightunit",
"reps",
"duration",
"duration_seconds",
"duration_minutes",
"time",
"time_seconds",
"time_minutes",
"distance",
"distance_unit",
"distanceunit",
"calories",
"rpe",
"notes",
"custom_metrics_json",
"custommetricsjson",
]);
for (const [date, rowsForDate] of workoutsByDate) { for (const [date, rowsForDate] of workoutsByDate) {
const exercisesMap = new Map< const exercisesMap = new Map<
@@ -198,6 +286,8 @@ export async function POST(request: NextRequest) {
{ {
exerciseId: string; exerciseId: string;
exerciseName: string; exerciseName: string;
sourceName?: string;
unmapped?: boolean;
sets: ParsedSet[]; sets: ParsedSet[];
} }
>(); >();
@@ -205,33 +295,80 @@ export async function POST(request: NextRequest) {
for (const row of rowsForDate) { for (const row of rowsForDate) {
const csvExerciseName = row.exercise || row.exercise_name || ""; const csvExerciseName = row.exercise || row.exercise_name || "";
const resolvedName = resolveExerciseName(csvExerciseName); const resolvedName = resolveExerciseName(csvExerciseName);
const exerciseId = const exerciseId = exerciseMap.get(resolvedName.toLowerCase()) || "";
exerciseMap.get(resolvedName.toLowerCase()) || ""; const isUnmapped = !exerciseId;
const exerciseKey = isUnmapped
? `unmapped:${csvExerciseName.toLowerCase()}`
: exerciseId;
if (!exerciseId) { if (!exercisesMap.has(exerciseKey)) {
unmappedExercises.add(csvExerciseName); exercisesMap.set(exerciseKey, {
continue; exerciseId: exerciseId || "",
} exerciseName: isUnmapped ? csvExerciseName : resolvedName,
sourceName: csvExerciseName,
if (!exercisesMap.has(exerciseId)) { unmapped: isUnmapped,
exercisesMap.set(exerciseId, {
exerciseId,
exerciseName: resolvedName,
sets: [], sets: [],
}); });
} }
const exerciseData = exercisesMap.get(exerciseId)!; const exerciseData = exercisesMap.get(exerciseKey)!;
const weight = row.weight ? parseFloat(row.weight) : undefined; const weight = parseFloatMaybe(row.weight);
const reps = row.reps ? parseInt(row.reps, 10) : undefined; const reps = parseIntMaybe(row.reps);
let notes = row.notes || ""; let notes = row.notes || "";
// Detect weight unit from notes // Detect weight unit from notes
let weightUnit = "lbs"; let weightUnit = row.weight_unit || row.weightunit || "lbs";
if (notes.toLowerCase().includes("kg")) { if (notes.toLowerCase().includes("kg")) {
weightUnit = "kg"; weightUnit = "kg";
} }
const durationSeconds =
parseIntMaybe(row.duration_seconds) ??
parseIntMaybe(row.time_seconds) ??
(parseFloatMaybe(row.duration_minutes) !== undefined
? Math.round((parseFloatMaybe(row.duration_minutes) || 0) * 60)
: undefined) ??
(parseFloatMaybe(row.time_minutes) !== undefined
? Math.round((parseFloatMaybe(row.time_minutes) || 0) * 60)
: undefined) ??
parseIntMaybe(row.duration) ??
parseIntMaybe(row.time);
const distance = parseFloatMaybe(row.distance);
const distanceUnit = row.distance_unit || row.distanceunit || (distance !== undefined ? "mi" : undefined);
const calories = parseIntMaybe(row.calories);
const rpe = parseIntMaybe(row.rpe);
const customMetrics: Record<string, string> = {};
const customJson = row.custom_metrics_json || row.custommetricsjson;
if (customJson) {
try {
const parsed = JSON.parse(customJson);
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
for (const [k, v] of Object.entries(parsed as Record<string, unknown>)) {
if (v !== null && v !== undefined && String(v).trim() !== "") {
customMetrics[String(k)] = String(v);
}
}
}
} catch {
// Ignore malformed JSON field.
}
}
for (const [col, val] of Object.entries(row)) {
if (!val) continue;
const normalizedCol = col.toLowerCase().trim();
if (knownColumns.has(normalizedCol)) continue;
if (normalizedCol.startsWith("custom_")) {
const key = normalizedCol.replace(/^custom_/, "");
if (key) customMetrics[key] = val;
continue;
}
// Treat extra columns as custom metrics too.
customMetrics[normalizedCol] = val;
}
// Add variation note if applicable // Add variation note if applicable
const variationNote = getVariationNote(csvExerciseName); const variationNote = getVariationNote(csvExerciseName);
if (variationNote) { if (variationNote) {
@@ -240,13 +377,22 @@ export async function POST(request: NextRequest) {
: `(${variationNote})`; : `(${variationNote})`;
} }
const setNumber = exerciseData.sets.length + 1; const setNumber =
parseIntMaybe(row.set_number) ??
parseIntMaybe(row.set) ??
exerciseData.sets.length + 1;
exerciseData.sets.push({ exerciseData.sets.push({
setNumber, setNumber,
weight, weight,
weightUnit, weightUnit,
reps, reps,
durationSeconds,
distance,
distanceUnit,
calories,
rpe,
customMetrics: Object.keys(customMetrics).length > 0 ? customMetrics : undefined,
notes: notes || undefined, notes: notes || undefined,
}); });
} }
@@ -0,0 +1,132 @@
import { getCurrentUser } from "@/lib/auth";
import { getTimestampFileSuffix } from "@/lib/db-file";
import { getCaloriesBurnedBulk, prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
function csvCell(value: unknown): string {
if (value === null || value === undefined) return "";
const text = String(value);
if (text.includes(",") || text.includes("\"") || text.includes("\n")) {
return `"${text.replace(/"/g, "\"\"")}"`;
}
return text;
}
/**
* GET /api/settings/export-csv
* Exports workout + set-level rows to CSV for offline backup.
*/
export async function GET() {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const setLogs = await prisma.setLog.findMany({
where: {
workout: {
userId: user.id,
deletedAt: null,
},
},
include: {
exercise: true,
workout: {
select: {
id: true,
date: true,
name: true,
notes: true,
durationMinutes: true,
difficulty: true,
},
},
},
orderBy: [
{ workout: { date: "desc" } },
{ setNumber: "asc" },
],
});
const workoutIds = Array.from(new Set(setLogs.map((s) => s.workout.id)));
const caloriesMap = await getCaloriesBurnedBulk(workoutIds);
const header = [
"workoutId",
"workoutDate",
"workoutName",
"workoutNotes",
"workoutDurationMinutes",
"workoutDifficulty",
"workoutCaloriesBurned",
"exerciseId",
"exerciseName",
"setNumber",
"reps",
"weight",
"weightUnit",
"durationMinutes",
"distance",
"distanceUnit",
"setCalories",
"rpe",
"setNotes",
"customMetricsJson",
];
const lines = [header.join(",")];
for (const set of setLogs) {
const durationMinutes =
typeof set.durationSeconds === "number"
? (set.durationSeconds / 60).toFixed(2)
: "";
const row = [
set.workout.id,
set.workout.date.toISOString(),
set.workout.name ?? "",
set.workout.notes ?? "",
set.workout.durationMinutes ?? "",
set.workout.difficulty ?? "",
caloriesMap[set.workout.id] ?? "",
set.exerciseId,
set.exercise.name,
set.setNumber,
set.reps ?? "",
set.weight ?? "",
set.weightUnit ?? "",
durationMinutes,
set.distance ?? "",
set.distanceUnit ?? "",
set.calories ?? "",
set.rpe ?? "",
set.notes ?? "",
set.customMetrics ?? "",
];
lines.push(row.map(csvCell).join(","));
}
const csv = lines.join("\n");
const fileName = `proof-of-work-export-${getTimestampFileSuffix()}.csv`;
return new NextResponse(csv, {
status: 200,
headers: {
"Content-Type": "text/csv; charset=utf-8",
"Content-Disposition": `attachment; filename="${fileName}"`,
"Cache-Control": "no-store",
},
});
} catch (error) {
console.error("CSV export error:", error);
return NextResponse.json(
{ error: "Failed to export CSV" },
{ status: 500 }
);
}
}
@@ -0,0 +1,39 @@
import { getCurrentUser } from "@/lib/auth";
import { getTimestampFileSuffix, resolveDatabasePath } from "@/lib/db-file";
import { readFile } from "fs/promises";
import { NextResponse } from "next/server";
import path from "path";
export const dynamic = "force-dynamic";
/**
* GET /api/settings/export-db
* Download the current SQLite database file.
*/
export async function GET() {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const dbPath = resolveDatabasePath();
const data = await readFile(dbPath);
const fileName = `proof-of-work-${getTimestampFileSuffix()}.db`;
return new NextResponse(data, {
status: 200,
headers: {
"Content-Type": "application/x-sqlite3",
"Content-Disposition": `attachment; filename="${path.basename(fileName)}"`,
"Cache-Control": "no-store",
},
});
} catch (error) {
console.error("Database export error:", error);
return NextResponse.json(
{ error: "Failed to export database" },
{ status: 500 }
);
}
}
@@ -4,6 +4,7 @@ import { writeFile, copyFile, unlink } from "fs/promises";
import { existsSync } from "fs"; import { existsSync } from "fs";
import path from "path"; import path from "path";
import { execSync } from "child_process"; import { execSync } from "child_process";
import { resolveDatabasePath } from "@/lib/db-file";
/** /**
* POST /api/settings/import-db * POST /api/settings/import-db
@@ -49,18 +50,7 @@ export async function POST(request: NextRequest) {
); );
} }
// Determine the database file path from DATABASE_URL const dbPath = resolveDatabasePath();
const dbUrl = process.env.DATABASE_URL || "file:./data/app.db";
let dbPath: string;
if (dbUrl.startsWith("file:")) {
dbPath = dbUrl.replace("file:", "");
// Handle relative paths
if (!path.isAbsolute(dbPath)) {
dbPath = path.resolve(process.cwd(), "prisma", dbPath.replace("./", ""));
}
} else {
dbPath = path.resolve(process.cwd(), "prisma", "data", "app.db");
}
// Write uploaded file to a temp location for validation // Write uploaded file to a temp location for validation
const tempPath = dbPath + ".upload-temp"; const tempPath = dbPath + ".upload-temp";
@@ -14,8 +14,8 @@ export async function GET(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const workout = await prisma.workout.findUnique({ const workout = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { include: {
@@ -57,6 +57,11 @@ const setSchema = z.object({
weight: z.number().optional().nullable(), weight: z.number().optional().nullable(),
weightUnit: z.string().default("lbs"), weightUnit: z.string().default("lbs"),
rpe: z.number().int().min(1).max(10).optional().nullable(), rpe: z.number().int().min(1).max(10).optional().nullable(),
durationSeconds: z.number().int().positive().optional().nullable(),
distance: z.number().positive().optional().nullable(),
distanceUnit: z.string().optional().nullable(),
calories: z.number().int().positive().optional().nullable(),
customMetrics: z.record(z.string()).optional().nullable(),
notes: z.string().optional().nullable(), notes: z.string().optional().nullable(),
}); });
@@ -80,8 +85,8 @@ export async function PATCH(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const workout = await prisma.workout.findUnique({ const workout = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
select: { userId: true }, select: { userId: true },
}); });
@@ -134,14 +139,22 @@ export async function PATCH(
weight: set.weight ?? undefined, weight: set.weight ?? undefined,
weightUnit: set.weightUnit, weightUnit: set.weightUnit,
rpe: set.rpe ?? undefined, rpe: set.rpe ?? undefined,
durationSeconds: set.durationSeconds ?? undefined,
distance: set.distance ?? undefined,
distanceUnit: set.distanceUnit ?? undefined,
calories: set.calories ?? undefined,
customMetrics:
set.customMetrics && Object.keys(set.customMetrics).length > 0
? JSON.stringify(set.customMetrics)
: undefined,
notes: set.notes ?? undefined, notes: set.notes ?? undefined,
} as any)), } as any)),
}); });
} }
// Return full updated workout // Return full updated workout
return tx.workout.findUnique({ return tx.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { exercise: true }, include: { exercise: true },
@@ -173,8 +186,8 @@ export async function PATCH(
await setCaloriesBurned(params.id, caloriesValue ?? null); await setCaloriesBurned(params.id, caloriesValue ?? null);
} }
const updated = await prisma.workout.findUnique({ const updated = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { exercise: true }, include: { exercise: true },
@@ -211,8 +224,8 @@ export async function DELETE(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const workout = await prisma.workout.findUnique({ const workout = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
select: { userId: true }, select: { userId: true },
}); });
@@ -224,8 +237,9 @@ export async function DELETE(
return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
} }
await prisma.workout.delete({ await prisma.workout.update({
where: { id: params.id }, where: { id: params.id },
data: { deletedAt: new Date() },
}); });
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
@@ -16,6 +16,7 @@ const addSetsSchema = z.object({
distance: z.number().positive().optional(), distance: z.number().positive().optional(),
distanceUnit: z.string().optional(), distanceUnit: z.string().optional(),
calories: z.number().int().positive().optional(), calories: z.number().int().positive().optional(),
customMetrics: z.record(z.string()).optional(),
notes: z.string().optional(), notes: z.string().optional(),
}) })
), ),
@@ -32,8 +33,8 @@ export async function POST(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const workout = await prisma.workout.findUnique({ const workout = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
select: { userId: true }, select: { userId: true },
}); });
@@ -70,13 +71,17 @@ export async function POST(
distance: set.distance, distance: set.distance,
distanceUnit: set.distanceUnit, distanceUnit: set.distanceUnit,
calories: set.calories, calories: set.calories,
customMetrics:
set.customMetrics && Object.keys(set.customMetrics).length > 0
? JSON.stringify(set.customMetrics)
: undefined,
notes: set.notes, notes: set.notes,
} as any)), } as any)),
}); });
// Return updated workout // Return updated workout
const updated = await prisma.workout.findUnique({ const updated = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { exercise: true }, include: { exercise: true },
@@ -112,8 +117,8 @@ export async function DELETE(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
} }
const workout = await prisma.workout.findUnique({ const workout = await prisma.workout.findFirst({
where: { id: params.id }, where: { id: params.id, deletedAt: null },
select: { userId: true }, select: { userId: true },
}); });
@@ -24,6 +24,7 @@ const createWorkoutSchema = z.object({
distance: z.number().positive().optional(), distance: z.number().positive().optional(),
distanceUnit: z.string().optional(), distanceUnit: z.string().optional(),
calories: z.number().int().positive().optional(), calories: z.number().int().positive().optional(),
customMetrics: z.record(z.string()).optional(),
notes: z.string().optional(), notes: z.string().optional(),
}) })
) )
@@ -48,6 +49,7 @@ export async function GET(request: NextRequest) {
const where: any = { const where: any = {
userId: user.id, userId: user.id,
deletedAt: null,
}; };
if (query) { if (query) {
@@ -154,6 +156,10 @@ export async function POST(request: NextRequest) {
distance: set.distance, distance: set.distance,
distanceUnit: set.distanceUnit, distanceUnit: set.distanceUnit,
calories: set.calories, calories: set.calories,
customMetrics:
set.customMetrics && Object.keys(set.customMetrics).length > 0
? JSON.stringify(set.customMetrics)
: undefined,
notes: set.notes, notes: set.notes,
} as any)), } as any)),
} }
@@ -40,7 +40,7 @@ export default function LoginPage() {
<div className="bg-zinc-900 rounded border border-zinc-800 shadow-2xl"> <div className="bg-zinc-900 rounded border border-zinc-800 shadow-2xl">
<div className="flex flex-col space-y-2 p-8 text-center"> <div className="flex flex-col space-y-2 p-8 text-center">
<h1 className="text-3xl font-bold leading-none tracking-tight text-white"> <h1 className="text-3xl font-bold leading-none tracking-tight text-white">
Workout Planner Proof of Work
</h1> </h1>
<p className="text-xs text-zinc-500 mt-2 uppercase tracking-widest"> <p className="text-xs text-zinc-500 mt-2 uppercase tracking-widest">
Track. Lift. Dominate. Track. Lift. Dominate.
@@ -25,16 +25,20 @@ export const viewport: Viewport = {
}; };
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Workout Planner', title: 'Proof of Work',
description: 'Track. Lift. Dominate.', description: 'Track. Lift. Dominate.',
manifest: '/manifest.json', manifest: '/manifest.json',
appleWebApp: { appleWebApp: {
capable: true, capable: true,
statusBarStyle: 'black-translucent', statusBarStyle: 'black-translucent',
title: 'Workout', title: 'Proof of Work',
}, },
icons: { icons: {
icon: '/icons/favicon.svg', icon: [
{ url: '/icons/favicon-16x16.png', sizes: '16x16', type: 'image/png' },
{ url: '/icons/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
{ url: '/icons/favicon-64x64.png', sizes: '64x64', type: 'image/png' },
],
apple: '/icons/icon-192x192.png', apple: '/icons/icon-192x192.png',
}, },
}; };
@@ -28,18 +28,6 @@ export default async function DashboardPage() {
return ( return (
<div className="min-h-screen bg-[#0A0A0A]"> <div className="min-h-screen bg-[#0A0A0A]">
{/* Header with greeting */}
<div className="px-4 py-6 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
<h1 className="text-4xl font-bold text-white">
Welcome back, {user.name || "Trainer"}!
</h1>
<p className="text-zinc-400 mt-2">
Keep pushing your limits and achieving your goals.
</p>
</div>
</div>
{/* Main content */} {/* Main content */}
<div className="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
{/* Stats Cards */} {/* Stats Cards */}
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,6 @@ import {
LayoutDashboard, LayoutDashboard,
Dumbbell, Dumbbell,
ListChecks, ListChecks,
Upload,
Settings, Settings,
LogOut, LogOut,
} from 'lucide-react'; } from 'lucide-react';
@@ -19,7 +18,6 @@ const navLinks = [
{ href: '/main/dashboard', label: 'Dashboard', icon: LayoutDashboard }, { href: '/main/dashboard', label: 'Dashboard', icon: LayoutDashboard },
{ href: '/main/workouts', label: 'Workouts', icon: Dumbbell }, { href: '/main/workouts', label: 'Workouts', icon: Dumbbell },
{ href: '/main/exercises', label: 'Exercises', icon: ListChecks }, { href: '/main/exercises', label: 'Exercises', icon: ListChecks },
{ href: '/main/import', label: 'Import', icon: Upload },
{ href: '/main/settings', label: 'Settings', icon: Settings }, { href: '/main/settings', label: 'Settings', icon: Settings },
]; ];
@@ -41,8 +39,7 @@ export default function Navigation({ userName }: NavigationProps) {
{/* Desktop Sidebar */} {/* Desktop Sidebar */}
<aside className="hidden md:flex fixed left-0 top-0 h-screen w-[var(--sidebar-width)] border-r border-zinc-800 bg-[#0A0A0A] flex-col"> <aside className="hidden md:flex fixed left-0 top-0 h-screen w-[var(--sidebar-width)] border-r border-zinc-800 bg-[#0A0A0A] flex-col">
<div className="p-6 border-b border-zinc-800"> <div className="p-6 border-b border-zinc-800">
<h2 className="text-3xl font-display text-white tracking-wider">Workout</h2> <h2 className="text-3xl font-display text-white tracking-wider">Proof of Work</h2>
<p className="text-xs text-zinc-500 mt-1 uppercase tracking-widest font-sans">Planner</p>
</div> </div>
<nav className="flex-1 overflow-y-auto p-4 space-y-2"> <nav className="flex-1 overflow-y-auto p-4 space-y-2">
@@ -23,13 +23,26 @@ function buildSetSummary(set: {
durationSeconds?: number | null; durationSeconds?: number | null;
distance?: number | null; distance?: number | null;
calories?: number | null; calories?: number | null;
customMetrics?: string | null;
}) { }) {
const parts: string[] = []; const parts: string[] = [];
if (set.weight) parts.push(`${set.weight} ${set.weightUnit === "kg" ? "kg" : "lbs"}`); if (set.weight) parts.push(`${set.weight} ${set.weightUnit === "kg" ? "kg" : "lbs"}`);
if (set.reps) parts.push(`${set.reps} reps`); if (set.reps) parts.push(`${set.reps} reps`);
if ((set as any).durationSeconds) parts.push(`${(set as any).durationSeconds}s`); if ((set as any).durationSeconds) {
const minutes = (set as any).durationSeconds / 60;
const rounded = Math.round(minutes * 10) / 10;
parts.push(`${Number.isInteger(rounded) ? Math.trunc(rounded) : rounded} min`);
}
if ((set as any).distance) parts.push(`${(set as any).distance} mi`); if ((set as any).distance) parts.push(`${(set as any).distance} mi`);
if ((set as any).calories) parts.push(`${(set as any).calories} cal`); if ((set as any).calories) parts.push(`${(set as any).calories} cal`);
if ((set as any).customMetrics) {
try {
const custom = JSON.parse((set as any).customMetrics) as Record<string, string>;
for (const [k, v] of Object.entries(custom)) {
if (v) parts.push(`${k}: ${v}`);
}
} catch {}
}
if (set.rpe) parts.push(`RPE ${set.rpe}`); if (set.rpe) parts.push(`RPE ${set.rpe}`);
if (set.notes) parts.push(set.notes); if (set.notes) parts.push(set.notes);
return parts.length > 0 ? parts.join(" · ") : "No data"; return parts.length > 0 ? parts.join(" · ") : "No data";
@@ -32,6 +32,14 @@ export default async function NewWorkoutPage({
const grouped: Record<string, EditWorkoutData["exercises"][number]> = {}; const grouped: Record<string, EditWorkoutData["exercises"][number]> = {};
for (const set of workout.setLogs) { for (const set of workout.setLogs) {
const exId = set.exercise.id; const exId = set.exercise.id;
let customMetrics: Record<string, string> | undefined;
if ((set as any).customMetrics) {
try {
customMetrics = JSON.parse((set as any).customMetrics);
} catch {
customMetrics = undefined;
}
}
if (!grouped[exId]) { if (!grouped[exId]) {
grouped[exId] = { grouped[exId] = {
exercise: set.exercise, exercise: set.exercise,
@@ -43,6 +51,10 @@ export default async function NewWorkoutPage({
reps: set.reps ?? undefined, reps: set.reps ?? undefined,
weight: set.weight ?? undefined, weight: set.weight ?? undefined,
rpe: set.rpe ?? undefined, rpe: set.rpe ?? undefined,
durationSeconds: set.durationSeconds ?? undefined,
distance: set.distance ?? undefined,
calories: set.calories ?? undefined,
customMetrics,
notes: set.notes ?? undefined, notes: set.notes ?? undefined,
}); });
} }
@@ -13,6 +13,8 @@ export const metadata = {
title: "Workout History", title: "Workout History",
description: "View your workout history", description: "View your workout history",
}; };
export const dynamic = "force-dynamic";
export const revalidate = 0;
export default async function WorkoutsPage({ searchParams }: PageProps) { export default async function WorkoutsPage({ searchParams }: PageProps) {
const user = await getCurrentUser(); const user = await getCurrentUser();
@@ -0,0 +1,401 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { Exercise } from "@prisma/client";
import { Loader2 } from "lucide-react";
import {
deriveEquipmentOptions,
deriveMuscleGroupOptions,
deriveTrackingFieldOptions,
displayLabel,
normalizeValue,
Option,
} from "@/lib/exerciseOptions";
interface AddExerciseFormProps {
onExerciseAdded: (exercise: Exercise) => void;
}
export default function AddExerciseForm({ onExerciseAdded }: AddExerciseFormProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [libraryExercises, setLibraryExercises] = useState<Exercise[]>([]);
const [formData, setFormData] = useState({
name: "",
type: "barbell",
muscleGroups: [] as string[],
inputFields: ["sets", "reps", "weight"] as string[],
description: "",
});
const [addingType, setAddingType] = useState(false);
const [newTypeText, setNewTypeText] = useState("");
const [sessionTypes, setSessionTypes] = useState<Option[]>([]);
const [addingMuscle, setAddingMuscle] = useState(false);
const [newMuscleText, setNewMuscleText] = useState("");
const [sessionMuscles, setSessionMuscles] = useState<string[]>([]);
const [addingField, setAddingField] = useState(false);
const [newFieldText, setNewFieldText] = useState("");
const [sessionFields, setSessionFields] = useState<Option[]>([]);
useEffect(() => {
const fetchExercises = async () => {
try {
const response = await fetch("/api/exercises");
if (!response.ok) return;
const data = await response.json();
setLibraryExercises(data);
} catch (err) {
console.error("Failed to fetch exercise library:", err);
}
};
fetchExercises();
}, []);
const equipmentOptions = useMemo(() => {
const base = deriveEquipmentOptions(libraryExercises);
const merged = [...base];
for (const option of sessionTypes) {
if (!merged.some((item) => item.value === option.value)) {
merged.push(option);
}
}
return merged;
}, [libraryExercises, sessionTypes]);
const muscleOptions = useMemo(() => {
const base = deriveMuscleGroupOptions(libraryExercises);
const merged = [...base];
for (const muscle of sessionMuscles) {
if (!merged.includes(muscle)) {
merged.push(muscle);
}
}
return merged;
}, [libraryExercises, sessionMuscles]);
const trackingOptions = useMemo(() => {
const base = deriveTrackingFieldOptions(libraryExercises);
const merged = [...base];
for (const option of sessionFields) {
if (!merged.some((item) => item.value === option.value)) {
merged.push(option);
}
}
return merged;
}, [libraryExercises, sessionFields]);
const getDefaultFieldsForType = (type: string): string[] => {
if (type === "cardio") return ["sets", "duration", "distance", "calories", "notes"];
if (type === "bodyweight") return ["sets", "reps", "notes"];
return ["sets", "reps", "weight", "notes"];
};
const handleTypeChange = (type: string) => {
setFormData((prev) => ({
...prev,
type,
inputFields: getDefaultFieldsForType(type),
}));
};
const handleMuscleGroupToggle = (group: string) => {
setFormData((prev) => ({
...prev,
muscleGroups: prev.muscleGroups.includes(group)
? prev.muscleGroups.filter((item) => item !== group)
: [...prev.muscleGroups, group],
}));
};
const handleInputFieldToggle = (field: string) => {
if (field === "sets") return;
setFormData((prev) => ({
...prev,
inputFields: prev.inputFields.includes(field)
? prev.inputFields.filter((item) => item !== field)
: [...prev.inputFields, field],
}));
};
const commitNewType = () => {
const value = normalizeValue(newTypeText);
if (!value) {
setAddingType(false);
setNewTypeText("");
return;
}
if (!equipmentOptions.some((option) => option.value === value)) {
setSessionTypes((prev) => [...prev, { value, label: displayLabel(value) }]);
}
handleTypeChange(value);
setAddingType(false);
setNewTypeText("");
};
const commitNewMuscle = () => {
const value = normalizeValue(newMuscleText);
if (!value) {
setAddingMuscle(false);
setNewMuscleText("");
return;
}
if (!muscleOptions.includes(value)) {
setSessionMuscles((prev) => [...prev, value]);
}
if (!formData.muscleGroups.includes(value)) {
setFormData((prev) => ({ ...prev, muscleGroups: [...prev.muscleGroups, value] }));
}
setAddingMuscle(false);
setNewMuscleText("");
};
const commitNewField = () => {
const value = normalizeValue(newFieldText);
if (!value || value === "sets") {
setAddingField(false);
setNewFieldText("");
return;
}
if (!trackingOptions.some((option) => option.value === value)) {
setSessionFields((prev) => [...prev, { value, label: displayLabel(value) }]);
}
if (!formData.inputFields.includes(value)) {
setFormData((prev) => ({ ...prev, inputFields: [...prev.inputFields, value] }));
}
setAddingField(false);
setNewFieldText("");
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
const response = await fetch("/api/exercises", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: formData.name,
type: formData.type,
muscleGroups: formData.muscleGroups,
inputFields: formData.inputFields,
description: formData.description || undefined,
}),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || "Failed to add exercise");
}
const exercise = await response.json();
onExerciseAdded(exercise);
setLibraryExercises((prev) => [...prev, exercise]);
setFormData({
name: "",
type: "barbell",
muscleGroups: [],
inputFields: ["sets", "reps", "weight"],
description: "",
});
setSessionTypes([]);
setSessionMuscles([]);
setSessionFields([]);
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-5">
{error && (
<div className="bg-red-900/50 border border-red-800 rounded-lg p-3 text-red-400 text-sm">
{error}
</div>
)}
<div>
<label className="block text-xs font-medium text-zinc-400 uppercase tracking-wider mb-2">
Exercise Name
</label>
<input
type="text"
required
value={formData.name}
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-white/20"
placeholder="e.g., Barbell Bench Press"
/>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 uppercase tracking-wider mb-2">
Equipment
</label>
<div className="flex flex-wrap gap-2">
{equipmentOptions.map((option) => (
<button
key={option.value}
type="button"
onClick={() => handleTypeChange(option.value)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition ${
formData.type === option.value
? "bg-white text-black"
: "bg-zinc-800 text-zinc-400 hover:text-white"
}`}
>
{option.label}
</button>
))}
{addingType ? (
<input
autoFocus
value={newTypeText}
onChange={(e) => setNewTypeText(e.target.value)}
onBlur={commitNewType}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
commitNewType();
}
}}
placeholder="New"
className="w-20 px-2 py-1.5 rounded-lg text-sm bg-zinc-800 border border-zinc-600 text-white"
/>
) : (
<button
type="button"
onClick={() => setAddingType(true)}
className="px-2.5 py-1.5 rounded-lg text-sm font-medium bg-zinc-800 text-zinc-500 hover:text-white border border-dashed border-zinc-600 transition"
>
+
</button>
)}
</div>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 uppercase tracking-wider mb-1">
Tracked Fields
</label>
<p className="text-xs text-zinc-600 mb-2">What data do you log for this exercise?</p>
<div className="flex flex-wrap gap-2">
{trackingOptions.map((field) => (
<button
key={field.value}
type="button"
onClick={() => handleInputFieldToggle(field.value)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition ${
formData.inputFields.includes(field.value)
? "bg-white text-black"
: "bg-zinc-800 text-zinc-400 hover:text-white"
}`}
>
{field.label}
</button>
))}
{addingField ? (
<input
autoFocus
value={newFieldText}
onChange={(e) => setNewFieldText(e.target.value)}
onBlur={commitNewField}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
commitNewField();
}
}}
placeholder="New"
className="w-20 px-2 py-1.5 rounded-lg text-sm bg-zinc-800 border border-zinc-600 text-white"
/>
) : (
<button
type="button"
onClick={() => setAddingField(true)}
className="px-2.5 py-1.5 rounded-lg text-sm font-medium bg-zinc-800 text-zinc-500 hover:text-white border border-dashed border-zinc-600 transition"
>
+
</button>
)}
</div>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 uppercase tracking-wider mb-2">
Muscle Groups
</label>
<div className="flex flex-wrap gap-2">
{muscleOptions.map((group) => (
<button
key={group}
type="button"
onClick={() => handleMuscleGroupToggle(group)}
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition ${
formData.muscleGroups.includes(group)
? "bg-white text-black"
: "bg-zinc-800 text-zinc-400 hover:text-white"
}`}
>
{displayLabel(group)}
</button>
))}
{addingMuscle ? (
<input
autoFocus
value={newMuscleText}
onChange={(e) => setNewMuscleText(e.target.value)}
onBlur={commitNewMuscle}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
commitNewMuscle();
}
}}
placeholder="New"
className="w-20 px-2 py-1.5 rounded-lg text-sm bg-zinc-800 border border-zinc-600 text-white"
/>
) : (
<button
type="button"
onClick={() => setAddingMuscle(true)}
className="px-2.5 py-1.5 rounded-lg text-sm font-medium bg-zinc-800 text-zinc-500 hover:text-white border border-dashed border-zinc-600 transition"
>
+
</button>
)}
</div>
</div>
<div>
<label className="block text-xs font-medium text-zinc-400 uppercase tracking-wider mb-2">
Description (optional)
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData((prev) => ({ ...prev, description: e.target.value }))}
className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-white/20 resize-none"
placeholder="Notes about form, tips, or variations..."
rows={2}
/>
</div>
<button
type="submit"
disabled={loading || !formData.name}
className="w-full bg-white hover:bg-zinc-200 disabled:bg-zinc-700 disabled:text-zinc-500 text-black font-bold py-3 px-4 rounded-lg transition flex items-center justify-center gap-2"
>
{loading && <Loader2 className="w-4 h-4 animate-spin" />}
{loading ? "Adding..." : "Add Exercise"}
</button>
</form>
);
}
@@ -2,7 +2,16 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import { Loader2, Eye, EyeOff, Upload, AlertTriangle, CheckCircle2 } from "lucide-react"; import {
Loader2,
Eye,
EyeOff,
Upload,
Download,
AlertTriangle,
CheckCircle2,
} from "lucide-react";
import Link from "next/link";
interface UserPreferences { interface UserPreferences {
theme: string; theme: string;
@@ -266,11 +275,145 @@ export default function SettingsForm({ user }: { user: User }) {
</button> </button>
{/* Database Import Section */} {/* Database Import Section */}
<DatabaseExport />
<WorkoutCsvImportShortcut />
<DatabaseImport /> <DatabaseImport />
</form> </form>
); );
} }
// ---------- Database Export Component ----------
function DatabaseExport() {
const [exportingDb, setExportingDb] = useState(false);
const [exportingCsv, setExportingCsv] = useState(false);
const [exportError, setExportError] = useState<string | null>(null);
const triggerDownload = async (
endpoint: string,
fallbackName: string,
setLoading: (v: boolean) => void
) => {
setLoading(true);
setExportError(null);
try {
const response = await fetch(endpoint, {
method: "GET",
});
if (!response.ok) {
const maybeJson = await response.json().catch(() => null);
throw new Error(maybeJson?.error || "Export failed");
}
const disposition = response.headers.get("content-disposition") || "";
const match = disposition.match(/filename="([^"]+)"/i);
const fileName = match?.[1] || fallbackName;
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = fileName;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
window.URL.revokeObjectURL(url);
} catch (err) {
setExportError(err instanceof Error ? err.message : "Export failed");
} finally {
setLoading(false);
}
};
return (
<div className="bg-zinc-900 border border-zinc-800 rounded-lg p-6">
<h2 className="text-lg font-bold text-white mb-1">Export Backups</h2>
<p className="text-sm text-zinc-500 mb-4">
Download a full database backup or a CSV export of workout logs.
</p>
{exportError && (
<div className="bg-red-900/30 border border-red-800 rounded-lg p-3 mb-4 flex items-start gap-2">
<AlertTriangle className="w-4 h-4 text-red-400 flex-shrink-0 mt-0.5" />
<span className="text-sm text-red-400">{exportError}</span>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<button
type="button"
onClick={() =>
triggerDownload(
"/api/settings/export-db",
"proof-of-work-backup.db",
setExportingDb
)
}
disabled={exportingDb || exportingCsv}
className="py-3 border border-zinc-700 rounded-lg text-zinc-300 text-sm font-medium hover:text-white hover:border-zinc-500 disabled:opacity-50 transition flex items-center justify-center gap-2"
>
{exportingDb ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Exporting DB...
</>
) : (
<>
<Download className="w-4 h-4" />
Export SQLite (.db)
</>
)}
</button>
<button
type="button"
onClick={() =>
triggerDownload(
"/api/settings/export-csv",
"proof-of-work-export.csv",
setExportingCsv
)
}
disabled={exportingDb || exportingCsv}
className="py-3 border border-zinc-700 rounded-lg text-zinc-300 text-sm font-medium hover:text-white hover:border-zinc-500 disabled:opacity-50 transition flex items-center justify-center gap-2"
>
{exportingCsv ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Exporting CSV...
</>
) : (
<>
<Download className="w-4 h-4" />
Export Workout CSV
</>
)}
</button>
</div>
</div>
);
}
// ---------- CSV Import Shortcut ----------
function WorkoutCsvImportShortcut() {
return (
<div className="bg-zinc-900 border border-zinc-800 rounded-lg p-6">
<h2 className="text-lg font-bold text-white mb-1">Import Workouts (CSV)</h2>
<p className="text-sm text-zinc-500 mb-4">
One-time migration tool for importing older workout history from CSV.
</p>
<Link
href="/main/import"
className="w-full inline-flex items-center justify-center gap-2 py-3 border border-zinc-700 rounded-lg text-zinc-300 text-sm font-medium hover:text-white hover:border-zinc-500 transition"
>
<Upload className="w-4 h-4" />
Open CSV Import Tool
</Link>
</div>
);
}
// ---------- Database Import Component ---------- // ---------- Database Import Component ----------
function DatabaseImport() { function DatabaseImport() {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@@ -3,7 +3,15 @@
import { Trash2, Check, Pencil, CornerDownLeft } from "lucide-react"; import { Trash2, Check, Pencil, CornerDownLeft } from "lucide-react";
import { useState, useCallback } from "react"; import { useState, useCallback } from "react";
export type InputField = "sets" | "reps" | "weight" | "duration" | "distance" | "calories" | "notes"; export type InputField =
| "sets"
| "reps"
| "weight"
| "duration"
| "distance"
| "calories"
| "notes"
| string;
export interface SetRowProps { export interface SetRowProps {
setNumber: number; setNumber: number;
@@ -16,6 +24,7 @@ export interface SetRowProps {
initialDuration?: number; initialDuration?: number;
initialDistance?: number; initialDistance?: number;
initialCalories?: number; initialCalories?: number;
initialCustomMetrics?: Record<string, string>;
initialLocked?: boolean; initialLocked?: boolean;
autoFocus?: boolean; autoFocus?: boolean;
onUpdate: (data: { onUpdate: (data: {
@@ -26,6 +35,7 @@ export interface SetRowProps {
durationSeconds?: number; durationSeconds?: number;
distance?: number; distance?: number;
calories?: number; calories?: number;
customMetrics?: Record<string, string>;
}) => void; }) => void;
onConfirm?: () => void; onConfirm?: () => void;
onNextSet?: (currentValues: { onNextSet?: (currentValues: {
@@ -51,6 +61,7 @@ export default function SetRow({
initialDuration, initialDuration,
initialDistance, initialDistance,
initialCalories, initialCalories,
initialCustomMetrics,
initialLocked = false, initialLocked = false,
autoFocus = false, autoFocus = false,
onUpdate, onUpdate,
@@ -58,13 +69,30 @@ export default function SetRow({
onNextSet, onNextSet,
onDelete, onDelete,
}: SetRowProps) { }: SetRowProps) {
const secondsToMinuteString = (seconds?: number) => {
if (seconds === undefined || seconds === null) return "";
const minutes = seconds / 60;
const rounded = Math.round(minutes * 10) / 10;
return Number.isInteger(rounded) ? String(Math.trunc(rounded)) : String(rounded);
};
const minuteStringToSeconds = (minutes: string) => {
if (!minutes) return undefined;
const parsed = parseFloat(minutes);
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
return Math.round(parsed * 60);
};
const [reps, setReps] = useState(initialReps?.toString() || ""); const [reps, setReps] = useState(initialReps?.toString() || "");
const [weight, setWeight] = useState(initialWeight?.toString() || ""); const [weight, setWeight] = useState(initialWeight?.toString() || "");
const [rpe, setRpe] = useState(initialRpe?.toString() || ""); const [rpe, setRpe] = useState(initialRpe?.toString() || "");
const [notes, setNotes] = useState(initialNotes || ""); const [notes, setNotes] = useState(initialNotes || "");
const [duration, setDuration] = useState(initialDuration?.toString() || ""); const [duration, setDuration] = useState(secondsToMinuteString(initialDuration));
const [distance, setDistance] = useState(initialDistance?.toString() || ""); const [distance, setDistance] = useState(initialDistance?.toString() || "");
const [calories, setCalories] = useState(initialCalories?.toString() || ""); const [calories, setCalories] = useState(initialCalories?.toString() || "");
const [customValues, setCustomValues] = useState<Record<string, string>>(
initialCustomMetrics || {}
);
const [showNotes, setShowNotes] = useState(!!initialNotes); const [showNotes, setShowNotes] = useState(!!initialNotes);
const [locked, setLocked] = useState(initialLocked); const [locked, setLocked] = useState(initialLocked);
@@ -74,6 +102,18 @@ export default function SetRow({
const showDistance = inputFields.includes("distance"); const showDistance = inputFields.includes("distance");
const showCalories = inputFields.includes("calories"); const showCalories = inputFields.includes("calories");
const showNotesField = inputFields.includes("notes"); const showNotesField = inputFields.includes("notes");
const customFields = inputFields.filter(
(f) =>
![
"sets",
"reps",
"weight",
"duration",
"distance",
"calories",
"notes",
].includes(f)
);
const emitUpdate = useCallback( const emitUpdate = useCallback(
(overrides: { (overrides: {
@@ -84,6 +124,7 @@ export default function SetRow({
duration?: string; duration?: string;
distance?: string; distance?: string;
calories?: string; calories?: string;
customMetrics?: Record<string, string>;
}) => { }) => {
const r = overrides.reps ?? reps; const r = overrides.reps ?? reps;
const w = overrides.weight ?? weight; const w = overrides.weight ?? weight;
@@ -92,18 +133,26 @@ export default function SetRow({
const dur = overrides.duration ?? duration; const dur = overrides.duration ?? duration;
const dist = overrides.distance ?? distance; const dist = overrides.distance ?? distance;
const cal = overrides.calories ?? calories; const cal = overrides.calories ?? calories;
const cm = overrides.customMetrics ?? customValues;
const cleanedCustomMetrics = Object.fromEntries(
Object.entries(cm).filter(([, value]) => value !== "")
);
onUpdate({ onUpdate({
reps: r ? parseInt(r) : undefined, reps: r ? parseInt(r) : undefined,
weight: w ? parseFloat(w) : undefined, weight: w ? parseFloat(w) : undefined,
rpe: p ? parseInt(p) : undefined, rpe: p ? parseInt(p) : undefined,
notes: n || undefined, notes: n || undefined,
durationSeconds: dur ? parseInt(dur) : undefined, durationSeconds: minuteStringToSeconds(dur),
distance: dist ? parseFloat(dist) : undefined, distance: dist ? parseFloat(dist) : undefined,
calories: cal ? parseInt(cal) : undefined, calories: cal ? parseInt(cal) : undefined,
customMetrics:
Object.keys(cleanedCustomMetrics).length > 0
? cleanedCustomMetrics
: undefined,
}); });
}, },
[reps, weight, rpe, notes, duration, distance, calories, onUpdate] [reps, weight, rpe, notes, duration, distance, calories, customValues, onUpdate]
); );
const handleConfirm = () => { const handleConfirm = () => {
@@ -134,9 +183,13 @@ export default function SetRow({
const parts: string[] = []; const parts: string[] = [];
if (showWeight && weight) parts.push(`${weight} ${weightUnit}`); if (showWeight && weight) parts.push(`${weight} ${weightUnit}`);
if (showReps && reps) parts.push(`${reps} reps`); if (showReps && reps) parts.push(`${reps} reps`);
if (showDuration && duration) parts.push(`${duration}s`); if (showDuration && duration) parts.push(`${duration} min`);
if (showDistance && distance) parts.push(`${distance} mi`); if (showDistance && distance) parts.push(`${distance} mi`);
if (showCalories && calories) parts.push(`${calories} cal`); if (showCalories && calories) parts.push(`${calories} cal`);
for (const field of customFields) {
const value = customValues[field];
if (value) parts.push(`${field}: ${value}`);
}
if (rpe) parts.push(`RPE ${rpe}`); if (rpe) parts.push(`RPE ${rpe}`);
if (showNotesField && notes) parts.push(notes); if (showNotesField && notes) parts.push(notes);
return parts.length > 0 ? parts.join(" · ") : "No data"; return parts.length > 0 ? parts.join(" · ") : "No data";
@@ -243,10 +296,11 @@ export default function SetRow({
{showDuration && ( {showDuration && (
<div className="flex-1 min-w-[55px]"> <div className="flex-1 min-w-[55px]">
<label className="block text-[10px] font-medium text-zinc-500 mb-0.5"> <label className="block text-[10px] font-medium text-zinc-500 mb-0.5">
Time (s) Time (min)
</label> </label>
<input <input
type="number" type="number"
step="0.1"
autoFocus={autoFocus && firstField === "duration"} autoFocus={autoFocus && firstField === "duration"}
value={duration} value={duration}
onChange={(e) => { onChange={(e) => {
@@ -359,6 +413,33 @@ export default function SetRow({
</button> </button>
</div> </div>
{/* Dynamic custom metrics configured on the exercise (e.g., watts) */}
{customFields.length > 0 && (
<div className="ml-8 grid grid-cols-2 gap-1.5">
{customFields.map((field) => (
<div key={field}>
<label className="block text-[10px] font-medium text-zinc-500 mb-0.5">
{field.charAt(0).toUpperCase() + field.slice(1)}
</label>
<input
type="text"
value={customValues[field] || ""}
onChange={(e) => {
const val = e.target.value;
setCustomValues((prev) => {
const next = { ...prev, [field]: val };
emitUpdate({ customMetrics: next });
return next;
});
}}
placeholder="-"
className="w-full px-2 py-1.5 border border-zinc-700 rounded-md text-sm bg-zinc-800 text-white focus:outline-none focus:ring-2 focus:ring-white/20 placeholder:text-zinc-600"
/>
</div>
))}
</div>
)}
{/* Notes — always visible when configured as input field */} {/* Notes — always visible when configured as input field */}
{showNotesField && ( {showNotesField && (
<div className="ml-8"> <div className="ml-8">
@@ -67,7 +67,7 @@ function ExerciseHistoryPopup({
<p className="text-xs text-zinc-500 text-center py-4">No history yet</p> <p className="text-xs text-zinc-500 text-center py-4">No history yet</p>
) : ( ) : (
<div className="divide-y divide-zinc-800/50"> <div className="divide-y divide-zinc-800/50">
{history.slice(0, 10).map((entry) => { {history.slice(0, 50).map((entry) => {
const d = new Date(entry.workout.date); const d = new Date(entry.workout.date);
const dateStr = d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); const dateStr = d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
const summary = formatSetsSummary(entry.sets); const summary = formatSetsSummary(entry.sets);
@@ -107,6 +107,10 @@ interface ExerciseWithSets {
reps?: number; reps?: number;
weight?: number; weight?: number;
rpe?: number; rpe?: number;
durationSeconds?: number;
distance?: number;
calories?: number;
customMetrics?: Record<string, string>;
notes?: string; notes?: string;
forceEdit?: boolean; // When true, start in edit mode even if data is pre-filled forceEdit?: boolean; // When true, start in edit mode even if data is pre-filled
}>; }>;
@@ -127,6 +131,10 @@ export interface EditWorkoutData {
reps?: number; reps?: number;
weight?: number; weight?: number;
rpe?: number; rpe?: number;
durationSeconds?: number;
distance?: number;
calories?: number;
customMetrics?: Record<string, string>;
notes?: string; notes?: string;
}>; }>;
}>; }>;
@@ -173,6 +181,7 @@ export default function WorkoutForm({
const [autoSaving, setAutoSaving] = useState(false); const [autoSaving, setAutoSaving] = useState(false);
const [showSavedFlash, setShowSavedFlash] = useState(false); const [showSavedFlash, setShowSavedFlash] = useState(false);
const savingRef = useRef(false); const savingRef = useRef(false);
const pendingAutoSaveRef = useRef(false);
const savedFlashTimer = useRef<NodeJS.Timeout | null>(null); const savedFlashTimer = useRef<NodeJS.Timeout | null>(null);
// Flash "Saved ✓" briefly after each successful save // Flash "Saved ✓" briefly after each successful save
@@ -208,6 +217,11 @@ export default function WorkoutForm({
weight: s.weight, weight: s.weight,
weightUnit: (e.exercise as any).defaultWeightUnit || "lbs", weightUnit: (e.exercise as any).defaultWeightUnit || "lbs",
rpe: s.rpe, rpe: s.rpe,
durationSeconds: s.durationSeconds,
distance: s.distance,
distanceUnit: s.distance !== undefined ? "mi" : undefined,
calories: s.calories,
customMetrics: s.customMetrics,
notes: s.notes, notes: s.notes,
})) }))
), ),
@@ -219,7 +233,11 @@ export default function WorkoutForm({
// ---------- Auto-save: create or update ---------- // ---------- Auto-save: create or update ----------
const autoSave = useCallback( const autoSave = useCallback(
async (overrideExercises?: ExerciseWithSets[]) => { async (overrideExercises?: ExerciseWithSets[]) => {
if (savingRef.current) return; if (savingRef.current) {
// Don't drop updates while a save is in-flight; queue one follow-up save.
pendingAutoSaveRef.current = true;
return;
}
savingRef.current = true; savingRef.current = true;
setAutoSaving(true); setAutoSaving(true);
@@ -264,6 +282,12 @@ export default function WorkoutForm({
} finally { } finally {
savingRef.current = false; savingRef.current = false;
setAutoSaving(false); setAutoSaving(false);
if (pendingAutoSaveRef.current) {
pendingAutoSaveRef.current = false;
setTimeout(() => {
void autoSave();
}, 0);
}
} }
}, },
[buildPayload, savedWorkoutId, triggerSavedFlash] [buildPayload, savedWorkoutId, triggerSavedFlash]
@@ -351,7 +375,16 @@ export default function WorkoutForm({
const handleUpdateSet = ( const handleUpdateSet = (
exerciseId: string, exerciseId: string,
setNumber: number, setNumber: number,
data: { reps?: number; weight?: number; rpe?: number; notes?: string } data: {
reps?: number;
weight?: number;
rpe?: number;
notes?: string;
durationSeconds?: number;
distance?: number;
calories?: number;
customMetrics?: Record<string, string>;
}
) => { ) => {
setAddedExercises((prev) => setAddedExercises((prev) =>
prev.map((e) => { prev.map((e) => {
@@ -456,6 +489,7 @@ export default function WorkoutForm({
} }
// Prevent auto-saves from starting while we do the final save // Prevent auto-saves from starting while we do the final save
savingRef.current = true; savingRef.current = true;
pendingAutoSaveRef.current = false;
const payload = buildPayload(); const payload = buildPayload();
@@ -696,9 +730,33 @@ export default function WorkoutForm({
initialReps={set.reps} initialReps={set.reps}
initialWeight={set.weight} initialWeight={set.weight}
initialRpe={set.rpe} initialRpe={set.rpe}
initialDuration={set.durationSeconds}
initialDistance={set.distance}
initialCalories={set.calories}
initialCustomMetrics={set.customMetrics}
initialNotes={set.notes} initialNotes={set.notes}
initialLocked={set.forceEdit ? false : !!(set.reps || set.weight)} initialLocked={
autoFocus={set.forceEdit || (idx === item.sets.length - 1 && !set.reps && !set.weight)} set.forceEdit
? false
: !!(
set.reps ||
set.weight ||
set.durationSeconds ||
set.distance ||
set.calories ||
(set.customMetrics &&
Object.values(set.customMetrics).some((v) => v))
)
}
autoFocus={
set.forceEdit ||
(idx === item.sets.length - 1 &&
!set.reps &&
!set.weight &&
!set.durationSeconds &&
!set.distance &&
!set.calories)
}
onUpdate={(data) => onUpdate={(data) =>
handleUpdateSet( handleUpdateSet(
item.exercise.id, item.exercise.id,
@@ -2,7 +2,7 @@
_Last updated: February 18, 2026_ _Last updated: February 18, 2026_
## Project Root (`workout-planner/`) ## Project Root (`proof-of-work/`)
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
@@ -166,7 +166,7 @@ All pages under `/main/` require authentication (enforced by middleware).
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `import-data/workout-log-feb2026.csv` | Parsed handwritten workout logs (JanFeb 2026, ~362 rows). Format: `date,exercise,weight,reps,notes`. More pages to be added. | | `import-data/proof-of-work-feb2026.csv` | Parsed handwritten workout logs (JanFeb 2026, ~362 rows). Format: `date,exercise,weight,reps,notes`. More pages to be added. |
## `docs/` — Project Documentation ## `docs/` — Project Documentation
@@ -129,5 +129,5 @@ Database lives at `prisma/data/app.db`. Environment variables in `.env` / `.env.
- The actual database file is `prisma/data/app.db`, NOT `prisma/dev.db` (which exists but is empty/stale). - The actual database file is `prisma/data/app.db`, NOT `prisma/dev.db` (which exists but is empty/stale).
- SQLite on some mounted filesystems (Docker volumes, network mounts) can have journal mode issues. If you get "disk I/O error", try copying the DB locally, modifying with `PRAGMA journal_mode=OFF`, then copying back. - SQLite on some mounted filesystems (Docker volumes, network mounts) can have journal mode issues. If you get "disk I/O error", try copying the DB locally, modifying with `PRAGMA journal_mode=OFF`, then copying back.
- The import CSV file at `import-data/workout-log-feb2026.csv` contains parsed data from handwritten logs covering January-February 2026. More pages will be added over time. - The import CSV file at `import-data/proof-of-work-feb2026.csv` contains parsed data from handwritten logs covering January-February 2026. More pages will be added over time.
- Exercise name mapping in the import parser (`NAME_MAP` in `app/api/import/parse/route.ts`) should be updated as new shorthand names are encountered in CSV data. - Exercise name mapping in the import parser (`NAME_MAP` in `app/api/import/parse/route.ts`) should be updated as new shorthand names are encountered in CSV data.
@@ -0,0 +1,473 @@
date,exercise,setNumber,reps,weight,weightUnit,durationMinutes,distance,distanceUnit,setCalories,rpe,notes,customMetricsJson
1/2/2026,Face Pulls,1,10,68,lbs,,,,,,,
1/2/2026,Rear delt,1,10,16,lbs,,,,,,,
1/2/2026,EQ Bar Incline Bench,1,13,16,kg,,,,,,16kg KB each side,
1/2/2026,EQ Bar Incline Bench,2,10,21,lbs,,,,,,16kg KB + 5lb each side,
1/2/2026,EQ Bar Incline Bench,3,10,26,lbs,,,,,,16kg KB + 10lb each side,
1/2/2026,EQ Bar Incline Bench,4,12,26,lbs,,,,,,16kg KB + 10lb each side,
1/2/2026,EQ Bar Incline Bench,5,10,31,lbs,,,,,,16kg KB + 15lb each side,
1/2/2026,Chinup,1,10,,lbs,,,,,,,
1/2/2026,Chinup,2,7,,lbs,,,,,,,
1/2/2026,Chinup,3,7,,lbs,,,,,,,
1/2/2026,Chinup,4,5,,lbs,,,,,,slow negatives,
1/2/2026,Chinup,5,5,,lbs,,,,,,slow negatives,
1/2/2026,Alternating Step Cable Cross,1,16,44,lbs,,,,,,,
1/2/2026,Alternating Step Cable Cross,2,16,44,lbs,,,,,,,
1/2/2026,Alternating Step Cable Cross,3,16,44,lbs,,,,,,,
1/2/2026,Tuck Inversion,1,2,,lbs,,,,,,vest,
1/2/2026,Tuck Inversion,2,2,,lbs,,,,,,vest,
1/2/2026,Ab Wheel Rollout,1,7,,lbs,,,,,,vest,
1/2/2026,Ab Wheel Rollout,2,7,,lbs,,,,,,vest,
1/2/2026,Ab Wheel Rollout,3,7,,lbs,,,,,,vest,
1/2/2026,Ring Dip,1,3,,lbs,,,,,,,
1/2/2026,Ring Dip,2,3,,lbs,,,,,,,
1/2/2026,Ring Dip,3,3,,lbs,,,,,,,
1/2/2026,Overhead Tricep Extension,1,13,44,lbs,,,,,,,
1/2/2026,Overhead Tricep Extension,2,13,44,lbs,,,,,,,
1/2/2026,Overhead Tricep Extension,3,13,44,lbs,,,,,,,
1/2/2026,Barbell Curl,1,17,45,lbs,,,,,,bar only,
1/2/2026,Barbell Curl,2,17,45,lbs,,,,,,bar only,
1/2/2026,Barbell Curl,3,17,45,lbs,,,,,,bar only,
1/2/2026,Band Pushup,1,7,,lbs,,,,,,black/gray band,
1/2/2026,Band Pushup,2,7,,lbs,,,,,,black/gray band,
1/2/2026,Band Pushup,3,7,,lbs,,,,,,black/gray band,
1/3/2026,TGU,1,1,36,kg,,,,,,,
1/3/2026,TGU,2,1,40,kg,,,,,,,
1/3/2026,TGU,3,1,44,kg,,,,,,,
1/3/2026,TGU,4,1,48,kg,,,,,,,
1/3/2026,TGU,5,1,48,kg,,,,,,,
1/3/2026,TGU,6,1,48,kg,,,,,,,
1/3/2026,Deadlift,1,5,185,lbs,,,,,,,
1/3/2026,Deadlift,2,5,225,lbs,,,,,,,
1/3/2026,Deadlift,3,5,275,lbs,,,,,,,
1/3/2026,Deadlift,4,5,275,lbs,,,,,,,
1/3/2026,Deadlift,5,5,275,lbs,,,,,,,
1/3/2026,Deadlift,6,6,275,lbs,,,,,,,
1/3/2026,Assault Bike,1,,,lbs,,,,72,,10/20 intervals,
1/4/2026,Assault Bike,1,,,lbs,5,,,,,target zone 4 cardio,
1/4/2026,SkiErg,1,,,lbs,5,,,,,target zone 4 cardio,
1/4/2026,Jump Rope,1,,,lbs,5,,,,,target zone 4 cardio,
1/4/2026,KB Swing,1,5,24,kg,,,,,,single arm every 60 sec / arm every 45 sec - 5 min,
1/4/2026,Neck Circuit,1,17,10,lbs,,,,,,,
1/6/2026,Squat,1,7,135,lbs,,,,,,,
1/6/2026,Squat,2,7,165,lbs,,,,,,,
1/6/2026,Squat,3,7,165,lbs,,,,,,chains - 207 total,
1/6/2026,Squat,4,7,165,lbs,,,,,,chains - 207 total,
1/6/2026,Squat,5,7,165,lbs,,,,,,chains - 207 total,
1/6/2026,Bulgarian Split Squat,1,10,30,lbs,,,,,,,
1/6/2026,Bulgarian Split Squat,2,10,30,lbs,,,,,,,
1/6/2026,Bulgarian Split Squat,3,10,30,lbs,,,,,,,
1/6/2026,Hip Flexor,1,10,12,kg,,,,,,,
1/6/2026,Hip Flexor,2,10,12,kg,,,,,,,
1/6/2026,Hip Flexor,3,10,12,kg,,,,,,,
1/6/2026,Kettlebell Leg Extension,1,10,12,kg,,,,,,,
1/6/2026,Kettlebell Leg Extension,2,10,12,kg,,,,,,,
1/6/2026,Kettlebell Leg Extension,3,10,12,kg,,,,,,,
1/6/2026,Adductor Bench,1,10,,lbs,,,,,,,
1/6/2026,Adductor Bench,2,10,,lbs,,,,,,,
1/6/2026,Adductor Bench,3,10,,lbs,,,,,,,
1/6/2026,Glute Bridge,1,13,135,lbs,,,,,,,
1/6/2026,Glute Bridge,2,13,135,lbs,,,,,,,
1/6/2026,Glute Bridge,3,13,135,lbs,,,,,,,
1/7/2026,Face Pulls,1,10,68,lbs,,,,,,,
1/7/2026,Rear delt,1,8,16,lbs,,,,,,,
1/7/2026,EQ Bar Incline Bench,1,10,16,kg,,,,,,16kg KB each side,
1/7/2026,EQ Bar Incline Bench,2,10,26,lbs,,,,,,16kg KB + 10lb each side,
1/7/2026,EQ Bar Incline Bench,3,10,26,lbs,,,,,,16kg KB + 10lb each side,
1/7/2026,EQ Bar Incline Bench,4,13,31,lbs,,,,,,16kg KB + 15lb each side,
1/7/2026,EQ Bar Incline Bench,5,9,31,lbs,,,,,,16kg KB + 15lb each side,
1/7/2026,EQ Bar Incline Bench,6,8,31,lbs,,,,,,16kg KB + 15lb each side,
1/7/2026,Chinup,1,8,,lbs,,,,,,,
1/7/2026,Chinup,2,6,,lbs,,,,,,,
1/7/2026,Chinup,3,6,,lbs,,,,,,,
1/7/2026,Chinup,4,6,,lbs,,,,,,,
1/7/2026,Alternating Step Cable Cross,1,16,44,lbs,,,,,,,
1/7/2026,Alternating Step Cable Cross,2,16,44,lbs,,,,,,,
1/7/2026,Alternating Step Cable Cross,3,16,44,lbs,,,,,,,
1/7/2026,Tuck Inversion,1,2,,lbs,,,,,,vest,
1/7/2026,Tuck Inversion,2,2,,lbs,,,,,,vest,
1/7/2026,Ab Wheel Rollout,1,7,,lbs,,,,,,vest,
1/7/2026,Ab Wheel Rollout,2,7,,lbs,,,,,,vest,
1/7/2026,Ab Wheel Rollout,3,7,,lbs,,,,,,vest,
1/7/2026,Ring Dip,1,7,,lbs,,,,,,,
1/7/2026,Ring Dip,2,7,,lbs,,,,,,,
1/10/2026,Deadlift,1,5,185,lbs,,,,,,,
1/10/2026,Deadlift,2,5,225,lbs,,,,,,,
1/10/2026,Deadlift,3,5,275,lbs,,,,,,,
1/10/2026,Deadlift,4,4,295,lbs,,,,,,,
1/10/2026,Deadlift,5,5,295,lbs,,,,,,,
1/10/2026,TGU,1,1,44,kg,,,,,,,
1/10/2026,TGU,2,1,48,kg,,,,,,,
1/10/2026,TGU,3,1,48,kg,,,,,,,
1/10/2026,Ring row,1,10,,lbs,,,,,,,
1/10/2026,Ring row,2,7,,lbs,,,,,,,
1/10/2026,Ring row,3,6,,lbs,,,,,,,
1/10/2026,Glute Bridge,1,13,135,lbs,,,,,,,
1/10/2026,Glute Bridge,2,13,135,lbs,,,,,,,
1/10/2026,Glute Bridge,3,13,135,lbs,,,,,,,
1/10/2026,Fire Hydrant,1,17,,lbs,,,,,,blue band,
1/10/2026,Fire Hydrant,2,13,,lbs,,,,,,blue band,
1/10/2026,Fire Hydrant,3,17,,lbs,,,,,,blue band,
1/10/2026,Glute ham developer,1,7,,lbs,,,,,,"pad on floor, gray band",
1/10/2026,Glute ham developer,2,4,,lbs,,,,,,"pad on floor, gray band",
1/10/2026,Glute ham developer,3,7,,lbs,,,,,,"pad on floor, gray band",
1/10/2026,Neck Circuit,1,17,10,lbs,,,,,,,
1/12/2026,Barbell step back lunge,1,10,75,lbs,,,,,,,
1/12/2026,Barbell step back lunge,2,10,95,lbs,,,,,,,
1/12/2026,Barbell step back lunge,3,10,115,lbs,,,,,,,
1/12/2026,Bench Step Up,1,10,16,kg,,,,,,quad band black,
1/12/2026,Bench Step Up,2,10,16,kg,,,,,,quad band black,
1/12/2026,Kettlebell Leg Extension,1,17,12,kg,,,,,,,
1/12/2026,Kettlebell Leg Extension,2,17,12,kg,,,,,,,
1/12/2026,Kettlebell Leg Extension,3,23,12,kg,,,,,,,
1/13/2026,Shoulder warmup,1,,15,lbs,,,,,,warmup,
1/13/2026,Mace warmup,1,,,lbs,,,,,,warmup,
1/13/2026,Face Pulls,1,10,72,lbs,,,,,,,
1/13/2026,Rear delt,1,7,16,lbs,,,,,,,
1/13/2026,Rear delt,2,10,16,lbs,,,,,,,
1/13/2026,Rear delt,3,7,16,lbs,,,,,,,
1/13/2026,Dumbbell Row,1,10,55,lbs,,,,,,,
1/13/2026,Dumbbell Row,2,10,55,lbs,,,,,,,
1/13/2026,Dumbbell Row,3,10,55,lbs,,,,,,,
1/13/2026,EQ Bar Incline Bench,1,10,16,kg,,,,,,16kg KB each side,
1/13/2026,EQ Bar Incline Bench,2,14,31,lbs,,,,,,16kg + 15 lbs,
1/13/2026,EQ Bar Incline Bench,3,15,31,lbs,,,,,,16kg + 15 lbs,
1/13/2026,EQ Bar Incline Bench,4,10,31,lbs,,,,,,slow,
1/13/2026,Chinup,1,10,,lbs,,,,,,,
1/13/2026,Chinup,2,6,,lbs,,,,,,,
1/13/2026,Chinup,3,6,,lbs,,,,,,,
1/13/2026,Exercise Ball Situp,1,17,,lbs,,,,,,,
1/13/2026,Exercise Ball Situp,2,13,,lbs,,,,,,,
1/13/2026,Exercise Ball Situp,3,17,,lbs,,,,,,,
1/13/2026,Alternating Step Cable Cross,1,16,44,lbs,,,,,,,
1/13/2026,Alternating Step Cable Cross,2,16,44,lbs,,,,,,,
1/13/2026,Alternating Step Cable Cross,3,10,44,lbs,,,,,,,
1/13/2026,Tuck Inversion,1,2,,lbs,,,,,,vest,
1/13/2026,Tuck Inversion,2,2,,lbs,,,,,,vest,
1/13/2026,Tuck Inversion,3,2,,lbs,,,,,,vest,
1/13/2026,Ab Wheel Rollout,1,10,,lbs,,,,,,vest,
1/13/2026,Ab Wheel Rollout,2,10,,lbs,,,,,,vest,
1/13/2026,Ring Dip,1,5,,lbs,,,,,,,
1/13/2026,Ring Dip,2,5,,lbs,,,,,,,
1/13/2026,Tricep Pushdown,1,10,44,lbs,,,,,,rope,
1/13/2026,Tricep Pushdown,2,10,44,lbs,,,,,,rope,
1/14/2026,Hex Bar Deadlift,1,5,200,lbs,,,,,,,
1/14/2026,Hex Bar Deadlift,2,5,240,lbs,,,,,,,
1/14/2026,Hex Bar Deadlift,3,5,290,lbs,,,,,,,
1/14/2026,Hex Bar Deadlift,4,5,320,lbs,,,,,,,
1/14/2026,Hex Bar Deadlift,5,3,340,lbs,,,,,,,
1/14/2026,Hex Bar Deadlift,6,3,355,lbs,,,,,,best ever,
1/14/2026,Glute Bridge,1,13,135,lbs,,,,,,,
1/14/2026,Glute Bridge,2,13,135,lbs,,,,,,,
1/14/2026,Glute Bridge,3,13,135,lbs,,,,,,,
1/14/2026,SL Quad Step Down,1,10,,lbs,,,,,,on bench,
1/14/2026,SL Quad Step Down,2,10,15,lbs,,,,,,on bench,
1/14/2026,SL Quad Step Down,3,10,16,kg,,,,,,"on bench, good weight",
1/14/2026,Fire Hydrant,1,17,,lbs,,,,,,black band,
1/14/2026,Fire Hydrant,2,17,,lbs,,,,,,black band,
1/14/2026,Assault Bike,1,,,lbs,,,,69,,10/20 intervals,
1/16/2026,Shoulder warmup,1,,,lbs,,,,,,warmup,
1/16/2026,Face Pulls,1,10,68,lbs,,,,,,,
1/16/2026,Mace warmup,1,,,lbs,,,,,,warmup,
1/16/2026,Rear delt,1,10,16,lbs,,,,,,,
1/16/2026,TGU,1,1,44,kg,,,,,,,
1/16/2026,TGU,2,1,48,kg,,,,,,,
1/16/2026,TGU,3,1,48,kg,,,,,,,
1/16/2026,TGU,4,1,48,kg,,,,,,,
1/16/2026,TGU,5,1,48,kg,,,,,,,
1/16/2026,TGU,6,1,48,kg,,,,,,,
1/16/2026,Overhead Press,1,7,75,lbs,,,,,,,
1/16/2026,Overhead Press,2,7,95,lbs,,,,,,,
1/16/2026,Overhead Press,3,5,115,lbs,,,,,,,
1/16/2026,Overhead Press,4,5,115,lbs,,,,,,,
1/16/2026,Overhead Press,5,5,115,lbs,,,,,,,
1/16/2026,Chinup,1,4,,lbs,,,,,,vest,
1/16/2026,Chinup,2,5,,lbs,,,,,,vest,
1/16/2026,Chinup,3,5,,lbs,,,,,,vest,
1/16/2026,Chinup,4,5,,lbs,,,,,,vest,
1/16/2026,Arnold Press,1,8,40,lbs,,,,,,,
1/16/2026,Arnold Press,2,10,40,lbs,,,,,,,
1/16/2026,Arnold Press,3,10,40,lbs,,,,,,,
1/16/2026,Barbell Curl,1,10,45,lbs,,,,,,bar only,
1/16/2026,Barbell Curl,2,10,65,lbs,,,,,,,
1/16/2026,Barbell Curl,3,10,65,lbs,,,,,,,
1/16/2026,Barbell Row,1,10,95,lbs,,,,,,,
1/16/2026,Barbell Row,2,14,95,lbs,,,,,,,
1/16/2026,Barbell Row,3,13,95,lbs,,,,,,,
1/16/2026,Waiter Carry,1,20,16,kg,,,,,,20 steps,
1/16/2026,Farmer's Carry,1,40,44,kg,,,,,,"40 steps, repeat circuit",
1/16/2026,Captains of Crush,1,7,0.5,lbs,,,,,,gripper #0.5,
1/16/2026,Captains of Crush,2,3,1,lbs,,,,,,gripper #1,
1/16/2026,Captains of Crush,3,5,1,lbs,,,,,,gripper #1,
1/16/2026,Captains of Crush,4,6,1,lbs,,,,,,gripper #1,
1/16/2026,Neck Circuit,1,17,10,lbs,,,,,,,
1/16/2026,Neck Circuit,2,17,10,lbs,,,,,,,
1/18/2026,Squat,1,7,135,lbs,,,,,,,
1/18/2026,Squat,2,5,155,lbs,,,,,,,
1/18/2026,Squat,3,5,170,lbs,,,,,,,
1/18/2026,Squat,4,11,185,lbs,,,,,,work set,
1/18/2026,Squat,5,10,185,lbs,,,,,,work set,
1/18/2026,DB Step Back Lunge,1,7,40,lbs,,,,,,,
1/18/2026,DB Step Back Lunge,2,7,60,lbs,,,,,,,
1/18/2026,DB Step Back Lunge,3,7,80,lbs,,,,,,work set,
1/18/2026,DB Step Back Lunge,4,10,80,lbs,,,,,,work set - max,
1/18/2026,Bench Press,1,7,135,lbs,,,,,,,
1/18/2026,Bench Press,2,5,165,lbs,,,,,,,
1/18/2026,Bench Press,3,9,185,lbs,,,,,,work set,
1/18/2026,Bench Press,4,8,185,lbs,,,,,,work set,
1/18/2026,Lat Pulldown,1,7,60,lbs,,,,,,single arm,
1/18/2026,Lat Pulldown,2,10,71,lbs,,,,,,"work set, single arm",
1/18/2026,Lat Pulldown,3,11,71,lbs,,,,,,"work set, single arm",
1/18/2026,Ab Wheel Rollout,1,11,,lbs,,,,,,vest,
1/18/2026,Ab Wheel Rollout,2,10,,lbs,,,,,,vest,
1/18/2026,Calf Raise,1,17,,lbs,,,,,,vest,
1/18/2026,Calf Raise,2,13,,lbs,,,,,,vest,
1/18/2026,KB Press,1,5,20,kg,,,,,,,
1/18/2026,KB Press,2,11,24,kg,,,,,,work set,
1/18/2026,KB Press,3,10,24,kg,,,,,,work set,
1/19/2026,Kettlebell Leg Extension,1,13,12,kg,,,,,,,
1/19/2026,Kettlebell Leg Extension,2,17,12,kg,,,,,,,
1/19/2026,Kettlebell Leg Extension,3,17,12,kg,,,,,,,
1/19/2026,Kettlebell Leg Extension,4,21,12,kg,,,,,,ankle weight,
1/19/2026,Kettlebell Leg Extension,5,21,12,kg,,,,,,ankle weight,
1/19/2026,Knee Raise,1,21,,lbs,,,,,,ankle weight,
1/19/2026,Knee Raise,2,17,,lbs,,,,,,ankle weight,
1/19/2026,Hip Flexor,1,10,12,lbs,,,,,,,
1/19/2026,Hip Flexor,2,10,12,lbs,,,,,,,
1/19/2026,Hip Flexor,3,10,12,lbs,,,,,,,
1/19/2026,Adductor Bench,1,17,,lbs,,,,,,,
1/19/2026,Adductor Bench,2,21,,lbs,,,,,,,
1/19/2026,Glute ham developer,1,7,,lbs,,,,,,,
1/19/2026,Glute ham developer,2,7,,lbs,,,,,,,
1/19/2026,Glute ham developer,3,7,,lbs,,,,,,,
1/20/2026,Ab KB Drag,1,10,12,lbs,,,,,,,
1/20/2026,Ab KB Drag,2,11,12,lbs,,,,,,,
1/20/2026,Dips (Chest),1,13,,lbs,,,,,,,
1/20/2026,Dips (Chest),2,17,,lbs,,,,,,,
1/20/2026,SA Tricep Extension,1,11,22,lbs,,,,,,,
1/20/2026,SA Tricep Extension,2,10,22,lbs,,,,,,,
1/20/2026,Captains of Crush,1,7,0.5,lbs,,,,,,gripper #0.5,
1/20/2026,Captains of Crush,2,6,1,lbs,,,,,,gripper #1,
1/20/2026,Captains of Crush,3,7,1,lbs,,,,,,gripper #1,
1/20/2026,Captains of Crush,4,3,1.5,lbs,,,,,,gripper #1.5,
1/20/2026,Captains of Crush,5,2,1.5,lbs,,,,,,gripper #1.5,
1/20/2026,Captains of Crush,6,10,1,lbs,,,,,,gripper #1,
1/20/2026,Ab Mat,1,17,,lbs,,,,,,,
1/20/2026,Ab Mat,2,13,,lbs,,,,,,,
1/20/2026,SA DB Curl,1,11,30,lbs,,,,,,,
1/20/2026,SA DB Curl,2,10,30,lbs,,,,,,,
1/20/2026,EQ Military Press,1,7,16,kg,,,,,,16kg KB each side,
1/20/2026,EQ Military Press,2,10,16,kg,,,,,,16kg KB each side,
1/20/2026,Tuck Inversion,1,2,,lbs,,,,,,,
1/20/2026,Tuck Inversion,2,2,,lbs,,,,,,,
1/20/2026,Rear delt,1,10,16,lbs,,,,,,,
1/20/2026,Rear delt,2,7,16,lbs,,,,,,,
1/20/2026,Ab Scissors,1,,,lbs,,,,,,,
1/20/2026,Ab Scissors,2,,,lbs,,,,,,,
1/20/2026,Chinup,1,2,,lbs,,,,,,negatives,
1/20/2026,Chinup,2,2,,lbs,,,,,,negatives,
1/22/2026,TGU,1,1,44,kg,,,,,,,
1/22/2026,TGU,2,1,44,kg,,,,,,,
1/22/2026,TGU,3,1,44,kg,,,,,,,
1/22/2026,EQ Military Press,1,10,16,kg,,,,,,16kg KB each side,
1/22/2026,EQ Military Press,2,10,16,kg,,,,,,16kg KB each side,
1/22/2026,EQ Military Press,3,10,16,kg,,,,,,16kg KB each side,
1/22/2026,Barbell Row,1,21,95,lbs,,,,,,,
1/22/2026,Barbell Row,2,17,95,lbs,,,,,,,
1/22/2026,Windmill,1,6,12,kg,,,,,,,
1/22/2026,Half Kneel Windmill,1,7,16,kg,,,,,,,
1/22/2026,Half Kneel Windmill,2,8,16,kg,,,,,,,
1/22/2026,Side Lying Press,1,21,20,lbs,,,,,,,
1/22/2026,Side Lying Press,2,21,24,lbs,,,,,,,
1/22/2026,Exercise Ball Situp,1,17,,lbs,,,,,,,
1/22/2026,Exercise Ball Situp,2,17,,lbs,,,,,,,
1/22/2026,Exercise Ball Situp,3,17,,lbs,,,,,,,
1/22/2026,Neck Circuit,1,17,10,lbs,,,,,,,
1/22/2026,Neck Circuit,2,17,10,lbs,,,,,,,
1/22/2026,Neck Circuit,3,17,10,lbs,,,,,,,
1/27/2026,Squat,1,7,135,lbs,,,,,,,
1/27/2026,Squat,2,7,185,lbs,,,,,,,
1/27/2026,Squat,3,5,185,lbs,,,,,,,
1/27/2026,Squat,4,5,225,lbs,,,,,,,
1/27/2026,Squat,5,5,225,lbs,,,,,,,
1/27/2026,Squat,6,5,225,lbs,,,,,,,
1/27/2026,Squat,7,6,225,lbs,,,,,,,
1/27/2026,Bulgarian Split Squat,1,10,15,lbs,,,,,,,
1/27/2026,Bulgarian Split Squat,2,10,15,lbs,,,,,,,
1/27/2026,Bulgarian Split Squat,3,10,15,lbs,,,,,,,
1/27/2026,Kettlebell Leg Extension,1,17,16,kg,,,,,,,
1/27/2026,Kettlebell Leg Extension,2,21,16,kg,,,,,,,
1/27/2026,Calf Raise,1,17,,lbs,,,,,,,
1/27/2026,Calf Raise,2,21,,lbs,,,,,,,
1/29/2026,Hamstring deadlift,1,10,50,lbs,,,,,,,
1/29/2026,Hex Bar Deadlift,1,5,240,lbs,,,,,,,
1/29/2026,Hex Bar Deadlift,2,7,295,lbs,,,,,,,
1/29/2026,Hex Bar Deadlift,3,7,295,lbs,,,,,,,
1/29/2026,Hex Bar Deadlift,4,7,295,lbs,,,,,,,
1/29/2026,Bench Press,1,5,135,lbs,,,,,,,
1/29/2026,Bench Press,2,7,185,lbs,,,,,,,
1/29/2026,Bench Press,3,8,185,lbs,,,,,,,
1/29/2026,Bench Press,4,6,185,lbs,,,,,,,
1/29/2026,Tuck Inversion,1,2,,lbs,,,,,,,
1/29/2026,Ring row,1,7,,lbs,,,,,,,
1/29/2026,Ring row,2,7,,lbs,,,,,,,
1/29/2026,Ring row,3,7,,lbs,,,,,,,
1/29/2026,Exercise Ball Situp,1,17,,lbs,,,,,,,
1/29/2026,Exercise Ball Situp,2,13,,lbs,,,,,,,
1/29/2026,Alt Leg Lift,1,10,,lbs,,,,,,,
1/29/2026,Alt Leg Lift,2,10,,lbs,,,,,,,
1/29/2026,Rear delt,1,7,16,lbs,,,,,,,
1/29/2026,Rear delt,2,10,16,lbs,,,,,,,
1/29/2026,SA Landmine Press,1,10,25,lbs,,,,,,,
1/29/2026,SA Landmine Press,2,10,25,lbs,,,,,,,
1/29/2026,SA Landmine Press,3,10,25,lbs,,,,,,,
1/29/2026,Landmine Pull and Press,1,10,25,lbs,,,,,,,
1/29/2026,Landmine Pull and Press,2,11,25,lbs,,,,,,,
1/29/2026,Wide Grip Pull Up,1,5,,lbs,,,,,,,
1/29/2026,Wide Grip Pull Up,2,5,,lbs,,,,,,,
1/29/2026,Wide Grip Pull Up,3,5,,lbs,,,,,,,
1/30/2026,Face Pulls,1,10,66,lbs,,,,,,,
1/30/2026,KB Press,1,7,20,kg,,,,,,,
1/30/2026,KB Press,2,7,24,kg,,,,,,,
1/30/2026,KB Press,3,3,32,kg,,,,,,,
1/30/2026,KB Press,4,3,32,kg,,,,,,,
1/30/2026,KB Press,5,3,32,kg,,,,,,,
1/30/2026,Side Lying Press,1,7,20,lbs,,,,,,,
1/30/2026,Side Lying Press,2,7,24,lbs,,,,,,,
1/30/2026,Side Lying Press,3,13,24,lbs,,,,,,,
1/30/2026,Side Lying Press,4,17,24,lbs,,,,,,,
1/30/2026,TGU,1,1,44,kg,,,,,,,
1/30/2026,TGU,2,1,48,kg,,,,,,,
1/30/2026,TGU,3,1,48,kg,,,,,,,
1/30/2026,BB Reverse Curl,1,17,45,lbs,,,,,,bar only,
1/30/2026,BB Reverse Curl,2,13,55,lbs,,,,,,,
1/30/2026,BB Reverse Curl,3,13,60,lbs,,,,,,,
1/30/2026,Captains of Crush,1,7,0.5,lbs,,,,,,gripper #0.5,
1/30/2026,Captains of Crush,2,5,1,lbs,,,,,,gripper #1,
1/30/2026,Captains of Crush,3,5,1,lbs,,,,,,gripper #1,
1/30/2026,SA Tricep Extension,1,10,27,lbs,,,,,,,
1/30/2026,SA Tricep Extension,2,11,27,lbs,,,,,,,
1/30/2026,Dumbbell Curl,1,10,27,lbs,,,,,,ball bicep curl,
1/30/2026,Dumbbell Curl,2,11,27,lbs,,,,,,ball bicep curl,
1/30/2026,Neck Circuit,1,17,10,lbs,,,,,,,
1/30/2026,Neck Circuit,2,17,10,lbs,,,,,,,
1/30/2026,Bench Dip,1,21,,lbs,,,,,,,
1/30/2026,Bench Dip,2,21,,lbs,,,,,,,
1/30/2026,Assault Bike,1,,,lbs,,,,77,,10/20 intervals,
2/2/2026,Squat,1,5,165,lbs,,,,,,,
2/2/2026,Zercher Squat,1,13,95,lbs,,,,,,,
2/2/2026,Zercher Squat,2,17,95,lbs,,,,,,,
2/2/2026,Zercher Squat,3,17,95,lbs,,,,,,,
2/2/2026,Zercher Squat,4,10,95,lbs,,,,,,,
2/2/2026,Zercher Squat,5,10,95,lbs,,,,,,,
2/2/2026,Hamstring deadlift,1,10,50,lbs,,,,,,,
2/2/2026,Hamstring deadlift,2,10,50,lbs,,,,,,,
2/2/2026,Hamstring deadlift,3,10,50,lbs,,,,,,,
2/2/2026,KB Sidestep,1,10,20,lbs,,,,,,,
2/2/2026,KB Sidestep,2,10,20,lbs,,,,,,,
2/2/2026,KB Sidestep,3,10,20,lbs,,,,,,,
2/2/2026,Calf Raise,1,17,,lbs,,,,,,,
2/2/2026,Calf Raise,2,17,,lbs,,,,,,,
2/2/2026,Calf Raise,3,17,,lbs,,,,,,,
2/2/2026,Bulgarian Split Squat,1,10,15,lbs,,,,,,,
2/2/2026,Bulgarian Split Squat,2,10,15,lbs,,,,,,,
2/2/2026,Bulgarian Split Squat,3,10,15,lbs,,,,,,,
2/2/2026,Bulgarian Split Squat,4,10,15,lbs,,,,,,,
2/3/2026,Face Pulls,1,10,72,lbs,,,,,,,
2/3/2026,Dumbbell Row,1,10,50,lbs,,,,,,,
2/3/2026,Dumbbell Row,2,10,60,lbs,,,,,,,
2/3/2026,Dumbbell Row,3,11,60,lbs,,,,,,,
2/3/2026,KB Press,1,10,24,kg,,,,,,,
2/3/2026,KB Press,2,11,24,kg,,,,,,,
2/3/2026,Cable Row,1,10,49,lbs,,,,,,,
2/3/2026,Cable Row,2,11,49,lbs,,,,,,,
2/3/2026,EQ Bar Incline Bench,1,7,16,kg,,,,,,16kg KB each side,
2/3/2026,EQ Bar Incline Bench,2,11,31,lbs,,,,,,16kg KB + 15lb DB each side,
2/3/2026,EQ Bar Incline Bench,3,10,31,lbs,,,,,,16kg KB + 15lb DB each side,
2/3/2026,EQ Bar Incline Bench,4,21,16,kg,,,,,,16kg KB each side,
2/3/2026,Upright Row,1,17,65,lbs,,,,,,,
2/3/2026,Upright Row,2,21,65,lbs,,,,,,,
2/3/2026,Cable Fly,1,17,53,lbs,,,,,,,
2/3/2026,Cable Fly,2,13,53,lbs,,,,,,,
2/3/2026,Lateral Raise,1,11,10,lbs,,,,,,,
2/3/2026,Lateral Raise,2,10,10,lbs,,,,,,,
2/9/2026,Squat,1,3,135,lbs,,,,,,foot elevated,
2/9/2026,Squat,2,7,165,lbs,,,,,,foot elevated,
2/9/2026,Squat,3,7,185,lbs,,,,,,foot elevated,
2/9/2026,Squat,4,7,205,lbs,,,,,,foot elevated,
2/9/2026,Squat,5,7,205,lbs,,,,,,foot elevated,
2/9/2026,Squat,6,7,205,lbs,,,,,,foot elevated,
2/9/2026,Hamstring deadlift,1,10,60,lbs,,,,,,,
2/9/2026,Hamstring deadlift,2,10,60,lbs,,,,,,,
2/9/2026,Hamstring deadlift,3,10,60,lbs,,,,,,,
2/9/2026,Hamstring deadlift,4,10,60,lbs,,,,,,,
2/9/2026,Kettlebell Leg Extension,1,10,16,kg,,,,,,,
2/9/2026,Kettlebell Leg Extension,2,10,16,kg,,,,,,,
2/9/2026,Kettlebell Leg Extension,3,17,16,kg,,,,,,,
2/9/2026,Kettlebell Leg Extension,4,21,16,kg,,,,,,,
2/9/2026,Calf Raise,1,17,,lbs,,,,,,,
2/9/2026,Calf Raise,2,13,,lbs,,,,,,,
2/9/2026,Calf Raise,3,21,,lbs,,,,,,,
2/9/2026,Calf Raise,4,21,,lbs,,,,,,,
2/9/2026,Bulgarian Split Squat,1,10,,lbs,,,,,,,
2/9/2026,Bulgarian Split Squat,2,10,15,lbs,,,,,,,
2/9/2026,Bulgarian Split Squat,3,10,25,lbs,,,,,,,
2/9/2026,Bulgarian Split Squat,4,12,30,lbs,,,,,,,
2/9/2026,Adductor Bench,1,21,,lbs,,,,,,,
2/9/2026,Adductor Bench,2,21,,lbs,,,,,,,
2/10/2026,EQ Bar Incline Bench,1,7,16,kg,,,,,,16kg KB each side,
2/10/2026,EQ Bar Incline Bench,2,13,31,lbs,,,,,,16kg KB + 15lb DB each side,
2/10/2026,EQ Bar Incline Bench,3,13,31,lbs,,,,,,16kg KB + 15lb DB each side,
2/10/2026,Ring row,1,10,,lbs,,,,,,,
2/10/2026,Ring row,2,10,,lbs,,,,,,,
2/10/2026,Ring row,3,11,,lbs,,,,,,,
2/10/2026,Ring row,4,11,,lbs,,,,,,,
2/10/2026,Low to High Crossover,1,17,16,lbs,,,,,,,
2/10/2026,Low to High Crossover,2,17,16,lbs,,,,,,,
2/10/2026,Chinup,1,7,,lbs,,,,,,,
2/10/2026,Chinup,2,7,,lbs,,,,,,,
2/10/2026,Chinup,3,7,,lbs,,,,,,,
2/10/2026,KB Press,1,17,32,kg,,,,,,,
2/10/2026,KB Press,2,17,32,kg,,,,,,,
2/10/2026,Ab Wheel Rollout,1,17,,lbs,,,,,,,
2/10/2026,Ab Wheel Rollout,2,21,,lbs,,,,,,,
2/10/2026,SA Tricep Extension,1,21,16,lbs,,,,,,,
2/10/2026,SA Tricep Extension,2,17,22,lbs,,,,,,,
2/11/2026,Zercher Squat,1,10,75,lbs,,,,,,,
2/11/2026,Zercher Squat,2,10,95,lbs,,,,,,,
2/11/2026,Zercher Squat,3,10,115,lbs,,,,,,,
2/11/2026,Zercher Squat,4,10,115,lbs,,,,,,,
2/11/2026,Zercher Squat,5,10,115,lbs,,,,,,,
2/11/2026,Zercher Squat,6,10,115,lbs,,,,,,,
2/11/2026,Zercher Squat,7,10,115,lbs,,,,,,,
2/11/2026,Calf Raise,1,21,,lbs,,,,,,,
2/11/2026,Calf Raise,2,21,,lbs,,,,,,,
2/12/2026,TGU,1,2,36,kg,,,,,,,
2/12/2026,TGU,2,2,40,kg,,,,,,,
2/12/2026,TGU,3,2,44,kg,,,,,,,
2/12/2026,TGU,4,2,44,kg,,,,,,,
2/12/2026,TGU,5,2,44,kg,,,,,,,
2/12/2026,Rear delt,1,10,16,lbs,,,,,,,
2/12/2026,Rear delt,2,11,16,lbs,,,,,,,
2/12/2026,Overhead Press,1,7,75,lbs,,,,,,,
2/12/2026,Overhead Press,2,7,95,lbs,,,,,,,
2/12/2026,Overhead Press,3,6,115,lbs,,,,,,,
2/12/2026,Overhead Press,4,3,135,lbs,,,,,,,
2/12/2026,Overhead Press,5,3,135,lbs,,,,,,,
2/12/2026,Overhead Press,6,3,135,lbs,,,,,,,
2/12/2026,Tuck Inversion,1,2,,lbs,,,,,,,
2/12/2026,Tuck Inversion,2,2,,lbs,,,,,,,
2/12/2026,Tuck Inversion,3,2,,lbs,,,,,,,
2/12/2026,Neck Circuit,1,17,10,lbs,,,,,,,
2/14/2026,Deadlift,1,6,185,lbs,,,,,,,
2/14/2026,Deadlift,2,5,225,lbs,,,,,,,
2/14/2026,Deadlift,3,5,275,lbs,,,,,,,
2/14/2026,Deadlift,4,5,275,lbs,,,,,,,
2/14/2026,Deadlift,5,6,275,lbs,,,,,,,
2/14/2026,Deadlift,6,5,275,lbs,,,,,,,
2/14/2026,Fire Hydrant,1,17,,lbs,,,,,,,
2/14/2026,Fire Hydrant,2,17,,lbs,,,,,,,
2/14/2026,Fire Hydrant,3,17,,lbs,,,,,,,
2/14/2026,SL Deadlift,1,10,36,lbs,,,,,,,
2/14/2026,SL Deadlift,2,10,40,lbs,,,,,,,
2/14/2026,Chinup,1,5,,lbs,,,,,,"narrow, vest",
1 date exercise setNumber reps weight weightUnit durationMinutes distance distanceUnit setCalories rpe notes customMetricsJson
2 1/2/2026 Face Pulls 1 10 68 lbs
3 1/2/2026 Rear delt 1 10 16 lbs
4 1/2/2026 EQ Bar Incline Bench 1 13 16 kg 16kg KB each side
5 1/2/2026 EQ Bar Incline Bench 2 10 21 lbs 16kg KB + 5lb each side
6 1/2/2026 EQ Bar Incline Bench 3 10 26 lbs 16kg KB + 10lb each side
7 1/2/2026 EQ Bar Incline Bench 4 12 26 lbs 16kg KB + 10lb each side
8 1/2/2026 EQ Bar Incline Bench 5 10 31 lbs 16kg KB + 15lb each side
9 1/2/2026 Chinup 1 10 lbs
10 1/2/2026 Chinup 2 7 lbs
11 1/2/2026 Chinup 3 7 lbs
12 1/2/2026 Chinup 4 5 lbs slow negatives
13 1/2/2026 Chinup 5 5 lbs slow negatives
14 1/2/2026 Alternating Step Cable Cross 1 16 44 lbs
15 1/2/2026 Alternating Step Cable Cross 2 16 44 lbs
16 1/2/2026 Alternating Step Cable Cross 3 16 44 lbs
17 1/2/2026 Tuck Inversion 1 2 lbs vest
18 1/2/2026 Tuck Inversion 2 2 lbs vest
19 1/2/2026 Ab Wheel Rollout 1 7 lbs vest
20 1/2/2026 Ab Wheel Rollout 2 7 lbs vest
21 1/2/2026 Ab Wheel Rollout 3 7 lbs vest
22 1/2/2026 Ring Dip 1 3 lbs
23 1/2/2026 Ring Dip 2 3 lbs
24 1/2/2026 Ring Dip 3 3 lbs
25 1/2/2026 Overhead Tricep Extension 1 13 44 lbs
26 1/2/2026 Overhead Tricep Extension 2 13 44 lbs
27 1/2/2026 Overhead Tricep Extension 3 13 44 lbs
28 1/2/2026 Barbell Curl 1 17 45 lbs bar only
29 1/2/2026 Barbell Curl 2 17 45 lbs bar only
30 1/2/2026 Barbell Curl 3 17 45 lbs bar only
31 1/2/2026 Band Pushup 1 7 lbs black/gray band
32 1/2/2026 Band Pushup 2 7 lbs black/gray band
33 1/2/2026 Band Pushup 3 7 lbs black/gray band
34 1/3/2026 TGU 1 1 36 kg
35 1/3/2026 TGU 2 1 40 kg
36 1/3/2026 TGU 3 1 44 kg
37 1/3/2026 TGU 4 1 48 kg
38 1/3/2026 TGU 5 1 48 kg
39 1/3/2026 TGU 6 1 48 kg
40 1/3/2026 Deadlift 1 5 185 lbs
41 1/3/2026 Deadlift 2 5 225 lbs
42 1/3/2026 Deadlift 3 5 275 lbs
43 1/3/2026 Deadlift 4 5 275 lbs
44 1/3/2026 Deadlift 5 5 275 lbs
45 1/3/2026 Deadlift 6 6 275 lbs
46 1/3/2026 Assault Bike 1 lbs 72 10/20 intervals
47 1/4/2026 Assault Bike 1 lbs 5 target zone 4 cardio
48 1/4/2026 SkiErg 1 lbs 5 target zone 4 cardio
49 1/4/2026 Jump Rope 1 lbs 5 target zone 4 cardio
50 1/4/2026 KB Swing 1 5 24 kg single arm every 60 sec / arm every 45 sec - 5 min
51 1/4/2026 Neck Circuit 1 17 10 lbs
52 1/6/2026 Squat 1 7 135 lbs
53 1/6/2026 Squat 2 7 165 lbs
54 1/6/2026 Squat 3 7 165 lbs chains - 207 total
55 1/6/2026 Squat 4 7 165 lbs chains - 207 total
56 1/6/2026 Squat 5 7 165 lbs chains - 207 total
57 1/6/2026 Bulgarian Split Squat 1 10 30 lbs
58 1/6/2026 Bulgarian Split Squat 2 10 30 lbs
59 1/6/2026 Bulgarian Split Squat 3 10 30 lbs
60 1/6/2026 Hip Flexor 1 10 12 kg
61 1/6/2026 Hip Flexor 2 10 12 kg
62 1/6/2026 Hip Flexor 3 10 12 kg
63 1/6/2026 Kettlebell Leg Extension 1 10 12 kg
64 1/6/2026 Kettlebell Leg Extension 2 10 12 kg
65 1/6/2026 Kettlebell Leg Extension 3 10 12 kg
66 1/6/2026 Adductor Bench 1 10 lbs
67 1/6/2026 Adductor Bench 2 10 lbs
68 1/6/2026 Adductor Bench 3 10 lbs
69 1/6/2026 Glute Bridge 1 13 135 lbs
70 1/6/2026 Glute Bridge 2 13 135 lbs
71 1/6/2026 Glute Bridge 3 13 135 lbs
72 1/7/2026 Face Pulls 1 10 68 lbs
73 1/7/2026 Rear delt 1 8 16 lbs
74 1/7/2026 EQ Bar Incline Bench 1 10 16 kg 16kg KB each side
75 1/7/2026 EQ Bar Incline Bench 2 10 26 lbs 16kg KB + 10lb each side
76 1/7/2026 EQ Bar Incline Bench 3 10 26 lbs 16kg KB + 10lb each side
77 1/7/2026 EQ Bar Incline Bench 4 13 31 lbs 16kg KB + 15lb each side
78 1/7/2026 EQ Bar Incline Bench 5 9 31 lbs 16kg KB + 15lb each side
79 1/7/2026 EQ Bar Incline Bench 6 8 31 lbs 16kg KB + 15lb each side
80 1/7/2026 Chinup 1 8 lbs
81 1/7/2026 Chinup 2 6 lbs
82 1/7/2026 Chinup 3 6 lbs
83 1/7/2026 Chinup 4 6 lbs
84 1/7/2026 Alternating Step Cable Cross 1 16 44 lbs
85 1/7/2026 Alternating Step Cable Cross 2 16 44 lbs
86 1/7/2026 Alternating Step Cable Cross 3 16 44 lbs
87 1/7/2026 Tuck Inversion 1 2 lbs vest
88 1/7/2026 Tuck Inversion 2 2 lbs vest
89 1/7/2026 Ab Wheel Rollout 1 7 lbs vest
90 1/7/2026 Ab Wheel Rollout 2 7 lbs vest
91 1/7/2026 Ab Wheel Rollout 3 7 lbs vest
92 1/7/2026 Ring Dip 1 7 lbs
93 1/7/2026 Ring Dip 2 7 lbs
94 1/10/2026 Deadlift 1 5 185 lbs
95 1/10/2026 Deadlift 2 5 225 lbs
96 1/10/2026 Deadlift 3 5 275 lbs
97 1/10/2026 Deadlift 4 4 295 lbs
98 1/10/2026 Deadlift 5 5 295 lbs
99 1/10/2026 TGU 1 1 44 kg
100 1/10/2026 TGU 2 1 48 kg
101 1/10/2026 TGU 3 1 48 kg
102 1/10/2026 Ring row 1 10 lbs
103 1/10/2026 Ring row 2 7 lbs
104 1/10/2026 Ring row 3 6 lbs
105 1/10/2026 Glute Bridge 1 13 135 lbs
106 1/10/2026 Glute Bridge 2 13 135 lbs
107 1/10/2026 Glute Bridge 3 13 135 lbs
108 1/10/2026 Fire Hydrant 1 17 lbs blue band
109 1/10/2026 Fire Hydrant 2 13 lbs blue band
110 1/10/2026 Fire Hydrant 3 17 lbs blue band
111 1/10/2026 Glute ham developer 1 7 lbs pad on floor, gray band
112 1/10/2026 Glute ham developer 2 4 lbs pad on floor, gray band
113 1/10/2026 Glute ham developer 3 7 lbs pad on floor, gray band
114 1/10/2026 Neck Circuit 1 17 10 lbs
115 1/12/2026 Barbell step back lunge 1 10 75 lbs
116 1/12/2026 Barbell step back lunge 2 10 95 lbs
117 1/12/2026 Barbell step back lunge 3 10 115 lbs
118 1/12/2026 Bench Step Up 1 10 16 kg quad band black
119 1/12/2026 Bench Step Up 2 10 16 kg quad band black
120 1/12/2026 Kettlebell Leg Extension 1 17 12 kg
121 1/12/2026 Kettlebell Leg Extension 2 17 12 kg
122 1/12/2026 Kettlebell Leg Extension 3 23 12 kg
123 1/13/2026 Shoulder warmup 1 15 lbs warmup
124 1/13/2026 Mace warmup 1 lbs warmup
125 1/13/2026 Face Pulls 1 10 72 lbs
126 1/13/2026 Rear delt 1 7 16 lbs
127 1/13/2026 Rear delt 2 10 16 lbs
128 1/13/2026 Rear delt 3 7 16 lbs
129 1/13/2026 Dumbbell Row 1 10 55 lbs
130 1/13/2026 Dumbbell Row 2 10 55 lbs
131 1/13/2026 Dumbbell Row 3 10 55 lbs
132 1/13/2026 EQ Bar Incline Bench 1 10 16 kg 16kg KB each side
133 1/13/2026 EQ Bar Incline Bench 2 14 31 lbs 16kg + 15 lbs
134 1/13/2026 EQ Bar Incline Bench 3 15 31 lbs 16kg + 15 lbs
135 1/13/2026 EQ Bar Incline Bench 4 10 31 lbs slow
136 1/13/2026 Chinup 1 10 lbs
137 1/13/2026 Chinup 2 6 lbs
138 1/13/2026 Chinup 3 6 lbs
139 1/13/2026 Exercise Ball Situp 1 17 lbs
140 1/13/2026 Exercise Ball Situp 2 13 lbs
141 1/13/2026 Exercise Ball Situp 3 17 lbs
142 1/13/2026 Alternating Step Cable Cross 1 16 44 lbs
143 1/13/2026 Alternating Step Cable Cross 2 16 44 lbs
144 1/13/2026 Alternating Step Cable Cross 3 10 44 lbs
145 1/13/2026 Tuck Inversion 1 2 lbs vest
146 1/13/2026 Tuck Inversion 2 2 lbs vest
147 1/13/2026 Tuck Inversion 3 2 lbs vest
148 1/13/2026 Ab Wheel Rollout 1 10 lbs vest
149 1/13/2026 Ab Wheel Rollout 2 10 lbs vest
150 1/13/2026 Ring Dip 1 5 lbs
151 1/13/2026 Ring Dip 2 5 lbs
152 1/13/2026 Tricep Pushdown 1 10 44 lbs rope
153 1/13/2026 Tricep Pushdown 2 10 44 lbs rope
154 1/14/2026 Hex Bar Deadlift 1 5 200 lbs
155 1/14/2026 Hex Bar Deadlift 2 5 240 lbs
156 1/14/2026 Hex Bar Deadlift 3 5 290 lbs
157 1/14/2026 Hex Bar Deadlift 4 5 320 lbs
158 1/14/2026 Hex Bar Deadlift 5 3 340 lbs
159 1/14/2026 Hex Bar Deadlift 6 3 355 lbs best ever
160 1/14/2026 Glute Bridge 1 13 135 lbs
161 1/14/2026 Glute Bridge 2 13 135 lbs
162 1/14/2026 Glute Bridge 3 13 135 lbs
163 1/14/2026 SL Quad Step Down 1 10 lbs on bench
164 1/14/2026 SL Quad Step Down 2 10 15 lbs on bench
165 1/14/2026 SL Quad Step Down 3 10 16 kg on bench, good weight
166 1/14/2026 Fire Hydrant 1 17 lbs black band
167 1/14/2026 Fire Hydrant 2 17 lbs black band
168 1/14/2026 Assault Bike 1 lbs 69 10/20 intervals
169 1/16/2026 Shoulder warmup 1 lbs warmup
170 1/16/2026 Face Pulls 1 10 68 lbs
171 1/16/2026 Mace warmup 1 lbs warmup
172 1/16/2026 Rear delt 1 10 16 lbs
173 1/16/2026 TGU 1 1 44 kg
174 1/16/2026 TGU 2 1 48 kg
175 1/16/2026 TGU 3 1 48 kg
176 1/16/2026 TGU 4 1 48 kg
177 1/16/2026 TGU 5 1 48 kg
178 1/16/2026 TGU 6 1 48 kg
179 1/16/2026 Overhead Press 1 7 75 lbs
180 1/16/2026 Overhead Press 2 7 95 lbs
181 1/16/2026 Overhead Press 3 5 115 lbs
182 1/16/2026 Overhead Press 4 5 115 lbs
183 1/16/2026 Overhead Press 5 5 115 lbs
184 1/16/2026 Chinup 1 4 lbs vest
185 1/16/2026 Chinup 2 5 lbs vest
186 1/16/2026 Chinup 3 5 lbs vest
187 1/16/2026 Chinup 4 5 lbs vest
188 1/16/2026 Arnold Press 1 8 40 lbs
189 1/16/2026 Arnold Press 2 10 40 lbs
190 1/16/2026 Arnold Press 3 10 40 lbs
191 1/16/2026 Barbell Curl 1 10 45 lbs bar only
192 1/16/2026 Barbell Curl 2 10 65 lbs
193 1/16/2026 Barbell Curl 3 10 65 lbs
194 1/16/2026 Barbell Row 1 10 95 lbs
195 1/16/2026 Barbell Row 2 14 95 lbs
196 1/16/2026 Barbell Row 3 13 95 lbs
197 1/16/2026 Waiter Carry 1 20 16 kg 20 steps
198 1/16/2026 Farmer's Carry 1 40 44 kg 40 steps, repeat circuit
199 1/16/2026 Captains of Crush 1 7 0.5 lbs gripper #0.5
200 1/16/2026 Captains of Crush 2 3 1 lbs gripper #1
201 1/16/2026 Captains of Crush 3 5 1 lbs gripper #1
202 1/16/2026 Captains of Crush 4 6 1 lbs gripper #1
203 1/16/2026 Neck Circuit 1 17 10 lbs
204 1/16/2026 Neck Circuit 2 17 10 lbs
205 1/18/2026 Squat 1 7 135 lbs
206 1/18/2026 Squat 2 5 155 lbs
207 1/18/2026 Squat 3 5 170 lbs
208 1/18/2026 Squat 4 11 185 lbs work set
209 1/18/2026 Squat 5 10 185 lbs work set
210 1/18/2026 DB Step Back Lunge 1 7 40 lbs
211 1/18/2026 DB Step Back Lunge 2 7 60 lbs
212 1/18/2026 DB Step Back Lunge 3 7 80 lbs work set
213 1/18/2026 DB Step Back Lunge 4 10 80 lbs work set - max
214 1/18/2026 Bench Press 1 7 135 lbs
215 1/18/2026 Bench Press 2 5 165 lbs
216 1/18/2026 Bench Press 3 9 185 lbs work set
217 1/18/2026 Bench Press 4 8 185 lbs work set
218 1/18/2026 Lat Pulldown 1 7 60 lbs single arm
219 1/18/2026 Lat Pulldown 2 10 71 lbs work set, single arm
220 1/18/2026 Lat Pulldown 3 11 71 lbs work set, single arm
221 1/18/2026 Ab Wheel Rollout 1 11 lbs vest
222 1/18/2026 Ab Wheel Rollout 2 10 lbs vest
223 1/18/2026 Calf Raise 1 17 lbs vest
224 1/18/2026 Calf Raise 2 13 lbs vest
225 1/18/2026 KB Press 1 5 20 kg
226 1/18/2026 KB Press 2 11 24 kg work set
227 1/18/2026 KB Press 3 10 24 kg work set
228 1/19/2026 Kettlebell Leg Extension 1 13 12 kg
229 1/19/2026 Kettlebell Leg Extension 2 17 12 kg
230 1/19/2026 Kettlebell Leg Extension 3 17 12 kg
231 1/19/2026 Kettlebell Leg Extension 4 21 12 kg ankle weight
232 1/19/2026 Kettlebell Leg Extension 5 21 12 kg ankle weight
233 1/19/2026 Knee Raise 1 21 lbs ankle weight
234 1/19/2026 Knee Raise 2 17 lbs ankle weight
235 1/19/2026 Hip Flexor 1 10 12 lbs
236 1/19/2026 Hip Flexor 2 10 12 lbs
237 1/19/2026 Hip Flexor 3 10 12 lbs
238 1/19/2026 Adductor Bench 1 17 lbs
239 1/19/2026 Adductor Bench 2 21 lbs
240 1/19/2026 Glute ham developer 1 7 lbs
241 1/19/2026 Glute ham developer 2 7 lbs
242 1/19/2026 Glute ham developer 3 7 lbs
243 1/20/2026 Ab KB Drag 1 10 12 lbs
244 1/20/2026 Ab KB Drag 2 11 12 lbs
245 1/20/2026 Dips (Chest) 1 13 lbs
246 1/20/2026 Dips (Chest) 2 17 lbs
247 1/20/2026 SA Tricep Extension 1 11 22 lbs
248 1/20/2026 SA Tricep Extension 2 10 22 lbs
249 1/20/2026 Captains of Crush 1 7 0.5 lbs gripper #0.5
250 1/20/2026 Captains of Crush 2 6 1 lbs gripper #1
251 1/20/2026 Captains of Crush 3 7 1 lbs gripper #1
252 1/20/2026 Captains of Crush 4 3 1.5 lbs gripper #1.5
253 1/20/2026 Captains of Crush 5 2 1.5 lbs gripper #1.5
254 1/20/2026 Captains of Crush 6 10 1 lbs gripper #1
255 1/20/2026 Ab Mat 1 17 lbs
256 1/20/2026 Ab Mat 2 13 lbs
257 1/20/2026 SA DB Curl 1 11 30 lbs
258 1/20/2026 SA DB Curl 2 10 30 lbs
259 1/20/2026 EQ Military Press 1 7 16 kg 16kg KB each side
260 1/20/2026 EQ Military Press 2 10 16 kg 16kg KB each side
261 1/20/2026 Tuck Inversion 1 2 lbs
262 1/20/2026 Tuck Inversion 2 2 lbs
263 1/20/2026 Rear delt 1 10 16 lbs
264 1/20/2026 Rear delt 2 7 16 lbs
265 1/20/2026 Ab Scissors 1 lbs
266 1/20/2026 Ab Scissors 2 lbs
267 1/20/2026 Chinup 1 2 lbs negatives
268 1/20/2026 Chinup 2 2 lbs negatives
269 1/22/2026 TGU 1 1 44 kg
270 1/22/2026 TGU 2 1 44 kg
271 1/22/2026 TGU 3 1 44 kg
272 1/22/2026 EQ Military Press 1 10 16 kg 16kg KB each side
273 1/22/2026 EQ Military Press 2 10 16 kg 16kg KB each side
274 1/22/2026 EQ Military Press 3 10 16 kg 16kg KB each side
275 1/22/2026 Barbell Row 1 21 95 lbs
276 1/22/2026 Barbell Row 2 17 95 lbs
277 1/22/2026 Windmill 1 6 12 kg
278 1/22/2026 Half Kneel Windmill 1 7 16 kg
279 1/22/2026 Half Kneel Windmill 2 8 16 kg
280 1/22/2026 Side Lying Press 1 21 20 lbs
281 1/22/2026 Side Lying Press 2 21 24 lbs
282 1/22/2026 Exercise Ball Situp 1 17 lbs
283 1/22/2026 Exercise Ball Situp 2 17 lbs
284 1/22/2026 Exercise Ball Situp 3 17 lbs
285 1/22/2026 Neck Circuit 1 17 10 lbs
286 1/22/2026 Neck Circuit 2 17 10 lbs
287 1/22/2026 Neck Circuit 3 17 10 lbs
288 1/27/2026 Squat 1 7 135 lbs
289 1/27/2026 Squat 2 7 185 lbs
290 1/27/2026 Squat 3 5 185 lbs
291 1/27/2026 Squat 4 5 225 lbs
292 1/27/2026 Squat 5 5 225 lbs
293 1/27/2026 Squat 6 5 225 lbs
294 1/27/2026 Squat 7 6 225 lbs
295 1/27/2026 Bulgarian Split Squat 1 10 15 lbs
296 1/27/2026 Bulgarian Split Squat 2 10 15 lbs
297 1/27/2026 Bulgarian Split Squat 3 10 15 lbs
298 1/27/2026 Kettlebell Leg Extension 1 17 16 kg
299 1/27/2026 Kettlebell Leg Extension 2 21 16 kg
300 1/27/2026 Calf Raise 1 17 lbs
301 1/27/2026 Calf Raise 2 21 lbs
302 1/29/2026 Hamstring deadlift 1 10 50 lbs
303 1/29/2026 Hex Bar Deadlift 1 5 240 lbs
304 1/29/2026 Hex Bar Deadlift 2 7 295 lbs
305 1/29/2026 Hex Bar Deadlift 3 7 295 lbs
306 1/29/2026 Hex Bar Deadlift 4 7 295 lbs
307 1/29/2026 Bench Press 1 5 135 lbs
308 1/29/2026 Bench Press 2 7 185 lbs
309 1/29/2026 Bench Press 3 8 185 lbs
310 1/29/2026 Bench Press 4 6 185 lbs
311 1/29/2026 Tuck Inversion 1 2 lbs
312 1/29/2026 Ring row 1 7 lbs
313 1/29/2026 Ring row 2 7 lbs
314 1/29/2026 Ring row 3 7 lbs
315 1/29/2026 Exercise Ball Situp 1 17 lbs
316 1/29/2026 Exercise Ball Situp 2 13 lbs
317 1/29/2026 Alt Leg Lift 1 10 lbs
318 1/29/2026 Alt Leg Lift 2 10 lbs
319 1/29/2026 Rear delt 1 7 16 lbs
320 1/29/2026 Rear delt 2 10 16 lbs
321 1/29/2026 SA Landmine Press 1 10 25 lbs
322 1/29/2026 SA Landmine Press 2 10 25 lbs
323 1/29/2026 SA Landmine Press 3 10 25 lbs
324 1/29/2026 Landmine Pull and Press 1 10 25 lbs
325 1/29/2026 Landmine Pull and Press 2 11 25 lbs
326 1/29/2026 Wide Grip Pull Up 1 5 lbs
327 1/29/2026 Wide Grip Pull Up 2 5 lbs
328 1/29/2026 Wide Grip Pull Up 3 5 lbs
329 1/30/2026 Face Pulls 1 10 66 lbs
330 1/30/2026 KB Press 1 7 20 kg
331 1/30/2026 KB Press 2 7 24 kg
332 1/30/2026 KB Press 3 3 32 kg
333 1/30/2026 KB Press 4 3 32 kg
334 1/30/2026 KB Press 5 3 32 kg
335 1/30/2026 Side Lying Press 1 7 20 lbs
336 1/30/2026 Side Lying Press 2 7 24 lbs
337 1/30/2026 Side Lying Press 3 13 24 lbs
338 1/30/2026 Side Lying Press 4 17 24 lbs
339 1/30/2026 TGU 1 1 44 kg
340 1/30/2026 TGU 2 1 48 kg
341 1/30/2026 TGU 3 1 48 kg
342 1/30/2026 BB Reverse Curl 1 17 45 lbs bar only
343 1/30/2026 BB Reverse Curl 2 13 55 lbs
344 1/30/2026 BB Reverse Curl 3 13 60 lbs
345 1/30/2026 Captains of Crush 1 7 0.5 lbs gripper #0.5
346 1/30/2026 Captains of Crush 2 5 1 lbs gripper #1
347 1/30/2026 Captains of Crush 3 5 1 lbs gripper #1
348 1/30/2026 SA Tricep Extension 1 10 27 lbs
349 1/30/2026 SA Tricep Extension 2 11 27 lbs
350 1/30/2026 Dumbbell Curl 1 10 27 lbs ball bicep curl
351 1/30/2026 Dumbbell Curl 2 11 27 lbs ball bicep curl
352 1/30/2026 Neck Circuit 1 17 10 lbs
353 1/30/2026 Neck Circuit 2 17 10 lbs
354 1/30/2026 Bench Dip 1 21 lbs
355 1/30/2026 Bench Dip 2 21 lbs
356 1/30/2026 Assault Bike 1 lbs 77 10/20 intervals
357 2/2/2026 Squat 1 5 165 lbs
358 2/2/2026 Zercher Squat 1 13 95 lbs
359 2/2/2026 Zercher Squat 2 17 95 lbs
360 2/2/2026 Zercher Squat 3 17 95 lbs
361 2/2/2026 Zercher Squat 4 10 95 lbs
362 2/2/2026 Zercher Squat 5 10 95 lbs
363 2/2/2026 Hamstring deadlift 1 10 50 lbs
364 2/2/2026 Hamstring deadlift 2 10 50 lbs
365 2/2/2026 Hamstring deadlift 3 10 50 lbs
366 2/2/2026 KB Sidestep 1 10 20 lbs
367 2/2/2026 KB Sidestep 2 10 20 lbs
368 2/2/2026 KB Sidestep 3 10 20 lbs
369 2/2/2026 Calf Raise 1 17 lbs
370 2/2/2026 Calf Raise 2 17 lbs
371 2/2/2026 Calf Raise 3 17 lbs
372 2/2/2026 Bulgarian Split Squat 1 10 15 lbs
373 2/2/2026 Bulgarian Split Squat 2 10 15 lbs
374 2/2/2026 Bulgarian Split Squat 3 10 15 lbs
375 2/2/2026 Bulgarian Split Squat 4 10 15 lbs
376 2/3/2026 Face Pulls 1 10 72 lbs
377 2/3/2026 Dumbbell Row 1 10 50 lbs
378 2/3/2026 Dumbbell Row 2 10 60 lbs
379 2/3/2026 Dumbbell Row 3 11 60 lbs
380 2/3/2026 KB Press 1 10 24 kg
381 2/3/2026 KB Press 2 11 24 kg
382 2/3/2026 Cable Row 1 10 49 lbs
383 2/3/2026 Cable Row 2 11 49 lbs
384 2/3/2026 EQ Bar Incline Bench 1 7 16 kg 16kg KB each side
385 2/3/2026 EQ Bar Incline Bench 2 11 31 lbs 16kg KB + 15lb DB each side
386 2/3/2026 EQ Bar Incline Bench 3 10 31 lbs 16kg KB + 15lb DB each side
387 2/3/2026 EQ Bar Incline Bench 4 21 16 kg 16kg KB each side
388 2/3/2026 Upright Row 1 17 65 lbs
389 2/3/2026 Upright Row 2 21 65 lbs
390 2/3/2026 Cable Fly 1 17 53 lbs
391 2/3/2026 Cable Fly 2 13 53 lbs
392 2/3/2026 Lateral Raise 1 11 10 lbs
393 2/3/2026 Lateral Raise 2 10 10 lbs
394 2/9/2026 Squat 1 3 135 lbs foot elevated
395 2/9/2026 Squat 2 7 165 lbs foot elevated
396 2/9/2026 Squat 3 7 185 lbs foot elevated
397 2/9/2026 Squat 4 7 205 lbs foot elevated
398 2/9/2026 Squat 5 7 205 lbs foot elevated
399 2/9/2026 Squat 6 7 205 lbs foot elevated
400 2/9/2026 Hamstring deadlift 1 10 60 lbs
401 2/9/2026 Hamstring deadlift 2 10 60 lbs
402 2/9/2026 Hamstring deadlift 3 10 60 lbs
403 2/9/2026 Hamstring deadlift 4 10 60 lbs
404 2/9/2026 Kettlebell Leg Extension 1 10 16 kg
405 2/9/2026 Kettlebell Leg Extension 2 10 16 kg
406 2/9/2026 Kettlebell Leg Extension 3 17 16 kg
407 2/9/2026 Kettlebell Leg Extension 4 21 16 kg
408 2/9/2026 Calf Raise 1 17 lbs
409 2/9/2026 Calf Raise 2 13 lbs
410 2/9/2026 Calf Raise 3 21 lbs
411 2/9/2026 Calf Raise 4 21 lbs
412 2/9/2026 Bulgarian Split Squat 1 10 lbs
413 2/9/2026 Bulgarian Split Squat 2 10 15 lbs
414 2/9/2026 Bulgarian Split Squat 3 10 25 lbs
415 2/9/2026 Bulgarian Split Squat 4 12 30 lbs
416 2/9/2026 Adductor Bench 1 21 lbs
417 2/9/2026 Adductor Bench 2 21 lbs
418 2/10/2026 EQ Bar Incline Bench 1 7 16 kg 16kg KB each side
419 2/10/2026 EQ Bar Incline Bench 2 13 31 lbs 16kg KB + 15lb DB each side
420 2/10/2026 EQ Bar Incline Bench 3 13 31 lbs 16kg KB + 15lb DB each side
421 2/10/2026 Ring row 1 10 lbs
422 2/10/2026 Ring row 2 10 lbs
423 2/10/2026 Ring row 3 11 lbs
424 2/10/2026 Ring row 4 11 lbs
425 2/10/2026 Low to High Crossover 1 17 16 lbs
426 2/10/2026 Low to High Crossover 2 17 16 lbs
427 2/10/2026 Chinup 1 7 lbs
428 2/10/2026 Chinup 2 7 lbs
429 2/10/2026 Chinup 3 7 lbs
430 2/10/2026 KB Press 1 17 32 kg
431 2/10/2026 KB Press 2 17 32 kg
432 2/10/2026 Ab Wheel Rollout 1 17 lbs
433 2/10/2026 Ab Wheel Rollout 2 21 lbs
434 2/10/2026 SA Tricep Extension 1 21 16 lbs
435 2/10/2026 SA Tricep Extension 2 17 22 lbs
436 2/11/2026 Zercher Squat 1 10 75 lbs
437 2/11/2026 Zercher Squat 2 10 95 lbs
438 2/11/2026 Zercher Squat 3 10 115 lbs
439 2/11/2026 Zercher Squat 4 10 115 lbs
440 2/11/2026 Zercher Squat 5 10 115 lbs
441 2/11/2026 Zercher Squat 6 10 115 lbs
442 2/11/2026 Zercher Squat 7 10 115 lbs
443 2/11/2026 Calf Raise 1 21 lbs
444 2/11/2026 Calf Raise 2 21 lbs
445 2/12/2026 TGU 1 2 36 kg
446 2/12/2026 TGU 2 2 40 kg
447 2/12/2026 TGU 3 2 44 kg
448 2/12/2026 TGU 4 2 44 kg
449 2/12/2026 TGU 5 2 44 kg
450 2/12/2026 Rear delt 1 10 16 lbs
451 2/12/2026 Rear delt 2 11 16 lbs
452 2/12/2026 Overhead Press 1 7 75 lbs
453 2/12/2026 Overhead Press 2 7 95 lbs
454 2/12/2026 Overhead Press 3 6 115 lbs
455 2/12/2026 Overhead Press 4 3 135 lbs
456 2/12/2026 Overhead Press 5 3 135 lbs
457 2/12/2026 Overhead Press 6 3 135 lbs
458 2/12/2026 Tuck Inversion 1 2 lbs
459 2/12/2026 Tuck Inversion 2 2 lbs
460 2/12/2026 Tuck Inversion 3 2 lbs
461 2/12/2026 Neck Circuit 1 17 10 lbs
462 2/14/2026 Deadlift 1 6 185 lbs
463 2/14/2026 Deadlift 2 5 225 lbs
464 2/14/2026 Deadlift 3 5 275 lbs
465 2/14/2026 Deadlift 4 5 275 lbs
466 2/14/2026 Deadlift 5 6 275 lbs
467 2/14/2026 Deadlift 6 5 275 lbs
468 2/14/2026 Fire Hydrant 1 17 lbs
469 2/14/2026 Fire Hydrant 2 17 lbs
470 2/14/2026 Fire Hydrant 3 17 lbs
471 2/14/2026 SL Deadlift 1 10 36 lbs
472 2/14/2026 SL Deadlift 2 10 40 lbs
473 2/14/2026 Chinup 1 5 lbs narrow, vest
@@ -0,0 +1,473 @@
date,exercise,set,weight,weight_unit,reps,duration_seconds,distance,distance_unit,calories,rpe,notes,custom_metrics_json
1/2/2026,Face Pulls,1,68,lbs,10,,,,,,,
1/2/2026,Rear delt,1,16,lbs,10,,,,,,,
1/2/2026,EQ Bar Incline Bench,1,16,kg,13,,,,,,16kg KB each side,
1/2/2026,EQ Bar Incline Bench,2,21,lbs,10,,,,,,16kg KB + 5lb each side,
1/2/2026,EQ Bar Incline Bench,3,26,lbs,10,,,,,,16kg KB + 10lb each side,
1/2/2026,EQ Bar Incline Bench,4,26,lbs,12,,,,,,16kg KB + 10lb each side,
1/2/2026,EQ Bar Incline Bench,5,31,lbs,10,,,,,,16kg KB + 15lb each side,
1/2/2026,Chinup,1,,lbs,10,,,,,,,
1/2/2026,Chinup,2,,lbs,7,,,,,,,
1/2/2026,Chinup,3,,lbs,7,,,,,,,
1/2/2026,Chinup,4,,lbs,5,,,,,,slow negatives,
1/2/2026,Chinup,5,,lbs,5,,,,,,slow negatives,
1/2/2026,Alternating Step Cable Cross,1,44,lbs,16,,,,,,,
1/2/2026,Alternating Step Cable Cross,2,44,lbs,16,,,,,,,
1/2/2026,Alternating Step Cable Cross,3,44,lbs,16,,,,,,,
1/2/2026,Tuck Inversion,1,,lbs,2,,,,,,vest,
1/2/2026,Tuck Inversion,2,,lbs,2,,,,,,vest,
1/2/2026,Ab Wheel Rollout,1,,lbs,7,,,,,,vest,
1/2/2026,Ab Wheel Rollout,2,,lbs,7,,,,,,vest,
1/2/2026,Ab Wheel Rollout,3,,lbs,7,,,,,,vest,
1/2/2026,Ring Dip,1,,lbs,3,,,,,,,
1/2/2026,Ring Dip,2,,lbs,3,,,,,,,
1/2/2026,Ring Dip,3,,lbs,3,,,,,,,
1/2/2026,Overhead Tricep Extension,1,44,lbs,13,,,,,,,
1/2/2026,Overhead Tricep Extension,2,44,lbs,13,,,,,,,
1/2/2026,Overhead Tricep Extension,3,44,lbs,13,,,,,,,
1/2/2026,Barbell Curl,1,45,lbs,17,,,,,,bar only,
1/2/2026,Barbell Curl,2,45,lbs,17,,,,,,bar only,
1/2/2026,Barbell Curl,3,45,lbs,17,,,,,,bar only,
1/2/2026,Band Pushup,1,,lbs,7,,,,,,black/gray band,
1/2/2026,Band Pushup,2,,lbs,7,,,,,,black/gray band,
1/2/2026,Band Pushup,3,,lbs,7,,,,,,black/gray band,
1/3/2026,TGU,1,36,kg,1,,,,,,,
1/3/2026,TGU,2,40,kg,1,,,,,,,
1/3/2026,TGU,3,44,kg,1,,,,,,,
1/3/2026,TGU,4,48,kg,1,,,,,,,
1/3/2026,TGU,5,48,kg,1,,,,,,,
1/3/2026,TGU,6,48,kg,1,,,,,,,
1/3/2026,Deadlift,1,185,lbs,5,,,,,,,
1/3/2026,Deadlift,2,225,lbs,5,,,,,,,
1/3/2026,Deadlift,3,275,lbs,5,,,,,,,
1/3/2026,Deadlift,4,275,lbs,5,,,,,,,
1/3/2026,Deadlift,5,275,lbs,5,,,,,,,
1/3/2026,Deadlift,6,275,lbs,6,,,,,,,
1/3/2026,Assault Bike,1,,lbs,,,,,72,,10/20 intervals,
1/4/2026,Assault Bike,1,,lbs,,300,,,,,target zone 4 cardio,
1/4/2026,SkiErg,1,,lbs,,300,,,,,target zone 4 cardio,
1/4/2026,Jump Rope,1,,lbs,,300,,,,,target zone 4 cardio,
1/4/2026,KB Swing,1,24,kg,5,,,,,,single arm every 60 sec / arm every 45 sec - 5 min,
1/4/2026,Neck Circuit,1,10,lbs,17,,,,,,,
1/6/2026,Squat,1,135,lbs,7,,,,,,,
1/6/2026,Squat,2,165,lbs,7,,,,,,,
1/6/2026,Squat,3,165,lbs,7,,,,,,chains - 207 total,
1/6/2026,Squat,4,165,lbs,7,,,,,,chains - 207 total,
1/6/2026,Squat,5,165,lbs,7,,,,,,chains - 207 total,
1/6/2026,Bulgarian Split Squat,1,30,lbs,10,,,,,,,
1/6/2026,Bulgarian Split Squat,2,30,lbs,10,,,,,,,
1/6/2026,Bulgarian Split Squat,3,30,lbs,10,,,,,,,
1/6/2026,Hip Flexor,1,12,kg,10,,,,,,,
1/6/2026,Hip Flexor,2,12,kg,10,,,,,,,
1/6/2026,Hip Flexor,3,12,kg,10,,,,,,,
1/6/2026,Kettlebell Leg Extension,1,12,kg,10,,,,,,,
1/6/2026,Kettlebell Leg Extension,2,12,kg,10,,,,,,,
1/6/2026,Kettlebell Leg Extension,3,12,kg,10,,,,,,,
1/6/2026,Adductor Bench,1,,lbs,10,,,,,,,
1/6/2026,Adductor Bench,2,,lbs,10,,,,,,,
1/6/2026,Adductor Bench,3,,lbs,10,,,,,,,
1/6/2026,Glute Bridge,1,135,lbs,13,,,,,,,
1/6/2026,Glute Bridge,2,135,lbs,13,,,,,,,
1/6/2026,Glute Bridge,3,135,lbs,13,,,,,,,
1/7/2026,Face Pulls,1,68,lbs,10,,,,,,,
1/7/2026,Rear delt,1,16,lbs,8,,,,,,,
1/7/2026,EQ Bar Incline Bench,1,16,kg,10,,,,,,16kg KB each side,
1/7/2026,EQ Bar Incline Bench,2,26,lbs,10,,,,,,16kg KB + 10lb each side,
1/7/2026,EQ Bar Incline Bench,3,26,lbs,10,,,,,,16kg KB + 10lb each side,
1/7/2026,EQ Bar Incline Bench,4,31,lbs,13,,,,,,16kg KB + 15lb each side,
1/7/2026,EQ Bar Incline Bench,5,31,lbs,9,,,,,,16kg KB + 15lb each side,
1/7/2026,EQ Bar Incline Bench,6,31,lbs,8,,,,,,16kg KB + 15lb each side,
1/7/2026,Chinup,1,,lbs,8,,,,,,,
1/7/2026,Chinup,2,,lbs,6,,,,,,,
1/7/2026,Chinup,3,,lbs,6,,,,,,,
1/7/2026,Chinup,4,,lbs,6,,,,,,,
1/7/2026,Alternating Step Cable Cross,1,44,lbs,16,,,,,,,
1/7/2026,Alternating Step Cable Cross,2,44,lbs,16,,,,,,,
1/7/2026,Alternating Step Cable Cross,3,44,lbs,16,,,,,,,
1/7/2026,Tuck Inversion,1,,lbs,2,,,,,,vest,
1/7/2026,Tuck Inversion,2,,lbs,2,,,,,,vest,
1/7/2026,Ab Wheel Rollout,1,,lbs,7,,,,,,vest,
1/7/2026,Ab Wheel Rollout,2,,lbs,7,,,,,,vest,
1/7/2026,Ab Wheel Rollout,3,,lbs,7,,,,,,vest,
1/7/2026,Ring Dip,1,,lbs,7,,,,,,,
1/7/2026,Ring Dip,2,,lbs,7,,,,,,,
1/10/2026,Deadlift,1,185,lbs,5,,,,,,,
1/10/2026,Deadlift,2,225,lbs,5,,,,,,,
1/10/2026,Deadlift,3,275,lbs,5,,,,,,,
1/10/2026,Deadlift,4,295,lbs,4,,,,,,,
1/10/2026,Deadlift,5,295,lbs,5,,,,,,,
1/10/2026,TGU,1,44,kg,1,,,,,,,
1/10/2026,TGU,2,48,kg,1,,,,,,,
1/10/2026,TGU,3,48,kg,1,,,,,,,
1/10/2026,Ring row,1,,lbs,10,,,,,,,
1/10/2026,Ring row,2,,lbs,7,,,,,,,
1/10/2026,Ring row,3,,lbs,6,,,,,,,
1/10/2026,Glute Bridge,1,135,lbs,13,,,,,,,
1/10/2026,Glute Bridge,2,135,lbs,13,,,,,,,
1/10/2026,Glute Bridge,3,135,lbs,13,,,,,,,
1/10/2026,Fire Hydrant,1,,lbs,17,,,,,,blue band,
1/10/2026,Fire Hydrant,2,,lbs,13,,,,,,blue band,
1/10/2026,Fire Hydrant,3,,lbs,17,,,,,,blue band,
1/10/2026,Glute ham developer,1,,lbs,7,,,,,,"pad on floor, gray band",
1/10/2026,Glute ham developer,2,,lbs,4,,,,,,"pad on floor, gray band",
1/10/2026,Glute ham developer,3,,lbs,7,,,,,,"pad on floor, gray band",
1/10/2026,Neck Circuit,1,10,lbs,17,,,,,,,
1/12/2026,Barbell step back lunge,1,75,lbs,10,,,,,,,
1/12/2026,Barbell step back lunge,2,95,lbs,10,,,,,,,
1/12/2026,Barbell step back lunge,3,115,lbs,10,,,,,,,
1/12/2026,Bench Step Up,1,16,kg,10,,,,,,quad band black,
1/12/2026,Bench Step Up,2,16,kg,10,,,,,,quad band black,
1/12/2026,Kettlebell Leg Extension,1,12,kg,17,,,,,,,
1/12/2026,Kettlebell Leg Extension,2,12,kg,17,,,,,,,
1/12/2026,Kettlebell Leg Extension,3,12,kg,23,,,,,,,
1/13/2026,Shoulder warmup,1,15,lbs,,,,,,,warmup,
1/13/2026,Mace warmup,1,,lbs,,,,,,,warmup,
1/13/2026,Face Pulls,1,72,lbs,10,,,,,,,
1/13/2026,Rear delt,1,16,lbs,7,,,,,,,
1/13/2026,Rear delt,2,16,lbs,10,,,,,,,
1/13/2026,Rear delt,3,16,lbs,7,,,,,,,
1/13/2026,Dumbbell Row,1,55,lbs,10,,,,,,,
1/13/2026,Dumbbell Row,2,55,lbs,10,,,,,,,
1/13/2026,Dumbbell Row,3,55,lbs,10,,,,,,,
1/13/2026,EQ Bar Incline Bench,1,16,kg,10,,,,,,16kg KB each side,
1/13/2026,EQ Bar Incline Bench,2,31,lbs,14,,,,,,16kg + 15 lbs,
1/13/2026,EQ Bar Incline Bench,3,31,lbs,15,,,,,,16kg + 15 lbs,
1/13/2026,EQ Bar Incline Bench,4,31,lbs,10,,,,,,slow,
1/13/2026,Chinup,1,,lbs,10,,,,,,,
1/13/2026,Chinup,2,,lbs,6,,,,,,,
1/13/2026,Chinup,3,,lbs,6,,,,,,,
1/13/2026,Exercise Ball Situp,1,,lbs,17,,,,,,,
1/13/2026,Exercise Ball Situp,2,,lbs,13,,,,,,,
1/13/2026,Exercise Ball Situp,3,,lbs,17,,,,,,,
1/13/2026,Alternating Step Cable Cross,1,44,lbs,16,,,,,,,
1/13/2026,Alternating Step Cable Cross,2,44,lbs,16,,,,,,,
1/13/2026,Alternating Step Cable Cross,3,44,lbs,10,,,,,,,
1/13/2026,Tuck Inversion,1,,lbs,2,,,,,,vest,
1/13/2026,Tuck Inversion,2,,lbs,2,,,,,,vest,
1/13/2026,Tuck Inversion,3,,lbs,2,,,,,,vest,
1/13/2026,Ab Wheel Rollout,1,,lbs,10,,,,,,vest,
1/13/2026,Ab Wheel Rollout,2,,lbs,10,,,,,,vest,
1/13/2026,Ring Dip,1,,lbs,5,,,,,,,
1/13/2026,Ring Dip,2,,lbs,5,,,,,,,
1/13/2026,Tricep Pushdown,1,44,lbs,10,,,,,,rope,
1/13/2026,Tricep Pushdown,2,44,lbs,10,,,,,,rope,
1/14/2026,Hex Bar Deadlift,1,200,lbs,5,,,,,,,
1/14/2026,Hex Bar Deadlift,2,240,lbs,5,,,,,,,
1/14/2026,Hex Bar Deadlift,3,290,lbs,5,,,,,,,
1/14/2026,Hex Bar Deadlift,4,320,lbs,5,,,,,,,
1/14/2026,Hex Bar Deadlift,5,340,lbs,3,,,,,,,
1/14/2026,Hex Bar Deadlift,6,355,lbs,3,,,,,,best ever,
1/14/2026,Glute Bridge,1,135,lbs,13,,,,,,,
1/14/2026,Glute Bridge,2,135,lbs,13,,,,,,,
1/14/2026,Glute Bridge,3,135,lbs,13,,,,,,,
1/14/2026,SL Quad Step Down,1,,lbs,10,,,,,,on bench,
1/14/2026,SL Quad Step Down,2,15,lbs,10,,,,,,on bench,
1/14/2026,SL Quad Step Down,3,16,kg,10,,,,,,"on bench, good weight",
1/14/2026,Fire Hydrant,1,,lbs,17,,,,,,black band,
1/14/2026,Fire Hydrant,2,,lbs,17,,,,,,black band,
1/14/2026,Assault Bike,1,,lbs,,,,,69,,10/20 intervals,
1/16/2026,Shoulder warmup,1,,lbs,,,,,,,warmup,
1/16/2026,Face Pulls,1,68,lbs,10,,,,,,,
1/16/2026,Mace warmup,1,,lbs,,,,,,,warmup,
1/16/2026,Rear delt,1,16,lbs,10,,,,,,,
1/16/2026,TGU,1,44,kg,1,,,,,,,
1/16/2026,TGU,2,48,kg,1,,,,,,,
1/16/2026,TGU,3,48,kg,1,,,,,,,
1/16/2026,TGU,4,48,kg,1,,,,,,,
1/16/2026,TGU,5,48,kg,1,,,,,,,
1/16/2026,TGU,6,48,kg,1,,,,,,,
1/16/2026,Overhead Press,1,75,lbs,7,,,,,,,
1/16/2026,Overhead Press,2,95,lbs,7,,,,,,,
1/16/2026,Overhead Press,3,115,lbs,5,,,,,,,
1/16/2026,Overhead Press,4,115,lbs,5,,,,,,,
1/16/2026,Overhead Press,5,115,lbs,5,,,,,,,
1/16/2026,Chinup,1,,lbs,4,,,,,,vest,
1/16/2026,Chinup,2,,lbs,5,,,,,,vest,
1/16/2026,Chinup,3,,lbs,5,,,,,,vest,
1/16/2026,Chinup,4,,lbs,5,,,,,,vest,
1/16/2026,Arnold Press,1,40,lbs,8,,,,,,,
1/16/2026,Arnold Press,2,40,lbs,10,,,,,,,
1/16/2026,Arnold Press,3,40,lbs,10,,,,,,,
1/16/2026,Barbell Curl,1,45,lbs,10,,,,,,bar only,
1/16/2026,Barbell Curl,2,65,lbs,10,,,,,,,
1/16/2026,Barbell Curl,3,65,lbs,10,,,,,,,
1/16/2026,Barbell Row,1,95,lbs,10,,,,,,,
1/16/2026,Barbell Row,2,95,lbs,14,,,,,,,
1/16/2026,Barbell Row,3,95,lbs,13,,,,,,,
1/16/2026,Waiter Carry,1,16,kg,20,,,,,,20 steps,
1/16/2026,Farmer's Carry,1,44,kg,40,,,,,,"40 steps, repeat circuit",
1/16/2026,Captains of Crush,1,0.5,lbs,7,,,,,,gripper #0.5,
1/16/2026,Captains of Crush,2,1,lbs,3,,,,,,gripper #1,
1/16/2026,Captains of Crush,3,1,lbs,5,,,,,,gripper #1,
1/16/2026,Captains of Crush,4,1,lbs,6,,,,,,gripper #1,
1/16/2026,Neck Circuit,1,10,lbs,17,,,,,,,
1/16/2026,Neck Circuit,2,10,lbs,17,,,,,,,
1/18/2026,Squat,1,135,lbs,7,,,,,,,
1/18/2026,Squat,2,155,lbs,5,,,,,,,
1/18/2026,Squat,3,170,lbs,5,,,,,,,
1/18/2026,Squat,4,185,lbs,11,,,,,,work set,
1/18/2026,Squat,5,185,lbs,10,,,,,,work set,
1/18/2026,DB Step Back Lunge,1,40,lbs,7,,,,,,,
1/18/2026,DB Step Back Lunge,2,60,lbs,7,,,,,,,
1/18/2026,DB Step Back Lunge,3,80,lbs,7,,,,,,work set,
1/18/2026,DB Step Back Lunge,4,80,lbs,10,,,,,,work set - max,
1/18/2026,Bench Press,1,135,lbs,7,,,,,,,
1/18/2026,Bench Press,2,165,lbs,5,,,,,,,
1/18/2026,Bench Press,3,185,lbs,9,,,,,,work set,
1/18/2026,Bench Press,4,185,lbs,8,,,,,,work set,
1/18/2026,Lat Pulldown,1,60,lbs,7,,,,,,single arm,
1/18/2026,Lat Pulldown,2,71,lbs,10,,,,,,"work set, single arm",
1/18/2026,Lat Pulldown,3,71,lbs,11,,,,,,"work set, single arm",
1/18/2026,Ab Wheel Rollout,1,,lbs,11,,,,,,vest,
1/18/2026,Ab Wheel Rollout,2,,lbs,10,,,,,,vest,
1/18/2026,Calf Raise,1,,lbs,17,,,,,,vest,
1/18/2026,Calf Raise,2,,lbs,13,,,,,,vest,
1/18/2026,KB Press,1,20,kg,5,,,,,,,
1/18/2026,KB Press,2,24,kg,11,,,,,,work set,
1/18/2026,KB Press,3,24,kg,10,,,,,,work set,
1/19/2026,Kettlebell Leg Extension,1,12,kg,13,,,,,,,
1/19/2026,Kettlebell Leg Extension,2,12,kg,17,,,,,,,
1/19/2026,Kettlebell Leg Extension,3,12,kg,17,,,,,,,
1/19/2026,Kettlebell Leg Extension,4,12,kg,21,,,,,,ankle weight,
1/19/2026,Kettlebell Leg Extension,5,12,kg,21,,,,,,ankle weight,
1/19/2026,Knee Raise,1,,lbs,21,,,,,,ankle weight,
1/19/2026,Knee Raise,2,,lbs,17,,,,,,ankle weight,
1/19/2026,Hip Flexor,1,12,lbs,10,,,,,,,
1/19/2026,Hip Flexor,2,12,lbs,10,,,,,,,
1/19/2026,Hip Flexor,3,12,lbs,10,,,,,,,
1/19/2026,Adductor Bench,1,,lbs,17,,,,,,,
1/19/2026,Adductor Bench,2,,lbs,21,,,,,,,
1/19/2026,Glute ham developer,1,,lbs,7,,,,,,,
1/19/2026,Glute ham developer,2,,lbs,7,,,,,,,
1/19/2026,Glute ham developer,3,,lbs,7,,,,,,,
1/20/2026,Ab KB Drag,1,12,lbs,10,,,,,,,
1/20/2026,Ab KB Drag,2,12,lbs,11,,,,,,,
1/20/2026,Dips (Chest),1,,lbs,13,,,,,,,
1/20/2026,Dips (Chest),2,,lbs,17,,,,,,,
1/20/2026,SA Tricep Extension,1,22,lbs,11,,,,,,,
1/20/2026,SA Tricep Extension,2,22,lbs,10,,,,,,,
1/20/2026,Captains of Crush,1,0.5,lbs,7,,,,,,gripper #0.5,
1/20/2026,Captains of Crush,2,1,lbs,6,,,,,,gripper #1,
1/20/2026,Captains of Crush,3,1,lbs,7,,,,,,gripper #1,
1/20/2026,Captains of Crush,4,1.5,lbs,3,,,,,,gripper #1.5,
1/20/2026,Captains of Crush,5,1.5,lbs,2,,,,,,gripper #1.5,
1/20/2026,Captains of Crush,6,1,lbs,10,,,,,,gripper #1,
1/20/2026,Ab Mat,1,,lbs,17,,,,,,,
1/20/2026,Ab Mat,2,,lbs,13,,,,,,,
1/20/2026,SA DB Curl,1,30,lbs,11,,,,,,,
1/20/2026,SA DB Curl,2,30,lbs,10,,,,,,,
1/20/2026,EQ Military Press,1,16,kg,7,,,,,,16kg KB each side,
1/20/2026,EQ Military Press,2,16,kg,10,,,,,,16kg KB each side,
1/20/2026,Tuck Inversion,1,,lbs,2,,,,,,,
1/20/2026,Tuck Inversion,2,,lbs,2,,,,,,,
1/20/2026,Rear delt,1,16,lbs,10,,,,,,,
1/20/2026,Rear delt,2,16,lbs,7,,,,,,,
1/20/2026,Ab Scissors,1,,lbs,,,,,,,,
1/20/2026,Ab Scissors,2,,lbs,,,,,,,,
1/20/2026,Chinup,1,,lbs,2,,,,,,negatives,
1/20/2026,Chinup,2,,lbs,2,,,,,,negatives,
1/22/2026,TGU,1,44,kg,1,,,,,,,
1/22/2026,TGU,2,44,kg,1,,,,,,,
1/22/2026,TGU,3,44,kg,1,,,,,,,
1/22/2026,EQ Military Press,1,16,kg,10,,,,,,16kg KB each side,
1/22/2026,EQ Military Press,2,16,kg,10,,,,,,16kg KB each side,
1/22/2026,EQ Military Press,3,16,kg,10,,,,,,16kg KB each side,
1/22/2026,Barbell Row,1,95,lbs,21,,,,,,,
1/22/2026,Barbell Row,2,95,lbs,17,,,,,,,
1/22/2026,Windmill,1,12,kg,6,,,,,,,
1/22/2026,Half Kneel Windmill,1,16,kg,7,,,,,,,
1/22/2026,Half Kneel Windmill,2,16,kg,8,,,,,,,
1/22/2026,Side Lying Press,1,20,lbs,21,,,,,,,
1/22/2026,Side Lying Press,2,24,lbs,21,,,,,,,
1/22/2026,Exercise Ball Situp,1,,lbs,17,,,,,,,
1/22/2026,Exercise Ball Situp,2,,lbs,17,,,,,,,
1/22/2026,Exercise Ball Situp,3,,lbs,17,,,,,,,
1/22/2026,Neck Circuit,1,10,lbs,17,,,,,,,
1/22/2026,Neck Circuit,2,10,lbs,17,,,,,,,
1/22/2026,Neck Circuit,3,10,lbs,17,,,,,,,
1/27/2026,Squat,1,135,lbs,7,,,,,,,
1/27/2026,Squat,2,185,lbs,7,,,,,,,
1/27/2026,Squat,3,185,lbs,5,,,,,,,
1/27/2026,Squat,4,225,lbs,5,,,,,,,
1/27/2026,Squat,5,225,lbs,5,,,,,,,
1/27/2026,Squat,6,225,lbs,5,,,,,,,
1/27/2026,Squat,7,225,lbs,6,,,,,,,
1/27/2026,Bulgarian Split Squat,1,15,lbs,10,,,,,,,
1/27/2026,Bulgarian Split Squat,2,15,lbs,10,,,,,,,
1/27/2026,Bulgarian Split Squat,3,15,lbs,10,,,,,,,
1/27/2026,Kettlebell Leg Extension,1,16,kg,17,,,,,,,
1/27/2026,Kettlebell Leg Extension,2,16,kg,21,,,,,,,
1/27/2026,Calf Raise,1,,lbs,17,,,,,,,
1/27/2026,Calf Raise,2,,lbs,21,,,,,,,
1/29/2026,Hamstring deadlift,1,50,lbs,10,,,,,,,
1/29/2026,Hex Bar Deadlift,1,240,lbs,5,,,,,,,
1/29/2026,Hex Bar Deadlift,2,295,lbs,7,,,,,,,
1/29/2026,Hex Bar Deadlift,3,295,lbs,7,,,,,,,
1/29/2026,Hex Bar Deadlift,4,295,lbs,7,,,,,,,
1/29/2026,Bench Press,1,135,lbs,5,,,,,,,
1/29/2026,Bench Press,2,185,lbs,7,,,,,,,
1/29/2026,Bench Press,3,185,lbs,8,,,,,,,
1/29/2026,Bench Press,4,185,lbs,6,,,,,,,
1/29/2026,Tuck Inversion,1,,lbs,2,,,,,,,
1/29/2026,Ring row,1,,lbs,7,,,,,,,
1/29/2026,Ring row,2,,lbs,7,,,,,,,
1/29/2026,Ring row,3,,lbs,7,,,,,,,
1/29/2026,Exercise Ball Situp,1,,lbs,17,,,,,,,
1/29/2026,Exercise Ball Situp,2,,lbs,13,,,,,,,
1/29/2026,Alt Leg Lift,1,,lbs,10,,,,,,,
1/29/2026,Alt Leg Lift,2,,lbs,10,,,,,,,
1/29/2026,Rear delt,1,16,lbs,7,,,,,,,
1/29/2026,Rear delt,2,16,lbs,10,,,,,,,
1/29/2026,SA Landmine Press,1,25,lbs,10,,,,,,,
1/29/2026,SA Landmine Press,2,25,lbs,10,,,,,,,
1/29/2026,SA Landmine Press,3,25,lbs,10,,,,,,,
1/29/2026,Landmine Pull and Press,1,25,lbs,10,,,,,,,
1/29/2026,Landmine Pull and Press,2,25,lbs,11,,,,,,,
1/29/2026,Wide Grip Pull Up,1,,lbs,5,,,,,,,
1/29/2026,Wide Grip Pull Up,2,,lbs,5,,,,,,,
1/29/2026,Wide Grip Pull Up,3,,lbs,5,,,,,,,
1/30/2026,Face Pulls,1,66,lbs,10,,,,,,,
1/30/2026,KB Press,1,20,kg,7,,,,,,,
1/30/2026,KB Press,2,24,kg,7,,,,,,,
1/30/2026,KB Press,3,32,kg,3,,,,,,,
1/30/2026,KB Press,4,32,kg,3,,,,,,,
1/30/2026,KB Press,5,32,kg,3,,,,,,,
1/30/2026,Side Lying Press,1,20,lbs,7,,,,,,,
1/30/2026,Side Lying Press,2,24,lbs,7,,,,,,,
1/30/2026,Side Lying Press,3,24,lbs,13,,,,,,,
1/30/2026,Side Lying Press,4,24,lbs,17,,,,,,,
1/30/2026,TGU,1,44,kg,1,,,,,,,
1/30/2026,TGU,2,48,kg,1,,,,,,,
1/30/2026,TGU,3,48,kg,1,,,,,,,
1/30/2026,BB Reverse Curl,1,45,lbs,17,,,,,,bar only,
1/30/2026,BB Reverse Curl,2,55,lbs,13,,,,,,,
1/30/2026,BB Reverse Curl,3,60,lbs,13,,,,,,,
1/30/2026,Captains of Crush,1,0.5,lbs,7,,,,,,gripper #0.5,
1/30/2026,Captains of Crush,2,1,lbs,5,,,,,,gripper #1,
1/30/2026,Captains of Crush,3,1,lbs,5,,,,,,gripper #1,
1/30/2026,SA Tricep Extension,1,27,lbs,10,,,,,,,
1/30/2026,SA Tricep Extension,2,27,lbs,11,,,,,,,
1/30/2026,Dumbbell Curl,1,27,lbs,10,,,,,,ball bicep curl,
1/30/2026,Dumbbell Curl,2,27,lbs,11,,,,,,ball bicep curl,
1/30/2026,Neck Circuit,1,10,lbs,17,,,,,,,
1/30/2026,Neck Circuit,2,10,lbs,17,,,,,,,
1/30/2026,Bench Dip,1,,lbs,21,,,,,,,
1/30/2026,Bench Dip,2,,lbs,21,,,,,,,
1/30/2026,Assault Bike,1,,lbs,,,,,77,,10/20 intervals,
2/2/2026,Squat,1,165,lbs,5,,,,,,,
2/2/2026,Zercher Squat,1,95,lbs,13,,,,,,,
2/2/2026,Zercher Squat,2,95,lbs,17,,,,,,,
2/2/2026,Zercher Squat,3,95,lbs,17,,,,,,,
2/2/2026,Zercher Squat,4,95,lbs,10,,,,,,,
2/2/2026,Zercher Squat,5,95,lbs,10,,,,,,,
2/2/2026,Hamstring deadlift,1,50,lbs,10,,,,,,,
2/2/2026,Hamstring deadlift,2,50,lbs,10,,,,,,,
2/2/2026,Hamstring deadlift,3,50,lbs,10,,,,,,,
2/2/2026,KB Sidestep,1,20,lbs,10,,,,,,,
2/2/2026,KB Sidestep,2,20,lbs,10,,,,,,,
2/2/2026,KB Sidestep,3,20,lbs,10,,,,,,,
2/2/2026,Calf Raise,1,,lbs,17,,,,,,,
2/2/2026,Calf Raise,2,,lbs,17,,,,,,,
2/2/2026,Calf Raise,3,,lbs,17,,,,,,,
2/2/2026,Bulgarian Split Squat,1,15,lbs,10,,,,,,,
2/2/2026,Bulgarian Split Squat,2,15,lbs,10,,,,,,,
2/2/2026,Bulgarian Split Squat,3,15,lbs,10,,,,,,,
2/2/2026,Bulgarian Split Squat,4,15,lbs,10,,,,,,,
2/3/2026,Face Pulls,1,72,lbs,10,,,,,,,
2/3/2026,Dumbbell Row,1,50,lbs,10,,,,,,,
2/3/2026,Dumbbell Row,2,60,lbs,10,,,,,,,
2/3/2026,Dumbbell Row,3,60,lbs,11,,,,,,,
2/3/2026,KB Press,1,24,kg,10,,,,,,,
2/3/2026,KB Press,2,24,kg,11,,,,,,,
2/3/2026,Cable Row,1,49,lbs,10,,,,,,,
2/3/2026,Cable Row,2,49,lbs,11,,,,,,,
2/3/2026,EQ Bar Incline Bench,1,16,kg,7,,,,,,16kg KB each side,
2/3/2026,EQ Bar Incline Bench,2,31,lbs,11,,,,,,16kg KB + 15lb DB each side,
2/3/2026,EQ Bar Incline Bench,3,31,lbs,10,,,,,,16kg KB + 15lb DB each side,
2/3/2026,EQ Bar Incline Bench,4,16,kg,21,,,,,,16kg KB each side,
2/3/2026,Upright Row,1,65,lbs,17,,,,,,,
2/3/2026,Upright Row,2,65,lbs,21,,,,,,,
2/3/2026,Cable Fly,1,53,lbs,17,,,,,,,
2/3/2026,Cable Fly,2,53,lbs,13,,,,,,,
2/3/2026,Lateral Raise,1,10,lbs,11,,,,,,,
2/3/2026,Lateral Raise,2,10,lbs,10,,,,,,,
2/9/2026,Squat,1,135,lbs,3,,,,,,foot elevated,
2/9/2026,Squat,2,165,lbs,7,,,,,,foot elevated,
2/9/2026,Squat,3,185,lbs,7,,,,,,foot elevated,
2/9/2026,Squat,4,205,lbs,7,,,,,,foot elevated,
2/9/2026,Squat,5,205,lbs,7,,,,,,foot elevated,
2/9/2026,Squat,6,205,lbs,7,,,,,,foot elevated,
2/9/2026,Hamstring deadlift,1,60,lbs,10,,,,,,,
2/9/2026,Hamstring deadlift,2,60,lbs,10,,,,,,,
2/9/2026,Hamstring deadlift,3,60,lbs,10,,,,,,,
2/9/2026,Hamstring deadlift,4,60,lbs,10,,,,,,,
2/9/2026,Kettlebell Leg Extension,1,16,kg,10,,,,,,,
2/9/2026,Kettlebell Leg Extension,2,16,kg,10,,,,,,,
2/9/2026,Kettlebell Leg Extension,3,16,kg,17,,,,,,,
2/9/2026,Kettlebell Leg Extension,4,16,kg,21,,,,,,,
2/9/2026,Calf Raise,1,,lbs,17,,,,,,,
2/9/2026,Calf Raise,2,,lbs,13,,,,,,,
2/9/2026,Calf Raise,3,,lbs,21,,,,,,,
2/9/2026,Calf Raise,4,,lbs,21,,,,,,,
2/9/2026,Bulgarian Split Squat,1,,lbs,10,,,,,,,
2/9/2026,Bulgarian Split Squat,2,15,lbs,10,,,,,,,
2/9/2026,Bulgarian Split Squat,3,25,lbs,10,,,,,,,
2/9/2026,Bulgarian Split Squat,4,30,lbs,12,,,,,,,
2/9/2026,Adductor Bench,1,,lbs,21,,,,,,,
2/9/2026,Adductor Bench,2,,lbs,21,,,,,,,
2/10/2026,EQ Bar Incline Bench,1,16,kg,7,,,,,,16kg KB each side,
2/10/2026,EQ Bar Incline Bench,2,31,lbs,13,,,,,,16kg KB + 15lb DB each side,
2/10/2026,EQ Bar Incline Bench,3,31,lbs,13,,,,,,16kg KB + 15lb DB each side,
2/10/2026,Ring row,1,,lbs,10,,,,,,,
2/10/2026,Ring row,2,,lbs,10,,,,,,,
2/10/2026,Ring row,3,,lbs,11,,,,,,,
2/10/2026,Ring row,4,,lbs,11,,,,,,,
2/10/2026,Low to High Crossover,1,16,lbs,17,,,,,,,
2/10/2026,Low to High Crossover,2,16,lbs,17,,,,,,,
2/10/2026,Chinup,1,,lbs,7,,,,,,,
2/10/2026,Chinup,2,,lbs,7,,,,,,,
2/10/2026,Chinup,3,,lbs,7,,,,,,,
2/10/2026,KB Press,1,32,kg,17,,,,,,,
2/10/2026,KB Press,2,32,kg,17,,,,,,,
2/10/2026,Ab Wheel Rollout,1,,lbs,17,,,,,,,
2/10/2026,Ab Wheel Rollout,2,,lbs,21,,,,,,,
2/10/2026,SA Tricep Extension,1,16,lbs,21,,,,,,,
2/10/2026,SA Tricep Extension,2,22,lbs,17,,,,,,,
2/11/2026,Zercher Squat,1,75,lbs,10,,,,,,,
2/11/2026,Zercher Squat,2,95,lbs,10,,,,,,,
2/11/2026,Zercher Squat,3,115,lbs,10,,,,,,,
2/11/2026,Zercher Squat,4,115,lbs,10,,,,,,,
2/11/2026,Zercher Squat,5,115,lbs,10,,,,,,,
2/11/2026,Zercher Squat,6,115,lbs,10,,,,,,,
2/11/2026,Zercher Squat,7,115,lbs,10,,,,,,,
2/11/2026,Calf Raise,1,,lbs,21,,,,,,,
2/11/2026,Calf Raise,2,,lbs,21,,,,,,,
2/12/2026,TGU,1,36,kg,2,,,,,,,
2/12/2026,TGU,2,40,kg,2,,,,,,,
2/12/2026,TGU,3,44,kg,2,,,,,,,
2/12/2026,TGU,4,44,kg,2,,,,,,,
2/12/2026,TGU,5,44,kg,2,,,,,,,
2/12/2026,Rear delt,1,16,lbs,10,,,,,,,
2/12/2026,Rear delt,2,16,lbs,11,,,,,,,
2/12/2026,Overhead Press,1,75,lbs,7,,,,,,,
2/12/2026,Overhead Press,2,95,lbs,7,,,,,,,
2/12/2026,Overhead Press,3,115,lbs,6,,,,,,,
2/12/2026,Overhead Press,4,135,lbs,3,,,,,,,
2/12/2026,Overhead Press,5,135,lbs,3,,,,,,,
2/12/2026,Overhead Press,6,135,lbs,3,,,,,,,
2/12/2026,Tuck Inversion,1,,lbs,2,,,,,,,
2/12/2026,Tuck Inversion,2,,lbs,2,,,,,,,
2/12/2026,Tuck Inversion,3,,lbs,2,,,,,,,
2/12/2026,Neck Circuit,1,10,lbs,17,,,,,,,
2/14/2026,Deadlift,1,185,lbs,6,,,,,,,
2/14/2026,Deadlift,2,225,lbs,5,,,,,,,
2/14/2026,Deadlift,3,275,lbs,5,,,,,,,
2/14/2026,Deadlift,4,275,lbs,5,,,,,,,
2/14/2026,Deadlift,5,275,lbs,6,,,,,,,
2/14/2026,Deadlift,6,275,lbs,5,,,,,,,
2/14/2026,Fire Hydrant,1,,lbs,17,,,,,,,
2/14/2026,Fire Hydrant,2,,lbs,17,,,,,,,
2/14/2026,Fire Hydrant,3,,lbs,17,,,,,,,
2/14/2026,SL Deadlift,1,36,lbs,10,,,,,,,
2/14/2026,SL Deadlift,2,40,lbs,10,,,,,,,
2/14/2026,Chinup,1,,lbs,5,,,,,,"narrow, vest",
1 date exercise set weight weight_unit reps duration_seconds distance distance_unit calories rpe notes custom_metrics_json
2 1/2/2026 Face Pulls 1 68 lbs 10
3 1/2/2026 Rear delt 1 16 lbs 10
4 1/2/2026 EQ Bar Incline Bench 1 16 kg 13 16kg KB each side
5 1/2/2026 EQ Bar Incline Bench 2 21 lbs 10 16kg KB + 5lb each side
6 1/2/2026 EQ Bar Incline Bench 3 26 lbs 10 16kg KB + 10lb each side
7 1/2/2026 EQ Bar Incline Bench 4 26 lbs 12 16kg KB + 10lb each side
8 1/2/2026 EQ Bar Incline Bench 5 31 lbs 10 16kg KB + 15lb each side
9 1/2/2026 Chinup 1 lbs 10
10 1/2/2026 Chinup 2 lbs 7
11 1/2/2026 Chinup 3 lbs 7
12 1/2/2026 Chinup 4 lbs 5 slow negatives
13 1/2/2026 Chinup 5 lbs 5 slow negatives
14 1/2/2026 Alternating Step Cable Cross 1 44 lbs 16
15 1/2/2026 Alternating Step Cable Cross 2 44 lbs 16
16 1/2/2026 Alternating Step Cable Cross 3 44 lbs 16
17 1/2/2026 Tuck Inversion 1 lbs 2 vest
18 1/2/2026 Tuck Inversion 2 lbs 2 vest
19 1/2/2026 Ab Wheel Rollout 1 lbs 7 vest
20 1/2/2026 Ab Wheel Rollout 2 lbs 7 vest
21 1/2/2026 Ab Wheel Rollout 3 lbs 7 vest
22 1/2/2026 Ring Dip 1 lbs 3
23 1/2/2026 Ring Dip 2 lbs 3
24 1/2/2026 Ring Dip 3 lbs 3
25 1/2/2026 Overhead Tricep Extension 1 44 lbs 13
26 1/2/2026 Overhead Tricep Extension 2 44 lbs 13
27 1/2/2026 Overhead Tricep Extension 3 44 lbs 13
28 1/2/2026 Barbell Curl 1 45 lbs 17 bar only
29 1/2/2026 Barbell Curl 2 45 lbs 17 bar only
30 1/2/2026 Barbell Curl 3 45 lbs 17 bar only
31 1/2/2026 Band Pushup 1 lbs 7 black/gray band
32 1/2/2026 Band Pushup 2 lbs 7 black/gray band
33 1/2/2026 Band Pushup 3 lbs 7 black/gray band
34 1/3/2026 TGU 1 36 kg 1
35 1/3/2026 TGU 2 40 kg 1
36 1/3/2026 TGU 3 44 kg 1
37 1/3/2026 TGU 4 48 kg 1
38 1/3/2026 TGU 5 48 kg 1
39 1/3/2026 TGU 6 48 kg 1
40 1/3/2026 Deadlift 1 185 lbs 5
41 1/3/2026 Deadlift 2 225 lbs 5
42 1/3/2026 Deadlift 3 275 lbs 5
43 1/3/2026 Deadlift 4 275 lbs 5
44 1/3/2026 Deadlift 5 275 lbs 5
45 1/3/2026 Deadlift 6 275 lbs 6
46 1/3/2026 Assault Bike 1 lbs 72 10/20 intervals
47 1/4/2026 Assault Bike 1 lbs 300 target zone 4 cardio
48 1/4/2026 SkiErg 1 lbs 300 target zone 4 cardio
49 1/4/2026 Jump Rope 1 lbs 300 target zone 4 cardio
50 1/4/2026 KB Swing 1 24 kg 5 single arm every 60 sec / arm every 45 sec - 5 min
51 1/4/2026 Neck Circuit 1 10 lbs 17
52 1/6/2026 Squat 1 135 lbs 7
53 1/6/2026 Squat 2 165 lbs 7
54 1/6/2026 Squat 3 165 lbs 7 chains - 207 total
55 1/6/2026 Squat 4 165 lbs 7 chains - 207 total
56 1/6/2026 Squat 5 165 lbs 7 chains - 207 total
57 1/6/2026 Bulgarian Split Squat 1 30 lbs 10
58 1/6/2026 Bulgarian Split Squat 2 30 lbs 10
59 1/6/2026 Bulgarian Split Squat 3 30 lbs 10
60 1/6/2026 Hip Flexor 1 12 kg 10
61 1/6/2026 Hip Flexor 2 12 kg 10
62 1/6/2026 Hip Flexor 3 12 kg 10
63 1/6/2026 Kettlebell Leg Extension 1 12 kg 10
64 1/6/2026 Kettlebell Leg Extension 2 12 kg 10
65 1/6/2026 Kettlebell Leg Extension 3 12 kg 10
66 1/6/2026 Adductor Bench 1 lbs 10
67 1/6/2026 Adductor Bench 2 lbs 10
68 1/6/2026 Adductor Bench 3 lbs 10
69 1/6/2026 Glute Bridge 1 135 lbs 13
70 1/6/2026 Glute Bridge 2 135 lbs 13
71 1/6/2026 Glute Bridge 3 135 lbs 13
72 1/7/2026 Face Pulls 1 68 lbs 10
73 1/7/2026 Rear delt 1 16 lbs 8
74 1/7/2026 EQ Bar Incline Bench 1 16 kg 10 16kg KB each side
75 1/7/2026 EQ Bar Incline Bench 2 26 lbs 10 16kg KB + 10lb each side
76 1/7/2026 EQ Bar Incline Bench 3 26 lbs 10 16kg KB + 10lb each side
77 1/7/2026 EQ Bar Incline Bench 4 31 lbs 13 16kg KB + 15lb each side
78 1/7/2026 EQ Bar Incline Bench 5 31 lbs 9 16kg KB + 15lb each side
79 1/7/2026 EQ Bar Incline Bench 6 31 lbs 8 16kg KB + 15lb each side
80 1/7/2026 Chinup 1 lbs 8
81 1/7/2026 Chinup 2 lbs 6
82 1/7/2026 Chinup 3 lbs 6
83 1/7/2026 Chinup 4 lbs 6
84 1/7/2026 Alternating Step Cable Cross 1 44 lbs 16
85 1/7/2026 Alternating Step Cable Cross 2 44 lbs 16
86 1/7/2026 Alternating Step Cable Cross 3 44 lbs 16
87 1/7/2026 Tuck Inversion 1 lbs 2 vest
88 1/7/2026 Tuck Inversion 2 lbs 2 vest
89 1/7/2026 Ab Wheel Rollout 1 lbs 7 vest
90 1/7/2026 Ab Wheel Rollout 2 lbs 7 vest
91 1/7/2026 Ab Wheel Rollout 3 lbs 7 vest
92 1/7/2026 Ring Dip 1 lbs 7
93 1/7/2026 Ring Dip 2 lbs 7
94 1/10/2026 Deadlift 1 185 lbs 5
95 1/10/2026 Deadlift 2 225 lbs 5
96 1/10/2026 Deadlift 3 275 lbs 5
97 1/10/2026 Deadlift 4 295 lbs 4
98 1/10/2026 Deadlift 5 295 lbs 5
99 1/10/2026 TGU 1 44 kg 1
100 1/10/2026 TGU 2 48 kg 1
101 1/10/2026 TGU 3 48 kg 1
102 1/10/2026 Ring row 1 lbs 10
103 1/10/2026 Ring row 2 lbs 7
104 1/10/2026 Ring row 3 lbs 6
105 1/10/2026 Glute Bridge 1 135 lbs 13
106 1/10/2026 Glute Bridge 2 135 lbs 13
107 1/10/2026 Glute Bridge 3 135 lbs 13
108 1/10/2026 Fire Hydrant 1 lbs 17 blue band
109 1/10/2026 Fire Hydrant 2 lbs 13 blue band
110 1/10/2026 Fire Hydrant 3 lbs 17 blue band
111 1/10/2026 Glute ham developer 1 lbs 7 pad on floor, gray band
112 1/10/2026 Glute ham developer 2 lbs 4 pad on floor, gray band
113 1/10/2026 Glute ham developer 3 lbs 7 pad on floor, gray band
114 1/10/2026 Neck Circuit 1 10 lbs 17
115 1/12/2026 Barbell step back lunge 1 75 lbs 10
116 1/12/2026 Barbell step back lunge 2 95 lbs 10
117 1/12/2026 Barbell step back lunge 3 115 lbs 10
118 1/12/2026 Bench Step Up 1 16 kg 10 quad band black
119 1/12/2026 Bench Step Up 2 16 kg 10 quad band black
120 1/12/2026 Kettlebell Leg Extension 1 12 kg 17
121 1/12/2026 Kettlebell Leg Extension 2 12 kg 17
122 1/12/2026 Kettlebell Leg Extension 3 12 kg 23
123 1/13/2026 Shoulder warmup 1 15 lbs warmup
124 1/13/2026 Mace warmup 1 lbs warmup
125 1/13/2026 Face Pulls 1 72 lbs 10
126 1/13/2026 Rear delt 1 16 lbs 7
127 1/13/2026 Rear delt 2 16 lbs 10
128 1/13/2026 Rear delt 3 16 lbs 7
129 1/13/2026 Dumbbell Row 1 55 lbs 10
130 1/13/2026 Dumbbell Row 2 55 lbs 10
131 1/13/2026 Dumbbell Row 3 55 lbs 10
132 1/13/2026 EQ Bar Incline Bench 1 16 kg 10 16kg KB each side
133 1/13/2026 EQ Bar Incline Bench 2 31 lbs 14 16kg + 15 lbs
134 1/13/2026 EQ Bar Incline Bench 3 31 lbs 15 16kg + 15 lbs
135 1/13/2026 EQ Bar Incline Bench 4 31 lbs 10 slow
136 1/13/2026 Chinup 1 lbs 10
137 1/13/2026 Chinup 2 lbs 6
138 1/13/2026 Chinup 3 lbs 6
139 1/13/2026 Exercise Ball Situp 1 lbs 17
140 1/13/2026 Exercise Ball Situp 2 lbs 13
141 1/13/2026 Exercise Ball Situp 3 lbs 17
142 1/13/2026 Alternating Step Cable Cross 1 44 lbs 16
143 1/13/2026 Alternating Step Cable Cross 2 44 lbs 16
144 1/13/2026 Alternating Step Cable Cross 3 44 lbs 10
145 1/13/2026 Tuck Inversion 1 lbs 2 vest
146 1/13/2026 Tuck Inversion 2 lbs 2 vest
147 1/13/2026 Tuck Inversion 3 lbs 2 vest
148 1/13/2026 Ab Wheel Rollout 1 lbs 10 vest
149 1/13/2026 Ab Wheel Rollout 2 lbs 10 vest
150 1/13/2026 Ring Dip 1 lbs 5
151 1/13/2026 Ring Dip 2 lbs 5
152 1/13/2026 Tricep Pushdown 1 44 lbs 10 rope
153 1/13/2026 Tricep Pushdown 2 44 lbs 10 rope
154 1/14/2026 Hex Bar Deadlift 1 200 lbs 5
155 1/14/2026 Hex Bar Deadlift 2 240 lbs 5
156 1/14/2026 Hex Bar Deadlift 3 290 lbs 5
157 1/14/2026 Hex Bar Deadlift 4 320 lbs 5
158 1/14/2026 Hex Bar Deadlift 5 340 lbs 3
159 1/14/2026 Hex Bar Deadlift 6 355 lbs 3 best ever
160 1/14/2026 Glute Bridge 1 135 lbs 13
161 1/14/2026 Glute Bridge 2 135 lbs 13
162 1/14/2026 Glute Bridge 3 135 lbs 13
163 1/14/2026 SL Quad Step Down 1 lbs 10 on bench
164 1/14/2026 SL Quad Step Down 2 15 lbs 10 on bench
165 1/14/2026 SL Quad Step Down 3 16 kg 10 on bench, good weight
166 1/14/2026 Fire Hydrant 1 lbs 17 black band
167 1/14/2026 Fire Hydrant 2 lbs 17 black band
168 1/14/2026 Assault Bike 1 lbs 69 10/20 intervals
169 1/16/2026 Shoulder warmup 1 lbs warmup
170 1/16/2026 Face Pulls 1 68 lbs 10
171 1/16/2026 Mace warmup 1 lbs warmup
172 1/16/2026 Rear delt 1 16 lbs 10
173 1/16/2026 TGU 1 44 kg 1
174 1/16/2026 TGU 2 48 kg 1
175 1/16/2026 TGU 3 48 kg 1
176 1/16/2026 TGU 4 48 kg 1
177 1/16/2026 TGU 5 48 kg 1
178 1/16/2026 TGU 6 48 kg 1
179 1/16/2026 Overhead Press 1 75 lbs 7
180 1/16/2026 Overhead Press 2 95 lbs 7
181 1/16/2026 Overhead Press 3 115 lbs 5
182 1/16/2026 Overhead Press 4 115 lbs 5
183 1/16/2026 Overhead Press 5 115 lbs 5
184 1/16/2026 Chinup 1 lbs 4 vest
185 1/16/2026 Chinup 2 lbs 5 vest
186 1/16/2026 Chinup 3 lbs 5 vest
187 1/16/2026 Chinup 4 lbs 5 vest
188 1/16/2026 Arnold Press 1 40 lbs 8
189 1/16/2026 Arnold Press 2 40 lbs 10
190 1/16/2026 Arnold Press 3 40 lbs 10
191 1/16/2026 Barbell Curl 1 45 lbs 10 bar only
192 1/16/2026 Barbell Curl 2 65 lbs 10
193 1/16/2026 Barbell Curl 3 65 lbs 10
194 1/16/2026 Barbell Row 1 95 lbs 10
195 1/16/2026 Barbell Row 2 95 lbs 14
196 1/16/2026 Barbell Row 3 95 lbs 13
197 1/16/2026 Waiter Carry 1 16 kg 20 20 steps
198 1/16/2026 Farmer's Carry 1 44 kg 40 40 steps, repeat circuit
199 1/16/2026 Captains of Crush 1 0.5 lbs 7 gripper #0.5
200 1/16/2026 Captains of Crush 2 1 lbs 3 gripper #1
201 1/16/2026 Captains of Crush 3 1 lbs 5 gripper #1
202 1/16/2026 Captains of Crush 4 1 lbs 6 gripper #1
203 1/16/2026 Neck Circuit 1 10 lbs 17
204 1/16/2026 Neck Circuit 2 10 lbs 17
205 1/18/2026 Squat 1 135 lbs 7
206 1/18/2026 Squat 2 155 lbs 5
207 1/18/2026 Squat 3 170 lbs 5
208 1/18/2026 Squat 4 185 lbs 11 work set
209 1/18/2026 Squat 5 185 lbs 10 work set
210 1/18/2026 DB Step Back Lunge 1 40 lbs 7
211 1/18/2026 DB Step Back Lunge 2 60 lbs 7
212 1/18/2026 DB Step Back Lunge 3 80 lbs 7 work set
213 1/18/2026 DB Step Back Lunge 4 80 lbs 10 work set - max
214 1/18/2026 Bench Press 1 135 lbs 7
215 1/18/2026 Bench Press 2 165 lbs 5
216 1/18/2026 Bench Press 3 185 lbs 9 work set
217 1/18/2026 Bench Press 4 185 lbs 8 work set
218 1/18/2026 Lat Pulldown 1 60 lbs 7 single arm
219 1/18/2026 Lat Pulldown 2 71 lbs 10 work set, single arm
220 1/18/2026 Lat Pulldown 3 71 lbs 11 work set, single arm
221 1/18/2026 Ab Wheel Rollout 1 lbs 11 vest
222 1/18/2026 Ab Wheel Rollout 2 lbs 10 vest
223 1/18/2026 Calf Raise 1 lbs 17 vest
224 1/18/2026 Calf Raise 2 lbs 13 vest
225 1/18/2026 KB Press 1 20 kg 5
226 1/18/2026 KB Press 2 24 kg 11 work set
227 1/18/2026 KB Press 3 24 kg 10 work set
228 1/19/2026 Kettlebell Leg Extension 1 12 kg 13
229 1/19/2026 Kettlebell Leg Extension 2 12 kg 17
230 1/19/2026 Kettlebell Leg Extension 3 12 kg 17
231 1/19/2026 Kettlebell Leg Extension 4 12 kg 21 ankle weight
232 1/19/2026 Kettlebell Leg Extension 5 12 kg 21 ankle weight
233 1/19/2026 Knee Raise 1 lbs 21 ankle weight
234 1/19/2026 Knee Raise 2 lbs 17 ankle weight
235 1/19/2026 Hip Flexor 1 12 lbs 10
236 1/19/2026 Hip Flexor 2 12 lbs 10
237 1/19/2026 Hip Flexor 3 12 lbs 10
238 1/19/2026 Adductor Bench 1 lbs 17
239 1/19/2026 Adductor Bench 2 lbs 21
240 1/19/2026 Glute ham developer 1 lbs 7
241 1/19/2026 Glute ham developer 2 lbs 7
242 1/19/2026 Glute ham developer 3 lbs 7
243 1/20/2026 Ab KB Drag 1 12 lbs 10
244 1/20/2026 Ab KB Drag 2 12 lbs 11
245 1/20/2026 Dips (Chest) 1 lbs 13
246 1/20/2026 Dips (Chest) 2 lbs 17
247 1/20/2026 SA Tricep Extension 1 22 lbs 11
248 1/20/2026 SA Tricep Extension 2 22 lbs 10
249 1/20/2026 Captains of Crush 1 0.5 lbs 7 gripper #0.5
250 1/20/2026 Captains of Crush 2 1 lbs 6 gripper #1
251 1/20/2026 Captains of Crush 3 1 lbs 7 gripper #1
252 1/20/2026 Captains of Crush 4 1.5 lbs 3 gripper #1.5
253 1/20/2026 Captains of Crush 5 1.5 lbs 2 gripper #1.5
254 1/20/2026 Captains of Crush 6 1 lbs 10 gripper #1
255 1/20/2026 Ab Mat 1 lbs 17
256 1/20/2026 Ab Mat 2 lbs 13
257 1/20/2026 SA DB Curl 1 30 lbs 11
258 1/20/2026 SA DB Curl 2 30 lbs 10
259 1/20/2026 EQ Military Press 1 16 kg 7 16kg KB each side
260 1/20/2026 EQ Military Press 2 16 kg 10 16kg KB each side
261 1/20/2026 Tuck Inversion 1 lbs 2
262 1/20/2026 Tuck Inversion 2 lbs 2
263 1/20/2026 Rear delt 1 16 lbs 10
264 1/20/2026 Rear delt 2 16 lbs 7
265 1/20/2026 Ab Scissors 1 lbs
266 1/20/2026 Ab Scissors 2 lbs
267 1/20/2026 Chinup 1 lbs 2 negatives
268 1/20/2026 Chinup 2 lbs 2 negatives
269 1/22/2026 TGU 1 44 kg 1
270 1/22/2026 TGU 2 44 kg 1
271 1/22/2026 TGU 3 44 kg 1
272 1/22/2026 EQ Military Press 1 16 kg 10 16kg KB each side
273 1/22/2026 EQ Military Press 2 16 kg 10 16kg KB each side
274 1/22/2026 EQ Military Press 3 16 kg 10 16kg KB each side
275 1/22/2026 Barbell Row 1 95 lbs 21
276 1/22/2026 Barbell Row 2 95 lbs 17
277 1/22/2026 Windmill 1 12 kg 6
278 1/22/2026 Half Kneel Windmill 1 16 kg 7
279 1/22/2026 Half Kneel Windmill 2 16 kg 8
280 1/22/2026 Side Lying Press 1 20 lbs 21
281 1/22/2026 Side Lying Press 2 24 lbs 21
282 1/22/2026 Exercise Ball Situp 1 lbs 17
283 1/22/2026 Exercise Ball Situp 2 lbs 17
284 1/22/2026 Exercise Ball Situp 3 lbs 17
285 1/22/2026 Neck Circuit 1 10 lbs 17
286 1/22/2026 Neck Circuit 2 10 lbs 17
287 1/22/2026 Neck Circuit 3 10 lbs 17
288 1/27/2026 Squat 1 135 lbs 7
289 1/27/2026 Squat 2 185 lbs 7
290 1/27/2026 Squat 3 185 lbs 5
291 1/27/2026 Squat 4 225 lbs 5
292 1/27/2026 Squat 5 225 lbs 5
293 1/27/2026 Squat 6 225 lbs 5
294 1/27/2026 Squat 7 225 lbs 6
295 1/27/2026 Bulgarian Split Squat 1 15 lbs 10
296 1/27/2026 Bulgarian Split Squat 2 15 lbs 10
297 1/27/2026 Bulgarian Split Squat 3 15 lbs 10
298 1/27/2026 Kettlebell Leg Extension 1 16 kg 17
299 1/27/2026 Kettlebell Leg Extension 2 16 kg 21
300 1/27/2026 Calf Raise 1 lbs 17
301 1/27/2026 Calf Raise 2 lbs 21
302 1/29/2026 Hamstring deadlift 1 50 lbs 10
303 1/29/2026 Hex Bar Deadlift 1 240 lbs 5
304 1/29/2026 Hex Bar Deadlift 2 295 lbs 7
305 1/29/2026 Hex Bar Deadlift 3 295 lbs 7
306 1/29/2026 Hex Bar Deadlift 4 295 lbs 7
307 1/29/2026 Bench Press 1 135 lbs 5
308 1/29/2026 Bench Press 2 185 lbs 7
309 1/29/2026 Bench Press 3 185 lbs 8
310 1/29/2026 Bench Press 4 185 lbs 6
311 1/29/2026 Tuck Inversion 1 lbs 2
312 1/29/2026 Ring row 1 lbs 7
313 1/29/2026 Ring row 2 lbs 7
314 1/29/2026 Ring row 3 lbs 7
315 1/29/2026 Exercise Ball Situp 1 lbs 17
316 1/29/2026 Exercise Ball Situp 2 lbs 13
317 1/29/2026 Alt Leg Lift 1 lbs 10
318 1/29/2026 Alt Leg Lift 2 lbs 10
319 1/29/2026 Rear delt 1 16 lbs 7
320 1/29/2026 Rear delt 2 16 lbs 10
321 1/29/2026 SA Landmine Press 1 25 lbs 10
322 1/29/2026 SA Landmine Press 2 25 lbs 10
323 1/29/2026 SA Landmine Press 3 25 lbs 10
324 1/29/2026 Landmine Pull and Press 1 25 lbs 10
325 1/29/2026 Landmine Pull and Press 2 25 lbs 11
326 1/29/2026 Wide Grip Pull Up 1 lbs 5
327 1/29/2026 Wide Grip Pull Up 2 lbs 5
328 1/29/2026 Wide Grip Pull Up 3 lbs 5
329 1/30/2026 Face Pulls 1 66 lbs 10
330 1/30/2026 KB Press 1 20 kg 7
331 1/30/2026 KB Press 2 24 kg 7
332 1/30/2026 KB Press 3 32 kg 3
333 1/30/2026 KB Press 4 32 kg 3
334 1/30/2026 KB Press 5 32 kg 3
335 1/30/2026 Side Lying Press 1 20 lbs 7
336 1/30/2026 Side Lying Press 2 24 lbs 7
337 1/30/2026 Side Lying Press 3 24 lbs 13
338 1/30/2026 Side Lying Press 4 24 lbs 17
339 1/30/2026 TGU 1 44 kg 1
340 1/30/2026 TGU 2 48 kg 1
341 1/30/2026 TGU 3 48 kg 1
342 1/30/2026 BB Reverse Curl 1 45 lbs 17 bar only
343 1/30/2026 BB Reverse Curl 2 55 lbs 13
344 1/30/2026 BB Reverse Curl 3 60 lbs 13
345 1/30/2026 Captains of Crush 1 0.5 lbs 7 gripper #0.5
346 1/30/2026 Captains of Crush 2 1 lbs 5 gripper #1
347 1/30/2026 Captains of Crush 3 1 lbs 5 gripper #1
348 1/30/2026 SA Tricep Extension 1 27 lbs 10
349 1/30/2026 SA Tricep Extension 2 27 lbs 11
350 1/30/2026 Dumbbell Curl 1 27 lbs 10 ball bicep curl
351 1/30/2026 Dumbbell Curl 2 27 lbs 11 ball bicep curl
352 1/30/2026 Neck Circuit 1 10 lbs 17
353 1/30/2026 Neck Circuit 2 10 lbs 17
354 1/30/2026 Bench Dip 1 lbs 21
355 1/30/2026 Bench Dip 2 lbs 21
356 1/30/2026 Assault Bike 1 lbs 77 10/20 intervals
357 2/2/2026 Squat 1 165 lbs 5
358 2/2/2026 Zercher Squat 1 95 lbs 13
359 2/2/2026 Zercher Squat 2 95 lbs 17
360 2/2/2026 Zercher Squat 3 95 lbs 17
361 2/2/2026 Zercher Squat 4 95 lbs 10
362 2/2/2026 Zercher Squat 5 95 lbs 10
363 2/2/2026 Hamstring deadlift 1 50 lbs 10
364 2/2/2026 Hamstring deadlift 2 50 lbs 10
365 2/2/2026 Hamstring deadlift 3 50 lbs 10
366 2/2/2026 KB Sidestep 1 20 lbs 10
367 2/2/2026 KB Sidestep 2 20 lbs 10
368 2/2/2026 KB Sidestep 3 20 lbs 10
369 2/2/2026 Calf Raise 1 lbs 17
370 2/2/2026 Calf Raise 2 lbs 17
371 2/2/2026 Calf Raise 3 lbs 17
372 2/2/2026 Bulgarian Split Squat 1 15 lbs 10
373 2/2/2026 Bulgarian Split Squat 2 15 lbs 10
374 2/2/2026 Bulgarian Split Squat 3 15 lbs 10
375 2/2/2026 Bulgarian Split Squat 4 15 lbs 10
376 2/3/2026 Face Pulls 1 72 lbs 10
377 2/3/2026 Dumbbell Row 1 50 lbs 10
378 2/3/2026 Dumbbell Row 2 60 lbs 10
379 2/3/2026 Dumbbell Row 3 60 lbs 11
380 2/3/2026 KB Press 1 24 kg 10
381 2/3/2026 KB Press 2 24 kg 11
382 2/3/2026 Cable Row 1 49 lbs 10
383 2/3/2026 Cable Row 2 49 lbs 11
384 2/3/2026 EQ Bar Incline Bench 1 16 kg 7 16kg KB each side
385 2/3/2026 EQ Bar Incline Bench 2 31 lbs 11 16kg KB + 15lb DB each side
386 2/3/2026 EQ Bar Incline Bench 3 31 lbs 10 16kg KB + 15lb DB each side
387 2/3/2026 EQ Bar Incline Bench 4 16 kg 21 16kg KB each side
388 2/3/2026 Upright Row 1 65 lbs 17
389 2/3/2026 Upright Row 2 65 lbs 21
390 2/3/2026 Cable Fly 1 53 lbs 17
391 2/3/2026 Cable Fly 2 53 lbs 13
392 2/3/2026 Lateral Raise 1 10 lbs 11
393 2/3/2026 Lateral Raise 2 10 lbs 10
394 2/9/2026 Squat 1 135 lbs 3 foot elevated
395 2/9/2026 Squat 2 165 lbs 7 foot elevated
396 2/9/2026 Squat 3 185 lbs 7 foot elevated
397 2/9/2026 Squat 4 205 lbs 7 foot elevated
398 2/9/2026 Squat 5 205 lbs 7 foot elevated
399 2/9/2026 Squat 6 205 lbs 7 foot elevated
400 2/9/2026 Hamstring deadlift 1 60 lbs 10
401 2/9/2026 Hamstring deadlift 2 60 lbs 10
402 2/9/2026 Hamstring deadlift 3 60 lbs 10
403 2/9/2026 Hamstring deadlift 4 60 lbs 10
404 2/9/2026 Kettlebell Leg Extension 1 16 kg 10
405 2/9/2026 Kettlebell Leg Extension 2 16 kg 10
406 2/9/2026 Kettlebell Leg Extension 3 16 kg 17
407 2/9/2026 Kettlebell Leg Extension 4 16 kg 21
408 2/9/2026 Calf Raise 1 lbs 17
409 2/9/2026 Calf Raise 2 lbs 13
410 2/9/2026 Calf Raise 3 lbs 21
411 2/9/2026 Calf Raise 4 lbs 21
412 2/9/2026 Bulgarian Split Squat 1 lbs 10
413 2/9/2026 Bulgarian Split Squat 2 15 lbs 10
414 2/9/2026 Bulgarian Split Squat 3 25 lbs 10
415 2/9/2026 Bulgarian Split Squat 4 30 lbs 12
416 2/9/2026 Adductor Bench 1 lbs 21
417 2/9/2026 Adductor Bench 2 lbs 21
418 2/10/2026 EQ Bar Incline Bench 1 16 kg 7 16kg KB each side
419 2/10/2026 EQ Bar Incline Bench 2 31 lbs 13 16kg KB + 15lb DB each side
420 2/10/2026 EQ Bar Incline Bench 3 31 lbs 13 16kg KB + 15lb DB each side
421 2/10/2026 Ring row 1 lbs 10
422 2/10/2026 Ring row 2 lbs 10
423 2/10/2026 Ring row 3 lbs 11
424 2/10/2026 Ring row 4 lbs 11
425 2/10/2026 Low to High Crossover 1 16 lbs 17
426 2/10/2026 Low to High Crossover 2 16 lbs 17
427 2/10/2026 Chinup 1 lbs 7
428 2/10/2026 Chinup 2 lbs 7
429 2/10/2026 Chinup 3 lbs 7
430 2/10/2026 KB Press 1 32 kg 17
431 2/10/2026 KB Press 2 32 kg 17
432 2/10/2026 Ab Wheel Rollout 1 lbs 17
433 2/10/2026 Ab Wheel Rollout 2 lbs 21
434 2/10/2026 SA Tricep Extension 1 16 lbs 21
435 2/10/2026 SA Tricep Extension 2 22 lbs 17
436 2/11/2026 Zercher Squat 1 75 lbs 10
437 2/11/2026 Zercher Squat 2 95 lbs 10
438 2/11/2026 Zercher Squat 3 115 lbs 10
439 2/11/2026 Zercher Squat 4 115 lbs 10
440 2/11/2026 Zercher Squat 5 115 lbs 10
441 2/11/2026 Zercher Squat 6 115 lbs 10
442 2/11/2026 Zercher Squat 7 115 lbs 10
443 2/11/2026 Calf Raise 1 lbs 21
444 2/11/2026 Calf Raise 2 lbs 21
445 2/12/2026 TGU 1 36 kg 2
446 2/12/2026 TGU 2 40 kg 2
447 2/12/2026 TGU 3 44 kg 2
448 2/12/2026 TGU 4 44 kg 2
449 2/12/2026 TGU 5 44 kg 2
450 2/12/2026 Rear delt 1 16 lbs 10
451 2/12/2026 Rear delt 2 16 lbs 11
452 2/12/2026 Overhead Press 1 75 lbs 7
453 2/12/2026 Overhead Press 2 95 lbs 7
454 2/12/2026 Overhead Press 3 115 lbs 6
455 2/12/2026 Overhead Press 4 135 lbs 3
456 2/12/2026 Overhead Press 5 135 lbs 3
457 2/12/2026 Overhead Press 6 135 lbs 3
458 2/12/2026 Tuck Inversion 1 lbs 2
459 2/12/2026 Tuck Inversion 2 lbs 2
460 2/12/2026 Tuck Inversion 3 lbs 2
461 2/12/2026 Neck Circuit 1 10 lbs 17
462 2/14/2026 Deadlift 1 185 lbs 6
463 2/14/2026 Deadlift 2 225 lbs 5
464 2/14/2026 Deadlift 3 275 lbs 5
465 2/14/2026 Deadlift 4 275 lbs 5
466 2/14/2026 Deadlift 5 275 lbs 6
467 2/14/2026 Deadlift 6 275 lbs 5
468 2/14/2026 Fire Hydrant 1 lbs 17
469 2/14/2026 Fire Hydrant 2 lbs 17
470 2/14/2026 Fire Hydrant 3 lbs 17
471 2/14/2026 SL Deadlift 1 36 lbs 10
472 2/14/2026 SL Deadlift 2 40 lbs 10
473 2/14/2026 Chinup 1 lbs 5 narrow, vest
+30
View File
@@ -0,0 +1,30 @@
import path from "path";
import { existsSync } from "fs";
export function resolveDatabasePath(): string {
const dbUrl = process.env.DATABASE_URL || "file:./data/app.db";
if (!dbUrl.startsWith("file:")) {
return path.resolve(process.cwd(), "prisma", "data", "app.db");
}
const rawPath = dbUrl.slice("file:".length);
if (path.isAbsolute(rawPath)) {
return rawPath;
}
const normalized = rawPath.replace(/^\.\//, "");
const directPath = path.resolve(process.cwd(), normalized);
if (existsSync(directPath)) {
return directPath;
}
const prismaPath = path.resolve(process.cwd(), "prisma", normalized);
return prismaPath;
}
export function getTimestampFileSuffix(now: Date = new Date()): string {
const iso = now.toISOString();
return iso.replace(/[:.]/g, "-");
}
@@ -62,6 +62,7 @@ export async function getExerciseHistory(
exerciseId, exerciseId,
workout: { workout: {
userId, userId,
deletedAt: null,
}, },
}, },
orderBy: { orderBy: {
@@ -82,6 +83,7 @@ export async function getPersonalBest(
exerciseId, exerciseId,
workout: { workout: {
userId, userId,
deletedAt: null,
}, },
}, },
orderBy: [ orderBy: [
@@ -20,6 +20,7 @@ export async function getWeeklyWorkoutCount(userId: string): Promise<number> {
const count = await prisma.workout.count({ const count = await prisma.workout.count({
where: { where: {
userId, userId,
deletedAt: null,
date: { date: {
gte: mondayStart, gte: mondayStart,
lte: sundayEnd, lte: sundayEnd,
@@ -35,7 +36,7 @@ export async function getWeeklyWorkoutCount(userId: string): Promise<number> {
*/ */
export async function getTotalWorkoutCount(userId: string): Promise<number> { export async function getTotalWorkoutCount(userId: string): Promise<number> {
return prisma.workout.count({ return prisma.workout.count({
where: { userId }, where: { userId, deletedAt: null },
}); });
} }
@@ -53,6 +54,7 @@ export async function getMonthlyWorkoutCount(userId: string): Promise<number> {
return prisma.workout.count({ return prisma.workout.count({
where: { where: {
userId, userId,
deletedAt: null,
date: { date: {
gte: monthStart, gte: monthStart,
lte: monthEnd, lte: monthEnd,
@@ -75,6 +77,7 @@ export async function getYearlyWorkoutCount(userId: string): Promise<number> {
return prisma.workout.count({ return prisma.workout.count({
where: { where: {
userId, userId,
deletedAt: null,
date: { date: {
gte: yearStart, gte: yearStart,
lte: yearEnd, lte: yearEnd,
@@ -88,7 +91,7 @@ export async function getYearlyWorkoutCount(userId: string): Promise<number> {
*/ */
export async function getCurrentStreak(userId: string): Promise<number> { export async function getCurrentStreak(userId: string): Promise<number> {
const workouts = await prisma.workout.findMany({ const workouts = await prisma.workout.findMany({
where: { userId }, where: { userId, deletedAt: null },
select: { date: true }, select: { date: true },
orderBy: { date: "desc" }, orderBy: { date: "desc" },
}); });
@@ -139,6 +142,7 @@ export async function getWeeklyVolume(userId: string): Promise<number> {
where: { where: {
workout: { workout: {
userId, userId,
deletedAt: null,
date: { date: {
gte: mondayStart, gte: mondayStart,
lte: sundayEnd, lte: sundayEnd,
@@ -176,7 +180,7 @@ export async function getDashboardStats(userId: string): Promise<DashboardStats>
getCurrentStreak(userId), getCurrentStreak(userId),
getWeeklyVolume(userId), getWeeklyVolume(userId),
prisma.workout.findMany({ prisma.workout.findMany({
where: { userId }, where: { userId, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { include: {
@@ -20,6 +20,7 @@ export async function getWorkouts(
const where: any = { const where: any = {
userId, userId,
deletedAt: null,
}; };
if (query) { if (query) {
@@ -67,8 +68,8 @@ export async function getWorkouts(
* Get a single workout by ID with all its sets * Get a single workout by ID with all its sets
*/ */
export async function getWorkoutById(id: string) { export async function getWorkoutById(id: string) {
return prisma.workout.findUnique({ return prisma.workout.findFirst({
where: { id }, where: { id, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { include: {
@@ -107,12 +108,9 @@ export async function createWorkout(data: {
* Delete a workout and all its associated sets * Delete a workout and all its associated sets
*/ */
export async function deleteWorkout(id: string): Promise<void> { export async function deleteWorkout(id: string): Promise<void> {
await prisma.setLog.deleteMany({ await prisma.workout.update({
where: { workoutId: id },
});
await prisma.workout.delete({
where: { id }, where: { id },
data: { deletedAt: new Date() },
}); });
} }
@@ -124,7 +122,7 @@ export async function getRecentWorkouts(
limit: number = 10 limit: number = 10
) { ) {
return prisma.workout.findMany({ return prisma.workout.findMany({
where: { userId }, where: { userId, deletedAt: null },
include: { include: {
setLogs: { setLogs: {
include: { include: {
+132
View File
@@ -0,0 +1,132 @@
import { Exercise } from "@prisma/client";
export type Option = {
value: string;
label: string;
};
export const BASE_EQUIPMENT_OPTIONS: Option[] = [
{ value: "barbell", label: "Barbell" },
{ value: "dumbbell", label: "Dumbbell" },
{ value: "machine", label: "Machine" },
{ value: "cable", label: "Cable" },
{ value: "bodyweight", label: "Bodyweight" },
{ value: "cardio", label: "Cardio" },
{ value: "kettlebell", label: "Kettlebell" },
{ value: "other", label: "Other" },
];
export const BASE_MUSCLE_GROUPS: string[] = [
"chest",
"back",
"shoulders",
"quads",
"hamstrings",
"glutes",
"biceps",
"triceps",
"forearms",
"core",
"calves",
"full body",
"cardio",
"abs",
"adductors",
"legs",
"traps",
"hip flexors",
"neck",
"obliques",
];
export const BASE_TRACKING_FIELDS: Option[] = [
{ value: "sets", label: "Sets" },
{ value: "reps", label: "Reps" },
{ value: "weight", label: "Weight" },
{ value: "duration", label: "Time" },
{ value: "distance", label: "Distance" },
{ value: "calories", label: "Calories" },
{ value: "notes", label: "Notes" },
];
function titleCaseToken(input: string): string {
return input
.split(" ")
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
function parseJsonArray(raw: string | null): string[] {
if (!raw) return [];
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.map(String) : [];
} catch {
return [];
}
}
export function normalizeValue(input: string): string {
return input.trim().toLowerCase().replace(/\s+/g, " ");
}
export function deriveEquipmentOptions(exercises: Exercise[]): Option[] {
const baseValues = new Set(BASE_EQUIPMENT_OPTIONS.map((item) => item.value));
const customValues = new Set<string>();
for (const exercise of exercises) {
const value = normalizeValue(exercise.type || "");
if (value && !baseValues.has(value)) {
customValues.add(value);
}
}
const customOptions = Array.from(customValues)
.sort()
.map((value) => ({ value, label: titleCaseToken(value) }));
return [...BASE_EQUIPMENT_OPTIONS, ...customOptions];
}
export function deriveMuscleGroupOptions(exercises: Exercise[]): string[] {
const baseValues = new Set(BASE_MUSCLE_GROUPS.map(normalizeValue));
const customValues = new Set<string>();
for (const exercise of exercises) {
const groups = parseJsonArray(exercise.muscleGroups);
for (const group of groups) {
const value = normalizeValue(group);
if (value && !baseValues.has(value)) {
customValues.add(value);
}
}
}
return [...BASE_MUSCLE_GROUPS, ...Array.from(customValues).sort()];
}
export function deriveTrackingFieldOptions(exercises: Exercise[]): Option[] {
const baseValues = new Set(BASE_TRACKING_FIELDS.map((item) => item.value));
const customValues = new Set<string>();
for (const exercise of exercises) {
const fields = parseJsonArray((exercise as any).inputFields || "[]");
for (const field of fields) {
const value = normalizeValue(field);
if (value && !baseValues.has(value)) {
customValues.add(value);
}
}
}
const customOptions = Array.from(customValues)
.sort()
.map((value) => ({ value, label: titleCaseToken(value) }));
return [...BASE_TRACKING_FIELDS, ...customOptions];
}
export function displayLabel(value: string): string {
return titleCaseToken(value);
}
@@ -1,7 +1,7 @@
{ {
"name": "workout-planner", "name": "proof-of-work",
"version": "1.0.0", "version": "1.0.0",
"description": "A modern workout planning application built with Next.js", "description": "Self-hosted multi-user workout planner and logger.",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -10,7 +10,8 @@
"lint": "next lint", "lint": "next lint",
"db:push": "prisma db push", "db:push": "prisma db push",
"db:seed": "npx tsx prisma/seed.ts", "db:seed": "npx tsx prisma/seed.ts",
"db:studio": "prisma studio" "db:studio": "prisma studio",
"sync-library": "node scripts/sync-library.cjs"
}, },
"dependencies": { "dependencies": {
"next": "^14.0.0", "next": "^14.0.0",
@@ -0,0 +1,103 @@
#!/usr/bin/env node
/**
* ensureExerciseLibrary — runs at every container boot from
* docker_entrypoint.sh. Inserts every exercise from
* /app/prisma/exercises.seed.json into the Exercise table for every
* existing user, using `INSERT OR IGNORE` keyed on (userId, name).
*
* Properties:
* - Multi-user-aware. Iterates all rows in `User` so every user on the
* instance gets the same curated library.
* - Idempotent. Re-running is a no-op for exercises that already exist.
* - Additive only. Never deletes or updates existing rows. Users keep
* their own custom exercises (isCustom=true) untouched, and existing
* library entries are not reshaped if the maintainer changed the
* description/inputFields/etc. for them downstream.
* - Cheap. Wrapped in a single transaction; ~164 rows x N users runs in
* well under a second.
*
* Invoked from docker_entrypoint.sh after the schema-compat ALTERs:
* node /app/prisma/ensureExerciseLibrary.cjs --db /data/app.db --json /app/prisma/exercises.seed.json
*
* Uses the sqlite3 CLI (already installed in the runner image) instead of a
* Node SQLite binding so we don't have to ship a native dep.
*/
const fs = require('fs');
const crypto = require('crypto');
const { execFileSync } = require('child_process');
function arg(name) {
const i = process.argv.indexOf(name);
return i >= 0 ? process.argv[i + 1] : null;
}
const dbPath = arg('--db');
const jsonPath = arg('--json');
if (!dbPath || !jsonPath) {
console.error('usage: ensureExerciseLibrary.cjs --db <path> --json <path>');
process.exit(64);
}
if (!fs.existsSync(dbPath)) {
console.error(`[ensure-library] db not found: ${dbPath}`);
process.exit(0); // soft-fail; entrypoint should keep going
}
if (!fs.existsSync(jsonPath)) {
console.error(`[ensure-library] library json not found: ${jsonPath}`);
process.exit(0);
}
const library = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
if (!Array.isArray(library) || library.length === 0) {
console.error('[ensure-library] library is empty; nothing to do');
process.exit(0);
}
// Get all user ids
const usersRaw = execFileSync('sqlite3', [dbPath, 'SELECT id FROM User;'], {
encoding: 'utf8',
});
const userIds = usersRaw.split('\n').map((s) => s.trim()).filter(Boolean);
if (userIds.length === 0) {
console.error('[ensure-library] no users yet; skipping (will run on next boot after a user exists)');
process.exit(0);
}
// Quote a string for safe use as a SQLite single-quoted literal.
const q = (s) => `'${String(s).replace(/'/g, "''")}'`;
// Generate a 25-char id with a "c" prefix to roughly match cuid shape.
// Uniqueness comes from 12 random bytes; collision probability is negligible.
const newId = () => 'c' + crypto.randomBytes(12).toString('hex');
const stmts = ['BEGIN;'];
let inserts = 0;
for (const userId of userIds) {
for (const ex of library) {
inserts++;
const muscleGroups = q(JSON.stringify(ex.muscleGroups || []));
const inputFields = q(
JSON.stringify(ex.inputFields || ['sets', 'reps', 'weight']),
);
const defaultWeightUnit =
ex.defaultWeightUnit == null ? 'NULL' : q(ex.defaultWeightUnit);
const description = ex.description == null ? 'NULL' : q(ex.description);
stmts.push(
`INSERT OR IGNORE INTO Exercise ` +
`(id, userId, name, description, muscleGroups, type, inputFields, defaultWeightUnit, isCustom, createdAt) ` +
`VALUES (${q(newId())}, ${q(userId)}, ${q(ex.name)}, ${description}, ` +
`${muscleGroups}, ${q(ex.type)}, ${inputFields}, ${defaultWeightUnit}, ` +
`0, CURRENT_TIMESTAMP);`,
);
}
}
stmts.push('COMMIT;');
execFileSync('sqlite3', [dbPath], { input: stmts.join('\n') });
console.error(
`[ensure-library] processed ${userIds.length} user(s) x ${library.length} exercise(s) ` +
`(${inserts} INSERT OR IGNORE statements)`,
);
File diff suppressed because it is too large Load Diff
@@ -76,6 +76,7 @@ model Workout {
durationMinutes Int? durationMinutes Int?
difficulty Int? // 1-10 scale difficulty Int? // 1-10 scale
caloriesBurned Int? caloriesBurned Int?
deletedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
@@ -85,6 +86,7 @@ model Workout {
@@index([userId]) @@index([userId])
@@index([date]) @@index([date])
@@index([deletedAt])
} }
model SetLog { model SetLog {
@@ -100,6 +102,7 @@ model SetLog {
distance Float? // for distance-based exercises distance Float? // for distance-based exercises
distanceUnit String? // "mi", "km", "m" distanceUnit String? // "mi", "km", "m"
calories Int? // for cardio machines that report calories calories Int? // for cardio machines that report calories
customMetrics String? // JSON map for dynamic custom metrics (e.g. {"watts":"157"})
notes String? notes String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
+111
View File
@@ -0,0 +1,111 @@
import { PrismaClient } from "@prisma/client";
import * as bcryptjs from "bcryptjs";
import * as fs from "fs";
import * as path from "path";
/**
* Seeds a fresh database with:
* 1. The default `admin@local` user (password: `workout123`).
* 2. Default UserPreferences for that user.
* 3. The full curated exercise library, loaded from
* `prisma/exercises.seed.json`.
*
* Idempotent — re-running upserts the user and exercises without
* duplicates. Used at Docker build time to populate the empty-schema
* fallback DB and at first boot for any host that didn't get a baked seed.
*
* The curated library is the same JSON read at runtime by
* `ensureExerciseLibrary.cjs` from docker_entrypoint.sh, so updates
* shipped in a new package version reach existing installs too.
*/
const prisma = new PrismaClient();
interface LibraryExercise {
name: string;
description: string | null;
type: string;
muscleGroups: string[];
inputFields: string[];
defaultWeightUnit: string | null;
}
function loadLibrary(): LibraryExercise[] {
const libPath = path.resolve(__dirname, "exercises.seed.json");
if (!fs.existsSync(libPath)) {
console.warn(`[seed] library file not found at ${libPath}; seeding 0 exercises`);
return [];
}
const raw = JSON.parse(fs.readFileSync(libPath, "utf8"));
if (!Array.isArray(raw)) {
throw new Error(`[seed] library file at ${libPath} is not an array`);
}
return raw as LibraryExercise[];
}
async function main() {
const hashedPassword = await bcryptjs.hash("workout123", 10);
const user = await prisma.user.upsert({
where: { email: "admin@local" },
update: {},
create: {
email: "admin@local",
passwordHash: hashedPassword,
name: "Admin User",
},
});
console.log("Created/verified user:", user.id);
await prisma.userPreferences.upsert({
where: { userId: user.id },
update: {},
create: {
userId: user.id,
theme: "system",
defaultWeightUnit: "lbs",
defaultRestSeconds: 90,
enableClaudeAI: false,
},
});
console.log("Created/verified user preferences");
const exercises = loadLibrary();
console.log(`[seed] loading ${exercises.length} exercises from exercises.seed.json`);
for (const exercise of exercises) {
await prisma.exercise.upsert({
where: {
userId_name: {
userId: user.id,
name: exercise.name,
},
},
update: {},
create: {
userId: user.id,
name: exercise.name,
description: exercise.description,
muscleGroups: JSON.stringify(exercise.muscleGroups),
type: exercise.type,
inputFields: JSON.stringify(exercise.inputFields),
defaultWeightUnit: exercise.defaultWeightUnit,
isCustom: false,
},
});
}
console.log(`Created/verified ${exercises.length} exercises`);
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<image href="/icons/gemini-kettlebell.png" width="1024" height="1024"/>
</svg>

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -1,6 +1,6 @@
{ {
"name": "Workout Planner", "name": "Proof of Work",
"short_name": "Workout", "short_name": "Proof",
"description": "Track. Lift. Dominate.", "description": "Track. Lift. Dominate.",
"start_url": "/main/dashboard", "start_url": "/main/dashboard",
"scope": "/", "scope": "/",
@@ -1,4 +1,4 @@
const CACHE_NAME = 'workout-planner-v1'; const CACHE_NAME = 'proof-of-work-v1';
// Assets to pre-cache for offline shell // Assets to pre-cache for offline shell
const PRECACHE_URLS = [ const PRECACHE_URLS = [
@@ -28,10 +28,9 @@ function ensureOutputDir() {
} }
/** /**
* Draw a stylized "W" character on a pixel buffer * Draw a simple white kettlebell on black background.
* Uses simple geometric shapes to create the letter
*/ */
function drawW(buffer, width, height) { function drawKettlebell(buffer, width, height) {
const canvasWidth = width; const canvasWidth = width;
const canvasHeight = height; const canvasHeight = height;
@@ -62,45 +61,52 @@ function drawW(buffer, width, height) {
// Fill background // Fill background
fillRect(0, 0, canvasWidth, canvasHeight, BACKGROUND_COLOR.r, BACKGROUND_COLOR.g, BACKGROUND_COLOR.b); fillRect(0, 0, canvasWidth, canvasHeight, BACKGROUND_COLOR.r, BACKGROUND_COLOR.g, BACKGROUND_COLOR.b);
// Calculate dimensions for the "W" const cx = canvasWidth / 2;
const padding = Math.floor(canvasHeight * 0.1); const bodyTop = Math.floor(canvasHeight * 0.34);
const letterWidth = canvasWidth - padding * 2; const bodyBottom = Math.floor(canvasHeight * 0.86);
const letterHeight = canvasHeight - padding * 2; const bodyHalfWidth = canvasWidth * 0.27;
const startX = padding; const handleOuterRadius = canvasWidth * 0.2;
const startY = padding; const handleInnerRadius = canvasWidth * 0.11;
const handleCenterY = canvasHeight * 0.35;
// Draw a stylized "W" using rectangles (five vertical bars forming W shape) // Draw ring handle (outer white circle, inner black circle).
const barWidth = letterWidth * 0.12; for (let y = 0; y < canvasHeight; y++) {
const spacing = letterWidth / 4; for (let x = 0; x < canvasWidth; x++) {
const dx = x - cx;
const dy = y - handleCenterY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= handleOuterRadius && dist >= handleInnerRadius) {
setPixel(x, y, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
}
}
}
// Create a V-V-V pattern (W shape) // Handle connectors.
// First V: two diagonal bars meeting at a point const connectorWidth = Math.floor(canvasWidth * 0.06);
const v1LeftX = startX + spacing * 0.2; const connectorHeight = Math.floor(canvasHeight * 0.1);
const v1RightX = startX + spacing * 0.8; const leftConnectorX = Math.floor(cx - handleOuterRadius - connectorWidth * 0.2);
const v1MidX = (v1LeftX + v1RightX) / 2; const rightConnectorX = Math.floor(cx + handleOuterRadius - connectorWidth * 0.8);
const v1BottomY = startY + letterHeight; const connectorY = Math.floor(canvasHeight * 0.39);
const v1TopY = startY; fillRect(leftConnectorX, connectorY, connectorWidth, connectorHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
fillRect(rightConnectorX, connectorY, connectorWidth, connectorHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
// Left bar of first V // Rounded-ish body made from stacked horizontal bars.
fillRect(v1LeftX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b); const bodyHeight = bodyBottom - bodyTop;
for (let i = 0; i < bodyHeight; i++) {
const t = i / bodyHeight;
const curve = 1 - Math.pow((t - 0.5) * 2, 2); // widest in middle
const half = bodyHalfWidth * (0.78 + 0.22 * Math.max(0, curve));
const y = bodyTop + i;
const x = Math.floor(cx - half);
const w = Math.ceil(half * 2);
fillRect(x, y, w, 1, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
}
// Right bar of first V // Flat base accent.
fillRect(v1RightX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b); const baseWidth = Math.floor(canvasWidth * 0.42);
const baseHeight = Math.max(1, Math.floor(canvasHeight * 0.035));
// Second V fillRect(Math.floor(cx - baseWidth / 2), bodyBottom - baseHeight, baseWidth, baseHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
const v2LeftX = startX + spacing * 0.9;
const v2RightX = startX + spacing * 1.5;
fillRect(v2LeftX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
fillRect(v2RightX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
// Third V (right side)
const v3LeftX = startX + spacing * 1.6;
const v3RightX = startX + spacing * 2.2;
fillRect(v3LeftX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
fillRect(v3RightX, v1TopY, barWidth, letterHeight, TEXT_COLOR.r, TEXT_COLOR.g, TEXT_COLOR.b);
} }
/** /**
@@ -193,7 +199,7 @@ function generateIcons() {
ICON_SIZES.forEach((size) => { ICON_SIZES.forEach((size) => {
// Regular icon // Regular icon
const buffer = Buffer.alloc(size * size * 4); const buffer = Buffer.alloc(size * size * 4);
drawW(buffer, size, size); drawKettlebell(buffer, size, size);
const outputPath = path.join(OUTPUT_DIR, `icon-${size}x${size}.png`); const outputPath = path.join(OUTPUT_DIR, `icon-${size}x${size}.png`);
createPNG(size, size, buffer, outputPath); createPNG(size, size, buffer, outputPath);
@@ -202,7 +208,7 @@ function generateIcons() {
// Maskable variant (for adaptive icons on Android) // Maskable variant (for adaptive icons on Android)
if (MASKABLE_SIZES.includes(size)) { if (MASKABLE_SIZES.includes(size)) {
const maskableBuffer = Buffer.alloc(size * size * 4); const maskableBuffer = Buffer.alloc(size * size * 4);
drawW(maskableBuffer, size, size); drawKettlebell(maskableBuffer, size, size);
const maskablePath = path.join(OUTPUT_DIR, `icon-${size}x${size}-maskable.png`); const maskablePath = path.join(OUTPUT_DIR, `icon-${size}x${size}-maskable.png`);
createPNG(size, size, maskableBuffer, maskablePath); createPNG(size, size, maskableBuffer, maskablePath);
@@ -210,11 +216,14 @@ function generateIcons() {
} }
}); });
// Create a favicon SVG as well // Create a matching favicon SVG as well.
const svgPath = path.join(OUTPUT_DIR, 'favicon.svg'); const svgPath = path.join(OUTPUT_DIR, 'favicon.svg');
const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192"> const svgContent = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192">
<rect width="192" height="192" fill="#0A0A0A"/> <rect width="192" height="192" fill="#0A0A0A"/>
<text x="96" y="132" font-size="120" font-weight="bold" text-anchor="middle" fill="white" font-family="Arial, sans-serif">W</text> <circle cx="96" cy="66" r="38" fill="none" stroke="#fff" stroke-width="18"/>
<rect x="52" y="69" width="18" height="22" fill="#fff"/>
<rect x="122" y="69" width="18" height="22" fill="#fff"/>
<path d="M 43 77 C 52 62, 140 62, 149 77 L 149 150 C 149 163, 131 170, 96 170 C 61 170, 43 163, 43 150 Z" fill="#fff"/>
</svg>`; </svg>`;
fs.writeFileSync(svgPath, svgContent); fs.writeFileSync(svgPath, svgContent);
console.log(`✓ Created ${path.basename(svgPath)}`); console.log(`✓ Created ${path.basename(svgPath)}`);
@@ -3,13 +3,13 @@
# Usage: ./scripts/setup-autostart.sh # Usage: ./scripts/setup-autostart.sh
# #
# This creates a Launch Agent plist that runs scripts/start.sh on login. # This creates a Launch Agent plist that runs scripts/start.sh on login.
# To remove: launchctl unload ~/Library/LaunchAgents/com.workout-planner.plist # To remove: launchctl unload ~/Library/LaunchAgents/com.proof-of-work.plist
set -e set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
PLIST_NAME="com.workout-planner" PLIST_NAME="com.proof-of-work"
PLIST_DIR="$HOME/Library/LaunchAgents" PLIST_DIR="$HOME/Library/LaunchAgents"
PLIST_PATH="$PLIST_DIR/$PLIST_NAME.plist" PLIST_PATH="$PLIST_DIR/$PLIST_NAME.plist"
LOG_DIR="$PROJECT_DIR/logs" LOG_DIR="$PROJECT_DIR/logs"
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* sync-library — extract the curated exercise library from a SQLite snapshot
* and write it to prisma/exercises.seed.json (the canonical library source
* of truth used by both `prisma/seed.ts` for fresh installs and the
* `ensureExerciseLibrary.cjs` runtime ensure step for updates on existing
* installs).
*
* Default source: ../start9/0.4/seed/data/app.db
* Default target: prisma/exercises.seed.json
*
* Maintainer release loop:
* 1. Add new exercises in the running app on your StartOS host.
* 2. ./start9/0.4/refresh_seed.sh <ssh-target> # pull a fresh snapshot
* 3. cd proof-of-work && npm run sync-library # update the JSON
* 4. git diff prisma/exercises.seed.json # eyeball the new rows
* 5. git add prisma/exercises.seed.json && commit
* 6. Bump start9/0.4/startos/utils.ts:PACKAGE_VERSION + add a version file
* 7. make -C start9/0.4 clean x86 && make -C start9/0.4 install
*
* Notes:
* - All exercises in the JSON ship with isCustom=false on consumption,
* regardless of how they were created on the maintainer's host. They are
* part of the curated library now, not user-custom.
* - Removing an exercise from the JSON does NOT remove it from existing
* installs (users may have logged sets against it). The system is
* additive only.
* - This script never touches the snapshot DB. Read-only.
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const repoRoot = path.resolve(__dirname, '..', '..');
const DEFAULT_DB = path.join(repoRoot, 'start9', '0.4', 'seed', 'data', 'app.db');
const DEFAULT_OUT = path.resolve(__dirname, '..', 'prisma', 'exercises.seed.json');
function arg(name, fallback) {
const i = process.argv.indexOf(name);
return i >= 0 ? process.argv[i + 1] : fallback;
}
const dbPath = arg('--db', DEFAULT_DB);
const outPath = arg('--out', DEFAULT_OUT);
if (!fs.existsSync(dbPath)) {
console.error(`[sync-library] snapshot DB not found at ${dbPath}`);
console.error(' Run start9/0.4/refresh_seed.sh first, or pass --db <path>.');
process.exit(2);
}
console.error(`[sync-library] reading from ${dbPath}`);
const sql =
'SELECT name, description, muscleGroups, type, inputFields, defaultWeightUnit ' +
'FROM Exercise ORDER BY type, name;';
const raw = execFileSync('sqlite3', ['-cmd', '.mode json', dbPath, sql], {
encoding: 'utf8',
});
const rows = JSON.parse(raw);
const library = rows.map((r) => ({
name: r.name,
description: r.description ?? null,
type: r.type,
muscleGroups: r.muscleGroups ? JSON.parse(r.muscleGroups) : [],
inputFields: r.inputFields ? JSON.parse(r.inputFields) : ['sets', 'reps', 'weight'],
defaultWeightUnit: r.defaultWeightUnit ?? null,
}));
fs.writeFileSync(outPath, JSON.stringify(library, null, 2) + '\n');
console.error(`[sync-library] wrote ${library.length} exercises to ${outPath}`);

Some files were not shown because too many files have changed in this diff Show More