Panics · P33 Devtools: handlers in new; stale collection moves, environments and diagram tables change nothing

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 05:15:49 -04:00
1 parent ffe4f38103
commit 05e846b62a
13 files changed
+220 -215

No files matched your search

+15 -7
View File
@@ -156,8 +156,7 @@ pub fn requests(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.flex_col()
.gap_4()
.child(
EnvironmentSelector::new("devtools-environments", environments(), environment)
.on_change(move |key, _, cx| change(&chose, cx, |desk| desk.environment = key.clone())),
EnvironmentSelector::new("devtools-environments", environments(), environment, move |key, _, cx| change(&chose, cx, |desk| desk.environment = key.clone())),
)
.child(
div()
@@ -178,7 +177,11 @@ pub fn requests(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
})
})
.on_move(move |key, target, at, _, cx| {
change(&dragged, cx, |desk| desk.collection = moved(&desk.collection, key, target, at))
change(&dragged, cx, |desk| {
if let Some(collection) = moved(&desk.collection, key, target, at) {
desk.collection = collection;
}
})
}),
),
),
@@ -255,8 +258,7 @@ pub fn socket(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
)
.child(
div().w(px(620.)).child(
WebSocketConsole::new("devtools-socket", "wss://stream.example.com/orders", state, messages, &draft, clock)
.on_send(move |text, _, cx| {
WebSocketConsole::new("devtools-socket", "wss://stream.example.com/orders", state, messages, &draft, clock, move |text, _, cx| {
sent.update(cx, |line, cx| {
let at = line.now;
line.messages.push(SocketMessage { direction: Direction::Sent, text, at });
@@ -333,8 +335,14 @@ pub fn graphql(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
fields: vec![("OPEN".into(), "".into()), ("SHIPPED".into(), "".into())],
},
];
let mut explorer = GraphQLExplorer::new("devtools-graphql", types, &query, &variables, &search)
.on_run(move |_, _, _, cx| change(&ran, cx, |graph| graph.answer = Some(ANSWER.into())));
let mut explorer = GraphQLExplorer::new(
"devtools-graphql",
types,
&query,
&variables,
&search,
move |_, _, _, cx| change(&ran, cx, |graph| graph.answer = Some(ANSWER.into())),
);
if let Some(answer) = answer {
explorer = explorer.answer(answer);
}
+2 -5
View File
@@ -270,15 +270,12 @@ pub fn structure(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
.flex_wrap()
.gap_6()
.child(
div().w(px(660.)).child(TableStructureEditor::new("devtools-structure", fields)
.on_change(move |next, _, cx| change(&edited, cx, |build| build.fields = next)),
div().w(px(660.)).child(TableStructureEditor::new("devtools-structure", fields, move |next, _, cx| change(&edited, cx, |build| build.fields = next)),
),
)
.child(
div().w(px(300.)).child(
IndexManager::new("devtools-indexes", indexes, names)
.on_drop(move |name, _, cx| change(&dropped, cx, |build| build.indexes.retain(|index| index.name != *name)))
.on_create(move |index, _, cx| change(&created, cx, |build| build.indexes.push(index))),
IndexManager::new("devtools-indexes", indexes, names, move |index, _, cx| change(&created, cx, |build| build.indexes.push(index)), move |name, _, cx| change(&dropped, cx, |build| build.indexes.retain(|index| index.name != *name))),
),
),
)
+1 -2
View File
@@ -35,8 +35,7 @@ pub fn text(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.child(div().w(px(380.)).child(Encoder::new("devtools-encoder", "Hello, 世界")))
.child(
div().w(px(360.)).child(
ColorContrastChecker::new("devtools-contrast", fore, back)
.on_change(move |fore, back, _, cx| change(&colors, cx, |colors| *colors = (fore, back))),
ColorContrastChecker::new("devtools-contrast", fore, back, move |fore, back, _, cx| change(&colors, cx, |colors| *colors = (fore, back))),
),
),
)
+23 -11
View File
@@ -85,7 +85,8 @@ fn placed(items: &mut Vec<Saved>, item: Saved, target: &str, at: DropAt) -> Resu
(DropAt::After, _) => items.insert(ix + 1, item),
(DropAt::Inside, Saved::Folder { items, .. }) => items.push(item),
(DropAt::Inside, Saved::Request { key, .. }) => {
panic!("collection: {key} is a request, and nothing goes inside it")
log::error!("collection: {key} is a request, and nothing goes inside it");
return Err(item);
}
}
return Ok(());
@@ -102,15 +103,19 @@ fn placed(items: &mut Vec<Saved>, item: Saved, target: &str, at: DropAt) -> Resu
Err(item)
}
/// `items` with `key` moved to `at` of `target`, as a drop in the tree asks.
pub fn moved(items: &[Saved], key: &str, target: &str, at: DropAt) -> Vec<Saved> {
/// `items` with `key` moved to `at` of `target`, as a drop in the tree asks; none, logged, when either left the items.
pub fn moved(items: &[Saved], key: &str, target: &str, at: DropAt) -> Option<Vec<Saved>> {
let mut items = items.to_vec();
let item = taken(&mut items, key).unwrap_or_else(|| panic!("collection: no {key} to move"));
let Some(item) = taken(&mut items, key) else {
log::error!("collection: no {key} to move");
return None;
};
if let Err(item) = placed(&mut items, item, target, at) {
panic!("collection: no {target} to move {} beside", item.key());
log::error!("collection: no {target} to move {} beside", item.key());
return None;
}
log::info!("collection: moved {key} {at:?} {target}");
items
Some(items)
}
type OnKey = Rc<dyn Fn(&SharedString, &mut Window, &mut App)>;
@@ -236,25 +241,32 @@ mod tests {
Saved::request("z", "Z", Method::Post),
];
assert_eq!(
keys(&moved(&items, "x", "b", DropAt::Inside)),
keys(&moved(&items, "x", "b", DropAt::Inside).expect("both are in the items")),
["a[y]", "b[x]", "z"]
);
assert_eq!(
keys(&moved(&items, "z", "a", DropAt::Inside)),
keys(&moved(&items, "z", "a", DropAt::Inside).expect("both are in the items")),
["a[x,y,z]", "b[]"],
"a drop inside a folder lands last"
);
assert_eq!(
keys(&moved(&items, "z", "x", DropAt::Before)),
keys(&moved(&items, "z", "x", DropAt::Before).expect("both are in the items")),
["a[z,x,y]", "b[]"]
);
assert_eq!(
keys(&moved(&items, "x", "z", DropAt::After)),
keys(&moved(&items, "x", "z", DropAt::After).expect("both are in the items")),
["a[y]", "b[]", "z", "x"]
);
assert_eq!(
keys(&moved(&items, "b", "a", DropAt::Before)),
keys(&moved(&items, "b", "a", DropAt::Before).expect("both are in the items")),
["b[]", "a[x,y]", "z"]
);
assert_eq!(moved(&items, "gone", "a", DropAt::Before), None);
assert_eq!(moved(&items, "x", "gone", DropAt::Before), None);
assert_eq!(
moved(&items, "x", "z", DropAt::Inside),
None,
"a request holds nothing"
);
}
}
+4 -14
View File
@@ -64,7 +64,7 @@ pub struct ColorContrastChecker {
id: ElementId,
text: Hsla,
background: Hsla,
on_change: Option<OnColors>,
on_change: OnColors,
}
impl ColorContrastChecker {
@@ -72,23 +72,15 @@ impl ColorContrastChecker {
id: impl Into<ElementId>,
text: impl Into<Hsla>,
background: impl Into<Hsla>,
on_change: impl Fn(Hsla, Hsla, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
text: text.into(),
background: background.into(),
on_change: None,
on_change: Rc::new(on_change),
}
}
/// Gets the text's and the background's colors after either changes.
pub fn on_change(
mut self,
handler: impl Fn(Hsla, Hsla, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for ColorContrastChecker {
@@ -99,9 +91,7 @@ impl RenderOnce for ColorContrastChecker {
..background
};
let ratio = contrast(over(text, solid), solid);
let on_change = self
.on_change
.unwrap_or_else(|| panic!("color contrast checker {:?} has no on_change", self.id));
let on_change = self.on_change;
let theme = cx.theme();
let well = |name: &'static str, color: Hsla, set: OnColor| {
div()
+69 -73
View File
@@ -30,7 +30,7 @@ pub struct EnvironmentSelector {
id: ElementId,
environments: Vec<Environment>,
selected: SharedString,
on_change: Option<OnKey>,
on_change: OnKey,
}
impl EnvironmentSelector {
@@ -38,22 +38,15 @@ impl EnvironmentSelector {
id: impl Into<ElementId>,
environments: impl IntoIterator<Item = Environment>,
selected: impl Into<SharedString>,
on_change: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
environments: environments.into_iter().collect(),
selected: selected.into(),
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
/// What a row shows for a value: dots for a secret until it is opened.
@@ -84,71 +77,71 @@ impl RenderOnce for EnvironmentSelector {
.environments
.iter()
.find(|environment| environment.key == self.selected)
.unwrap_or_else(|| {
panic!(
"environment selector {id:?}: no environment {}",
self.selected
)
})
.clone();
.cloned();
if chosen.is_none() {
log::error!(
"environment selector {id:?}: no environment {}; none chosen",
self.selected
);
}
let shown = window.use_keyed_state((id.clone(), "shown"), cx, |_, _| Vec::<Secret>::new());
let theme = cx.theme();
let on_change = self
.on_change
.unwrap_or_else(|| panic!("environment selector {id:?} has no on_change"));
let rows = chosen.variables.iter().map(|(name, value)| {
let secret = chosen.secrets.contains(name);
let key: Secret = (chosen.key.clone(), name.clone());
let open = shown.read(cx).contains(&key);
let flip = shown.clone();
let seen = format!(
"environment-{}-{name}-{}",
chosen.key,
if secret && !open { "masked" } else { "shown" }
);
div()
.flex()
.items_center()
.gap_2()
.py_1()
.child(
div()
.flex_none()
.w(theme.label_width())
.font_family(theme.mono_family.clone())
.text_size(theme.text_size(TextSize::Sm))
.child(Ellipsis::new(format!("{{{{{name}}}}}"))),
)
.child(
div()
.flex_1()
.min_w_0()
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg_muted)
.debug_selector(move || seen)
.child(Ellipsis::new(shown_value(value, secret, open))),
)
.children(secret.then(|| {
IconButton::new(
(id.clone(), format!("reveal-{name}")),
if open {
IconName::EyeOff
} else {
IconName::Eye
},
let on_change = self.on_change;
let rows = chosen.iter().flat_map(|chosen| {
chosen.variables.iter().map(|(name, value)| {
let secret = chosen.secrets.contains(name);
let key: Secret = (chosen.key.clone(), name.clone());
let open = shown.read(cx).contains(&key);
let flip = shown.clone();
let seen = format!(
"environment-{}-{name}-{}",
chosen.key,
if secret && !open { "masked" } else { "shown" }
);
div()
.flex()
.items_center()
.gap_2()
.py_1()
.child(
div()
.flex_none()
.w(theme.label_width())
.font_family(theme.mono_family.clone())
.text_size(theme.text_size(TextSize::Sm))
.child(Ellipsis::new(format!("{{{{{name}}}}}"))),
)
.tooltip(if open {
"Hide the value"
} else {
"Show the value"
})
.on_click(move |_, _, cx| {
flip.update(cx, |shown, cx| {
flipped(shown, &key);
cx.notify();
.child(
div()
.flex_1()
.min_w_0()
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg_muted)
.debug_selector(move || seen)
.child(Ellipsis::new(shown_value(value, secret, open))),
)
.children(secret.then(|| {
IconButton::new(
(id.clone(), format!("reveal-{name}")),
if open {
IconName::EyeOff
} else {
IconName::Eye
},
)
.tooltip(if open {
"Hide the value"
} else {
"Show the value"
})
})
}))
.on_click(move |_, _, cx| {
flip.update(cx, |shown, cx| {
flipped(shown, &key);
cx.notify();
})
})
}))
})
});
div()
.flex()
@@ -161,7 +154,7 @@ impl RenderOnce for EnvironmentSelector {
Choice::new(environment.key.clone(), environment.name.clone())
}),
)
.selected(chosen.key.clone())
.selected(self.selected.clone())
.on_change(move |key, window, cx| {
log::info!("environment selector: {key}");
on_change(key, window, cx);
@@ -172,7 +165,10 @@ impl RenderOnce for EnvironmentSelector {
.text_size(theme.text_size(TextSize::Xs))
.text_color(theme.colors.fg_subtle)
.font_weight(FontWeight::MEDIUM)
.child(format!("{} variables", chosen.variables.len())),
.child(match &chosen {
Some(chosen) => format!("{} variables", chosen.variables.len()),
None => "No environment chosen".into(),
}),
)
.child(div().flex().flex_col().children(rows))
}
+4 -13
View File
@@ -57,7 +57,7 @@ pub struct GraphQLExplorer {
answer: Option<SharedString>,
search: Entity<TextInput>,
running: bool,
on_run: Option<OnRun>,
on_run: OnRun,
on_pick: Option<OnKey>,
}
@@ -69,6 +69,7 @@ impl GraphQLExplorer {
query: &Entity<CodeEditor>,
variables: &Entity<CodeEditor>,
search: &Entity<TextInput>,
on_run: impl Fn(String, String, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
@@ -78,7 +79,7 @@ impl GraphQLExplorer {
answer: None,
search: search.clone(),
running: false,
on_run: None,
on_run: Rc::new(on_run),
on_pick: None,
}
}
@@ -94,14 +95,6 @@ impl GraphQLExplorer {
self
}
pub fn on_run(
mut self,
handler: impl Fn(String, String, &mut Window, &mut App) + 'static,
) -> Self {
self.on_run = Some(Rc::new(handler));
self
}
pub fn on_pick(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
@@ -149,9 +142,7 @@ impl RenderOnce for GraphQLExplorer {
.child(text)
};
let tall = theme.list_max_height();
let on_run = self
.on_run
.unwrap_or_else(|| panic!("graphql explorer {id:?} has no on_run"));
let on_run = self.on_run;
let (query, variables) = (self.query.clone(), self.variables.clone());
let typed = variables.read(cx).text().to_string();
let unread = (!typed.trim().is_empty())
+11 -11
View File
@@ -245,19 +245,19 @@ pub(crate) fn diagram(
) -> (Vec<Node>, Vec<Edge>) {
let nodes = tables
.iter()
.map(|table| {
let at = places
.iter()
.find(|(name, _)| *name == table.name)
.unwrap_or_else(|| panic!("ER diagram: no place for {}", table.name))
.1;
table.fields.iter().fold(
.filter_map(|table| {
let Some((_, at)) = places.iter().find(|(name, _)| *name == table.name) else {
log::error!("ER diagram: no place for {}; left out", table.name);
return None;
};
let at = *at;
Some(table.fields.iter().fold(
Node::new(table.name.clone(), table.name.clone(), at),
|node, field| {
node.input(Port::new(field.name.clone(), field.name.clone(), "field"))
.output(Port::new(field.name.clone(), field.typed(), "field"))
},
)
))
})
.collect();
let edges = tables
@@ -337,8 +337,8 @@ mod tests {
}
#[test]
#[should_panic(expected = "no place for orders")]
fn a_table_without_a_place_fails_loud() {
diagram(&[DbTable::new("orders", [])], &[]);
fn a_table_without_a_place_is_left_out() {
let (nodes, edges) = diagram(&[DbTable::new("orders", [])], &[]);
assert!(nodes.is_empty() && edges.is_empty());
}
}
+4 -14
View File
@@ -53,7 +53,7 @@ pub struct WebSocketConsole {
messages: Vec<SocketMessage>,
draft: Entity<TextInput>,
now: Timestamp,
on_send: Option<OnText>,
on_send: OnText,
on_toggle: Option<Run>,
}
@@ -66,6 +66,7 @@ impl WebSocketConsole {
messages: impl IntoIterator<Item = SocketMessage>,
draft: &Entity<TextInput>,
now: Timestamp,
on_send: impl Fn(SharedString, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
@@ -74,20 +75,11 @@ impl WebSocketConsole {
messages: messages.into_iter().collect(),
draft: draft.clone(),
now,
on_send: None,
on_send: Rc::new(on_send),
on_toggle: None,
}
}
/// Gets the text to send; the field empties after.
pub fn on_send(
mut self,
handler: impl Fn(SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_send = Some(Rc::new(handler));
self
}
/// Asked to connect while closed, or to disconnect while open.
pub fn on_toggle(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
self.on_toggle = Some(Rc::new(handler));
@@ -159,9 +151,7 @@ impl RenderOnce for WebSocketConsole {
};
let open = self.state == SocketState::Open;
let (messages, now) = (Rc::new(self.messages), self.now);
let on_send = self
.on_send
.unwrap_or_else(|| panic!("websocket console {:?} has no on_send", self.id));
let on_send = self.on_send;
let draft = self.draft.clone();
let ready = open && !draft.read(cx).text().trim().is_empty();
let send = Rc::new(move |window: &mut Window, cx: &mut App| {
+16 -33
View File
@@ -100,32 +100,26 @@ struct Room(f32);
pub struct TableStructureEditor {
id: ElementId,
fields: Vec<Field>,
on_change: Option<OnEdit<Vec<Field>>>,
on_change: OnEdit<Vec<Field>>,
}
impl TableStructureEditor {
pub fn new(id: impl Into<ElementId>, fields: impl IntoIterator<Item = Field>) -> Self {
pub fn new(
id: impl Into<ElementId>,
fields: impl IntoIterator<Item = Field>,
on_change: impl Fn(Vec<Field>, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
fields: fields.into_iter().collect(),
on_change: None,
on_change: Rc::new(on_change),
}
}
pub fn on_change(
mut self,
handler: impl Fn(Vec<Field>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
}
impl RenderOnce for TableStructureEditor {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let on_change = self
.on_change
.unwrap_or_else(|| panic!("table structure editor {:?} has no on_change", self.id));
let on_change = self.on_change;
let (id, fields, on_change) = (self.id, self.fields, &on_change);
let room = window.use_keyed_state((id.clone(), "room"), cx, |_, _| Room::default());
let rem = window.rem_size();
@@ -303,8 +297,8 @@ pub struct IndexManager {
id: ElementId,
indexes: Vec<Index>,
fields: Vec<SharedString>,
on_drop: Option<OnName>,
on_create: Option<OnIndex>,
on_drop: OnName,
on_create: OnIndex,
}
impl IndexManager {
@@ -313,34 +307,23 @@ impl IndexManager {
id: impl Into<ElementId>,
indexes: impl IntoIterator<Item = Index>,
fields: impl IntoIterator<Item = impl Into<SharedString>>,
on_create: impl Fn(Index, &mut Window, &mut App) + 'static,
on_drop: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
indexes: indexes.into_iter().collect(),
fields: fields.into_iter().map(Into::into).collect(),
on_drop: None,
on_create: None,
on_drop: Rc::new(on_drop),
on_create: Rc::new(on_create),
}
}
pub fn on_drop(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_drop = Some(Rc::new(handler));
self
}
pub fn on_create(mut self, handler: impl Fn(Index, &mut Window, &mut App) + 'static) -> Self {
self.on_create = Some(Rc::new(handler));
self
}
}
impl RenderOnce for IndexManager {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let on_create = self.on_create.expect("an index manager needs on_create");
let on_drop = self.on_drop.expect("an index manager needs on_drop");
let on_create = self.on_create;
let on_drop = self.on_drop;
let id = self.id;
let draft = window.use_keyed_state((id.clone(), "draft"), cx, |window, cx| Draft {
name: cx.new(|cx| TextInput::new(window, cx).placeholder("Index name")),
+54 -17
View File
@@ -100,10 +100,17 @@ fn console(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench
_ => SocketState::Closed,
};
let toggled = owner.clone();
WebSocketConsole::new("socket", "wss://example.com", state, [], &draft, now)
.on_send(move |text, _, cx| say(&owner, format!("send {text}"), cx))
.on_toggle(move |_, cx| say(&toggled, "toggle".into(), cx))
.into_any_element()
WebSocketConsole::new(
"socket",
"wss://example.com",
state,
[],
&draft,
now,
move |text, _, cx| say(&owner, format!("send {text}"), cx),
)
.on_toggle(move |_, cx| say(&toggled, "toggle".into(), cx))
.into_any_element()
}
/// Stops: Disconnect, the message field, then Send once it holds words.
@@ -179,9 +186,15 @@ fn graph(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench>)
return div().into_any_element();
};
let search = window.use_keyed_state("search", cx, TextInput::new);
GraphQLExplorer::new("graph", [], &query, &variables, &search)
.on_run(move |query, variables, _, cx| say(&owner, format!("{query} {variables}"), cx))
.into_any_element()
GraphQLExplorer::new(
"graph",
[],
&query,
&variables,
&search,
move |query, variables, _, cx| say(&owner, format!("{query} {variables}"), cx),
)
.into_any_element()
}
/// Stops: the schema, the query, the variables, then Run while they read.
@@ -216,10 +229,10 @@ fn environments(bench: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) ->
variables: vec![("token".into(), token.to_string().into())],
secrets: vec!["token".into()],
};
let selected = if bench.choice == 0 {
"staging"
} else {
"production"
let selected = match bench.choice {
0 => "staging",
1 => "production",
_ => "gone",
};
EnvironmentSelector::new(
"environments",
@@ -228,8 +241,8 @@ fn environments(bench: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) ->
env("production", "sk_live_9999"),
],
selected,
|_, _, _| {},
)
.on_change(|_, _, _| {})
.into_any_element()
}
@@ -265,10 +278,16 @@ fn schema(_: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench>) ->
kind: TypeKind::Object,
fields: vec![("id".into(), "ID!".into())],
};
GraphQLExplorer::new("graph", [order], &query, &variables, &search)
.on_run(|_, _, _, _| {})
.on_pick(move |path, _, cx| say(&owner, path.to_string(), cx))
.into_any_element()
GraphQLExplorer::new(
"graph",
[order],
&query,
&variables,
&search,
|_, _, _, _| {},
)
.on_pick(move |path, _, cx| say(&owner, path.to_string(), cx))
.into_any_element()
}
/// Stops: the schema first, its cursor on Order.
@@ -297,8 +316,8 @@ fn log(_: &Bench, window: &mut Window, cx: &mut App, owner: Entity<Bench>) -> An
messages,
&draft,
now,
move |text, _, cx| say(&owner, format!("send {text}"), cx),
)
.on_send(move |text, _, cx| say(&owner, format!("send {text}"), cx))
.into_any_element()
}
@@ -314,3 +333,21 @@ fn the_log_holds_at_its_newest_message(cx: &mut TestAppContext) {
"the newest message lies inside the log: {newest:?} in {log:?}"
);
}
#[gpui::test]
fn an_environment_that_left_shows_none_chosen(cx: &mut TestAppContext) {
let (host, cx) = bench(environments, cx);
host.update(cx, |bench, cx| {
bench.choice = 2;
cx.notify();
});
settle(cx);
assert!(
cx.debug_bounds("environment-staging-token-masked")
.is_none()
);
assert!(
cx.debug_bounds("environment-production-token-masked")
.is_none()
);
}
+15 -14
View File
@@ -107,15 +107,18 @@ fn enter_opens_a_connection_and_a_rows_button_leaves_the_row_alone(cx: &mut Test
}
fn structure(_: &Bench, _: &mut Window, _: &mut App, owner: Entity<Bench>) -> AnyElement {
TableStructureEditor::new("structure", [Field::new("id", "int").primary()])
.on_change(move |fields, _, cx| {
TableStructureEditor::new(
"structure",
[Field::new("id", "int").primary()],
move |fields, _, cx| {
let names: Vec<String> = fields
.iter()
.map(|field| format!("{}:{}", field.name, field.ty))
.collect();
say(&owner, names.join(" "), cx)
})
.into_any_element()
},
)
.into_any_element()
}
/// A row's stops: name, type, empty, key, default and remove; Add field comes after.
@@ -204,16 +207,14 @@ fn another_engine_brings_its_port(cx: &mut TestAppContext) {
fn narrow(_: &Bench, _: &mut Window, _: &mut App, _: Entity<Bench>) -> AnyElement {
div()
.w(px(280.0))
.child(
TableStructureEditor::new(
"structure",
[
Field::new("user_id", "timestamptz"),
Field::new("total", "numeric"),
],
)
.on_change(|_, _, _| {}),
)
.child(TableStructureEditor::new(
"structure",
[
Field::new("user_id", "timestamptz"),
Field::new("total", "numeric"),
],
|_, _, _| {},
))
.into_any_element()
}
+2 -1
View File
@@ -47,7 +47,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca
- [x] P30 Calendar — `src/calendar`
- [x] P31 Project — `src/project`
- [x] P32 Canvas & Design — `src/canvas`
- [ ] P33 DB & Dev Tools — `src/devtools`
- [x] P33 DB & Dev Tools — `src/devtools`
- [ ] P34 Dashboard — `src/dashboard`
- [ ] P35 Settings — `src/settings`
- [ ] P36 Account — `src/account`
@@ -98,3 +98,4 @@ One line per item: what changed, what stayed and why, review rounds.
- P30: calendar views step only within jiff's range less a year at each end (`calendar::within`, `stepped`), so every grid around a day fits, and a step past logs and stays; recurrences end where jiff's range ends; the year view's marks stop at an event's last day; the event editor refuses moves, ends, all-day starts and switches to timed that leave jiff's range or a stale painted kind, each with a log; a backward timed draft's days are its first day. Kept: owner dates at jiff's far edges, one list's uniqueness, an event's own order, one organizer, working hours, slots, reminder offsets, constant patterns, picks that always name a value. Review: 2 rounds.
- P31: a gone assignee logs and shows nobody; a milestone past its count fills, the text real; a roadmap past jiff's range ends inclusively on its last day; the database timeline leaves out and counts tasks due before they start and counts months in `i32`, and its calendar keeps to days `calendar::within` holds; the board's drag and drop take only columns, cards and places this frame holds. Kept: one list's uniqueness, an initiative's and a milestone's own order, shares, month counts, pomodoro lengths, issue keys, lookups over the components' own tables. Review: 3 rounds.
- P32: the eight design panels take their handlers in `new` (AGENTS.md says so); edges, links, wires, links in progress, resizes, restacks, topics and whiteboard edits that name a node, port, shape, layer or topic gone from this frame log and change nothing; a wire's release fits its source as it now stands; `Topic::adding`, `removing` and `renaming` return `Option`; a mind map's selection, a palette's tool and a history position no longer held mark none. Kept: one-value contracts (alignment counts, frames, zoom, polygon sides, brush hues and type weights among the theme's tables), lookups over the panels' own option tables, button-gated expects. Review: 2 rounds.
- P33: six devtools take their handlers in `new` (contrast checker, environment selector, GraphQL explorer, WebSocket console, structure editor, index manager); `collection::moved` returns `Option` for a gone key or target or a drop into a request; a gone environment shows none chosen; an ER table without a place stays out. Kept: readings, costs, lookups over the components' own tables, button-gated expects, JSON that read writes back. Review: 1 round.