titan-cli 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. titan_cli/core/config.py +3 -1
  2. titan_cli/core/workflows/__init__.py +2 -1
  3. titan_cli/core/workflows/project_step_source.py +48 -30
  4. titan_cli/core/workflows/workflow_filter_service.py +14 -8
  5. titan_cli/core/workflows/workflow_registry.py +12 -1
  6. titan_cli/core/workflows/workflow_sources.py +1 -1
  7. titan_cli/engine/steps/ai_assistant_step.py +42 -7
  8. titan_cli/engine/workflow_executor.py +6 -1
  9. titan_cli/ui/tui/screens/workflow_execution.py +8 -28
  10. titan_cli/ui/tui/textual_components.py +59 -6
  11. titan_cli/ui/tui/textual_workflow_executor.py +9 -1
  12. titan_cli/ui/tui/widgets/__init__.py +2 -0
  13. titan_cli/ui/tui/widgets/step_container.py +70 -0
  14. {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/METADATA +6 -3
  15. {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/RECORD +39 -37
  16. {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/WHEEL +1 -1
  17. titan_plugin_git/clients/git_client.py +82 -4
  18. titan_plugin_git/plugin.py +3 -0
  19. titan_plugin_git/steps/ai_commit_message_step.py +33 -28
  20. titan_plugin_git/steps/branch_steps.py +18 -37
  21. titan_plugin_git/steps/commit_step.py +18 -22
  22. titan_plugin_git/steps/diff_summary_step.py +182 -0
  23. titan_plugin_git/steps/push_step.py +27 -11
  24. titan_plugin_git/steps/status_step.py +15 -18
  25. titan_plugin_git/workflows/commit-ai.yaml +5 -0
  26. titan_plugin_github/agents/pr_agent.py +15 -2
  27. titan_plugin_github/steps/ai_pr_step.py +12 -21
  28. titan_plugin_github/steps/create_pr_step.py +17 -7
  29. titan_plugin_github/steps/github_prompt_steps.py +52 -0
  30. titan_plugin_github/steps/issue_steps.py +28 -14
  31. titan_plugin_github/steps/preview_step.py +11 -0
  32. titan_plugin_github/utils.py +5 -4
  33. titan_plugin_github/workflows/create-pr-ai.yaml +5 -0
  34. titan_plugin_jira/steps/ai_analyze_issue_step.py +8 -3
  35. titan_plugin_jira/steps/get_issue_step.py +16 -12
  36. titan_plugin_jira/steps/prompt_select_issue_step.py +22 -9
  37. titan_plugin_jira/steps/search_saved_query_step.py +21 -19
  38. {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info}/entry_points.txt +0 -0
  39. {titan_cli-0.1.4.dist-info → titan_cli-0.1.5.dist-info/licenses}/LICENSE +0 -0
@@ -1,7 +1,6 @@
1
1
  import ast
2
2
  from titan_cli.engine.context import WorkflowContext
3
3
  from titan_cli.engine.results import WorkflowResult, Success, Error, Skip
4
- from titan_cli.ui.tui.widgets import Panel
5
4
  from ..agents.issue_generator import IssueGeneratorAgent
6
5
 
7
6
  def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult:
@@ -12,14 +11,21 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
12
11
  if not ctx.textual:
13
12
  return Error("Textual UI context is not available for this step.")
14
13
 
14
+ # Begin step container
15
+ ctx.textual.begin_step("Categorize and Generate Issue")
16
+
15
17
  if not ctx.ai:
18
+ ctx.textual.text("AI client not available", markup="dim")
19
+ ctx.textual.end_step("skip")
16
20
  return Skip("AI client not available")
17
21
 
18
22
  issue_body_prompt = ctx.get("issue_body")
19
23
  if not issue_body_prompt:
24
+ ctx.textual.text("issue_body not found in context", markup="red")
25
+ ctx.textual.end_step("error")
20
26
  return Error("issue_body not found in context")
21
27
 
22
- ctx.textual.text("Using AI to categorize and generate issue...", markup="cyan")
28
+ ctx.textual.text("Using AI to categorize and generate issue...", markup="dim")
23
29
 
24
30
  try:
25
31
  # Get available labels from repository for smart mapping
@@ -46,12 +52,8 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
46
52
  category = result["category"]
47
53
  template_used = result.get("template_used", False)
48
54
 
49
- ctx.textual.mount(
50
- Panel(
51
- text=f"Category detected: {category}",
52
- panel_type="success"
53
- )
54
- )
55
+ ctx.textual.text("") # spacing
56
+ ctx.textual.text(f"Category detected: {category}", markup="green")
55
57
 
56
58
  if template_used:
57
59
  ctx.textual.text(f"Using template: {category}.md", markup="cyan")
@@ -63,8 +65,11 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
63
65
  ctx.set("issue_category", category)
64
66
  ctx.set("labels", result["labels"])
65
67
 
68
+ ctx.textual.end_step("success")
66
69
  return Success(f"AI-generated issue ({category}) created successfully")
67
70
  except Exception as e:
71
+ ctx.textual.text(f"Failed to generate issue: {e}", markup="red")
72
+ ctx.textual.end_step("error")
68
73
  return Error(f"Failed to generate issue: {e}")
69
74
 
70
75
 
@@ -75,7 +80,12 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
75
80
  if not ctx.textual:
76
81
  return Error("Textual UI context is not available for this step.")
77
82
 
83
+ # Begin step container
84
+ ctx.textual.begin_step("Create Issue")
85
+
78
86
  if not ctx.github:
87
+ ctx.textual.text("GitHub client not available", markup="red")
88
+ ctx.textual.end_step("error")
79
89
  return Error("GitHub client not available")
80
90
 
81
91
  issue_title = ctx.get("issue_title")
@@ -103,9 +113,13 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
103
113
  labels = []
104
114
 
105
115
  if not issue_title:
116
+ ctx.textual.text("issue_title not found in context", markup="red")
117
+ ctx.textual.end_step("error")
106
118
  return Error("issue_title not found in context")
107
119
 
108
120
  if not issue_body:
121
+ ctx.textual.text("issue_body not found in context", markup="red")
122
+ ctx.textual.end_step("error")
109
123
  return Error("issue_body not found in context")
110
124
 
111
125
  # Filter labels to only those that exist in the repository
@@ -123,21 +137,21 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
123
137
  pass
124
138
 
125
139
  try:
140
+ ctx.textual.text(f"Creating issue: {issue_title}...", markup="dim")
126
141
  issue = ctx.github.create_issue(
127
142
  title=issue_title,
128
143
  body=issue_body,
129
144
  assignees=assignees,
130
145
  labels=labels,
131
146
  )
132
- ctx.textual.mount(
133
- Panel(
134
- text=f"Successfully created issue #{issue.number}",
135
- panel_type="success"
136
- )
137
- )
147
+ ctx.textual.text("") # spacing
148
+ ctx.textual.text(f"Successfully created issue #{issue.number}", markup="green")
149
+ ctx.textual.end_step("success")
138
150
  return Success(
139
151
  f"Successfully created issue #{issue.number}",
140
152
  metadata={"issue": issue}
141
153
  )
142
154
  except Exception as e:
155
+ ctx.textual.text(f"Failed to create issue: {e}", markup="red")
156
+ ctx.textual.end_step("error")
143
157
  return Error(f"Failed to create issue: {e}")
@@ -8,10 +8,15 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
8
8
  if not ctx.textual:
9
9
  return Error("Textual UI context is not available for this step.")
10
10
 
11
+ # Begin step container
12
+ ctx.textual.begin_step("Preview and Confirm Issue")
13
+
11
14
  issue_title = ctx.get("issue_title")
12
15
  issue_body = ctx.get("issue_body")
13
16
 
14
17
  if not issue_title or not issue_body:
18
+ ctx.textual.text("issue_title or issue_body not found in context", markup="red")
19
+ ctx.textual.end_step("error")
15
20
  return Error("issue_title or issue_body not found in context")
16
21
 
17
22
  # Show preview header
@@ -33,8 +38,14 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
33
38
 
34
39
  try:
35
40
  if not ctx.textual.ask_confirm("Use this AI-generated issue?", default=True):
41
+ ctx.textual.text("User rejected AI-generated issue", markup="yellow")
42
+ ctx.textual.end_step("error")
36
43
  return Error("User rejected AI-generated issue")
37
44
  except (KeyboardInterrupt, EOFError):
45
+ ctx.textual.text("User cancelled operation", markup="red")
46
+ ctx.textual.end_step("error")
38
47
  return Error("User cancelled operation")
39
48
 
49
+ ctx.textual.text("User confirmed AI-generated issue", markup="green")
50
+ ctx.textual.end_step("success")
40
51
  return Success("User confirmed AI-generated issue")
@@ -54,21 +54,22 @@ def calculate_pr_size(diff: str) -> PRSizeEstimation:
54
54
  files_changed = len(re.findall(file_pattern, diff, re.MULTILINE))
55
55
 
56
56
  # Dynamic character limit based on PR size
57
+ # Increased limits to accommodate PR templates with images/GIFs
57
58
  if files_changed <= 3 and diff_lines < 100:
58
59
  # Small PR: bug fix, doc update, small feature
59
- max_chars = 800
60
+ max_chars = 1500
60
61
  pr_size = "small"
61
62
  elif files_changed <= 10 and diff_lines < 500:
62
63
  # Medium PR: feature, moderate refactor
63
- max_chars = 1800
64
+ max_chars = 2500
64
65
  pr_size = "medium"
65
66
  elif files_changed <= 30 and diff_lines < 2000:
66
67
  # Large PR: architectural changes, new modules
67
- max_chars = 3000
68
+ max_chars = 4000
68
69
  pr_size = "large"
69
70
  else:
70
71
  # Very large PR: major refactor, breaking changes
71
- max_chars = 4500
72
+ max_chars = 6000
72
73
  pr_size = "very large"
73
74
 
74
75
  return PRSizeEstimation(
@@ -19,6 +19,11 @@ steps:
19
19
  plugin: git
20
20
  step: get_current_branch
21
21
 
22
+ - id: branch_diff_summary
23
+ name: "Show Branch Changes Summary"
24
+ plugin: git
25
+ step: show_branch_diff_summary
26
+
22
27
  # AI generates PR title and description from branch commits
23
28
  - id: ai_pr_description
24
29
  name: "AI PR Description"
@@ -3,7 +3,6 @@ AI-powered JIRA issue analysis step
3
3
  """
4
4
 
5
5
  from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error, Skip
6
- from titan_cli.ui.tui.widgets import Panel
7
6
  from ..messages import msg
8
7
  from ..agents import JiraAgent
9
8
  from ..formatters import IssueAnalysisMarkdownFormatter
@@ -32,15 +31,20 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
32
31
  if not ctx.textual:
33
32
  return Error("Textual UI context is not available for this step.")
34
33
 
34
+ # Begin step container
35
+ ctx.textual.begin_step("AI Analyze Issue")
36
+
35
37
  # Check if AI is available
36
38
  if not ctx.ai or not ctx.ai.is_available():
37
- ctx.textual.mount(Panel(msg.Steps.AIIssue.AI_NOT_CONFIGURED_SKIP, panel_type="info"))
39
+ ctx.textual.text(msg.Steps.AIIssue.AI_NOT_CONFIGURED_SKIP, markup="dim")
40
+ ctx.textual.end_step("skip")
38
41
  return Skip(msg.Steps.AIIssue.AI_NOT_CONFIGURED)
39
42
 
40
43
  # Get issue to analyze
41
44
  issue = ctx.get("jira_issue") or ctx.get("selected_issue")
42
45
  if not issue:
43
- ctx.textual.mount(Panel(msg.Steps.AIIssue.NO_ISSUE_FOUND, panel_type="error"))
46
+ ctx.textual.text(msg.Steps.AIIssue.NO_ISSUE_FOUND, markup="red")
47
+ ctx.textual.end_step("error")
44
48
  return Error(msg.Steps.AIIssue.NO_ISSUE_FOUND)
45
49
 
46
50
  # Create JiraAgent instance and analyze issue with loading indicator
@@ -90,6 +94,7 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
90
94
  "estimated_effort": analysis.estimated_effort
91
95
  })
92
96
 
97
+ ctx.textual.end_step("success")
93
98
  return Success(
94
99
  "AI analysis completed",
95
100
  metadata={
@@ -3,7 +3,6 @@ Get JIRA issue details step
3
3
  """
4
4
 
5
5
  from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
6
- from titan_cli.ui.tui.widgets import Panel
7
6
  from ..exceptions import JiraAPIError
8
7
  from ..messages import msg
9
8
 
@@ -26,15 +25,20 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
26
25
  if not ctx.textual:
27
26
  return Error("Textual UI context is not available for this step.")
28
27
 
28
+ # Begin step container
29
+ ctx.textual.begin_step("Get Full Issue Details")
30
+
29
31
  # Check if JIRA client is available
30
32
  if not ctx.jira:
31
- ctx.textual.mount(Panel(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, panel_type="error"))
33
+ ctx.textual.text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, markup="red")
34
+ ctx.textual.end_step("error")
32
35
  return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
33
36
 
34
37
  # Get issue key
35
38
  issue_key = ctx.get("jira_issue_key")
36
39
  if not issue_key:
37
- ctx.textual.mount(Panel("JIRA issue key is required", panel_type="error"))
40
+ ctx.textual.text("JIRA issue key is required", markup="red")
41
+ ctx.textual.end_step("error")
38
42
  return Error("JIRA issue key is required")
39
43
 
40
44
  # Get optional expand fields
@@ -46,12 +50,8 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
46
50
  issue = ctx.jira.get_ticket(ticket_key=issue_key, expand=expand)
47
51
 
48
52
  # Show success
49
- ctx.textual.mount(
50
- Panel(
51
- text=msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
52
- panel_type="success"
53
- )
54
- )
53
+ ctx.textual.text("") # spacing
54
+ ctx.textual.text(msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key), markup="green")
55
55
 
56
56
  # Show issue details
57
57
  ctx.textual.text(f" Title: {issue.summary}", markup="cyan")
@@ -60,6 +60,7 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
60
60
  ctx.textual.text(f" Assignee: {issue.assignee or 'Unassigned'}")
61
61
  ctx.textual.text("")
62
62
 
63
+ ctx.textual.end_step("success")
63
64
  return Success(
64
65
  msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
65
66
  metadata={"jira_issue": issue}
@@ -68,14 +69,17 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
68
69
  except JiraAPIError as e:
69
70
  if e.status_code == 404:
70
71
  error_msg = msg.Steps.GetIssue.ISSUE_NOT_FOUND.format(issue_key=issue_key)
71
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
72
+ ctx.textual.text(error_msg, markup="red")
73
+ ctx.textual.end_step("error")
72
74
  return Error(error_msg)
73
75
  error_msg = msg.Steps.GetIssue.GET_FAILED.format(e=e)
74
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
76
+ ctx.textual.text(error_msg, markup="red")
77
+ ctx.textual.end_step("error")
75
78
  return Error(error_msg)
76
79
  except Exception as e:
77
80
  error_msg = f"Unexpected error getting issue: {e}"
78
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
81
+ ctx.textual.text(error_msg, markup="red")
82
+ ctx.textual.end_step("error")
79
83
  return Error(error_msg)
80
84
 
81
85
 
@@ -3,7 +3,6 @@ Prompt user to select an issue from search results
3
3
  """
4
4
 
5
5
  from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
6
- from titan_cli.ui.tui.widgets import Panel
7
6
  from ..messages import msg
8
7
 
9
8
 
@@ -21,12 +20,19 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
21
20
  if not ctx.textual:
22
21
  return Error("Textual UI context is not available for this step.")
23
22
 
23
+ # Begin step container
24
+ ctx.textual.begin_step("Select Issue to Analyze")
25
+
24
26
  # Get issues from previous search
25
27
  issues = ctx.get("jira_issues")
26
28
  if not issues:
29
+ ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE, markup="red")
30
+ ctx.textual.end_step("error")
27
31
  return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
28
32
 
29
33
  if len(issues) == 0:
34
+ ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE, markup="red")
35
+ ctx.textual.end_step("error")
30
36
  return Error(msg.Steps.PromptSelectIssue.NO_ISSUES_AVAILABLE)
31
37
 
32
38
  # Prompt user to select issue (issues already displayed in table from previous step)
@@ -40,32 +46,37 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
40
46
  )
41
47
 
42
48
  if not response or not response.strip():
49
+ ctx.textual.text(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED, markup="red")
50
+ ctx.textual.end_step("error")
43
51
  return Error(msg.Steps.PromptSelectIssue.NO_ISSUE_SELECTED)
44
52
 
45
53
  # Validate it's a number
46
54
  try:
47
55
  selected_index = int(response.strip())
48
56
  except ValueError:
57
+ ctx.textual.text(f"Invalid input: '{response}' is not a number", markup="red")
58
+ ctx.textual.end_step("error")
49
59
  return Error(f"Invalid input: '{response}' is not a number")
50
60
 
51
61
  # Validate it's in range
52
62
  if selected_index < 1 or selected_index > len(issues):
63
+ ctx.textual.text(f"Invalid selection: must be between 1 and {len(issues)}", markup="red")
64
+ ctx.textual.end_step("error")
53
65
  return Error(f"Invalid selection: must be between 1 and {len(issues)}")
54
66
 
55
67
  # Convert to 0-based index
56
68
  selected_issue = issues[selected_index - 1]
57
69
 
58
70
  ctx.textual.text("")
59
- ctx.textual.mount(
60
- Panel(
61
- text=msg.Steps.PromptSelectIssue.ISSUE_SELECTION_CONFIRM.format(
62
- key=selected_issue.key,
63
- summary=selected_issue.summary
64
- ),
65
- panel_type="success"
66
- )
71
+ ctx.textual.text(
72
+ msg.Steps.PromptSelectIssue.ISSUE_SELECTION_CONFIRM.format(
73
+ key=selected_issue.key,
74
+ summary=selected_issue.summary
75
+ ),
76
+ markup="green"
67
77
  )
68
78
 
79
+ ctx.textual.end_step("success")
69
80
  return Success(
70
81
  msg.Steps.PromptSelectIssue.SELECT_SUCCESS.format(key=selected_issue.key),
71
82
  metadata={
@@ -74,6 +85,8 @@ def prompt_select_issue_step(ctx: WorkflowContext) -> WorkflowResult:
74
85
  }
75
86
  )
76
87
  except (KeyboardInterrupt, EOFError):
88
+ ctx.textual.text("User cancelled issue selection", markup="red")
89
+ ctx.textual.end_step("error")
77
90
  return Error("User cancelled issue selection")
78
91
 
79
92
 
@@ -3,7 +3,7 @@ Search JIRA issues using saved query from utils registry
3
3
  """
4
4
 
5
5
  from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
6
- from titan_cli.ui.tui.widgets import Panel, Table
6
+ from titan_cli.ui.tui.widgets import Table
7
7
  from ..exceptions import JiraAPIError
8
8
  from ..messages import msg
9
9
  from ..utils import SAVED_QUERIES, IssueSorter
@@ -57,14 +57,19 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
57
57
  if not ctx.textual:
58
58
  return Error("Textual UI context is not available for this step.")
59
59
 
60
+ # Begin step container
61
+ ctx.textual.begin_step("Search Open Issues")
62
+
60
63
  if not ctx.jira:
61
- ctx.textual.mount(Panel(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, panel_type="error"))
64
+ ctx.textual.text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, markup="red")
65
+ ctx.textual.end_step("error")
62
66
  return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
63
67
 
64
68
  # Get query name
65
69
  query_name = ctx.get("query_name")
66
70
  if not query_name:
67
- ctx.textual.mount(Panel(msg.Steps.Search.QUERY_NAME_REQUIRED, panel_type="error"))
71
+ ctx.textual.text(msg.Steps.Search.QUERY_NAME_REQUIRED, markup="red")
72
+ ctx.textual.end_step("error")
68
73
  return Error(msg.Steps.Search.QUERY_NAME_REQUIRED)
69
74
 
70
75
  # Get all predefined queries from utils
@@ -102,7 +107,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
102
107
  error_msg += "\n\n" + msg.Steps.Search.ADD_CUSTOM_HINT + "\n"
103
108
  error_msg += msg.Steps.Search.CUSTOM_QUERY_EXAMPLE
104
109
 
105
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
110
+ ctx.textual.text(error_msg, markup="red")
111
+ ctx.textual.end_step("error")
106
112
  return Error(error_msg)
107
113
 
108
114
  jql = all_queries[query_name]
@@ -119,7 +125,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
119
125
 
120
126
  if not project:
121
127
  error_msg = msg.Steps.Search.PROJECT_REQUIRED.format(query_name=query_name, jql=jql)
122
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
128
+ ctx.textual.text(error_msg, markup="red")
129
+ ctx.textual.end_step("error")
123
130
  return Error(error_msg)
124
131
 
125
132
  jql = jql.format(project=project)
@@ -142,12 +149,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
142
149
  issues = ctx.jira.search_tickets(jql=jql, max_results=max_results)
143
150
 
144
151
  if not issues:
145
- ctx.textual.mount(
146
- Panel(
147
- text=f"No issues found for query: {query_name}",
148
- panel_type="info"
149
- )
150
- )
152
+ ctx.textual.text(f"No issues found for query: {query_name}", markup="dim")
153
+ ctx.textual.end_step("success")
151
154
  return Success(
152
155
  "No issues found",
153
156
  metadata={
@@ -158,12 +161,8 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
158
161
  )
159
162
 
160
163
  # Show results
161
- ctx.textual.mount(
162
- Panel(
163
- text=f"Found {len(issues)} issues",
164
- panel_type="success"
165
- )
166
- )
164
+ ctx.textual.text("") # spacing
165
+ ctx.textual.text(f"Found {len(issues)} issues", markup="green")
167
166
  ctx.textual.text("")
168
167
 
169
168
  # Show detailed table
@@ -214,6 +213,7 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
214
213
  ctx.textual.text(f"{i}. {issue.key} - {getattr(issue, 'summary', 'N/A')}")
215
214
  ctx.textual.text("")
216
215
 
216
+ ctx.textual.end_step("success")
217
217
  return Success(
218
218
  f"Found {len(issues)} issues using query: {query_name}",
219
219
  metadata={
@@ -225,13 +225,15 @@ def search_saved_query_step(ctx: WorkflowContext) -> WorkflowResult:
225
225
 
226
226
  except JiraAPIError as e:
227
227
  error_msg = f"JIRA search failed: {e}"
228
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
228
+ ctx.textual.text(error_msg, markup="red")
229
+ ctx.textual.end_step("error")
229
230
  return Error(error_msg)
230
231
  except Exception as e:
231
232
  import traceback
232
233
  error_detail = traceback.format_exc()
233
234
  error_msg = f"Unexpected error: {e}\n\nTraceback:\n{error_detail}"
234
- ctx.textual.mount(Panel(error_msg, panel_type="error"))
235
+ ctx.textual.text(error_msg, markup="red")
236
+ ctx.textual.end_step("error")
235
237
  return Error(error_msg)
236
238
 
237
239