Panics · P25 Generative: gone choices mark none, overruns peg; seed fields hold seeds through fits, undo and input methods

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 02:12:07 -04:00
1 parent fb7ee42e3d
commit 68a98198b0
17 files changed
+451 -123

No files matched your search

+16 -4
View File
@@ -180,6 +180,13 @@ impl TextInput {
&self.text
}
/// The text as committed: while an input method composes, the text before it began.
pub(crate) fn committed(&self) -> &str {
self.composing
.as_ref()
.map_or(&self.text, |start| &start.text)
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
@@ -245,7 +252,10 @@ impl TextInput {
pub fn set_text(&mut self, text: impl Into<String>, cx: &mut Context<Self>) {
let text = self.admit(&text.into(), 0);
let text = match &self.fit {
Some(fit) => fit(&self.text, 0..self.text.len(), &text).0,
Some(fit) => {
let base = self.committed();
fit(base, 0..base.len(), &text).0
}
None => text,
};
self.selection = text.len()..text.len();
@@ -256,9 +266,10 @@ impl TextInput {
cx.notify();
}
/// Reshapes every edit, as a mask does: text, replaced range and typed text in; text and caret out.
/// Reshapes every edit, as a mask does: text, replaced range and typed text in; text and caret out. Undo starts afresh, since earlier text need not fit.
pub(crate) fn set_fit(&mut self, fit: Fit) {
self.fit = Some(fit);
self.history = History::default();
}
/// Replaces the selection, as typing would. An undo step.
@@ -353,11 +364,12 @@ impl TextInput {
}
}
/// The state as committed, so undo never keeps an input method's text before it commits.
fn snapshot(&self) -> Snapshot {
Snapshot {
self.composing.clone().unwrap_or_else(|| Snapshot {
text: self.text.clone(),
selection: self.selection.clone(),
}
})
}
pub(crate) fn restore(&mut self, snapshot: Snapshot, cx: &mut Context<Self>) {
+10 -6
View File
@@ -80,12 +80,16 @@ impl RenderOnce for ABCompareView {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
let count = self.sides.len();
assert!((2..=4).contains(&count), "a comparison of {count} sides");
if let Some(Verdict::Side(ix)) = self.verdict {
assert!(ix < count, "verdict for side {ix} of {count}");
}
let verdict = match self.verdict {
Some(Verdict::Side(ix)) if ix >= count => {
log::error!("comparison: verdict for side {ix} of {count}; none given");
None
}
verdict => verdict,
};
let theme = cx.theme();
let colors = theme.colors.clone();
let decided = self.verdict.is_some();
let decided = verdict.is_some();
let judge = |verdict: Verdict, label: SharedString, key: String| {
self.on_verdict.clone().map(|judge| {
Button::new((self.id.clone(), key), label)
@@ -103,7 +107,7 @@ impl RenderOnce for ABCompareView {
.into_iter()
.enumerate()
.map(|(ix, (name, answer))| {
let won = self.verdict == Some(Verdict::Side(ix));
let won = verdict == Some(Verdict::Side(ix));
let title = heading(ix, &name, self.blind, decided);
titles.push(title.clone());
div()
@@ -147,7 +151,7 @@ impl RenderOnce for ABCompareView {
)
})
.collect();
let verdicts = match self.verdict {
let verdicts = match verdict {
Some(Verdict::Tie) => vec![div().child("Called a tie").into_any_element()],
Some(Verdict::Side(_)) => Vec::new(),
None => titles
+15 -7
View File
@@ -63,14 +63,22 @@ impl EmbeddingVisualizer {
for (ix, name) in groups.iter().enumerate() {
assert!(!groups[..ix].contains(name), "group {name} named twice");
}
let points: Vec<Embedded> = points
.into_iter()
.filter(|point| {
let held = point.group < groups.len();
if !held {
log::error!(
"embedding: point {} in group {} of {}; left out",
point.key,
point.group,
groups.len()
);
}
held
})
.collect();
for point in &points {
assert!(
point.group < groups.len(),
"point {} in group {} of {}",
point.key,
point.group,
groups.len()
);
assert!(
point.at.iter().all(|axis| (-1.0..=1.0).contains(axis)),
"point {} sits outside -1 to 1",
+5 -2
View File
@@ -225,13 +225,16 @@ fn shaped(
}
}
/// `at` as shares of `bounds`.
/// `at` as shares of `bounds`, kept on the picture as the pen keeps its points in the pad; a canvas that shrank mid-stroke ends the stroke at its edge.
fn share(bounds: Bounds<Pixels>, at: Point<Pixels>) -> (f32, f32) {
let (width, height) = (
f32::from(bounds.size.width).max(1.0),
f32::from(bounds.size.height).max(1.0),
);
(f32::from(at.x) / width, f32::from(at.y) / height)
(
(f32::from(at.x) / width).clamp(0.0, 1.0),
(f32::from(at.y) / height).clamp(0.0, 1.0),
)
}
impl RenderOnce for InpaintCanvas {
+5 -2
View File
@@ -203,7 +203,10 @@ impl ModelStatus {
/// GPU memory it holds and the card's whole, in bytes.
pub fn memory(mut self, used: u64, total: u64) -> Self {
assert!(total > 0 && used <= total, "GPU memory {used} of {total}");
assert!(total > 0, "GPU memory {used} of none");
if used > total {
log::error!("model status: GPU memory {used} past {total}; the meter fills");
}
self.memory = Some((used, total));
self
}
@@ -247,7 +250,7 @@ impl RenderOnce for ModelStatus {
Meter::new(
(self.id.clone(), "memory"),
"GPU memory",
used as f32 / total as f32,
used.min(total) as f32 / total as f32,
)
.detail(format!(
"{} of {}",
+7 -6
View File
@@ -38,12 +38,13 @@ impl AspectRatioPicker {
selected: (u32, u32),
) -> Self {
let ratios: Vec<(u32, u32)> = ratios.into_iter().collect();
assert!(
ratios.contains(&selected),
"ratio {}:{} is not among the ratios",
selected.0,
selected.1
);
if !ratios.contains(&selected) {
log::error!(
"aspect ratio: {}:{} is not among the ratios; none selected",
selected.0,
selected.1
);
}
Self {
id: id.into(),
ratios,
+17 -8
View File
@@ -4,7 +4,7 @@ use std::{
rc::Rc,
};
use gpui::{App, ElementId, Entity, IntoElement, RenderOnce, Window};
use gpui::{App, ElementId, Entity, EntityId, IntoElement, RenderOnce, Window};
use crate::{
buttons::IconButton,
@@ -43,22 +43,31 @@ impl SeedInput {
}
}
/// The seed in `field`; none while it is empty.
/// The seed in `field`, read after a `SeedInput` drew it; none while it is empty.
pub fn read(field: &Entity<TextInput>, cx: &App) -> Option<u32> {
let text = field.read(cx).text();
let text = field.read(cx).committed();
(!text.is_empty()).then(|| text.parse().expect("a seed field holds digits"))
}
}
impl RenderOnce for SeedInput {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let fitted = window.use_keyed_state((self.id.clone(), "fitted"), cx, |_, _| false);
if !*fitted.read(cx) {
fitted.update(cx, |fitted, _| *fitted = true);
let fitted =
window.use_keyed_state((self.id.clone(), "fitted"), cx, |_, _| None::<EntityId>);
let field_id = self.field.entity_id();
if *fitted.read(cx) != Some(field_id) {
fitted.update(cx, |fitted, _| *fitted = Some(field_id));
self.field.update(cx, |input, cx| {
input.set_fit(Rc::new(digits));
let text = input.text().to_string();
input.set_text(text, cx);
let kept = match text.is_empty() || text.parse::<u32>().is_ok() {
true => text,
false => {
log::error!("seed input: {text:?} is no seed; cleared");
String::new()
}
};
input.set_fit(Rc::new(digits));
input.set_text(kept, cx);
input.set_placeholder("Random", cx);
});
}
+3 -4
View File
@@ -39,10 +39,9 @@ impl StylePresetPicker {
) -> Self {
let (presets, selected): (Vec<StylePreset>, SharedString) =
(presets.into_iter().collect(), selected.into());
assert!(
presets.iter().any(|preset| preset.key == selected),
"style {selected} is not among the presets"
);
if !presets.iter().any(|preset| preset.key == selected) {
log::error!("style presets: {selected} is not among the presets; none selected");
}
Self {
id: id.into(),
presets,
+1
View File
@@ -8,6 +8,7 @@ mod models;
mod results;
mod settings;
mod sound;
mod stale;
fn settle(cx: &mut VisualTestContext) {
cx.run_until_parked();
+1 -7
View File
@@ -5,7 +5,7 @@ use gpui::{Context, IntoElement, ParentElement, Render, Styled, TestAppContext,
use super::{press, settle, setup, tab};
use crate::generative::{
Download, DownloadState, FineTuneJobCard, HardwareMonitor, ModelCard, ModelDownloadManager,
ModelState, ModelStatus, Reading, TunePhase,
Reading, TunePhase,
};
type Heard = Rc<RefCell<Vec<String>>>;
@@ -120,12 +120,6 @@ fn cards_offer_what_their_state_allows(cx: &mut TestAppContext) {
);
}
#[test]
#[should_panic(expected = "GPU memory 5 of 4")]
fn memory_past_the_card_fails_loud() {
let _ = ModelStatus::new("model", "Orchid", ModelState::Ready).memory(5, 4);
}
#[test]
#[should_panic(expected = "GPU reads outside 0 to 1")]
fn a_reading_past_full_fails_loud() {
+270
View File
@@ -0,0 +1,270 @@
use std::{cell::RefCell, rc::Rc, time::Duration};
use gpui::{
AppContext as _, Context, Entity, EntityInputHandler as _, Focusable as _, IntoElement,
Modifiers, MouseButton, ParentElement, Render, Styled, TestAppContext, Window, div, point, px,
};
use super::{press, settle, setup, tab};
use crate::forms::TextInput;
use crate::generative::{
ABCompareView, AspectRatioPicker, Embedded, EmbeddingVisualizer, FineTuneJobCard,
InpaintCanvas, MaskStroke, ModelState, ModelStatus, Outcome, PromptVersionHistory, SeedInput,
Shot, StylePreset, StylePresetPicker, TTSVoicePicker, TunePhase, VariationPicker, Verdict,
VideoGenerationTimeline, Voice,
};
/// Each picker asked for what its list no longer holds, and counts past their whole.
struct Stale;
impl Render for Stale {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let preset = StylePreset {
key: "ink".into(),
name: "Ink".into(),
picture: None,
};
let voice = Voice {
key: "iris".into(),
name: "Iris".into(),
about: "a voice".into(),
tags: Vec::new(),
};
let shot = Shot {
key: "a".into(),
prompt: "a pan".into(),
length: Duration::from_secs(4),
outcome: Outcome::Done("missing.jpg".into()),
};
let point = |group| Embedded {
key: format!("in-{group}").into(),
label: "a point".into(),
group,
at: [0.0, 0.0, 0.0],
};
div()
.w(px(640.0))
.child(
ABCompareView::new("compare")
.side("Orchid", "one answer")
.side("Juniper", "another")
.verdict(Verdict::Side(5)),
)
.child(AspectRatioPicker::new("ratios", [(1, 1), (4, 3)], (16, 9)))
.child(StylePresetPicker::new("styles", [preset], "gone"))
.child(
VideoGenerationTimeline::new("timeline", [shot], Duration::ZERO).selected("gone"),
)
.child(TTSVoicePicker::new("voices", [voice]).selected("gone"))
.child(VariationPicker::new("variations", ["a.jpg"], 1.5, 4))
.child(PromptVersionHistory::new("history", [], 0))
.child(FineTuneJobCard::new(
"tune",
"Orchid tuned",
"Orchid",
"notes.jsonl",
TunePhase::Running(7, 3, 0.5),
))
.child(ModelStatus::new("model", "Orchid", ModelState::Ready).memory(5, 4))
.child(div().h(px(320.0)).child(EmbeddingVisualizer::new(
"space",
[point(0), point(9)],
["Docs"],
)))
}
}
#[gpui::test]
fn gone_choices_mark_none_and_overruns_peg(cx: &mut TestAppContext) {
setup(cx);
let (_, cx) = cx.add_window_view(|_, _| Stale);
settle(cx);
}
/// A seed field the owner may swap for another.
struct Seeded(Entity<TextInput>);
impl Render for Seeded {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div().w(px(420.0)).child(SeedInput::new("seed", &self.0))
}
}
#[gpui::test]
fn a_seed_field_fits_whatever_it_held_and_each_new_field(cx: &mut TestAppContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|window, cx| {
let field = cx.new(|cx| TextInput::new(window, cx));
field.update(cx, |input, cx| {
input.set_text("4294967296", cx);
input.insert("7", cx);
});
Seeded(field)
});
cx.update(|window, _| window.activate_window());
settle(cx);
view.read_with(cx, |view, cx| {
assert_eq!(SeedInput::read(&view.0, cx), None)
});
cx.update(|window, cx| {
let field = view.read(cx).0.clone();
window.focus(&field.focus_handle(cx), cx);
});
cx.simulate_keystrokes("secondary-z");
settle(cx);
view.read_with(cx, |view, cx| {
assert_eq!(
SeedInput::read(&view.0, cx),
None,
"undo reaches no unfit text"
)
});
let field = cx.update(|window, cx| cx.new(|cx| TextInput::new(window, cx)));
view.update(cx, |view, cx| {
view.0 = field.clone();
cx.notify();
});
settle(cx);
cx.update(|window, cx| window.focus(&field.focus_handle(cx), cx));
cx.simulate_input("12a3");
settle(cx);
view.read_with(cx, |view, cx| {
assert_eq!(SeedInput::read(&view.0, cx), Some(123))
});
cx.update(|window, cx| {
field.update(cx, |input, cx| {
input.replace_and_mark_text_in_range(None, "ni", None, window, cx)
})
});
settle(cx);
assert_eq!(
field.read_with(cx, |input, _| input.text().to_string()),
"123ni"
);
view.read_with(cx, |view, cx| {
assert_eq!(
SeedInput::read(&view.0, cx),
Some(123),
"a composition is not yet the seed"
)
});
cx.simulate_keystrokes("secondary-z secondary-shift-z");
settle(cx);
view.read_with(cx, |view, cx| {
assert_eq!(
SeedInput::read(&view.0, cx),
Some(123),
"redo brings back what was committed"
)
});
cx.update(|window, cx| {
field.update(cx, |input, cx| {
input.replace_and_mark_text_in_range(None, "ni", None, window, cx);
input.set_text("4294967296", cx);
})
});
settle(cx);
view.read_with(cx, |view, cx| {
assert_eq!(
SeedInput::read(&view.0, cx),
Some(123),
"a refused text keeps what was committed"
)
});
}
type Heard = Rc<RefCell<Vec<String>>>;
/// Variations whose chosen one may leave, and the actions asked.
struct Varied {
chosen: usize,
heard: Heard,
}
impl Render for Varied {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let (vary, upscale) = (self.heard.clone(), self.heard.clone());
div().w(px(420.0)).child(
VariationPicker::new("variations", ["a.jpg"], 1.5, self.chosen)
.on_vary(move |strong, _, _| vary.borrow_mut().push(format!("vary {strong}")))
.on_upscale(move |_, _| upscale.borrow_mut().push("upscale".into())),
)
}
}
#[gpui::test]
fn no_picture_chosen_offers_no_way_on(cx: &mut TestAppContext) {
setup(cx);
let heard = Heard::default();
let kept = heard.clone();
let (view, cx) = cx.add_window_view(|_, _| Varied {
chosen: 0,
heard: kept,
});
cx.update(|window, _| window.activate_window());
settle(cx);
tab(2, cx);
press("enter", cx);
assert_eq!(*heard.borrow(), ["vary false"]);
heard.borrow_mut().clear();
view.update(cx, |view, cx| {
view.chosen = 4;
cx.notify();
});
settle(cx);
cx.update(|window, cx| window.blur(cx));
for _ in 0..4 {
tab(1, cx);
press("enter", cx);
}
assert_eq!(*heard.borrow(), Vec::<String>::new());
}
/// A canvas the owner narrows, keeping every stroke it hands over.
struct Painting {
width: f32,
strokes: Vec<MaskStroke>,
}
impl Render for Painting {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let owner = cx.entity();
div().w(px(self.width)).child(
InpaintCanvas::new("canvas", "missing.jpg", 2.0, self.strokes.clone()).on_stroke(
move |stroke, _, cx| {
owner.update(cx, |view, cx| {
view.strokes.push(stroke);
cx.notify();
})
},
),
)
}
}
#[gpui::test]
fn a_canvas_that_shrinks_under_the_pen_ends_the_stroke_on_it(cx: &mut TestAppContext) {
setup(cx);
let (view, cx) = cx.add_window_view(|_, _| Painting {
width: 400.0,
strokes: Vec::new(),
});
cx.update(|window, _| window.activate_window());
settle(cx);
let none = Modifiers::none();
cx.simulate_mouse_down(point(px(300.0), px(100.0)), MouseButton::Left, none);
cx.simulate_mouse_move(point(px(320.0), px(100.0)), MouseButton::Left, none);
cx.simulate_mouse_move(point(px(340.0), px(100.0)), MouseButton::Left, none);
settle(cx);
view.update(cx, |view, cx| {
view.width = 200.0;
cx.notify();
});
settle(cx);
cx.simulate_mouse_up(point(px(160.0), px(50.0)), MouseButton::Left, none);
settle(cx);
let strokes = view.read_with(cx, |view, _| view.strokes.clone());
assert_eq!(strokes.len(), 1);
let on = |share: f32| (0.0..=1.0).contains(&share);
assert!(strokes[0].points.iter().all(|&(x, y)| on(x) && on(y)));
}
+7 -2
View File
@@ -71,11 +71,16 @@ impl FineTuneJobCard {
data: impl Into<SharedString>,
phase: TunePhase,
) -> Self {
if let TunePhase::Running(epoch, epochs, share) = phase {
let mut phase = phase;
if let TunePhase::Running(epoch, epochs, share) = &mut phase {
assert!(
(1..=epochs).contains(&epoch) && (0.0..=1.0).contains(&share),
*epochs > 0 && (0.0..=1.0).contains(share),
"epoch {epoch} of {epochs} at {share}"
);
if !(1..=*epochs).contains(epoch) {
log::error!("fine-tuning: epoch {epoch} of {epochs}; pegged");
*epoch = (*epoch).clamp(1, *epochs);
}
}
Self {
id: id.into(),
+59 -47
View File
@@ -21,7 +21,7 @@ pub struct VariationPicker {
id: ElementId,
pictures: Vec<SharedString>,
ratio: f32,
chosen: usize,
chosen: Option<usize>,
on_choose: Option<Pick>,
on_vary: Option<OnFlag>,
on_upscale: Option<Run>,
@@ -36,16 +36,18 @@ impl VariationPicker {
chosen: usize,
) -> Self {
let pictures: Vec<SharedString> = pictures.into_iter().map(Into::into).collect();
assert!(
chosen < pictures.len(),
"variation {chosen} of {}",
pictures.len()
);
let held = chosen < pictures.len();
if !held {
log::error!(
"variations: variation {chosen} of {}; none chosen",
pictures.len()
);
}
Self {
id: id.into(),
pictures,
ratio: checked_ratio(ratio),
chosen,
chosen: held.then_some(chosen),
on_choose: None,
on_vary: None,
on_upscale: None,
@@ -89,45 +91,50 @@ impl RenderOnce for VariationPicker {
let (large, small) = (theme.radius(Radius::Lg), theme.radius(Radius::Md));
let thumb = theme.avatar_size(AvatarSize::Xl);
let length = motion::duration(motion::BASE, cx);
let picture = Image::new(
(self.id.clone(), format!("large-{}", self.chosen)),
source(&self.pictures[self.chosen]),
)
.size_full()
.rounded(large);
let shown = match turn {
0 => picture.into_any_element(),
_ => div()
.size_full()
.child(picture)
.with_animation(
(self.id.clone(), format!("fade-{turn}")),
Animation::new(length).with_easing(motion::ease_out_cubic),
|shown, t| shown.opacity(0.4 + 0.6 * t),
)
.into_any_element(),
};
let shown = self.chosen.map(|chosen| {
let picture = Image::new(
(self.id.clone(), format!("large-{chosen}")),
source(&self.pictures[chosen]),
)
.size_full()
.rounded(large);
match turn {
0 => picture.into_any_element(),
_ => div()
.size_full()
.child(picture)
.with_animation(
(self.id.clone(), format!("fade-{turn}")),
Animation::new(length).with_easing(motion::ease_out_cubic),
|shown, t| shown.opacity(0.4 + 0.6 * t),
)
.into_any_element(),
}
});
let vary = |strong: bool, label: &'static str| {
self.on_vary.clone().map(|vary| {
Button::new((self.id.clone(), label), label)
.variant(ButtonVariant::Secondary)
.size(ControlSize::Sm)
.icon(IconName::Shuffle)
.on_click(move |_, window, cx| {
log::info!("variation picker: vary, strong {strong}");
vary(strong, window, cx)
})
})
self.on_vary
.clone()
.filter(|_| self.chosen.is_some())
.map(|vary| {
Button::new((self.id.clone(), label), label)
.variant(ButtonVariant::Secondary)
.size(ControlSize::Sm)
.icon(IconName::Shuffle)
.on_click(move |_, window, cx| {
log::info!("variation picker: vary, strong {strong}");
vary(strong, window, cx)
})
})
};
div()
.flex()
.flex_col()
.gap_3()
.child(framed(self.ratio, cx).rounded(large).child(shown))
.child(framed(self.ratio, cx).rounded(large).children(shown))
.child(div().flex().flex_wrap().gap_2().children(
self.pictures.iter().zip(thumbs).enumerate().map(
|(ix, (picture, (focus, focused)))| {
let chosen = ix == self.chosen;
let chosen = self.chosen == Some(ix);
let choose = self.on_choose.clone();
div()
.id((self.id.clone(), format!("thumb-{ix}")))
@@ -176,16 +183,21 @@ impl RenderOnce for VariationPicker {
.gap_2()
.children(vary(false, "Vary subtly"))
.children(vary(true, "Vary strongly"))
.children(self.on_upscale.clone().map(|upscale| {
Button::new((self.id.clone(), "upscale"), "Upscale")
.variant(ButtonVariant::Secondary)
.size(ControlSize::Sm)
.icon(IconName::Maximize2)
.on_click(move |_, window, cx| {
log::info!("variation picker: upscale");
upscale(window, cx)
})
})),
.children(
self.on_upscale
.clone()
.filter(|_| self.chosen.is_some())
.map(|upscale| {
Button::new((self.id.clone(), "upscale"), "Upscale")
.variant(ButtonVariant::Secondary)
.size(ControlSize::Sm)
.icon(IconName::Maximize2)
.on_click(move |_, window, cx| {
log::info!("variation picker: upscale");
upscale(window, cx)
})
}),
),
)
}
}
+23 -16
View File
@@ -118,7 +118,7 @@ pub struct PromptVersion {
pub struct PromptVersionHistory {
id: ElementId,
versions: Vec<PromptVersion>,
chosen: usize,
chosen: Option<usize>,
on_choose: Option<Pick>,
on_restore: Option<Pick>,
}
@@ -131,15 +131,17 @@ impl PromptVersionHistory {
chosen: usize,
) -> Self {
let versions: Vec<PromptVersion> = versions.into_iter().collect();
assert!(
chosen < versions.len(),
"version {chosen} of {}",
versions.len()
);
let held = chosen < versions.len();
if !held {
log::error!(
"prompt versions: version {chosen} of {}; none chosen",
versions.len()
);
}
Self {
id: id.into(),
versions,
chosen,
chosen: held.then_some(chosen),
on_choose: None,
on_restore: None,
}
@@ -175,13 +177,18 @@ impl RenderOnce for PromptVersionHistory {
let theme = cx.theme();
let colors = theme.colors.clone();
let chosen = self.chosen;
let (heading, before) = compared(&self.versions, chosen);
let shown = &self.versions[chosen];
let restore = self
.on_restore
.clone()
.filter(|_| chosen > 0)
.map(|restore| {
let diff = chosen.map(|chosen| {
let (heading, before) = compared(&self.versions, chosen);
(heading, before, self.versions[chosen].text.clone())
});
let heading: SharedString = match &diff {
Some((heading, ..)) => heading.clone(),
None => "No version is chosen".into(),
};
let restore = chosen
.filter(|chosen| *chosen > 0)
.zip(self.on_restore.clone())
.map(|(chosen, restore)| {
Button::new((self.id.clone(), "restore"), "Restore")
.variant(ButtonVariant::Secondary)
.size(ControlSize::Sm)
@@ -192,7 +199,7 @@ impl RenderOnce for PromptVersionHistory {
});
let rows = self.versions.iter().zip(focuses).enumerate().map(
|(ix, (version, (focus, focused)))| {
let on = ix == chosen;
let on = chosen == Some(ix);
let choose = self.on_choose.clone();
div()
.id((self.id.clone(), format!("version-{ix}")))
@@ -291,7 +298,7 @@ impl RenderOnce for PromptVersionHistory {
)
.children(restore),
)
.child(PromptDiff::new(before, shown.text.clone())),
.children(diff.map(|(_, before, text)| PromptDiff::new(before, text))),
)
}
}
+5 -3
View File
@@ -93,9 +93,11 @@ impl VideoGenerationTimeline {
impl RenderOnce for VideoGenerationTimeline {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let named = (self.selected.as_ref())
.is_none_or(|key| self.shots.iter().any(|shot| &shot.key == key));
assert!(named, "the chosen shot is not listed");
if let Some(key) = &self.selected
&& !self.shots.iter().any(|shot| &shot.key == key)
{
log::error!("video timeline: shot {key} is not listed; none selected");
}
let pickable = self.on_select.is_some();
let scroll = window
.use_keyed_state((self.id.clone(), "scroll"), cx, |_, _| ScrollHandle::new())
+5 -8
View File
@@ -80,14 +80,11 @@ impl TTSVoicePicker {
impl RenderOnce for TTSVoicePicker {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let known = |key: &Option<SharedString>| {
key.as_ref()
.is_none_or(|key| self.voices.iter().any(|voice| &voice.key == key))
};
assert!(
known(&self.selected) && known(&self.playing),
"a voice named is not listed"
);
for key in [&self.selected, &self.playing].into_iter().flatten() {
if !self.voices.iter().any(|voice| &voice.key == key) {
log::error!("voice picker: voice {key} is not listed; none marked");
}
}
let pickable = self.on_select.is_some();
let focuses: Vec<_> = self
.voices
+2 -1
View File
@@ -39,7 +39,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca
- [x] P22 Collaboration — `src/collab`
- [x] P23 AI Chat — `src/chat`
- [x] P24 Agent — `src/agent`
- [ ] P25 Generative — `src/generative`
- [x] P25 Generative — `src/generative`
- [ ] P26 Media — `src/media`
- [ ] P27 Files — `src/files`
- [ ] P28 Messaging — `src/messaging`
@@ -90,3 +90,4 @@ One line per item: what changed, what stayed and why, review rounds.
- P22: annotations and suggestions outside their text log and stay out, picks and decisions keeping the owner's index; `accept` returns `Option`; a gone pick, active thread or followed peer logs; yours at no count comes off; a masked field clamps a stale offset, so remote cursors and selections draw. Kept: a thread's comments, presence max, one list's order, empty marks, constant role and mode tables. Review: 3 rounds.
- P23: a branch version past its total logs and shows a dash, its arrows off; a parameter outside its range logs and pegs; a passage's matches outside it log and stay out. Kept: one-value contracts (progress, scores, shapes, peaks, levels, limits, prices, capacity, format, hour), the composer's trigger rows and stream reveal by construction; `places` skips zero, so `decimals` meets no zero step. Review: 2 rounds.
- P24: a checkpoint or browser frame past its list logs and marks none; progress past its total and memory past its limit log and fill their bars, the text keeping the real counts; three `should_panic` tests became render tests. Kept: the artifact's preview-or-source builder check, one-value contracts (cost, cpu, pointer, totals and limits above zero), registry lookups over a constant table. Review: 1 round.
- P25: a verdict, ratio, style, shot, voice, variation or prompt version gone from its list logs and marks none (no picture, no Vary or Upscale, no diff); embedding points in a gone group stay out; an epoch and GPU memory past their whole log and peg. A seed field holds a seed: each field handed in is fitted, and text that is no seed clears; in `TextInput`, a fit starts a fresh undo history, undo keeps and `set_text` fits against the committed text, never an input method's unfinished one (`committed`), and a lifted mask stroke stays on its picture. Kept: one-value contracts, unique group names, one snapshot's loss lists, two to four sides, the legend's lookup. Review: 5 rounds, the cap; round 5's finding (`set_text` over a composition) is fixed and tested, and P26's review checks it.