Skip to content

Commit 07add0b

Browse files
committed
Set-remote popup: self-key the workflow fetch so a stale intent can't be submitted during refetch
1 parent 662f392 commit 07add0b

1 file changed

Lines changed: 20 additions & 16 deletions

File tree

quilt-sync/ui/src/components/set_remote_popup.rs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,9 @@ pub fn SetRemotePopup(
8686
let workflows = LocalResource::new(move || {
8787
let target = debounced_target.get();
8888
async move {
89-
match target {
90-
Some((host, bucket)) => Some(commands::get_bucket_workflows(host, bucket).await),
91-
None => None,
92-
}
89+
let (host, bucket) = target?;
90+
let res = commands::get_bucket_workflows(host.clone(), bucket.clone()).await;
91+
Some(((host, bucket), res))
9392
}
9493
});
9594

@@ -98,9 +97,16 @@ pub fn SetRemotePopup(
9897
// bucket default is preselected. `None` here means "no target / still
9998
// loading"; a fetch error maps to the `Unavailable` view.
10099
let wf_view = Memo::new(move |_| {
101-
workflows.get().flatten().map(|res| match res {
102-
Ok(cw) => build_workflow_view(&cw, None),
103-
Err(_) => build_workflow_view(&CommitWorkflows::Unavailable, None),
100+
workflows.get().flatten().and_then(|(target, res)| {
101+
// Self-keying: ignore a result whose target no longer matches the
102+
// typed one. Covers BOTH the debounce-pending window and the
103+
// in-flight-refetch window (the resource keeps the previous bucket's
104+
// value while re-running), so the view is `None` (loading) until the
105+
// fetch for the *current* bucket resolves.
106+
(Some(target) == valid_target.get()).then(|| match res {
107+
Ok(cw) => build_workflow_view(&cw, None),
108+
Err(_) => build_workflow_view(&CommitWorkflows::Unavailable, None),
109+
})
104110
})
105111
});
106112

@@ -116,15 +122,13 @@ pub fn SetRemotePopup(
116122
// No previous revision on a first push, so the divergence note never shows.
117123
let workflow_note = Memo::new(|_| None::<String>);
118124

119-
// True while a valid target's workflow config isn't ready yet — the fetch is
120-
// in flight (`wf_view` is `None`) OR the debounce hasn't caught up so the
121-
// shown view still belongs to the previous bucket (`valid_target` has moved
122-
// ahead of `debounced_target`). Blocking Save on both keeps the submitted
123-
// intent matched to the currently-typed bucket, never a stale one.
124-
let workflow_loading = Memo::new(move |_| {
125-
valid_target.get().is_some()
126-
&& (wf_view.get().is_none() || valid_target.get() != debounced_target.get())
127-
});
125+
// True while a valid target's workflow config isn't ready. Because `wf_view`
126+
// is self-keyed to the current `valid_target` (above), a `None` here covers
127+
// every not-yet-current window — debounce pending AND in-flight refetch —
128+
// so blocking Save on it keeps the submitted intent matched to the
129+
// currently-typed bucket, never a stale one.
130+
let workflow_loading =
131+
Memo::new(move |_| valid_target.get().is_some() && wf_view.get().is_none());
128132
// Disable Save while submitting or while the workflow config is loading, so
129133
// the submitted intent always corresponds to the displayed bucket's config
130134
// and never a stale one from an earlier keystroke.

0 commit comments

Comments
 (0)