vibecore 0.4.1__tar.gz → 0.4.2__tar.gz

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.

Potentially problematic release.


This version of vibecore might be problematic. Click here for more details.

Files changed (88) hide show
  1. {vibecore-0.4.1 → vibecore-0.4.2}/PKG-INFO +1 -1
  2. {vibecore-0.4.1 → vibecore-0.4.2}/pyproject.toml +1 -1
  3. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/main.py +1 -3
  4. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/todo/manager.py +2 -10
  5. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/todo/models.py +5 -14
  6. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/todo/tools.py +3 -3
  7. vibecore-0.4.2/src/vibecore/widgets/feedback.py +164 -0
  8. vibecore-0.4.2/src/vibecore/widgets/feedback.tcss +121 -0
  9. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/messages.py +7 -1
  10. {vibecore-0.4.1 → vibecore-0.4.2}/.gitignore +0 -0
  11. {vibecore-0.4.1 → vibecore-0.4.2}/LICENSE +0 -0
  12. {vibecore-0.4.1 → vibecore-0.4.2}/README.md +0 -0
  13. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/__init__.py +0 -0
  14. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/agents/default.py +0 -0
  15. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/agents/prompts.py +0 -0
  16. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/agents/task.py +0 -0
  17. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/__init__.py +0 -0
  18. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/config.py +0 -0
  19. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/interceptor.py +0 -0
  20. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/manager.py +0 -0
  21. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/models.py +0 -0
  22. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/oauth_flow.py +0 -0
  23. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/pkce.py +0 -0
  24. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/storage.py +0 -0
  25. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/auth/token_manager.py +0 -0
  26. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/cli.py +0 -0
  27. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/context.py +0 -0
  28. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/flow.py +0 -0
  29. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/handlers/__init__.py +0 -0
  30. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/handlers/stream_handler.py +0 -0
  31. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/main.tcss +0 -0
  32. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/mcp/__init__.py +0 -0
  33. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/mcp/manager.py +0 -0
  34. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/mcp/server_wrapper.py +0 -0
  35. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/models/__init__.py +0 -0
  36. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/models/anthropic.py +0 -0
  37. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/models/anthropic_auth.py +0 -0
  38. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/prompts/common_system_prompt.txt +0 -0
  39. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/py.typed +0 -0
  40. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/session/__init__.py +0 -0
  41. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/session/file_lock.py +0 -0
  42. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/session/jsonl_session.py +0 -0
  43. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/session/loader.py +0 -0
  44. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/session/path_utils.py +0 -0
  45. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/settings.py +0 -0
  46. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/__init__.py +0 -0
  47. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/base.py +0 -0
  48. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/file/__init__.py +0 -0
  49. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/file/executor.py +0 -0
  50. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/file/tools.py +0 -0
  51. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/file/utils.py +0 -0
  52. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/path_validator.py +0 -0
  53. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/__init__.py +0 -0
  54. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/backends/__init__.py +0 -0
  55. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/backends/terminal_backend.py +0 -0
  56. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/helpers.py +0 -0
  57. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/manager.py +0 -0
  58. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/python/tools.py +0 -0
  59. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/shell/__init__.py +0 -0
  60. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/shell/executor.py +0 -0
  61. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/shell/tools.py +0 -0
  62. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/task/__init__.py +0 -0
  63. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/task/executor.py +0 -0
  64. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/task/tools.py +0 -0
  65. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/todo/__init__.py +0 -0
  66. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/webfetch/__init__.py +0 -0
  67. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/webfetch/executor.py +0 -0
  68. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/webfetch/models.py +0 -0
  69. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/webfetch/tools.py +0 -0
  70. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/__init__.py +0 -0
  71. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/base.py +0 -0
  72. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/ddgs/__init__.py +0 -0
  73. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/ddgs/backend.py +0 -0
  74. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/executor.py +0 -0
  75. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/models.py +0 -0
  76. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/tools/websearch/tools.py +0 -0
  77. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/utils/__init__.py +0 -0
  78. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/utils/text.py +0 -0
  79. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/core.py +0 -0
  80. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/core.tcss +0 -0
  81. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/expandable.py +0 -0
  82. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/expandable.tcss +0 -0
  83. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/info.py +0 -0
  84. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/info.tcss +0 -0
  85. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/messages.tcss +0 -0
  86. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/tool_message_factory.py +0 -0
  87. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/tool_messages.py +0 -0
  88. {vibecore-0.4.1 → vibecore-0.4.2}/src/vibecore/widgets/tool_messages.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vibecore
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: Build your own AI-powered automation tools in the terminal with this extensible agent framework
5
5
  Project-URL: Homepage, https://github.com/serialx/vibecore
