summaryrefslogtreecommitdiff
path: root/makima/src/daemon/tui/ui.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-20 17:23:34 +0000
committersoryu <soryu@soryu.co>2026-01-20 17:23:46 +0000
commit055e2c4a72e3b2331a18fdc9f8132ef990af7e38 (patch)
tree75b92637b9132594b76a5a86f5f854bca1ddee49 /makima/src/daemon/tui/ui.rs
parent54c6d409e1d5667f4ab7f63a43e1459e68575c94 (diff)
downloadsoryu-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.rs156
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;