codeplain 0.2.3__py3-none-any.whl → 0.2.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 (43) hide show
  1. {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/METADATA +3 -3
  2. {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/RECORD +43 -43
  3. codeplain_REST_api.py +57 -43
  4. config/system_config.yaml +1 -16
  5. file_utils.py +5 -4
  6. git_utils.py +43 -13
  7. memory_management.py +1 -1
  8. module_renderer.py +114 -0
  9. plain2code.py +91 -30
  10. plain2code_console.py +3 -5
  11. plain2code_exceptions.py +26 -6
  12. plain2code_logger.py +11 -5
  13. plain2code_utils.py +7 -5
  14. plain_file.py +15 -37
  15. plain_modules.py +1 -4
  16. plain_spec.py +24 -6
  17. render_machine/actions/create_dist.py +1 -1
  18. render_machine/actions/exit_with_error.py +1 -1
  19. render_machine/actions/prepare_testing_environment.py +1 -1
  20. render_machine/actions/render_conformance_tests.py +2 -4
  21. render_machine/actions/render_functional_requirement.py +6 -6
  22. render_machine/actions/run_conformance_tests.py +3 -2
  23. render_machine/actions/run_unit_tests.py +1 -1
  24. render_machine/render_context.py +3 -3
  25. render_machine/render_utils.py +14 -6
  26. standard_template_library/golang-console-app-template.plain +2 -2
  27. standard_template_library/python-console-app-template.plain +2 -2
  28. standard_template_library/typescript-react-app-template.plain +2 -2
  29. system_config.py +3 -11
  30. tests/test_imports.py +2 -2
  31. tests/test_plainfile.py +2 -2
  32. tests/test_plainfileparser.py +10 -10
  33. tests/test_plainspec.py +2 -2
  34. tests/test_requires.py +2 -1
  35. tui/components.py +311 -103
  36. tui/plain2code_tui.py +101 -52
  37. tui/state_handlers.py +94 -47
  38. tui/styles.css +240 -52
  39. tui/widget_helpers.py +43 -47
  40. {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/WHEEL +0 -0
  41. {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/entry_points.txt +0 -0
  42. {codeplain-0.2.3.dist-info → codeplain-0.2.5.dist-info}/licenses/LICENSE +0 -0
  43. /spinner.py → /tui/spinner.py +0 -0
tui/plain2code_tui.py CHANGED
@@ -1,12 +1,12 @@
1
1
  import os
2
2
  import threading
3
3
  import time
4
- from typing import Callable
4
+ from typing import Callable, Optional
5
5
 
6
6
  from textual.app import App, ComposeResult
7
7
  from textual.containers import Vertical, VerticalScroll
8
- from textual.widgets import ContentSwitcher, Footer, Header, Static
9
- from textual.worker import Worker, WorkerState
8
+ from textual.widgets import ContentSwitcher, Static
9
+ from textual.worker import Worker, WorkerFailed, WorkerState
10
10
 
11
11
  from event_bus import EventBus
12
12
  from plain2code_console import console
@@ -19,14 +19,19 @@ from plain2code_events import (
19
19
  RenderModuleStarted,
20
20
  RenderStateUpdated,
21
21
  )
22
+ from plain2code_exceptions import InternalServerError
22
23
  from render_machine.states import States
24
+ from tui.widget_helpers import log_to_widget
23
25
 
24
26
  from .components import (
27
+ CustomFooter,
25
28
  FRIDProgress,
26
29
  LogFilterChanged,
27
30
  LogLevelFilter,
31
+ RenderingInfoBox,
28
32
  ScriptOutputType,
29
33
  StructuredLogView,
34
+ TestScriptsContainer,
30
35
  TUIComponents,
31
36
  )
32
37
  from .state_handlers import (
@@ -37,6 +42,7 @@ from .state_handlers import (
37
42
  RenderErrorHandler,
38
43
  RenderSuccessHandler,
39
44
  ScriptOutputsHandler,
45
+ StateCompletionHandler,
40
46
  StateHandler,
41
47
  UnitTestsHandler,
42
48
  )
@@ -60,6 +66,7 @@ class Plain2CodeTUI(App):
60
66
  unittests_script: str,
61
67
  conformance_tests_script: str,
62
68
  prepare_environment_script: str,
69
+ state_machine_version: str,
63
70
  **kwargs,
64
71
  ):
65
72
  super().__init__(**kwargs)
@@ -67,21 +74,35 @@ class Plain2CodeTUI(App):
67
74
  self.event_bus = event_bus
68
75
  self.worker_fun = worker_fun
69
76
  self.render_id = render_id
70
- self.unittests_script = unittests_script
71
- self.conformance_tests_script = conformance_tests_script
72
- self.prepare_environment_script = prepare_environment_script
77
+ self.unittests_script: Optional[str] = unittests_script
78
+ self.conformance_tests_script: Optional[str] = conformance_tests_script
79
+ self.prepare_environment_script: Optional[str] = prepare_environment_script
80
+ self.state_machine_version = state_machine_version
73
81
 
74
82
  # Initialize state handlers
75
83
  self._state_handlers: dict[str, StateHandler] = {
76
- States.READY_FOR_FRID_IMPLEMENTATION.value: FridReadyHandler(self),
77
- States.PROCESSING_UNIT_TESTS.value: UnitTestsHandler(self),
78
- States.REFACTORING_CODE.value: RefactoringHandler(self),
79
- States.PROCESSING_CONFORMANCE_TESTS.value: ConformanceTestsHandler(self),
80
- States.FRID_FULLY_IMPLEMENTED.value: FridFullyImplementedHandler(self),
84
+ States.READY_FOR_FRID_IMPLEMENTATION.value: FridReadyHandler(
85
+ self, self.unittests_script, self.conformance_tests_script
86
+ ),
87
+ States.PROCESSING_UNIT_TESTS.value: UnitTestsHandler(
88
+ self, self.unittests_script, self.conformance_tests_script
89
+ ),
90
+ States.REFACTORING_CODE.value: RefactoringHandler(
91
+ self, self.unittests_script, self.conformance_tests_script
92
+ ),
93
+ States.PROCESSING_CONFORMANCE_TESTS.value: ConformanceTestsHandler(
94
+ self, self.unittests_script, self.conformance_tests_script
95
+ ),
96
+ States.FRID_FULLY_IMPLEMENTED.value: FridFullyImplementedHandler(
97
+ self, self.unittests_script, self.conformance_tests_script
98
+ ),
81
99
  }
82
100
  self._script_outputs_handler = ScriptOutputsHandler(self)
83
101
  self._render_error_handler = RenderErrorHandler(self)
84
102
  self._render_success_handler = RenderSuccessHandler(self)
103
+ self._state_completion_handler = StateCompletionHandler(
104
+ self, self.unittests_script, self.conformance_tests_script
105
+ )
85
106
 
86
107
  def get_active_script_types(self) -> list[ScriptOutputType]:
87
108
  """Get the list of active script output types based on which scripts exist.
@@ -114,52 +135,74 @@ class Plain2CodeTUI(App):
114
135
  def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
115
136
  """Handle worker state changes."""
116
137
  if event.worker.state == WorkerState.ERROR:
117
- # Exit the TUI and return the exception so the caller can handle it
118
- self.exit(result=event.worker.error)
138
+ # Extract the original exception from WorkerFailed wrapper
139
+ error = event.worker.error
140
+ original_error = error.__cause__ if isinstance(error, WorkerFailed) and error.__cause__ else error
141
+
142
+ # Every error in worker thread gets converted to InternalServerError so it's handled by the common call to
143
+ # action in plain2code.py
144
+ internal_error = InternalServerError(str(original_error))
145
+ internal_error.__cause__ = original_error
146
+
147
+ # Exit the TUI and return the wrapped exception
148
+ self.exit(result=internal_error)
149
+
150
+ def _handle_exception(self, error: Exception) -> None:
151
+ """Override Textual's exception handler to suppress console tracebacks for worker errors.
152
+
153
+ Worker exceptions are logged to file via the logging system (configured in file handler in plain2code.py),
154
+ but the verbose Textual/Rich traceback is suppressed from the terminal. The clean error message
155
+ is displayed via the exception handlers in main().
156
+ """
157
+ # Because TUI is running in main thread and code renderer is running in a worker thread, textual models this
158
+ # by raising a WorkerFailed exception in this case
159
+ if isinstance(error, WorkerFailed):
160
+ # Here, we still print the error to get some additional information to the file, but it's probably
161
+ # not necessary because we either way print the entire traceback to the console. Here, we could in the future
162
+ # print more information if we would want to.
163
+ original_error = error.__cause__ if error.__cause__ else error
164
+ console.error(
165
+ f"Worker failed with exception: {type(original_error).__name__}: {original_error}",
166
+ exc_info=(type(original_error), original_error, original_error.__traceback__),
167
+ stack_info=False,
168
+ )
169
+ return
170
+
171
+ # For non-worker exceptions, use the default Textual behavior
172
+ super()._handle_exception(error)
119
173
 
120
174
  def compose(self) -> ComposeResult:
121
175
  """Create child widgets for the app."""
122
- yield Header()
123
176
  with ContentSwitcher(id=TUIComponents.CONTENT_SWITCHER.value, initial=TUIComponents.DASHBOARD_VIEW.value):
124
177
  with Vertical(id=TUIComponents.DASHBOARD_VIEW.value):
125
178
  with VerticalScroll():
126
179
  yield Static(
127
- f"Render ID: {self.render_id}",
128
- id=TUIComponents.RENDER_ID_WIDGET.value,
180
+ f"[#FFFFFF]*codeplain[/#FFFFFF] [#888888](v{self.state_machine_version})[/#888888]\n",
181
+ id="codeplain-header",
182
+ classes="codeplain-header",
129
183
  )
130
184
  yield Static(
131
- "Rendering in progress...",
185
+ "[#FFFFFF]Rendering in progress...[/#FFFFFF]",
132
186
  id=TUIComponents.RENDER_STATUS_WIDGET.value,
133
187
  )
134
- yield FRIDProgress(id=TUIComponents.FRID_PROGRESS.value)
135
-
136
- # Get active script types for proper label alignment
137
- active_script_types = self.get_active_script_types()
138
-
139
- # Conditionally display unit test output widget
140
- if self.unittests_script is not None:
141
- yield Static(
142
- ScriptOutputType.UNIT_TEST_OUTPUT_TEXT.get_padded_label(active_script_types),
143
- id=TUIComponents.UNIT_TEST_SCRIPT_OUTPUT_WIDGET.value,
144
- )
145
-
146
- # Conditionally display conformance test output widget
147
- if self.conformance_tests_script is not None:
148
- yield Static(
149
- ScriptOutputType.CONFORMANCE_TEST_OUTPUT_TEXT.get_padded_label(active_script_types),
150
- id=TUIComponents.CONFORMANCE_TESTS_SCRIPT_OUTPUT_WIDGET.value,
151
- )
152
-
153
- # Conditionally display testing environment preparation output widget
154
- if self.prepare_environment_script is not None:
155
- yield Static(
156
- ScriptOutputType.TESTING_ENVIRONMENT_OUTPUT_TEXT.get_padded_label(active_script_types),
157
- id=TUIComponents.TESTING_ENVIRONMENT_SCRIPT_OUTPUT_WIDGET.value,
158
- )
188
+ yield FRIDProgress(
189
+ id=TUIComponents.FRID_PROGRESS.value,
190
+ unittests_script=self.unittests_script,
191
+ conformance_tests_script=self.conformance_tests_script,
192
+ )
193
+
194
+ # Test scripts container with border
195
+ yield TestScriptsContainer(
196
+ id=TUIComponents.TEST_SCRIPTS_CONTAINER.value,
197
+ show_unit_test=self.unittests_script is not None,
198
+ show_conformance_test=self.conformance_tests_script is not None,
199
+ show_testing_env=self.prepare_environment_script is not None,
200
+ )
159
201
  with Vertical(id=TUIComponents.LOG_VIEW.value):
160
202
  yield LogLevelFilter(id=TUIComponents.LOG_FILTER.value)
203
+ yield Static("", classes="filter-spacer")
161
204
  yield StructuredLogView(id=TUIComponents.LOG_WIDGET.value)
162
- yield Footer()
205
+ yield CustomFooter(render_id=self.render_id)
163
206
 
164
207
  def action_toggle_logs(self) -> None:
165
208
  """Toggle between dashboard and log view."""
@@ -172,18 +215,20 @@ class Plain2CodeTUI(App):
172
215
  def on_render_module_started(self, event: RenderModuleStarted):
173
216
  """Update TUI based on the current state machine state."""
174
217
  try:
175
- widget = self.query_one(f"#{TUIComponents.RENDER_MODULE_NAME_WIDGET.value}", Static)
176
- widget.update(f"{FRIDProgress.RENDERING_MODULE_TEXT}{event.module_name}")
218
+ frid_progress = self.query_one(f"#{TUIComponents.FRID_PROGRESS.value}", FRIDProgress)
219
+ info_box = frid_progress.query_one(RenderingInfoBox)
220
+ info_box.update_module(f"{FRIDProgress.RENDERING_MODULE_TEXT}{event.module_name}")
177
221
  except Exception as e:
178
- console.debug(f"Error updating render module name: {type(e).__name__}: {e}")
222
+ log_to_widget(self, "WARNING", f"Error updating render module name: {type(e).__name__}: {e}")
179
223
 
180
224
  def on_render_module_completed(self, _event: RenderModuleCompleted):
181
225
  """Update TUI based on the current state machine state."""
182
226
  try:
183
- widget = self.query_one(f"#{TUIComponents.RENDER_MODULE_NAME_WIDGET.value}", Static)
184
- widget.update(FRIDProgress.RENDERING_MODULE_TEXT)
227
+ frid_progress = self.query_one(f"#{TUIComponents.FRID_PROGRESS.value}", FRIDProgress)
228
+ info_box = frid_progress.query_one(RenderingInfoBox)
229
+ info_box.update_module(FRIDProgress.RENDERING_MODULE_TEXT)
185
230
  except Exception as e:
186
- console.debug(f"Error resetting render module name: {type(e).__name__}: {e}")
231
+ log_to_widget(self, "WARNING", f"Error resetting render module name: {type(e).__name__}: {e}")
187
232
 
188
233
  def on_log_message_emitted(self, event: LogMessageEmitted):
189
234
  try:
@@ -196,7 +241,9 @@ class Plain2CodeTUI(App):
196
241
  event.timestamp,
197
242
  )
198
243
  except Exception as e:
199
- console.debug(f"Error adding log message from {event.logger_name}: {type(e).__name__}: {e}")
244
+ log_to_widget(
245
+ self, "WARNING", f"Error adding log message from {event.logger_name}: {type(e).__name__}: {e}"
246
+ )
200
247
 
201
248
  def on_log_filter_changed(self, event: LogFilterChanged):
202
249
  """Handle log filter changes from LogLevelFilter widget."""
@@ -204,7 +251,7 @@ class Plain2CodeTUI(App):
204
251
  log_widget = self.query_one(f"#{TUIComponents.LOG_WIDGET.value}", StructuredLogView)
205
252
  log_widget.filter_logs(event.min_level)
206
253
  except Exception as e:
207
- console.debug(f"Error filtering logs to level {event.min_level}: {type(e).__name__}: {e}")
254
+ log_to_widget(self, "WARNING", f"Error filtering logs to level {event.min_level}: {type(e).__name__}: {e}")
208
255
 
209
256
  def on_render_state_updated(self, event: RenderStateUpdated):
210
257
  """Update TUI based on the current state machine state."""
@@ -231,6 +278,8 @@ class Plain2CodeTUI(App):
231
278
  if snapshot.script_execution_history.should_update_script_outputs:
232
279
  self._script_outputs_handler.handle(segments, snapshot, previous_state_segments)
233
280
 
281
+ self._state_completion_handler.handle(segments, snapshot, previous_state_segments)
282
+
234
283
  def on_render_completed(self, _event: RenderCompleted):
235
284
  """Handle successful render completion."""
236
285
  self._render_success_handler.handle()
@@ -250,7 +299,7 @@ class Plain2CodeTUI(App):
250
299
  # It ensures terminal reset sequences are flushed before exiting.
251
300
  self._driver.suspend_application_mode()
252
301
  except Exception as e:
253
- console.debug(f"Error suspending application mode: {type(e).__name__}: {e}")
302
+ log_to_widget(self, "WARNING", f"Error suspending application mode: {type(e).__name__}: {e}")
254
303
  finally:
255
304
  os._exit(0) # Kill process immediately, no cleanup - terminates all threads
256
305
 
tui/state_handlers.py CHANGED
@@ -1,12 +1,13 @@
1
1
  """State handlers for Plain2Code TUI state machine transitions."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
+ from typing import Optional
4
5
 
5
6
  from plain2code_events import RenderContextSnapshot
6
7
  from render_machine.states import States
7
8
 
8
9
  from . import components as tui_components
9
- from .components import ProgressItem, ScriptOutputType, TUIComponents
10
+ from .components import ProgressItem, ScriptOutputType, TestScriptsContainer, TUIComponents
10
11
  from .models import Substate
11
12
  from .widget_helpers import (
12
13
  display_error_message,
@@ -15,7 +16,6 @@ from .widget_helpers import (
15
16
  set_frid_progress_to_stopped,
16
17
  update_progress_item_status,
17
18
  update_progress_item_substates,
18
- update_widget_text,
19
19
  )
20
20
 
21
21
 
@@ -53,13 +53,15 @@ class StateHandler(ABC):
53
53
  class FridReadyHandler(StateHandler):
54
54
  """Handler for READY_FOR_FRID_IMPLEMENTATION state."""
55
55
 
56
- def __init__(self, tui):
56
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
57
57
  """Initialize handler with TUI instance.
58
58
 
59
59
  Args:
60
60
  tui: The Plain2CodeTUI instance
61
61
  """
62
62
  self.tui = tui
63
+ self.unittests_script = unittests_script
64
+ self.conformance_tests_script = conformance_tests_script
63
65
 
64
66
  def handle(self, _: list[str], snapshot: RenderContextSnapshot, _previous_state_segments: list[str]) -> None:
65
67
  """Handle READY_FOR_FRID_IMPLEMENTATION state."""
@@ -69,9 +71,13 @@ class FridReadyHandler(StateHandler):
69
71
 
70
72
  # Set progress states
71
73
  update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_RENDER_FR.value, ProgressItem.PROCESSING)
72
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.PENDING)
74
+ if self.conformance_tests_script is not None:
75
+ update_progress_item_status(
76
+ self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.PENDING
77
+ )
73
78
  # Reset others to PENDING if this is a restart/loop
74
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.PENDING)
79
+ if self.unittests_script is not None:
80
+ update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.PENDING)
75
81
  update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_REFACTORING.value, ProgressItem.PENDING)