6
6
  Project-URL: Repository, https://github.com/serialx/vibecore
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vibecore"
3
- version = "0.4.1"
3
+ version = "0.4.2"
4
4
  description = "Build your own AI-powered automation tools in the terminal with this extensible agent framework"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -65,6 +65,7 @@ class VibecoreApp(App):
65
65
  CSS_PATH: ClassVar = [
66
66
  "widgets/core.tcss",
67
67
  "widgets/messages.tcss",
68
+ "widgets/feedback.tcss",
68
69
  "widgets/tool_messages.tcss",
69
70
  "widgets/expandable.tcss",
70
71
  "widgets/info.tcss",
@@ -355,9 +356,6 @@ class VibecoreApp(App):
355
356
 
356
357
  self.current_worker = self.handle_streamed_response(result)
357
358
 
358
- def on_click(self) -> None:
359
- self.query_one("#input-textarea").focus()
360
-
361
359
  def _get_model_context_window(self) -> int:
362
360
  from vibecore.settings import settings
363
361
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from .models import TodoItem, TodoPriority, TodoStatus
5
+ from .models import TodoItem
6
6
 
7
7
 
8
8
  class TodoManager:
@@ -20,12 +20,4 @@ class TodoManager:
20
20
 
21
21
  def write(self, todos: list[dict[str, Any]]) -> None:
22
22
  """Replace the entire todo list."""
23
- self.todos = [
24
- TodoItem(
25
- id=todo["id"],
26
- content=todo["content"],
27
- status=TodoStatus(todo["status"]),
28
- priority=TodoPriority(todo["priority"]),
29
- )
30
- for todo in todos
31
- ]
23
+ self.todos = [TodoItem(**todo) for todo in todos]
@@ -1,10 +1,9 @@
1
1
  """Todo data models."""
2
2
 
3
3
  import uuid
4
- from dataclasses import dataclass, field
5
4
  from enum import Enum
6
5
 
7
- from pydantic import BaseModel
6
+ from pydantic import BaseModel, Field
8
7
 
9
8
 
10
9
  class TodoStatus(str, Enum):
@@ -19,18 +18,10 @@ class TodoPriority(str, Enum):
19
18
  LOW = "low"
20
19
 
21
20
 
22
- @dataclass
23
- class TodoItem:
24
- content: str
25
- status: TodoStatus
26
- priority: TodoPriority
27
- id: str = field(default_factory=lambda: str(uuid.uuid4()))
28
-
29
-
30
- class TodoItemModel(BaseModel):
21
+ class TodoItem(BaseModel):
31
22
  """Pydantic model for todo items."""
32
23
 
33
- id: str
24
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
34
25
  content: str
35
- status: str
36
- priority: str
26
+ status: TodoStatus
27
+ priority: TodoPriority
@@ -6,7 +6,7 @@ from agents import RunContextWrapper, function_tool
6
6
 
7
7
  from vibecore.context import VibecoreContext
8
8
 
9
- from .models import TodoItemModel
9
+ from .models import TodoItem
10
10
 
11
11
 
12
12
  @function_tool
@@ -38,7 +38,7 @@ async def todo_read(ctx: RunContextWrapper[VibecoreContext]) -> list[dict[str, A
38
38
 
39
39
 
40
40
  @function_tool
41
- async def todo_write(ctx: RunContextWrapper[VibecoreContext], todos: list[TodoItemModel]) -> str:
41
+ async def todo_write(ctx: RunContextWrapper[VibecoreContext], todos: list[TodoItem]) -> str:
42
42
  """Use this tool to create and manage a structured task list for your current coding session. This helps you
43
43
  track progress, organize complex tasks, and demonstrate thoroughness to the user. It also helps the user
44
44
  understand the progress of the task and overall progress of their requests.
@@ -106,6 +106,6 @@ async def todo_write(ctx: RunContextWrapper[VibecoreContext], todos: list[TodoIt
106
106
  Success message.
107
107
  """
108
108
  # Convert Pydantic models to dicts for the implementation
109
- todos_dict = [todo.model_dump() for todo in todos]
109
+ todos_dict = [todo.model_dump() if isinstance(todo, TodoItem) else TodoItem(**todo).model_dump() for todo in todos]
110
110
  ctx.context.todo_manager.write(todos_dict)
111
111
  return "Todo list updated successfully."
@@ -0,0 +1,164 @@
1
+ """Feedback widget for collecting user feedback."""
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import Horizontal, Vertical
5
+ from textual.content import Content
6
+ from textual.message import Message
7
+ from textual.reactive import reactive
8
+ from textual.widgets import Button, Checkbox, Static, TextArea
9
+
10
+ from vibecore.widgets.messages import BaseMessage, MessageHeader, MessageStatus
11
+
12
+
13
+ class FeedbackWidget(BaseMessage):
14
+ """A widget to collect user feedback with Good/Bad rating and optional text comment."""
15
+
16
+ class FeedbackSubmitted(Message):
17
+ """Event emitted when feedback is submitted."""
18
+
19
+ def __init__(self, rating: str, comment: str, criteria: dict[str, bool]) -> None:
20
+ """
21
+ Construct a FeedbackSubmitted message.
22
+
23
+ Args:
24
+ rating: The rating ("good" or "bad").
25
+ comment: Optional text comment from the user.
26
+ criteria: Dict of criterion name to boolean value.
27
+ """
28
+ super().__init__()
29
+ self.rating = rating
30
+ self.comment = comment
31
+ self.criteria = criteria
32
+
33
+ rating: reactive[str | None] = reactive(None)
34
+ comment: reactive[str] = reactive("")
35
+ submitted: reactive[bool] = reactive(False)
36
+ show_comment_input: reactive[bool] = reactive(False)
37
+ show_criteria: reactive[bool] = reactive(False)
38
+
39
+ def __init__(self, prompt: str = "How was this response?", **kwargs) -> None:
40
+ """
41
+ Construct a FeedbackWidget.
42
+
43
+ Args:
44
+ prompt: The prompt text to display.
45
+ **kwargs: Additional keyword arguments for Widget.
46
+ """
47
+ super().__init__(status=MessageStatus.IDLE, **kwargs)
48
+ self.prompt = prompt
49
+ self.add_class("feedback-widget")
50
+
51
+ def get_header_params(self) -> tuple[str, str, bool]:
52
+ """Get parameters for MessageHeader."""
53
+ return ("⏺", self.prompt, False)
54
+
55
+ def compose(self) -> ComposeResult:
56
+ """Create child widgets for the feedback widget."""
57
+ yield MessageHeader("⏺", self.prompt, status=self.status)
58
+
59
+ with Horizontal(classes="feedback-controls"):
60
+ yield Button("👍 Good", id="feedback-good", classes="feedback-button good-button", variant="success")
61
+ yield Button("👎 Bad", id="feedback-bad", classes="feedback-button bad-button", variant="error")
62
+
63
+ # Feedback form containing criteria, comment area, and submit button (shown after rating selection)
64
+ with Vertical(id="feedback-form", classes="feedback-form"):
65
+ # Structured feedback criteria checkboxes
66
+ with Vertical(classes="feedback-criteria"):
67
+ yield Static("Please check any criteria that apply:", classes="criteria-label")
68
+ yield Checkbox("Factual accuracy - Was the information correct?", id="criteria-accuracy")
69
+ yield Checkbox("Task completion - Did it fully address the request?", id="criteria-completion")
70
+ yield Checkbox(
71
+ "Instruction following - Did it follow specific constraints or requirements?",
72
+ id="criteria-instructions",
73
+ )
74
+ yield Checkbox(
75
+ "Good format/structure - Are the format and structure appropriate?", id="criteria-format"
76
+ )
77
+
78
+ # Comment text area
79
+ with Vertical(classes="feedback-comment-area"):
80
+ yield Static("Optional comment:", classes="comment-label")
81
+ yield TextArea(id="feedback-textarea", classes="feedback-textarea", show_line_numbers=False)
82
+
83
+ # Submit button
84
+ yield Button("Submit", id="feedback-submit", classes="feedback-submit-button", variant="primary")
85
+
86
+ with Vertical(id="feedback-result", classes="feedback-result"):
87
+ yield Static("", id="feedback-result-text")
88
+
89
+ def on_mount(self) -> None:
90
+ """Initialize widget state on mount."""
91
+ # Hide feedback form and result initially
92
+ self.query_one("#feedback-form").display = False
93
+ self.query_one("#feedback-result").display = False
94
+
95
+ def on_button_pressed(self, event: Button.Pressed) -> None:
96
+ """Handle button press events."""
97
+ button_id = event.button.id
98
+
99
+ if self.submitted:
100
+ return # Ignore clicks after submission
101
+
102
+ if button_id == "feedback-good":
103
+ self.rating = "good"
104
+ self._show_feedback_form()
105
+ event.button.add_class("selected")
106
+ self.query_one("#feedback-bad").remove_class("selected")
107
+
108
+ elif button_id == "feedback-bad":
109
+ self.rating = "bad"
110
+ self._show_feedback_form()
111
+ event.button.add_class("selected")
112
+ self.query_one("#feedback-good").remove_class("selected")
113
+
114
+ elif button_id == "feedback-submit":
115
+ self._submit_feedback()
116
+
117
+ def _show_feedback_form(self) -> None:
118
+ """Show the feedback form (criteria, comment area, and submit button)."""
119
+ self.show_criteria = True
120
+ self.show_comment_input = True
121
+ feedback_form = self.query_one("#feedback-form")
122
+ feedback_form.display = True
123
+ feedback_form.refresh()
124
+
125
+ def _get_criteria_values(self) -> dict[str, bool]:
126
+ """Get the current state of all criteria checkboxes."""
127
+ return {
128
+ "accuracy": self.query_one("#criteria-accuracy", Checkbox).value,
129
+ "completion": self.query_one("#criteria-completion", Checkbox).value,
130
+ "instructions": self.query_one("#criteria-instructions", Checkbox).value,
131
+ "format": self.query_one("#criteria-format", Checkbox).value,
132
+ }
133
+
134
+ def _submit_feedback(self) -> None:
135
+ """Submit the feedback."""
136
+ if not self.rating:
137
+ # Require a rating before submission
138
+ return
139
+
140
+ self.submitted = True
141
+ self.comment = self.query_one("#feedback-textarea", TextArea).text
142
+ criteria = self._get_criteria_values()
143
+
144
+ # Hide controls and form, show result
145
+ self.query_one(".feedback-controls").display = False
146
+ self.query_one("#feedback-form").display = False
147
+
148
+ # Show result
149
+ result_container = self.query_one("#feedback-result")
150
+ result_text = self.query_one("#feedback-result-text", Static)
151
+
152
+ rating_emoji = "👍" if self.rating == "good" else "👎"
153
+ result_msg = f"{rating_emoji} Thank you for your feedback!"
154
+ if self.comment:
155
+ result_msg += f"\nComment: {self.comment}"
156
+
157
+ result_text.update(Content(result_msg))
158
+ result_container.display = True
159
+
160
+ # Update status to success
161
+ self.status = MessageStatus.SUCCESS
162
+
163
+ # Emit feedback event
164
+ self.post_message(self.FeedbackSubmitted(self.rating, self.comment, criteria))
@@ -0,0 +1,121 @@
1
+ /* Feedback widget styles */
2
+
3
+ FeedbackWidget {
4
+ color: $text;
5
+
6
+ .feedback-controls {
7
+ height: auto;
8
+ width: 1fr;
9
+ padding: 0 0 0 4;
10
+ margin-top: 1;
11
+
12
+ .feedback-button {
13
+ height: 3;
14
+ min-width: 12;
15
+ margin-right: 1;
16
+
17
+ &.selected {
18
+ text-style: bold;
19
+ border: heavy;
20
+ }
21
+
22
+ &.good-button {
23
+ background: $success;
24
+ color: $text;
25
+
26
+ &:hover {
27
+ background: $success-lighten-1;
28
+ }
29
+
30
+ &.selected {
31
+ background: $success-darken-1;
32
+ }
33
+ }
34
+
35
+ &.bad-button {
36
+ background: $error;
37
+ color: $text;
38
+
39
+ &:hover {
40
+ background: $error-lighten-1;
41
+ }
42
+
43
+ &.selected {
44
+ background: $error-darken-1;
45
+ }
46
+ }
47
+ }
48
+
49
+ }
50
+
51
+ .feedback-form {
52
+ height: auto;
53
+ width: 1fr;
54
+ padding: 0 0 0 4;
55
+ margin-top: 1;
56
+
57
+ .feedback-criteria {
58
+ height: auto;
59
+ width: 1fr;
60
+
61
+ .criteria-label {
62
+ color: $text-muted;
63
+ margin-bottom: 0;
64
+ height: 1;
65
+ }
66
+
67
+ Checkbox {
68
+ border: none;
69
+ margin: 0;
70
+ padding: 0;
71
+ color: $text;
72
+
73
+ &:focus {
74
+ background: $panel;
75
+ }
76
+ }
77
+ }
78
+
79
+ .feedback-comment-area {
80
+ height: auto;
81
+ width: 1fr;
82
+ margin-top: 1;
83
+
84
+ .comment-label {
85
+ color: $text-muted;
86
+ margin-bottom: 0;
87
+ height: 1;
88
+ }
89
+
90
+ .feedback-textarea {
91
+ height: 6;
92
+ width: 1fr;
93
+ border: tall $primary;
94
+ }
95
+ }
96
+
97
+ .feedback-submit-button {
98
+ height: 3;
99
+ min-width: 10;
100
+ margin-top: 1;
101
+ background: $primary;
102
+ color: $text;
103
+
104
+ &:hover {
105
+ background: $primary-lighten-1;
106
+ }
107
+ }
108
+ }
109
+
110
+ .feedback-result {
111
+ height: auto;
112
+ width: 1fr;
113
+ padding: 1 0 0 4;
114
+ margin-top: 1;
115
+ color: $success;
116
+
117
+ #feedback-result-text {
118
+ color: $success;
119
+ }
120
+ }
121
+ }
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from enum import StrEnum
2
3
 
3
4
  from textual.app import ComposeResult
@@ -84,11 +85,16 @@ class MessageHeader(Widget):
84
85
  # self.query_one(".prefix").visible = self._prefix_visible
85
86
 
86
87
  def _on_mount(self, event) -> None:
88
+ disable_blink = bool(os.environ.get("TEXTUAL_SNAPSHOT_TEMPDIR"))
87
89
  self.blink_timer = self.set_interval(
88
90
  0.5,
89
91
  self._toggle_cursor_blink_visible,
90
- pause=(self.status != MessageStatus.EXECUTING),
92
+ pause=(self.status != MessageStatus.EXECUTING) or disable_blink,
91
93
  )
94
+ # Ensure the prefix starts visible for executing statuses so snapshot tests
95
+ # and initial renders see the indicator before the first timer tick hides it.
96
+ if self.status == MessageStatus.EXECUTING:
97
+ self._prefix_visible = True
92
98
 
93
99
 
94
100
  class BaseMessage(Widget):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes