Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/Plan.roc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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({}, _)
Expand Down
23 changes: 23 additions & 0 deletions tests/e2e.roc
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,29 @@ 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;"))
# 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")
# 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 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 id = ${early_id};")
Ok({})
}

Expand Down
Loading