76
82
 
77
83
  # Set substate for initial implementation
@@ -85,51 +91,59 @@ class FridReadyHandler(StateHandler):
85
91
  class UnitTestsHandler(StateHandler):
86
92
  """Handler for PROCESSING_UNIT_TESTS state."""
87
93
 
88
- def __init__(self, tui):
94
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
89
95
  """Initialize handler with TUI instance.
90
96
 
91
97
  Args:
92
98
  tui: The Plain2CodeTUI instance
93
99
  """
94
100
  self.tui = tui
101
+ self.unittests_script = unittests_script
102
+ self.conformance_tests_script = conformance_tests_script
95
103
 
96
104
  def handle(
97
105
  self, segments: list[str], _snapshot: RenderContextSnapshot, _previous_state_segments: list[str]
98
106
  ) -> None:
99
107
  """Handle PROCESSING_UNIT_TESTS state."""
100
108
  if segments[2] == States.UNIT_TESTS_READY.value:
101
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_RENDER_FR.value, ProgressItem.COMPLETED)
102
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.PROCESSING)
109
+ if self.unittests_script is not None:
110
+ update_progress_item_status(
111
+ self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.PROCESSING
112
+ )
103
113
 
104
114
  # Clear substates from completed implementation phase
105
- update_progress_item_substates(
106
- self.tui,
107
- TUIComponents.FRID_PROGRESS_UNIT_TEST.value,
108
- [Substate("Running unit tests")],
109
- )
115
+ if self.unittests_script is not None:
116
+ update_progress_item_substates(
117
+ self.tui,
118
+ TUIComponents.FRID_PROGRESS_UNIT_TEST.value,
119
+ [Substate("Running unit tests")],
120
+ )
121
+
110
122
  if segments[2] == States.UNIT_TESTS_FAILED.value:
