Refactor: split ImportWizard into pure ImportPipeline and UI wrapper

ImportPipeline holds all data and logic (raw JSON, records, proposals,
model_name, build_model). ImportWizard wraps it with UI-only state
(step, cursor, message). Rename WizardState → WizardStep throughout.
Headless import in dispatch.rs now constructs ImportPipeline directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ed L
2026-03-21 23:28:27 -07:00
parent 413601517d
commit 197b66e4e1
5 changed files with 303 additions and 169 deletions

View File

@ -5,7 +5,7 @@ use ratatui::{
widgets::{Block, Borders, Clear, Widget},
};
use crate::import::wizard::{ImportWizard, WizardState};
use crate::import::wizard::{ImportWizard, WizardStep};
use crate::import::analyzer::FieldKind;
pub struct ImportWizardWidget<'a> {
@ -28,12 +28,12 @@ impl<'a> Widget for ImportWizardWidget<'a> {
Clear.render(popup_area, buf);
let title = match self.wizard.state {
WizardState::Preview => " Import Wizard — Step 1: Preview ",
WizardState::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
WizardState::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
WizardState::NameModel => " Import Wizard — Step 4: Name Model ",
WizardState::Done => " Import Wizard — Done ",
let title = match self.wizard.step {
WizardStep::Preview => " Import Wizard — Step 1: Preview ",
WizardStep::SelectArrayPath => " Import Wizard — Step 2: Select Array ",
WizardStep::ReviewProposals => " Import Wizard — Step 3: Review Fields ",
WizardStep::NameModel => " Import Wizard — Step 4: Name Model ",
WizardStep::Done => " Import Wizard — Done ",
};
let block = Block::default()
@ -47,21 +47,21 @@ impl<'a> Widget for ImportWizardWidget<'a> {
let x = inner.x;
let w = inner.width as usize;
match &self.wizard.state {
WizardState::Preview => {
let summary = self.wizard.preview_summary();
match &self.wizard.step {
WizardStep::Preview => {
let summary = self.wizard.pipeline.preview_summary();
buf.set_string(x, y, truncate(&summary, w), Style::default());
y += 2;
buf.set_string(x, y,
"Press Enter to continue",
"Press Enter to continue\u{2026}",
Style::default().fg(Color::Yellow));
}
WizardState::SelectArrayPath => {
WizardStep::SelectArrayPath => {
buf.set_string(x, y,
"Select the path containing records:",
Style::default().fg(Color::Yellow));
y += 1;
for (i, path) in self.wizard.array_paths.iter().enumerate() {
for (i, path) in self.wizard.pipeline.array_paths.iter().enumerate() {
if y >= inner.y + inner.height { break; }
let is_sel = i == self.wizard.cursor;
let style = if is_sel {
@ -74,9 +74,9 @@ impl<'a> Widget for ImportWizardWidget<'a> {
y += 1;
}
y += 1;
buf.set_string(x, y, "↑↓ select Enter confirm", Style::default().fg(Color::DarkGray));
buf.set_string(x, y, "\u{2191}\u{2193} select Enter confirm", Style::default().fg(Color::DarkGray));
}
WizardState::ReviewProposals => {
WizardStep::ReviewProposals => {
buf.set_string(x, y,
"Review field proposals (Space toggle, c cycle kind):",
Style::default().fg(Color::Yellow));
@ -85,7 +85,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
buf.set_string(x, y, truncate(&header, w), Style::default().fg(Color::Gray).add_modifier(Modifier::UNDERLINED));
y += 1;
for (i, proposal) in self.wizard.proposals.iter().enumerate() {
for (i, proposal) in self.wizard.pipeline.proposals.iter().enumerate() {
if y >= inner.y + inner.height - 2 { break; }
let is_sel = i == self.wizard.cursor;
@ -96,7 +96,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
FieldKind::Label => Color::DarkGray,
};
let accept_str = if proposal.accepted { "[]" } else { "[ ]" };
let accept_str = if proposal.accepted { "[\u{2713}]" } else { "[ ]" };
let row = format!(" {:<20} {:<22} {}",
truncate(&proposal.field, 20),
truncate(proposal.kind_label(), 22),
@ -117,10 +117,10 @@ impl<'a> Widget for ImportWizardWidget<'a> {
buf.set_string(x, hint_y, "Enter: next Space: toggle c: cycle kind Esc: cancel",
Style::default().fg(Color::DarkGray));
}
WizardState::NameModel => {
WizardStep::NameModel => {
buf.set_string(x, y, "Model name:", Style::default().fg(Color::Yellow));
y += 1;
let name_str = format!("> {}", self.wizard.model_name);
let name_str = format!("> {}\u{2588}", self.wizard.pipeline.model_name);
buf.set_string(x, y, truncate(&name_str, w),
Style::default().fg(Color::Green));
y += 2;
@ -133,7 +133,7 @@ impl<'a> Widget for ImportWizardWidget<'a> {
Style::default().fg(Color::Red));
}
}
WizardState::Done => {
WizardStep::Done => {
buf.set_string(x, y, "Import complete!", Style::default().fg(Color::Green));
}
}
@ -142,6 +142,6 @@ impl<'a> Widget for ImportWizardWidget<'a> {
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max { s.to_string() }
else if max > 1 { format!("{}", &s[..max-1]) }
else if max > 1 { format!("{}\u{2026}", &s[..max-1]) }
else { s[..max].to_string() }
}