diff options
| author | soryu <soryu@soryu.co> | 2026-01-20 17:23:34 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-20 17:23:46 +0000 |
| commit | 055e2c4a72e3b2331a18fdc9f8132ef990af7e38 (patch) | |
| tree | 75b92637b9132594b76a5a86f5f854bca1ddee49 /makima/src/daemon/tui/ui.rs | |
| parent | 54c6d409e1d5667f4ab7f63a43e1459e68575c94 (diff) | |
| download | soryu-055e2c4a72e3b2331a18fdc9f8132ef990af7e38.tar.gz soryu-055e2c4a72e3b2331a18fdc9f8132ef990af7e38.zip | |
Update CLI to show log history as well
Diffstat (limited to 'makima/src/daemon/tui/ui.rs')
| -rw-r--r-- | makima/src/daemon/tui/ui.rs | 156 |
1 files changed, 155 insertions, 1 deletions
diff --git a/makima/src/daemon/tui/ui.rs b/makima/src/daemon/tui/ui.rs index 9349183..de15320 100644 --- a/makima/src/daemon/tui/ui.rs +++ b/makima/src/daemon/tui/ui.rs @@ -8,7 +8,7 @@ use ratatui::{ Frame, }; -use super::app::{App, InputMode, ViewType, OutputMessageType, WsConnectionState}; +use super::app::{App, CreateFormField, InputMode, ViewType, OutputMessageType, WsConnectionState}; use super::event::{get_help_text, get_output_help_text}; /// Main render function @@ -36,6 +36,11 @@ pub fn render(frame: &mut Frame, app: &App) { if matches!(app.input_mode, InputMode::EditName | InputMode::EditDescription) { render_edit_dialog(frame, app); } + + // Render create contract dialog if in create mode + if matches!(app.input_mode, InputMode::CreateName | InputMode::CreateDescription) { + render_create_dialog(frame, app); + } } /// Render header with breadcrumb and search bar @@ -355,6 +360,155 @@ fn render_edit_dialog(frame: &mut Frame, app: &App) { frame.render_widget(popup, popup_area); } +/// Render the create contract dialog +fn render_create_dialog(frame: &mut Frame, app: &App) { + // Calculate popup size and position + let area = frame.area(); + let popup_width = 70.min(area.width.saturating_sub(4)); + let popup_height = 20; + + let popup_x = (area.width.saturating_sub(popup_width)) / 2; + let popup_y = (area.height.saturating_sub(popup_height)) / 2; + + let popup_area = Rect { + x: popup_x, + y: popup_y, + width: popup_width, + height: popup_height, + }; + + // Clear the area behind the popup + frame.render_widget(Clear, popup_area); + + let state = &app.create_state; + let current_field = state.current_field(); + let max_field_width = (popup_width as usize).saturating_sub(18); + + // Styles + let active_style = Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD); + let inactive_style = Style::default().fg(Color::White); + let label_style = Style::default().fg(Color::DarkGray); + let hint_style = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC); + + // Helper to build text field with cursor + let build_field = |value: &str, cursor: usize, is_active: bool| -> String { + if is_active { + let cursor_pos = cursor.min(value.len()); + let (before, after) = value.split_at(cursor_pos); + let display = format!("{}|{}", before, after); + if display.len() > max_field_width { + let start = display.len().saturating_sub(max_field_width); + format!("...{}", &display[start..]) + } else { + display + } + } else { + if value.len() > max_field_width { + format!("{}...", &value[..max_field_width.saturating_sub(3)]) + } else if value.is_empty() { + "(empty)".to_string() + } else { + value.to_string() + } + } + }; + + // Build field displays + let name_display = build_field(&state.name, state.cursor, current_field == CreateFormField::Name); + let desc_display = build_field(&state.description, state.cursor, current_field == CreateFormField::Description); + let repo_display = build_field(&state.repository_url, state.cursor, current_field == CreateFormField::Repository); + + // Contract type selector + let type_display = if state.contract_type == "simple" { + vec![ + Span::styled("[●] ", Style::default().fg(Color::Green)), + Span::raw("Simple "), + Span::styled("[ ] ", Style::default().fg(Color::DarkGray)), + Span::styled("Specification", Style::default().fg(Color::DarkGray)), + ] + } else { + vec![ + Span::styled("[ ] ", Style::default().fg(Color::DarkGray)), + Span::styled("Simple ", Style::default().fg(Color::DarkGray)), + Span::styled("[●] ", Style::default().fg(Color::Green)), + Span::raw("Specification"), + ] + }; + + let text = vec![ + Line::from(""), + Line::from(Span::styled( + " New Contract", + Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), + )), + Line::from(""), + // Name field (required) + Line::from(vec![ + Span::styled(" Name*: ", label_style), + Span::styled( + name_display, + if current_field == CreateFormField::Name { active_style } else { inactive_style }, + ), + ]), + Line::from(Span::styled(" Contract name (required)", hint_style)), + Line::from(""), + // Description field + Line::from(vec![ + Span::styled(" Description: ", label_style), + Span::styled( + desc_display, + if current_field == CreateFormField::Description { active_style } else { inactive_style }, + ), + ]), + Line::from(Span::styled(" Brief description of the work", hint_style)), + Line::from(""), + // Contract type selector + Line::from(vec![ + Span::styled(" Type: ", label_style), + ].into_iter().chain( + if current_field == CreateFormField::ContractType { + type_display.into_iter().map(|s| s).collect::<Vec<_>>() + } else { + type_display.into_iter().map(|mut s| { + s.style = s.style.fg(Color::DarkGray); + s + }).collect() + } + ).collect::<Vec<_>>()), + Line::from(Span::styled(" Simple: Plan→Execute | Spec: Research→Specify→Plan→Execute→Review", hint_style)), + Line::from(""), + // Repository URL field + Line::from(vec![ + Span::styled(" Repository: ", label_style), + Span::styled( + repo_display, + if current_field == CreateFormField::Repository { active_style } else { inactive_style }, + ), + ]), + Line::from(Span::styled(" Git repository URL (optional)", hint_style)), + Line::from(""), + // Help line + Line::from(vec![ + Span::styled(" Tab/↑↓", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)), + Span::raw(": switch "), + Span::styled("Enter", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)), + Span::raw(": create "), + Span::styled("Space", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)), + Span::raw(": toggle type "), + Span::styled("Esc", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)), + Span::raw(": cancel"), + ]), + ]; + + let popup = Paragraph::new(text) + .block(Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::Cyan)) + .title(" Create Contract ")); + + frame.render_widget(popup, popup_area); +} + /// Render the task output view fn render_output_view(frame: &mut Frame, app: &App, area: Rect) { let buffer = &app.output_buffer; |