111
- update_progress_item_substates(
112
- self.tui,
113
- TUIComponents.FRID_PROGRESS_UNIT_TEST.value,
114
- [Substate("Fixing unit tests")],
115
- )
123
+ if self.unittests_script is not None:
124
+ update_progress_item_substates(
125
+ self.tui,
126
+ TUIComponents.FRID_PROGRESS_UNIT_TEST.value,
127
+ [Substate("Fixing unit tests")],
128
+ )
116
129
 
117
130
 
118
131
  class RefactoringHandler(StateHandler):
119
132
  """Handler for REFACTORING_CODE state."""
120
133
 
121
- def __init__(self, tui):
134
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
122
135
  """Initialize handler with TUI instance.
123
136
 
124
137
  Args:
125
138
  tui: The Plain2CodeTUI instance
126
139
  """
127
140
  self.tui = tui
141
+ self.unittests_script = unittests_script
142
+ self.conformance_tests_script = conformance_tests_script
128
143
 
129
144
  def handle(self, segments: list[str], _snapshot: RenderContextSnapshot, previous_state_segments: list[str]) -> None:
130
145
  """Handle REFACTORING_CODE state."""
131
146
  if len(previous_state_segments) == 2 and previous_state_segments[1] == States.STEP_COMPLETED.value:
