T23d round 1 fix
- the conversation list reads the zone through format::system_zone, which fails loud; the gallery expects the system zone - Combobox opens with its cursor on its value, MultiSelect on the first row ticked; tests for all three lists open r25 of thirty in view - AGENTS: the zone rule; lists open on their choice
This commit is contained in:
8 files changed
+87
-26
No files matched your search
@@ -131,6 +131,8 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
|
|||||||
- Editor rows read their text through `CodeEditor::row_text`: a multi-line ghost cuts its line, and the rest follows the ghost's last line.
|
- Editor rows read their text through `CodeEditor::row_text`: a multi-line ghost cuts its line, and the rest follows the ghost's last line.
|
||||||
- gpui styles a drag-over only on an element with its own hitbox. A veil that shows while files hover takes the drop itself (`chat::DragDropOverlay`).
|
- gpui styles a drag-over only on an element with its own hitbox. A veil that shows while files hover takes the drop itself (`chat::DragDropOverlay`).
|
||||||
- gpui 0.2.2 never clears `debug_bounds`: a test sees an element appear, not leave.
|
- gpui 0.2.2 never clears `debug_bounds`: a test sees an element appear, not leave.
|
||||||
|
- The system time zone comes from `typography::format::system_zone`, which fails loud; jiff's `TimeZone::system()` falls back to UTC in silence.
|
||||||
|
- A list opens with its cursor on the current choice: Select on the chosen row, Combobox on its value, MultiSelect on the first one ticked.
|
||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
|||||||
@@ -264,3 +264,4 @@ Per-component lines live in `tasks/`. Tags there:
|
|||||||
| T23c | 1 | FAIL | suggestion arrows landed on disabled rows and left the cursor out of view; a dot ended a file mention; the drop veil never showed, since gpui styles a drag-over only on an element with a hitbox; a parameter's places came from its step alone; the gallery's prompt menu and Reset only logged |
|
| T23c | 1 | FAIL | suggestion arrows landed on disabled rows and left the cursor out of view; a dot ended a file mention; the drop veil never showed, since gpui styles a drag-over only on an element with a hitbox; a parameter's places came from its step alone; the gallery's prompt menu and Reset only logged |
|
||||||
| T23c | 2 | FAIL | a slash inside a path read as a new trigger, closing the file suggestions; a list whose first rows were disabled opened with its cursor out of view |
|
| T23c | 2 | FAIL | a slash inside a path read as a new trigger, closing the file suggestions; a list whose first rows were disabled opened with its cursor out of view |
|
||||||
| T23c | 3 | PASS | |
|
| T23c | 3 | PASS | |
|
||||||
|
| T23d | 1 | FAIL | the list grouped days in a silent UTC fallback when the system zone was unknown; Combobox and MultiSelect opened a long list at its top, away from the choice |
|
||||||
@@ -79,7 +79,9 @@ pub fn conversations_section(
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> impl IntoElement + use<> {
|
) -> impl IntoElement + use<> {
|
||||||
let today = Timestamp::now().to_zoned(TimeZone::system()).date();
|
let today = Timestamp::now()
|
||||||
|
.to_zoned(TimeZone::try_system().expect("the gallery reads the system time zone"))
|
||||||
|
.date();
|
||||||
let query = window.use_keyed_state("chat-find-conversation", cx, |window, cx| {
|
let query = window.use_keyed_state("chat-find-conversation", cx, |window, cx| {
|
||||||
TextInput::new(window, cx).placeholder("Search conversations")
|
TextInput::new(window, cx).placeholder("Search conversations")
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -115,7 +115,9 @@ fn row(ix: usize, row: &Row, today: Date, now: Timestamp) -> gpui::AnyElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn conversation(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
pub fn conversation(_: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||||
let today = Timestamp::now().to_zoned(TimeZone::system()).date();
|
let today = Timestamp::now()
|
||||||
|
.to_zoned(TimeZone::try_system().expect("the gallery reads the system time zone"))
|
||||||
|
.date();
|
||||||
let now = Timestamp::now();
|
let now = Timestamp::now();
|
||||||
let all = rows(today);
|
let all = rows(today);
|
||||||
let count = all.len();
|
let count = all.len();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::{
|
|||||||
menus::{Menu, MenuItem, OverflowMenu},
|
menus::{Menu, MenuItem, OverflowMenu},
|
||||||
primitives::{FocusRing, Icon, IconName, tab_stop},
|
primitives::{FocusRing, Icon, IconName, tab_stop},
|
||||||
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
||||||
typography::Ellipsis,
|
typography::{Ellipsis, format::system_zone},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A conversation in a list: its key, its title, when it last moved, and whether it is pinned.
|
/// A conversation in a list: its key, its title, when it last moved, and whether it is pinned.
|
||||||
@@ -302,7 +302,7 @@ impl RenderOnce for ConversationList {
|
|||||||
&self.conversations,
|
&self.conversations,
|
||||||
&self.query,
|
&self.query,
|
||||||
self.today,
|
self.today,
|
||||||
&TimeZone::system(),
|
&system_zone("conversation list"),
|
||||||
);
|
);
|
||||||
let empty = groups.is_empty();
|
let empty = groups.is_empty();
|
||||||
div()
|
div()
|
||||||
|
|||||||
@@ -158,6 +158,18 @@ impl RenderOnce for Combobox {
|
|||||||
};
|
};
|
||||||
let rows = Rc::new(matching(&self.choices, query));
|
let rows = Rc::new(matching(&self.choices, query));
|
||||||
let open = focused && !typing.read(cx).dismissed && !rows.is_empty();
|
let open = focused && !typing.read(cx).dismissed && !rows.is_empty();
|
||||||
|
if open != picker.read(cx).open {
|
||||||
|
let chosen = self.selected.as_ref().and_then(|value| {
|
||||||
|
rows.iter()
|
||||||
|
.position(|row| !row.disabled && row.value == *value)
|
||||||
|
});
|
||||||
|
picker.update(cx, |picker, _| {
|
||||||
|
picker.open = open;
|
||||||
|
if let Some(at) = chosen.filter(|_| open) {
|
||||||
|
picker.highlighted = at;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
let kept = picker.read(cx).highlighted;
|
let kept = picker.read(cx).highlighted;
|
||||||
let highlighted = if rows.get(kept).is_some_and(|row| !row.disabled) {
|
let highlighted = if rows.get(kept).is_some_and(|row| !row.disabled) {
|
||||||
kept
|
kept
|
||||||
|
|||||||
+4
-1
@@ -79,7 +79,10 @@ impl RenderOnce for MultiSelect {
|
|||||||
picker.update(cx, |picker, _| picker.open = false);
|
picker.update(cx, |picker, _| picker.open = false);
|
||||||
}
|
}
|
||||||
let (choices, selected) = (Rc::new(self.choices), Rc::new(self.selected));
|
let (choices, selected) = (Rc::new(self.choices), Rc::new(self.selected));
|
||||||
let start = step(&choices, choices.len() - 1, 1);
|
let start = choices
|
||||||
|
.iter()
|
||||||
|
.position(|choice| !choice.disabled && selected.contains(&choice.value))
|
||||||
|
.unwrap_or_else(|| step(&choices, choices.len() - 1, 1));
|
||||||
if picker.read(cx).highlighted >= choices.len() {
|
if picker.read(cx).highlighted >= choices.len() {
|
||||||
picker.update(cx, |picker, _| picker.highlighted = start);
|
picker.update(cx, |picker, _| picker.highlighted = start);
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-21
@@ -87,38 +87,59 @@ fn select_opens_moves_and_picks_from_the_keyboard(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(view.read_with(cx, |view, _| view.chosen.clone()), "b");
|
assert_eq!(view.read_with(cx, |view, _| view.chosen.clone()), "b");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A select of thirty rows, low in the window, and the rows it picked.
|
/// Which list of thirty rows a test opens.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum Kind {
|
||||||
|
Select,
|
||||||
|
Combobox,
|
||||||
|
Multi,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of thirty rows with r25 chosen, low in the window, and what each press picked.
|
||||||
struct Long {
|
struct Long {
|
||||||
chosen: SharedString,
|
kind: Kind,
|
||||||
picks: Vec<SharedString>,
|
field: Entity<TextInput>,
|
||||||
|
picks: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Long {
|
impl Render for Long {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let view = cx.entity();
|
|
||||||
let rows = (0..30).map(|ix| Choice::new(format!("r{ix:02}"), format!("Row {ix:02}")));
|
let rows = (0..30).map(|ix| Choice::new(format!("r{ix:02}"), format!("Row {ix:02}")));
|
||||||
div().pt(px(200.0)).w(px(240.0)).child(
|
let view = cx.entity();
|
||||||
Select::new("long", rows)
|
let note = move |pick: String, cx: &mut gpui::App| {
|
||||||
.selected(self.chosen.clone())
|
view.update(cx, |view, cx| {
|
||||||
.on_change(move |value, _, cx| {
|
view.picks.push(pick);
|
||||||
view.update(cx, |view, cx| {
|
cx.notify();
|
||||||
view.chosen = value.clone();
|
})
|
||||||
view.picks.push(value.clone());
|
};
|
||||||
cx.notify();
|
let list = match self.kind {
|
||||||
})
|
Kind::Select => Select::new("long", rows)
|
||||||
}),
|
.selected("r25")
|
||||||
)
|
.on_change(move |value, _, cx| note(value.to_string(), cx))
|
||||||
|
.into_any_element(),
|
||||||
|
Kind::Combobox => Combobox::new("long", &self.field, rows)
|
||||||
|
.selected("r25")
|
||||||
|
.on_change(move |value, _, cx| note(value.to_string(), cx))
|
||||||
|
.into_any_element(),
|
||||||
|
Kind::Multi => MultiSelect::new("long", rows)
|
||||||
|
.selected([SharedString::from("r25")])
|
||||||
|
.on_change(move |next, _, cx| note(next.join(","), cx))
|
||||||
|
.into_any_element(),
|
||||||
|
};
|
||||||
|
div().pt(px(200.0)).w(px(240.0)).child(list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
/// Opens the list with a press on its field, then presses the row at its foot.
|
||||||
fn a_long_list_opens_with_its_choice_in_view(cx: &mut TestAppContext) {
|
fn press_the_foot(kind: Kind, cx: &mut TestAppContext) -> Vec<String> {
|
||||||
setup(cx);
|
setup(cx);
|
||||||
cx.update(|cx| Theme::update(cx, |theme| theme.reduced_motion = true));
|
cx.update(|cx| Theme::update(cx, |theme| theme.reduced_motion = true));
|
||||||
let (view, cx) = cx.add_window_view(|_, _| Long {
|
let (view, cx) = cx.add_window_view(|window, cx| Long {
|
||||||
chosen: "r25".into(),
|
kind,
|
||||||
|
field: cx.new(|cx| TextInput::new(window, cx)),
|
||||||
picks: Vec::new(),
|
picks: Vec::new(),
|
||||||
});
|
});
|
||||||
|
cx.update(|window, _| window.activate_window());
|
||||||
let (trigger, list) = cx.update(|window, cx| {
|
let (trigger, list) = cx.update(|window, cx| {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
(
|
(
|
||||||
@@ -139,8 +160,26 @@ fn a_long_list_opens_with_its_choice_in_view(cx: &mut TestAppContext) {
|
|||||||
cx.simulate_mouse_move(foot, None, Modifiers::none());
|
cx.simulate_mouse_move(foot, None, Modifiers::none());
|
||||||
cx.simulate_click(foot, Modifiers::none());
|
cx.simulate_click(foot, Modifiers::none());
|
||||||
frames(cx);
|
frames(cx);
|
||||||
let picks = view.read_with(cx, |view, _| view.picks.clone());
|
view.read_with(cx, |view, _| view.picks.clone())
|
||||||
assert_eq!(picks, ["r25"], "the list opens with its choice at the foot");
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_long_select_opens_with_its_choice_in_view(cx: &mut TestAppContext) {
|
||||||
|
assert_eq!(press_the_foot(Kind::Select, cx), ["r25"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_long_combobox_opens_with_its_choice_in_view(cx: &mut TestAppContext) {
|
||||||
|
assert_eq!(press_the_foot(Kind::Combobox, cx), ["r25"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_long_multi_select_opens_with_its_first_choice_in_view(cx: &mut TestAppContext) {
|
||||||
|
assert_eq!(
|
||||||
|
press_the_foot(Kind::Multi, cx),
|
||||||
|
[""],
|
||||||
|
"the press unticks r25"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Frames 2ms apart, past reduced motion's 1ms entrances.
|
/// Frames 2ms apart, past reduced motion's 1ms entrances.
|
||||||
|
|||||||
Reference in new issue
Block a user