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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-27 01:51:51 -04:00
1 parent 5b0a8443a7
commit 9abab5f2ba
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"
);
}
}