132
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.COMPLETED)
133
147
  update_progress_item_status(
134
148
  self.tui, TUIComponents.FRID_PROGRESS_REFACTORING.value, ProgressItem.PROCESSING
135
149
  )
@@ -151,7 +165,7 @@ class RefactoringHandler(StateHandler):
151
165
  elif segments[3] == States.UNIT_TESTS_FAILED.value:
152
166
  update_progress_item_substates(
153
167
  self.tui,
154
- TUIComponents.FRID_PROGRESS_UNIT_TEST.value,
168
+ TUIComponents.FRID_PROGRESS_REFACTORING.value,
155
169
  [Substate("Refactoring code", children=[Substate("Fixing unit tests")])],
156
170
  )
157
171
 
@@ -159,21 +173,23 @@ class RefactoringHandler(StateHandler):
159
173
  class ConformanceTestsHandler(StateHandler):
160
174
  """Handler for PROCESSING_CONFORMANCE_TESTS state."""
161
175
 
162
- def __init__(self, tui):
176
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
163
177
  """Initialize handler with TUI instance.
164
178
 
165
179
  Args:
166
180
  tui: The Plain2CodeTUI instance
167
181
  """
168
182
  self.tui = tui
183
+ self.unittests_script = unittests_script
184
+ self.conformance_tests_script = conformance_tests_script
169
185
 
