Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions crates/moon-ui-gpui/src/controls/core_update/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,36 +71,6 @@ pub(crate) fn update_scope(
});
}

/// Enqueue the fleet for a plain release update -- every core, or only the ones behind.
///
/// Args:
/// backend: Shared terminal state.
/// only_behind: `true` selects through `session.cores_behind()`; `false` takes every core the
/// enqueue gate accepts.
/// app: Application context used to reach the session.
pub(crate) fn update_fleet(backend: &Entity<Backend>, only_behind: bool, app: &mut App) {
backend.update(app, |backend, cx| {
let now_ms = moon_core::util::now_unix_ms_i64();
let cores: Vec<CoreId> = if only_behind {
backend.session.cores_behind()
} else {
backend.session.sessions().iter().map(|s| s.id).collect()
};
let report =
backend
.session
.enqueue_core_updates(&cores, UpdateTarget::Release, now_ms);
log::info!(
"fleet update enqueue ({}): {} accepted, {} skipped offline/unreachable, {} skipped already tracked",
if only_behind { "behind only" } else { "every core" },
report.accepted,
report.skipped_offline,
report.skipped_already,
);
cx.notify();
});
}

/// Retry one core whose last update attempt ended `Done`, using the target its last attempt
/// used.
///
Expand Down
8 changes: 5 additions & 3 deletions crates/moon-ui-gpui/src/controls/core_update/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The shared core UPDATE control: enqueue a plain release update for one core or a whole
//! server, retry a core whose last attempt failed, and update the fleet.
//! The shared core UPDATE control: enqueue a release or a named build for one core, a whole
//! server, an exchange section or the whole panel scope, and retry a core whose last attempt failed.
//!
//! A SIBLING of [`crate::controls::core_run`], not a fork of it -- read that module before this
//! one. What is REUSED is its vocabulary and its rules, never its code:
Expand All @@ -20,9 +20,11 @@
//! directly: bulk fills the queue, it never bursts commands.

mod actions;
mod scope;
mod view;

pub(crate) use actions::{retry_core, update_core, update_fleet, update_scope};
pub(crate) use actions::{retry_core, update_core, update_scope};
pub(crate) use scope::resolve_menu_scope;
pub(crate) use view::update_button;

use moon_core::feed::ConnStatus;
Expand Down
58 changes: 58 additions & 0 deletions crates/moon-ui-gpui/src/controls/core_update/scope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Which cores one click stands for -- the pure half of every bulk update this control performs.
//!
//! GPUI-free on purpose. This is the single most dangerous decision in the update path: the queue
//! it feeds reaches LIVE cores that trade real money, so "which cores did the user actually point
//! at" has to be a function that can be read, tested and mutated in isolation rather than a rule
//! spread across three click handlers.

use std::rc::Rc;

use moon_core::session::CoreId;

use crate::controls::row_selection::RowSelection;

/// Resolve the cores a context-menu click commands.
///
/// Windows Explorer semantics, and they are the whole point: a right-click on a row INSIDE the
/// current selection acts on the whole selection, and a right-click anywhere else acts on that row
/// alone. The click never MOVES the selection -- opening a menu must not change what it is about to
/// act on -- so this is the only place the two readings are reconciled.
///
/// The result is filtered through `order` rather than taken from the selection set directly, for
/// two reasons that both matter here: the set has no order of its own (it is a hash set), and it
/// can outlive a row leaving the view for one frame. Anything the user cannot currently see is not
/// something a menu may enqueue.
///
/// Args:
/// clicked: The core whose row was right-clicked.
/// order: Every selectable core currently rendered, in visual order.
/// selection: The panel's controlled row selection.
///
/// Returns:
/// The cores to command, in visual order. Never empty: a click outside the selection -- and a
/// click made with nothing selected -- yields the clicked core alone.
pub(crate) fn resolve_menu_scope(
clicked: CoreId,
order: &[CoreId],
selection: &RowSelection<CoreId>,
) -> Rc<[CoreId]> {
if !selection.contains(Some(clicked)) {
return Rc::from(vec![clicked]);
}
let scope: Vec<CoreId> = order
.iter()
.copied()
.filter(|core| selection.contains(Some(*core)))
.collect();
// The membership test above already proved `clicked` is selected, but it proves nothing about
// `order`: a caller can hand a stale order that no longer draws it. Falling back to the clicked
// core keeps the invariant this function is trusted for -- the result is never empty, so a menu
// entry can never enqueue "everything" by resolving to nothing.
if scope.is_empty() {
return Rc::from(vec![clicked]);
}
Rc::from(scope)
}

