Organize tasks by priority and due dates.
Changes sync two-way with Google Sheets.
Total Tasks
0
Completed
0
Pending
0
Overdue
0
Done
Task Name
Category
Priority
Due Date
Actions
Workouts &
Progressive Overload
Track your lifts and let Gemini AI
calculate your progressive overload recommendations. Import completed sessions from
IronPULSE to bring them into this tracker.
Gemini Progressive
Overload Analysis
Schedule & Nagging
Reminders
Set a due time, then get nagged at your
chosen interval until it is marked done. Optionally repeat daily, weekly and more.
Add Task / Homework
Log Workout Exercise
Add Schedule &
Reminder Item
Settings & Two-Way Google Sheets Sync
Dark Appearance
Toggle dark mode styling for the app.
Google Sheets Web App URL
Enter your Google Apps Script Web App URL
below to enable robust two-way synchronization.
Google Apps Script Setup
Instructions
Create a Google Sheet with 3 tabs: Tasks, Workouts, and
Schedule. Then open Extensions > Apps Script, paste
the code below, and deploy as a Web App (Access: "Anyone").
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Sync Tasks
if (data.tasks) {
var sheet = ss.getSheetByName("Tasks");
if (!sheet) { sheet = ss.insertSheet("Tasks"); }
sheet.clear();
sheet.getRange("E:E").setNumberFormat("@"); // dueDate stays plain text
sheet.appendRow(["id", "title", "category", "priority", "dueDate", "completed", "deleted", "updatedAt"]);
data.tasks.forEach(function(t) {
sheet.appendRow([t.id, t.title, t.category, t.priority, t.dueDate, t.completed, t.deleted === true, t.updatedAt]);
});
}
// Sync Workouts
if (data.workouts) {
var sheet = ss.getSheetByName("Workouts");
if (!sheet) { sheet = ss.insertSheet("Workouts"); }
sheet.clear();
sheet.getRange("F:F").setNumberFormat("@"); // date stays plain text
sheet.appendRow(["id", "name", "weight", "sets", "reps", "date", "unit", "deleted", "updatedAt"]);
data.workouts.forEach(function(w) {
sheet.appendRow([w.id, w.name, w.weight, w.sets, w.reps, w.date, w.unit || "lbs", w.deleted === true, w.updatedAt]);
});
}
// Sync Schedule
if (data.schedule) {
var sheet = ss.getSheetByName("Schedule");
if (!sheet) { sheet = ss.insertSheet("Schedule"); }
sheet.clear();
sheet.getRange("C:D").setNumberFormat("@"); // date + time stay plain text
sheet.appendRow(["id", "title", "date", "time", "completed", "nagInterval", "repeat", "deleted", "updatedAt"]);
data.schedule.forEach(function(s) {
sheet.appendRow([s.id, s.title, s.date, s.time, s.completed, s.nagInterval, s.repeat, s.deleted === true, s.updatedAt]);
});
}
return ContentService.createTextOutput(JSON.stringify({status: "success"}))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({status: "error", message: err.toString()}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tz = ss.getSpreadsheetTimeZone();
var result = {};
["Tasks", "Workouts", "Schedule"].forEach(function(tabName) {
var sheet = ss.getSheetByName(tabName);
if (sheet) {
var rows = sheet.getDataRange().getValues();
if (rows.length > 1) {
var headers = rows[0];
var items = [];
for (var i = 1; i < rows.length; i++) {
var obj = {};
for (var j = 0; j < headers.length; j++) {
var v = rows[i][j];
if (v instanceof Date) {
v = Utilities.formatDate(v, tz, headers[j] === "time" ? "HH:mm" : "yyyy-MM-dd");
}
obj[headers[j]] = v;
}
items.push(obj);
}
result[tabName.toLowerCase()] = items;
} else {
result[tabName.toLowerCase()] = [];
}
} else {
result[tabName.toLowerCase()] = [];
}
});
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}