170
186
  def handle(self, segments: list[str], snapshot: RenderContextSnapshot, previous_state_segments: list[str]) -> None:
171
187
  """Handle PROCESSING_CONFORMANCE_TESTS state."""
172
188
  if previous_state_segments[1] == States.REFACTORING_CODE.value:
173
- update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_REFACTORING.value, ProgressItem.COMPLETED)
174
- update_progress_item_status(
175
- self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.PROCESSING
176
- )
189
+ if self.conformance_tests_script is not None:
190
+ update_progress_item_status(
191
+ self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.PROCESSING
192
+ )
177
193
 
178
194
  if segments[2] != States.POSTPROCESSING_CONFORMANCE_TESTS.value:
179
195
  if segments[2] == States.CONFORMANCE_TESTING_INITIALISED.value:
@@ -229,50 +245,47 @@ class ScriptOutputsHandler(StateHandler):
229
245
  self.tui = tui
230
246
 
231
247
  def handle(self, _segments: list[str], snapshot: RenderContextSnapshot, previous_state_segments: list[str]) -> None:
232
- # Get active script types for proper label alignment
233
- active_script_types = self.tui.get_active_script_types()
248
+ # Update test scripts container
249
+ container = self.tui.query_one(f"#{TUIComponents.TEST_SCRIPTS_CONTAINER.value}", TestScriptsContainer)
234
250
 