#[cfg(test)]
mod tests;
66 changes: 66 additions & 0 deletions crates/moon-ui-gpui/src/controls/core_update/scope/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Contract tests for Core Status context-menu update scope resolution.

use crate::controls::row_selection::RowSelection;
use moon_core::session::CoreId;

use super::resolve_menu_scope;

/// Build a selection through the public click rules rather than by reaching into its private set.
fn selected(ids: &[CoreId], selected_ids: &[CoreId]) -> RowSelection<CoreId> {
let mut selection = RowSelection::default();
for id in selected_ids {
selection.click(
Some(*id),
&ids.iter().copied().map(Some).collect::<Vec<_>>(),
false,
true,
);
}
selection
}

/// `controls/core_update/scope.rs:resolve_menu_scope` must use an in-selection clicked core's
/// whole selection in rendered order and filter stale keys. Iterating the set directly could send
/// updates in a nondeterministic order or target a core that is no longer shown.
#[test]
fn selected_click_uses_the_visible_selection_in_panel_order() {
let order = [40, 10, 30, 20];
let mut selection = selected(&order, &[10, 20]);
let visible = order.map(Some);
selection.click(Some(99), &visible, false, true);

let scope = resolve_menu_scope(20, &order, &selection);

assert_eq!(scope.as_ref(), &[10, 20]);
}

/// `controls/core_update/scope.rs:resolve_menu_scope` must treat an unselected right-click as
/// one core, even when another selection exists. Returning a non-empty selection unconditionally
/// would enqueue real updates on every previously highlighted trading core.
#[test]
fn unselected_click_never_expands_to_an_existing_selection() {
let order = [40, 10, 30, 20];
let selection = selected(&order, &[10, 20]);

let scope = resolve_menu_scope(30, &order, &selection);

assert_eq!(scope.as_ref(), &[30]);
}

/// `controls/core_update/scope.rs:resolve_menu_scope` must never include a selected id absent
/// from `order` or return an empty scope. Keeping a vanished selection would tell the menu one
/// thing while enqueueing an invisible core that the panel no longer owns.
#[test]
fn stale_selected_identity_is_filtered_from_a_nonempty_visible_scope() {
let order = [40, 10, 30, 20];
let selection = selected(&order, &[10, 99]);

let scope = resolve_menu_scope(99, &order, &selection);

assert_eq!(scope.as_ref(), &[10]);
assert!(
!scope.is_empty(),
"a menu scope must always name at least one core"
);
assert!(scope.iter().all(|id| order.contains(id)));
}
3 changes: 3 additions & 0 deletions crates/moon-ui-gpui/src/controls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
//! - [`fmt`] formats size, sell, and field values and computes mouse-wheel steps;
//! - [`manual_strat`] provides the header's manual-strategy toggle and picker;
//! - [`metric`] provides TP/SL/leverage trigger buttons and popup content;
//! - [`row_selection`] holds the controlled multi-row click algorithm — plain, Ctrl, Shift and
//! select-all over a keyed row order — shared by Report and Core Status;
//! - [`strips`] provides size and sell preset strips with native MoonUI interaction;
//! - [`scale`] provides price-scale dropdowns for tabs, AddToChart stacks, and trade windows;
//! - [`wrap_fit`] lets a wrapping panel row shrink its selectors before it takes a second line;
Expand All @@ -47,6 +49,7 @@ mod fmt;
mod label_fields;
mod manual_strat;
mod metric;
pub(crate) mod row_selection;
mod scale;
mod strips;
pub(crate) mod toolbar;
Expand Down
Loading