From bf3b8e2f674c063fd0c3d3e6d484b4b9629b1706 Mon Sep 17 00:00:00 2001 From: PureTensor Date: Sun, 25 Oct 2026 04:56:25 +0000 Subject: [PATCH] v3.51.8: keep an offset deadline's written civil date in the cockpit Co-Authored-By: Claude Sonnet 5.5 --- Cargo.lock | 14 +++++------ Cargo.toml | 14 +++++------ crates/ptask-server/src/routes/dashboard.rs | 27 ++++++++++++++++++++- docs/gen/pt.1 | 4 +-- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 178b654..bd34f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2708,7 +2708,7 @@ dependencies = [ [[package]] name = "ptask-bot" -version = "3.51.7" +version = "3.51.8" dependencies = [ "anyhow", "jiff", @@ -2724,7 +2724,7 @@ dependencies = [ [[package]] name = "ptask-cli" -version = "3.51.7" +version = "3.51.8" dependencies = [ "anyhow", "axum", @@ -2752,7 +2752,7 @@ dependencies = [ [[package]] name = "ptask-core" -version = "3.51.7" +version = "3.51.8" dependencies = [ "hex", "interim", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "ptask-distill" -version = "3.51.7" +version = "3.51.8" dependencies = [ "anyhow", "candle-core", @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "ptask-notify" -version = "3.51.7" +version = "3.51.8" dependencies = [ "lettre", "ptask-core", @@ -2810,7 +2810,7 @@ dependencies = [ [[package]] name = "ptask-server" -version = "3.51.7" +version = "3.51.8" dependencies = [ "anyhow", "axum", @@ -2842,7 +2842,7 @@ dependencies = [ [[package]] name = "ptask-tui" -version = "3.51.7" +version = "3.51.8" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 9bfef82..c513df5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "3.51.7" +version = "3.51.8" edition = "2024" license-file = "LICENSE" authors = ["PureTensor, Inc. "] @@ -52,12 +52,12 @@ rmcp = { version = "3.3.0", features = ["server", "macros", "schemars", "transpo futures-util = { version = "0.3", default-features = false } # Internal crates -ptask-core = { path = "crates/ptask-core", version = "3.51.7" } -ptask-tui = { path = "crates/ptask-tui", version = "3.51.7" } -ptask-server = { path = "crates/ptask-server", version = "3.51.7" } -ptask-bot = { path = "crates/ptask-bot", version = "3.51.7" } -ptask-distill = { path = "crates/ptask-distill", version = "3.51.7" } -ptask-notify = { path = "crates/ptask-notify", version = "3.51.7" } +ptask-core = { path = "crates/ptask-core", version = "3.51.8" } +ptask-tui = { path = "crates/ptask-tui", version = "3.51.8" } +ptask-server = { path = "crates/ptask-server", version = "3.51.8" } +ptask-bot = { path = "crates/ptask-bot", version = "3.51.8" } +ptask-distill = { path = "crates/ptask-distill", version = "3.51.8" } +ptask-notify = { path = "crates/ptask-notify", version = "3.51.8" } # HMAC for outbound webhook signing (v0.3.5). sha2 = "0.10" diff --git a/crates/ptask-server/src/routes/dashboard.rs b/crates/ptask-server/src/routes/dashboard.rs index f2ccfd0..70fd655 100644 --- a/crates/ptask-server/src/routes/dashboard.rs +++ b/crates/ptask-server/src/routes/dashboard.rs @@ -366,6 +366,12 @@ fn parse_deadline(s: &str) -> Option<(String, f64)> { let days = today.until(date).ok()?.get_days(); return Some((date.to_string(), f64::from(days))); } + // Civil date is the date written on the timestamp, matching the sidecar + // `datetime.date()`. `parse_iso_to_utc` folds the instant into UTC + // first, so 00:30 at +01:00 became the previous UTC date. jiff refuses + // to parse an offset timestamp as `Zoned` without a `[Time/Zone]` + // annotation, so the written `YYYY-MM-DD` is read from the text. The + // day count stays an instant delta. let z = ptask_core::dates::parse_iso_to_utc(s).or_else(|| { // Date-only "YYYY-MM-DD" → midnight UTC. `s.get(..10)` is char-safe: // a raw `&s[..10]` panics when byte 10 lands inside a multi-byte scalar @@ -379,7 +385,12 @@ fn parse_deadline(s: &str) -> Option<(String, f64)> { .ok()? .with_time_zone(ptask_core::jiff::tz::TimeZone::UTC); let days = (z.timestamp().as_second() - now.timestamp().as_second()) as f64 / 86_400.0; - Some((z.date().to_string(), (days * 10.0).round() / 10.0)) + let date = s + .get(..10) + .filter(|prefix| prefix.parse::().is_ok()) + .map(str::to_string) + .unwrap_or_else(|| z.date().to_string()); + Some((date, (days * 10.0).round() / 10.0)) } fn round4(v: f64) -> f64 { @@ -1692,6 +1703,20 @@ mod tests { ); } + #[test] + fn timed_deadline_keeps_the_offset_civil_date() { + // The sidecar's datetime.date() keeps the offset's calendar day. + // 00:30 at +01:00 is still 26 Oct there and 23:30 UTC on 25 Oct. + // Converting to UTC before reading the date files that deadline + // on the previous day in /api/tasks and /api/timeline. + let (date, _) = parse_deadline("2026-10-26T00:30:00+01:00").unwrap(); + assert_eq!(date, "2026-10-26"); + // A `Z` timestamp's civil date is the UTC date. The sidecar test + // locks the same string. + let (zdate, _) = parse_deadline("2026-01-01T09:00:00Z").unwrap(); + assert_eq!(zdate, "2026-01-01"); + } + #[test] fn parse_deadline_is_char_boundary_safe() { // Regression: a raw `&s[..10]` panics when byte 10 lands inside a diff --git a/docs/gen/pt.1 b/docs/gen/pt.1 index 1d45959..adbba03 100644 --- a/docs/gen/pt.1 +++ b/docs/gen/pt.1 @@ -1,6 +1,6 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' -.TH pt 1 "pt 3.51.7" +.TH pt 1 "pt 3.51.8" .SH NAME pt \- Sovereign task manager for PureTensor .SH SYNOPSIS @@ -208,4 +208,4 @@ Generate shell completions (bash/zsh/fish) to stdout pt\-help(1) Print this message or the help of the given subcommand(s) .SH VERSION -v3.51.7 +v3.51.8