235
251
  if any(segment == States.UNIT_TESTS_READY.value for segment in previous_state_segments):
236
- update_widget_text(
237
- self.tui,
238
- TUIComponents.UNIT_TEST_SCRIPT_OUTPUT_WIDGET.value,
239
- f"{ScriptOutputType.UNIT_TEST_OUTPUT_TEXT.get_padded_label(active_script_types)}{snapshot.script_execution_history.latest_unit_test_output_path}", # type: ignore
240
- )
252
+ if snapshot.script_execution_history.latest_unit_test_output_path:
253
+ container.update_unit_test(
254
+ f"{ScriptOutputType.UNIT_TEST_OUTPUT_TEXT.value}{snapshot.script_execution_history.latest_unit_test_output_path}"
255
+ )
241
256
 
242
257
  if len(previous_state_segments) > 2 and previous_state_segments[2] == States.CONFORMANCE_TEST_GENERATED.value:
243
- update_widget_text(
244
- self.tui,
245
- TUIComponents.TESTING_ENVIRONMENT_SCRIPT_OUTPUT_WIDGET.value,
246
- f"{ScriptOutputType.TESTING_ENVIRONMENT_OUTPUT_TEXT.get_padded_label(active_script_types)}{snapshot.script_execution_history.latest_testing_environment_output_path}", # type: ignore
247
- )
258
+ if snapshot.script_execution_history.latest_testing_environment_output_path:
259
+ container.update_testing_env(
260
+ f"{ScriptOutputType.TESTING_ENVIRONMENT_OUTPUT_TEXT.value}{snapshot.script_execution_history.latest_testing_environment_output_path}"
261
+ )
248
262
 
