diff --git a/TASKS.md b/TASKS.md index de730f6..fc02275 100644 --- a/TASKS.md +++ b/TASKS.md @@ -355,3 +355,4 @@ Per-component lines live in `tasks/`. Tags there: | T29d | 2 | FAIL | a raise inside a raise took its place in the queue as its namespace, so one appearing before it rebuilt its state and dropped its focus | | T29d | 3 | PASS | | | T30a | 1 | PASS | | +| T30b | 1 | FAIL | the week and day views froze the line at now and today: the moment read once went down as given and turned the ticker off | diff --git a/src/calendar/days.rs b/src/calendar/days.rs index c0bc787..eca84fa 100644 --- a/src/calendar/days.rs +++ b/src/calendar/days.rs @@ -262,9 +262,9 @@ impl RenderOnce for CalendarDays { Some(on_event) => whole.on_event(move |key, window, cx| on_event(key, window, cx)), None => whole, }; - let mut grid = TimeGrid::new((self.id.clone(), "grid"), days.clone(), self.events) - .zone(zone) - .now(now); + let mut grid = + TimeGrid::new((self.id.clone(), "grid"), days.clone(), self.events).zone(zone); + grid.now = self.now; grid.overlay = self.overlay; grid.on_create = self.on_create; grid.on_resize = self.on_resize; diff --git a/src/calendar/grid.rs b/src/calendar/grid.rs index 13502d6..63f8183 100644 --- a/src/calendar/grid.rs +++ b/src/calendar/grid.rs @@ -17,7 +17,7 @@ use crate::{ forms::{Drawing, OnValue, Stroke, pen}, layout::on_axis, theme::{ActiveTheme, Radius, TextSize}, - typography::format, + typography::{format, fresh}, }; /// Where a grid first opens: half past seven, or an hour and a half before now when that is later. @@ -53,7 +53,7 @@ pub struct TimeGrid { days: Vec, events: Vec, zone: Option, - now: Option, + pub(crate) now: Option, pub(crate) overlay: Option, pub(crate) on_create: Option, pub(crate) on_resize: Option, @@ -361,6 +361,9 @@ impl TimeGrid { impl RenderOnce for TimeGrid { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + if self.now.is_none() { + fresh((self.id.clone(), "clock"), window, cx); + } let zone = self .zone .clone() diff --git a/src/calendar/month.rs b/src/calendar/month.rs index dc1e1cf..224eb3d 100644 --- a/src/calendar/month.rs +++ b/src/calendar/month.rs @@ -18,7 +18,7 @@ use crate::{ layout::seeded::use_seeded, primitives::{FocusRing, tab_stop}, theme::{ActiveTheme, Radius, TextSize}, - typography::{Ellipsis, format}, + typography::{Ellipsis, format, fresh}, }; const WEEKDAYS: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; @@ -287,6 +287,9 @@ impl Row<'_> { impl RenderOnce for CalendarMonthView { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + if self.today.is_none() { + fresh((self.id.clone(), "clock"), window, cx); + } let zone = self .zone .unwrap_or_else(|| format::system_zone("calendar month view")); diff --git a/src/calendar/tests/times.rs b/src/calendar/tests/times.rs index bc614cd..4249875 100644 --- a/src/calendar/tests/times.rs +++ b/src/calendar/tests/times.rs @@ -226,3 +226,92 @@ fn a_grid_without_today_opens_on_the_morning(cx: &mut TestAppContext) { let (_, cx) = planning(nights, cx); assert_eq!(opened(cx), px(48.0 * 7.5), "from 07:30"); } + +/// How often the host was told to redraw across a clock tick and a half, with no refresh of its own. +fn redraws( + part: fn(&Planning, Entity) -> gpui::AnyElement, + cx: &mut TestAppContext, +) -> usize { + let (host, cx) = planning(part, cx); + let told = std::rc::Rc::new(std::cell::Cell::new(0)); + let count = told.clone(); + cx.update(|_, cx| { + cx.observe(&host, move |_, _| count.set(count.get() + 1)) + .detach() + }); + cx.executor() + .advance_clock(std::time::Duration::from_secs(45)); + cx.run_until_parked(); + told.get() +} + +fn clocked_day(_: &Planning, _: Entity) -> gpui::AnyElement { + let today = jiff::Timestamp::now().to_zoned(TimeZone::UTC).date(); + div() + .h(px(600.0)) + .child(crate::calendar::CalendarDayView::new("day", today, []).zone(TimeZone::UTC)) + .into_any_element() +} + +fn still_day(_: &Planning, _: Entity) -> gpui::AnyElement { + div() + .h(px(600.0)) + .child( + crate::calendar::CalendarDayView::new("day", date(2026, 9, 22), []) + .zone(TimeZone::UTC) + .now(at(22, 12)), + ) + .into_any_element() +} + +#[gpui::test] +fn a_day_on_the_clock_redraws_as_it_runs(cx: &mut TestAppContext) { + assert!( + redraws(clocked_day, cx) > 0, + "the line at now moves with the clock" + ); +} + +#[gpui::test] +fn a_day_given_its_moment_holds_still(cx: &mut TestAppContext) { + assert_eq!(redraws(still_day, cx), 0); +} + +fn clocked_month(_: &Planning, _: Entity) -> gpui::AnyElement { + crate::calendar::CalendarMonthView::new("month", date(2026, 9, 1), []) + .zone(TimeZone::UTC) + .into_any_element() +} + +#[gpui::test] +fn a_month_on_the_clock_redraws_so_today_turns_over(cx: &mut TestAppContext) { + assert!(redraws(clocked_month, cx) > 0); +} + +fn clocked_year(_: &Planning, _: Entity) -> gpui::AnyElement { + crate::calendar::YearView::new("year", 2026, []) + .zone(TimeZone::UTC) + .into_any_element() +} + +#[gpui::test] +fn a_year_on_the_clock_redraws_so_today_turns_over(cx: &mut TestAppContext) { + assert!(redraws(clocked_year, cx) > 0); +} + +fn clocked_other_day(_: &Planning, _: Entity) -> gpui::AnyElement { + div() + .h(px(600.0)) + .child( + crate::calendar::CalendarDayView::new("day", date(2026, 9, 22), []).zone(TimeZone::UTC), + ) + .into_any_element() +} + +#[gpui::test] +fn a_day_before_today_still_watches_the_clock_for_midnight(cx: &mut TestAppContext) { + assert!( + redraws(clocked_other_day, cx) > 0, + "no line to tick, the grid itself does" + ); +} diff --git a/src/calendar/year.rs b/src/calendar/year.rs index f02b40e..827486f 100644 --- a/src/calendar/year.rs +++ b/src/calendar/year.rs @@ -16,7 +16,7 @@ use crate::{ layout::seeded::use_seeded, primitives::tab_stop, theme::{ActiveTheme, TextSize}, - typography::format, + typography::{format, fresh}, }; const INITIALS: [&str; 7] = ["M", "T", "W", "T", "F", "S", "S"]; @@ -191,6 +191,9 @@ impl Mini<'_> { impl RenderOnce for YearView { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + if self.today.is_none() { + fresh((self.id.clone(), "clock"), window, cx); + } let zone = self .zone .unwrap_or_else(|| format::system_zone("year view"));