fix(workouts): refuse a manual workout that would end in the future

The form's start defaults to the minute it opened, so a workout logged
right after it finished was stored as ending up to a day ahead. The
duration now fails validation when start + duration lies past now, with
the same one-minute slack the start check uses.
This commit is contained in:
Marc-André Bombeck committed 2026-10-03 00:35:26 +02:00
1 parent 2cd8b28a22
commit a14ae57283
9 files changed
+28 -2

No files matched your search

@@ -136,9 +136,9 @@ describe("buildManualWorkoutEntry — validation", () => {
});
it("tolerates the minute the form was opened in", () => {
// 14:00:30 Berlin is 30 s past `now`: inside the slack.
// 13:15 Berlin + 45 min ends at 12:00Z, 30 s past `now`: inside the slack.
const result = buildManualWorkoutEntry(
draft({ start: "2026-09-15T14:00" }),
draft({ start: "2026-09-15T13:15" }),
{
...CTX,
now: new Date("2026-09-15T11:59:30Z"),
@@ -147,6 +147,13 @@ describe("buildManualWorkoutEntry — validation", () => {
expect(result.ok).toBe(true);
});
it("refuses a workout that would end in the future", () => {
// Logged at 14:00 Berlin with the start left at 13:30: 45 min ends 14:15.
expect(errorsOf({ start: "2026-09-15T13:30" }).duration).toBe(
"insights.workouts.manual.errors.endInFuture",
);
});
it("asks for a start", () => {
expect(errorsOf({ start: "" }).start).toBe(
"insights.workouts.manual.errors.startRequired",
+12
View File
@@ -235,6 +235,18 @@ function manualWorkoutSchema(ctx: ManualWorkoutContext) {
path: ["duration"],
message: `${ERR}.durationTooLong`,
});
} else if (
// The default start is "now", so a workout logged right after it
// finished would otherwise be stored as ending in the future.
startedAt !== null &&
startedAt.getTime() + sec * 1000 >
ctx.now.getTime() + FUTURE_TOLERANCE_MS
) {
issue.addIssue({
code: "custom",
path: ["duration"],
message: `${ERR}.endInFuture`,
});
}
}