From f9806bf921cc651a8c9eb7e2be825cee864695f3 Mon Sep 17 00:00:00 2001 From: mariano Date: Sat, 8 Aug 2026 17:25:53 -0500 Subject: [PATCH 1/3] fix: show when a session was actually completed A session finished by an activity from another day rendered exactly like one finished on time, so the plan quietly implied the work happened on the date it was prescribed for. Reading the week back, a ride done on Friday against a Saturday prescription made Friday look empty. The completing activity's own date is shown when it differs from the target. Full date including the year: plan all spans years, so a bare month-day would be ambiguous exactly where the log is longest. Closes #84 follow-up. --- src/Plan.roc | 21 ++++++++++++++++++--- tests/e2e.roc | 12 ++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Plan.roc b/src/Plan.roc index 1f8dabf..a309e8e 100644 --- a/src/Plan.roc +++ b/src/Plan.roc @@ -97,7 +97,8 @@ Plan :: [].{ \\SELECT id AS id, COALESCE(created_at,'') AS created_at, COALESCE(target_date,'') AS target_date, \\ COALESCE(session_type,'') AS session_type, COALESCE(detail,'') AS detail, \\ COALESCE(rationale,'') AS rationale, COALESCE(completed_activity_id,0) AS completed_activity_id, - \\ COALESCE(status,'open') AS status, COALESCE(skipped_reason,'') AS skipped_reason + \\ COALESCE(status,'open') AS status, COALESCE(skipped_reason,'') AS skipped_reason, + \\ COALESCE((SELECT substr(a.start_local,1,10) FROM activities a WHERE a.id = planned_sessions.completed_activity_id), '') AS done_date \\FROM planned_sessions \\WHERE (:all = 1 OR (COALESCE(target_date,'') >= '${Metrics.days_to_date_str(mon)}' AND COALESCE(target_date,'') <= '${Metrics.days_to_date_str(mon + 6)}' AND (COALESCE(status,'open') <> 'skipped' OR NOT EXISTS (SELECT 1 FROM planned_sessions p2 WHERE p2.target_date = planned_sessions.target_date AND (COALESCE(p2.status,'open') <> 'skipped' OR p2.id > planned_sessions.id))))) \\ORDER BY target_date DESC, id DESC LIMIT 100 @@ -113,7 +114,8 @@ Plan :: [].{ completed_activity_id = Sqlite.i64("completed_activity_id")(cols)(stmt)? status = Sqlite.str("status")(cols)(stmt)? skipped_reason = Sqlite.str("skipped_reason")(cols)(stmt)? - Ok({ id, created_at, target_date, session_type, detail, rationale, completed_activity_id, status, skipped_reason }) + done_date = Sqlite.str("done_date")(cols)(stmt)? + Ok({ id, created_at, target_date, session_type, detail, rationale, completed_activity_id, status, skipped_reason, done_date }) }, })? # most recent 100 by date, displayed in calendar order @@ -136,11 +138,24 @@ Plan :: [].{ completed_activity_id: p.completed_activity_id, status: p.status, skipped_reason: p.skipped_reason, + done_date: p.done_date, + # A session completed by an activity from ANOTHER day used to render exactly + # like one completed on time — the plan silently implied the work happened on + # the date it was prescribed for. Show the real day when they differ. + status_shown: + if p.status == "done" and p.done_date != "" and p.done_date != p.target_date { + # Full date, year included: `plan all` spans years, so a bare month-day + # would be ambiguous exactly where the log is longest. The wider cell + # costs a line of wrapping in the detail column; that is the cheaper loss. + "done (${dow(p.done_date)} ${p.done_date})" + } else { + p.status + }, }) Output.out!(enriched, |rows_enriched| Render.render_table( ["day", "date", "type", "status", "detail", "id"], - List.map(rows_enriched, |p| [p.day, p.target_date, p.session_type, p.status, p.detail, (p.id).to_str()]), + List.map(rows_enriched, |p| [p.day, p.target_date, p.session_type, p.status_shown, p.detail, (p.id).to_str()]), )) } plan_add! : Str, Str, Str, Str => Try({}, _) diff --git a/tests/e2e.roc b/tests/e2e.roc index a9535b6..a3b9e02 100644 --- a/tests/e2e.roc +++ b/tests/e2e.roc @@ -415,6 +415,18 @@ b_plan! = |ctx| { check!("fully-skipped day shows ONE row", strjq!(ctx, ["plan"], "[.data[] | select(.target_date==\"${ctx.today}\")] | length") == "1")? check!("fully-skipped day shows the FINAL tombstone", strjq!(ctx, ["plan"], ".data[] | select(.target_date==\"${ctx.today}\") | .id") == "6")? check!("plan all keeps every draft", strjq!(ctx, ["plan", "all"], "[.data[] | select(.target_date==\"${ctx.today}\")] | length") == "2")? + # #84 follow-up: a session completed by an activity from ANOTHER day rendered exactly + # like one completed on time, so the plan quietly implied the work happened on the date + # it was prescribed for. The completing activity's own day is shown when they differ. + # Activity 101 lives on ctx.d1, so target a fixed date it cannot coincide with. + _ = sql!(ctx.db, "INSERT INTO planned_sessions (created_at, target_date, session_type, detail, rationale, status) VALUES ('0','2025-01-15','endurance','early ride','r','open');") + early_id = Str.trim(sql!(ctx.db, "SELECT MAX(id) FROM planned_sessions;")) + _ = stride!(ctx.bin, ctx.home, ["complete", early_id, "101"]) + date_101 = Str.trim(sql!(ctx.db, "SELECT substr(start_local,1,10) FROM activities WHERE id=101;")) + plan_early = stride_human!(ctx.bin, ctx.home, ["plan", "all"]) + check!("a session finished on another day shows that day", Str.contains(plan_early, "done (") and Str.contains(plan_early, date_101))? + check!("a session finished on its target date just says done", Str.contains(plan_early, "│ done "))? + _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE target_date = '2025-01-15';") Ok({}) } From a619a428142f25902ddecb9eab9ea0b0f9985d33 Mon Sep 17 00:00:00 2001 From: mariano Date: Sat, 8 Aug 2026 17:40:23 -0500 Subject: [PATCH 2/3] test: control on an on-time session by id, not a substring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The control asserted the output contained "| done ", which is a prefix of "| done (Fri ..." — so it passed even when every row carried a date. It now completes a session on its own target date and checks that row's status_shown by id is exactly done, with the early one still carrying its real date. Verified against a build that dates every completed row. --- tests/e2e.roc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/e2e.roc b/tests/e2e.roc index a3b9e02..2912eac 100644 --- a/tests/e2e.roc +++ b/tests/e2e.roc @@ -425,7 +425,17 @@ b_plan! = |ctx| { date_101 = Str.trim(sql!(ctx.db, "SELECT substr(start_local,1,10) FROM activities WHERE id=101;")) plan_early = stride_human!(ctx.bin, ctx.home, ["plan", "all"]) check!("a session finished on another day shows that day", Str.contains(plan_early, "done (") and Str.contains(plan_early, date_101))? - check!("a session finished on its target date just says done", Str.contains(plan_early, "│ done "))? + # The control has to be an ON-TIME session checked BY ID. Asserting the output merely + # contains "│ done " is a false positive: it is a prefix of "│ done (Fri ...", so the + # check passed even when every row carried a date. + _ = sql!(ctx.db, "INSERT INTO planned_sessions (created_at, target_date, session_type, detail, rationale, status) VALUES ('0','${date_101}','endurance','same day ride','r','open');") + ontime_id = Str.trim(sql!(ctx.db, "SELECT MAX(id) FROM planned_sessions;")) + _ = stride!(ctx.bin, ctx.home, ["complete", ontime_id, "101"]) + ontime_status = strjq!(ctx, ["plan", "all"], ".data[] | select(.id==${ontime_id}) | .status_shown") + early_status = strjq!(ctx, ["plan", "all"], ".data[] | select(.id==${early_id}) | .status_shown") + check!("an on-time session renders exactly done", ontime_status == "done")? + check!("the early one carries its real date", Str.starts_with(early_status, "done ("))? + _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE id = ${ontime_id};") _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE target_date = '2025-01-15';") Ok({}) } From 3b800a929ddd4c768c881319ff8dbc5315f45a67 Mon Sep 17 00:00:00 2001 From: mariano Date: Sat, 8 Aug 2026 17:49:34 -0500 Subject: [PATCH 3/3] test: select every row by id, and clean up by id too The remaining human-output assertion was a second false positive: session 2 is completed with activity 101 earlier in the same scenario, so the plan already contains "done (" and that date no matter what this row renders. Both directions now read status_shown for their own id. Cleanup deleted by target_date, which would take any other row sharing the date; it deletes the id it created. --- tests/e2e.roc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/e2e.roc b/tests/e2e.roc index 2912eac..0b6633a 100644 --- a/tests/e2e.roc +++ b/tests/e2e.roc @@ -423,8 +423,6 @@ b_plan! = |ctx| { early_id = Str.trim(sql!(ctx.db, "SELECT MAX(id) FROM planned_sessions;")) _ = stride!(ctx.bin, ctx.home, ["complete", early_id, "101"]) date_101 = Str.trim(sql!(ctx.db, "SELECT substr(start_local,1,10) FROM activities WHERE id=101;")) - plan_early = stride_human!(ctx.bin, ctx.home, ["plan", "all"]) - check!("a session finished on another day shows that day", Str.contains(plan_early, "done (") and Str.contains(plan_early, date_101))? # The control has to be an ON-TIME session checked BY ID. Asserting the output merely # contains "│ done " is a false positive: it is a prefix of "│ done (Fri ...", so the # check passed even when every row carried a date. @@ -433,10 +431,13 @@ b_plan! = |ctx| { _ = stride!(ctx.bin, ctx.home, ["complete", ontime_id, "101"]) ontime_status = strjq!(ctx, ["plan", "all"], ".data[] | select(.id==${ontime_id}) | .status_shown") early_status = strjq!(ctx, ["plan", "all"], ".data[] | select(.id==${early_id}) | .status_shown") + # Every assertion selects its OWN row by id. Matching the whole plan output for + # "done (" proved nothing: session 2 is completed with activity 101 earlier in this + # scenario, so that string is already present regardless of what this row renders. check!("an on-time session renders exactly done", ontime_status == "done")? - check!("the early one carries its real date", Str.starts_with(early_status, "done ("))? + check!("the early one carries its real completion date", Str.starts_with(early_status, "done (") and Str.contains(early_status, date_101))? _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE id = ${ontime_id};") - _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE target_date = '2025-01-15';") + _ = sql!(ctx.db, "DELETE FROM planned_sessions WHERE id = ${early_id};") Ok({}) }