Primitives · raise: what opens inside a dialog lies over it

gpui 0.2.2 asserts on a deferred draw inside another, and every dialog,
sheet and popover draws inside one, so opening a date field in the
snooze or schedule dialog panicked, as would any list, menu or picker
opened in an overlay. primitives::raise replaces each deferred draw:
at the top it defers as before; inside something raised it leaves a
placeholder in the tree, and its layer lays it out on its own, then
draws it last. check.sh refuses gpui's deferred anywhere else.

The time dialog's rule is now times::ahead, tested for a moment
behind, now itself, a moment ahead and another zone; a gpui test picks
yesterday in the snooze dialog, sees that it has passed and that
nothing is sent, then picks two days ahead and hears that moment.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-27 01:51:51 -04:00
1 parent ccbc0970e5
commit 341420d1e4
20 files changed
+405 -49

No files matched your search

+34 -2
View File
@@ -2,12 +2,13 @@ use gpui::{
Context, Entity, IntoElement, Modifiers, ParentElement, Render, SharedString, Styled,
TestAppContext, VisualTestContext, Window, div, px,
};
use jiff::{Timestamp, tz::TimeZone};
use jiff::{Timestamp, ToSpan, tz::TimeZone};
use super::{Mailing, heard, mailing, note, press, settle, setup, tab_to};
use crate::{
forms,
mail::{Label, LabelPicker, SnoozePicker, snooze::snoozes},
mail::{Label, LabelPicker, SnoozePicker, snooze::snoozes, times::at_hour},
typography::format::system_zone,
};
/// Labels the host keeps, the keys on, and what it heard.
@@ -166,3 +167,34 @@ fn the_last_row_asks_for_a_time_of_ones_own(cx: &mut TestAppContext) {
assert!(cx.debug_bounds("time-dialog").is_some(), "the dialog shows");
assert!(heard(&host, cx).is_empty(), "no time yet");
}
#[gpui::test]
fn the_dialog_takes_only_a_time_ahead_of_now(cx: &mut TestAppContext) {
let (host, cx) = mailing(snoozing, cx);
tab_to(1, cx);
for key in [
"enter", "up", "enter", "tab", "enter", "left", "enter", "escape",
] {
press(key, cx);
}
assert!(
cx.debug_bounds("time-late").is_some(),
"yesterday has passed"
);
for key in ["tab", "tab", "enter"] {
press(key, cx);
}
assert!(heard(&host, cx).is_empty(), "Snooze waits for a time ahead");
for key in [
"right", "right", "right", "enter", "escape", "tab", "tab", "enter",
] {
press(key, cx);
}
let today = Timestamp::now().to_zoned(system_zone("a test")).date();
let ahead = at_hour(
today.checked_add(2.days()).expect("a day"),
0,
&TimeZone::UTC,
);
assert_eq!(heard(&host, cx), [format!("until {ahead}")]);
}
+53 -9
View File
@@ -40,6 +40,15 @@ pub(super) fn named(name: &str, at: Timestamp, zone: &TimeZone) -> (String, Time
(format!("{name} · {hour}"), at)
}
/// The moment `chosen` names in `zone`, once it lies after `now`.
pub(super) fn ahead(chosen: DateTime, zone: &TimeZone, now: Timestamp) -> Option<Timestamp> {
let at = chosen
.to_zoned(zone.clone())
.expect("the zone holds the time")
.timestamp();
(at > now).then_some(at)
}
/// `times` less each moment an earlier one names, as tomorrow names next Monday on a Sunday.
pub(super) fn distinct(
times: impl IntoIterator<Item = (String, Timestamp)>,
@@ -99,16 +108,16 @@ pub(super) fn time_dialog(
return None;
}
let chosen = now.chosen;
let at = chosen.map(|chosen| {
chosen
.to_zoned(zone.clone())
.expect("the zone holds the time")
.timestamp()
});
let ahead = at.filter(|at| *at > Timestamp::now());
let ahead = chosen.and_then(|chosen| ahead(chosen, zone, Timestamp::now()));
let (closing, choosing, id) = (picking.clone(), picking.clone(), id.clone());
let late = (at.is_some() && ahead.is_none())
.then(|| FormError::new((id.clone(), "late"), "That time has passed."));
let late = (chosen.is_some() && ahead.is_none()).then(|| {
div()
.debug_selector(|| "time-late".into())
.child(FormError::new(
(id.clone(), "late"),
"That time has passed.",
))
});
let dialog = Dialog::new((id.clone(), "dialog"), ask.title, move |_, cx| {
closing.update(cx, |picking, cx| {
*picking = Picking::default();
@@ -155,3 +164,38 @@ pub(super) fn time_dialog(
});
Some(dialog)
}
#[cfg(test)]
mod tests {
use jiff::{
SignedDuration,
civil::date,
tz::{TimeZone, offset},
};
use super::{ahead, at_hour};
#[test]
fn a_picked_time_counts_once_it_lies_ahead_of_now() {
let day = date(2026, 9, 27);
let now = at_hour(day, 9, &TimeZone::UTC);
let utc = &TimeZone::UTC;
assert_eq!(
ahead(day.at(8, 59, 0, 0), utc, now),
None,
"a minute behind"
);
assert_eq!(ahead(day.at(9, 0, 0, 0), utc, now), None, "now itself");
assert_eq!(
ahead(day.at(9, 1, 0, 0), utc, now),
Some(now + SignedDuration::from_mins(1)),
"a minute ahead"
);
let tokyo = TimeZone::fixed(offset(9));
assert_eq!(
ahead(day.at(10, 0, 0, 0), &tokyo, now),
None,
"10:00 in Tokyo is 01:00 here"
);
}
}