feat: continue tree rust family cutover
- add rust renderer/state-family scaffolds and inline compat host thinning for page tree, file tree, and picker - route tree/filetree preflight, file projection, resource artifact, and stream delta contracts through rust plans - preserve canonical move-order validation, file-tree search projection, and related frontend/runtime regression coverage
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
use super::filetree_selection::{FileTreeSelectionReducerContract, FileTreeSelectionState};
|
||||
use super::focus_state::PageFocusKeyboardReducerContract;
|
||||
use super::picker_state::PickerStateReducerContract;
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TreeShellRendererMode {
|
||||
Page,
|
||||
FileTree,
|
||||
Picker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TreeShellRendererInput {
|
||||
pub mode: TreeShellRendererMode,
|
||||
pub projection_item_ids: Vec<String>,
|
||||
pub expanded_ids: BTreeSet<String>,
|
||||
pub focused_id: Option<String>,
|
||||
pub page_focus_keyboard_reducer: Option<PageFocusKeyboardReducerContract>,
|
||||
pub filetree_selection: FileTreeSelectionState,
|
||||
pub filetree_selection_reducer: Option<FileTreeSelectionReducerContract>,
|
||||
pub active_picker_item: Option<String>,
|
||||
pub excluded_picker_ids: BTreeSet<String>,
|
||||
pub picker_state_reducer: Option<PickerStateReducerContract>,
|
||||
pub command_dispatcher: TreeShellCommandDispatcher,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TreeShellCommandDispatcher {
|
||||
pub channel: String,
|
||||
pub command_names: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl TreeShellRendererInput {
|
||||
pub fn page(input: PageTreeRendererInput) -> Self {
|
||||
Self {
|
||||
mode: TreeShellRendererMode::Page,
|
||||
projection_item_ids: input.projection_item_ids,
|
||||
expanded_ids: input.expanded_ids,
|
||||
focused_id: input.focused_id,
|
||||
page_focus_keyboard_reducer: Some(PageFocusKeyboardReducerContract::default()),
|
||||
filetree_selection: FileTreeSelectionState::default(),
|
||||
filetree_selection_reducer: None,
|
||||
active_picker_item: None,
|
||||
excluded_picker_ids: BTreeSet::new(),
|
||||
picker_state_reducer: None,
|
||||
command_dispatcher: input.command_dispatcher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filetree(input: FileTreeRendererInput) -> Self {
|
||||
Self {
|
||||
mode: TreeShellRendererMode::FileTree,
|
||||
projection_item_ids: input.projection_item_ids,
|
||||
expanded_ids: input.expanded_ids,
|
||||
focused_id: input.filetree_selection.focused_row_id.clone(),
|
||||
page_focus_keyboard_reducer: None,
|
||||
filetree_selection: input.filetree_selection,
|
||||
filetree_selection_reducer: Some(FileTreeSelectionReducerContract::default()),
|
||||
active_picker_item: None,
|
||||
excluded_picker_ids: BTreeSet::new(),
|
||||
picker_state_reducer: None,
|
||||
command_dispatcher: input.command_dispatcher,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn picker(input: PickerRendererInput) -> Self {
|
||||
Self {
|
||||
mode: TreeShellRendererMode::Picker,
|
||||
projection_item_ids: input.projection_item_ids,
|
||||
expanded_ids: input.expanded_ids,
|
||||
focused_id: input.active_picker_item.clone(),
|
||||
page_focus_keyboard_reducer: None,
|
||||
filetree_selection: FileTreeSelectionState::default(),
|
||||
filetree_selection_reducer: None,
|
||||
active_picker_item: input.active_picker_item,
|
||||
excluded_picker_ids: input.excluded_picker_ids,
|
||||
picker_state_reducer: Some(PickerStateReducerContract::default()),
|
||||
command_dispatcher: input.command_dispatcher,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PageTreeRendererInput {
|
||||
pub projection_item_ids: Vec<String>,
|
||||
pub expanded_ids: BTreeSet<String>,
|
||||
pub focused_id: Option<String>,
|
||||
pub command_dispatcher: TreeShellCommandDispatcher,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FileTreeRendererInput {
|
||||
pub projection_item_ids: Vec<String>,
|
||||
pub expanded_ids: BTreeSet<String>,
|
||||
pub filetree_selection: FileTreeSelectionState,
|
||||
pub command_dispatcher: TreeShellCommandDispatcher,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PickerRendererInput {
|
||||
pub projection_item_ids: Vec<String>,
|
||||
pub expanded_ids: BTreeSet<String>,
|
||||
pub active_picker_item: Option<String>,
|
||||
pub excluded_picker_ids: BTreeSet<String>,
|
||||
pub command_dispatcher: TreeShellCommandDispatcher,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
FileTreeRendererInput, PageTreeRendererInput, PickerRendererInput, TreeShellCommandDispatcher,
|
||||
TreeShellRendererInput, TreeShellRendererMode,
|
||||
};
|
||||
use crate::tree_shell::filetree_selection::FileTreeSelectionState;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
fn set(values: &[&str]) -> BTreeSet<String> {
|
||||
values.iter().map(|value| (*value).to_string()).collect()
|
||||
}
|
||||
|
||||
fn dispatcher() -> TreeShellCommandDispatcher {
|
||||
TreeShellCommandDispatcher {
|
||||
channel: "mnote.tree.shell".into(),
|
||||
command_names: set(&["tree.node.create", "tree.resource.move"]),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_shell_renderer_input_contract_covers_page_filetree_and_picker_state() {
|
||||
let page = TreeShellRendererInput::page(PageTreeRendererInput {
|
||||
projection_item_ids: vec!["doc:root".into()],
|
||||
expanded_ids: set(&["doc:root"]),
|
||||
focused_id: Some("doc:root".into()),
|
||||
command_dispatcher: dispatcher(),
|
||||
});
|
||||
assert_eq!(page.mode, TreeShellRendererMode::Page);
|
||||
assert_eq!(page.focused_id.as_deref(), Some("doc:root"));
|
||||
assert_eq!(page.command_dispatcher.channel, "mnote.tree.shell");
|
||||
assert_eq!(
|
||||
page.page_focus_keyboard_reducer
|
||||
.as_ref()
|
||||
.map(|contract| contract.contract_name),
|
||||
Some("rust_page_focus_keyboard_reducer_v1")
|
||||
);
|
||||
|
||||
let mut selection = FileTreeSelectionState::from_selected(&["asset:a".into()]);
|
||||
selection.focused_row_id = Some("asset:a".into());
|
||||
let filetree = TreeShellRendererInput::filetree(FileTreeRendererInput {
|
||||
projection_item_ids: vec!["doc:root".into(), "asset:a".into()],
|
||||
expanded_ids: set(&["doc:root"]),
|
||||
filetree_selection: selection,
|
||||
command_dispatcher: dispatcher(),
|
||||
});
|
||||
assert_eq!(filetree.mode, TreeShellRendererMode::FileTree);
|
||||
assert_eq!(filetree.focused_id.as_deref(), Some("asset:a"));
|
||||
assert!(filetree.filetree_selection.selected_row_ids.contains("asset:a"));
|
||||
assert!(filetree.page_focus_keyboard_reducer.is_none());
|
||||
assert_eq!(
|
||||
filetree
|
||||
.filetree_selection_reducer
|
||||
.as_ref()
|
||||
.map(|contract| contract.contract_name),
|
||||
Some("rust_filetree_selection_reducer_v1")
|
||||
);
|
||||
|
||||
let picker = TreeShellRendererInput::picker(PickerRendererInput {
|
||||
projection_item_ids: vec!["doc:root".into(), "doc:child".into()],
|
||||
expanded_ids: set(&["doc:root"]),
|
||||
active_picker_item: Some("doc:child".into()),
|
||||
excluded_picker_ids: set(&["doc:archived"]),
|
||||
command_dispatcher: dispatcher(),
|
||||
});
|
||||
assert_eq!(picker.mode, TreeShellRendererMode::Picker);
|
||||
assert_eq!(picker.focused_id.as_deref(), Some("doc:child"));
|
||||
assert!(picker.excluded_picker_ids.contains("doc:archived"));
|
||||
assert!(picker.page_focus_keyboard_reducer.is_none());
|
||||
assert!(picker.filetree_selection_reducer.is_none());
|
||||
assert_eq!(
|
||||
picker
|
||||
.picker_state_reducer
|
||||
.as_ref()
|
||||
.map(|contract| contract.contract_name),
|
||||
Some("rust_picker_state_reducer_v1")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user