249
263
  if (
250
264
  len(previous_state_segments) > 2
251
265
  and previous_state_segments[2] == States.CONFORMANCE_TEST_ENV_PREPARED.value
252
266
  ):
253
- update_widget_text(
254
- self.tui,
255
- TUIComponents.CONFORMANCE_TESTS_SCRIPT_OUTPUT_WIDGET.value,
256
- f"{ScriptOutputType.CONFORMANCE_TEST_OUTPUT_TEXT.get_padded_label(active_script_types)}{snapshot.script_execution_history.latest_conformance_test_output_path}", # type: ignore
257
- )
267
+ if snapshot.script_execution_history.latest_conformance_test_output_path:
268
+ container.update_conformance_test(
269
+ f"{ScriptOutputType.CONFORMANCE_TEST_OUTPUT_TEXT.value}{snapshot.script_execution_history.latest_conformance_test_output_path}"
270
+ )
258
271
 
259
272
 
260
273
  class FridFullyImplementedHandler(StateHandler):
261
274
  """Handler for FRID_FULLY_IMPLEMENTED state."""
262
275
 
263
- def __init__(self, tui):
276
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
264
277
  """Initialize handler with TUI instance.
265
278
 
266
279
  Args:
267
280
  tui: The Plain2CodeTUI instance
268
281
  """
269
282
  self.tui = tui
283
+ self.unittests_script = unittests_script
284
+ self.conformance_tests_script = conformance_tests_script
270
285
 
271
286
  def handle(self, _: list[str], _snapshot: RenderContextSnapshot, _previous_state_segments: list[str]) -> None:
272
287
  """Handle FRID_FULLY_IMPLEMENTED state."""
273
- update_progress_item_status(
274
- self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.COMPLETED
275
- )
288
+ pass
276
289
 
277
290
 
278
291
  class RenderSuccessHandler:
@@ -305,3 +318,37 @@ class RenderErrorHandler:
305
318
  def handle(self, error_message: str) -> None:
306
319
  set_frid_progress_to_stopped(self.tui)
307
320
  display_error_message(self.tui, error_message)
321
+
322
+
323
+ class StateCompletionHandler(StateHandler):
324
+ """Handler for state completion."""
325
+
326
+ def __init__(self, tui, unittests_script: Optional[str], conformance_tests_script: Optional[str]):
327
+ """Initialize handler with TUI instance.
328
+
329
+ Args:
330
+ tui: The Plain2CodeTUI instance
331
+ """
332
+ self.tui = tui
333
+ self.unittests_script = unittests_script
334
+ self.conformance_tests_script = conformance_tests_script
335
+
336
+ def handle(self, segments: list[str], _snapshot: RenderContextSnapshot, previous_state_segments: list[str]) -> None:
337
+ if len(previous_state_segments) < 2 or len(segments) < 2:
338
+ return
339
+ current_segment = segments[1]
340
+ previous_segment = previous_state_segments[1]
341
+ should_update_state = current_segment != previous_segment
342
+ if not should_update_state:
343
+ return
344
+
345
+ if previous_segment == States.READY_FOR_FRID_IMPLEMENTATION.value:
346
+ update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_RENDER_FR.value, ProgressItem.COMPLETED)
347
+ if previous_segment == States.PROCESSING_UNIT_TESTS.value:
348
+ update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_UNIT_TEST.value, ProgressItem.COMPLETED)
349
+ if previous_segment == States.REFACTORING_CODE.value:
350
+ update_progress_item_status(self.tui, TUIComponents.FRID_PROGRESS_REFACTORING.value, ProgressItem.COMPLETED)
351
+ if previous_segment == States.PROCESSING_CONFORMANCE_TESTS.value:
352
+ update_progress_item_status(
353
+ self.tui, TUIComponents.FRID_PROGRESS_CONFORMANCE_TEST.value, ProgressItem.COMPLETED
354
+ )