5353from snakesee .state .clock import get_clock
5454from snakesee .state .paths import WorkflowPaths
5555from snakesee .state .workflow_state import WorkflowState
56+ from snakesee .tui .accessibility import ACCESSIBLE_CONFIG
57+ from snakesee .tui .accessibility import DEFAULT_CONFIG
58+ from snakesee .tui .accessibility import AccessibilityConfig
5659from snakesee .validation import EventAccumulator
5760from snakesee .validation import ValidationLogger
5861from snakesee .validation import compare_states
@@ -145,6 +148,7 @@ class WorkflowMonitorTUI:
145148 p: Pause/resume auto-refresh
146149 e: Toggle time estimation
147150 w: Toggle wildcard conditioning (estimate per sample/batch)
151+ a: Toggle colorblind-accessible mode
148152 r: Force refresh
149153 Ctrl+r: Hard refresh (reload historical data)
150154
@@ -192,6 +196,7 @@ def __init__(
192196 weighting_strategy : WeightingStrategy = "index" ,
193197 half_life_logs : int = 10 ,
194198 half_life_days : float = 7.0 ,
199+ accessibility_config : AccessibilityConfig | None = None ,
195200 ) -> None :
196201 """
197202 Initialize the TUI.
@@ -205,6 +210,8 @@ def __init__(
205210 weighting_strategy: Strategy for weighting historical data ("index" or "time").
206211 half_life_logs: Half-life in run count for index-based weighting.
207212 half_life_days: Half-life in days for time-based weighting.
213+ accessibility_config: Visual encoding config for colorblind accessibility.
214+ Defaults to DEFAULT_CONFIG (standard block characters).
208215 """
209216 self .workflow_dir = workflow_dir
210217 self .refresh_rate = refresh_rate
@@ -213,6 +220,7 @@ def __init__(
213220 self .weighting_strategy = weighting_strategy
214221 self .half_life_logs = half_life_logs
215222 self .half_life_days = half_life_days
223+ self ._accessibility_config = accessibility_config or DEFAULT_CONFIG
216224 self .console = Console ()
217225 self ._running = True
218226 self ._estimator : TimeEstimator | None = None
@@ -1019,7 +1027,7 @@ def _handle_key(self, key: str) -> bool:
10191027 return False
10201028
10211029 def _handle_toggle_key (self , key : str ) -> bool :
1022- """Handle toggle keys (?, p, e, w, r, Ctrl+r). Returns True if key was handled."""
1030+ """Handle toggle keys (?, p, e, w, a, r, Ctrl+r). Returns True if handled."""
10231031 if key == "?" :
10241032 self ._show_help = True
10251033 self ._force_refresh = True
@@ -1042,6 +1050,14 @@ def _handle_toggle_key(self, key: str) -> bool:
10421050 if key .lower () == "r" :
10431051 self ._force_refresh = True
10441052 return True
1053+ if key .lower () == "a" :
1054+ # Toggle colorblind-accessible mode
1055+ if self ._accessibility_config == DEFAULT_CONFIG :
1056+ self ._accessibility_config = ACCESSIBLE_CONFIG
1057+ else :
1058+ self ._accessibility_config = DEFAULT_CONFIG
1059+ self ._force_refresh = True
1060+ return True
10451061 if key == "\x12 " : # Ctrl+r - hard refresh
10461062 self ._init_estimator ()
10471063 self ._force_refresh = True
@@ -1559,6 +1575,7 @@ def _make_help_panel(self) -> Panel:
15591575 help_text .add_row ("p" , "Pause/resume auto-refresh" )
15601576 help_text .add_row ("e" , "Toggle time estimation" )
15611577 help_text .add_row ("w" , "Toggle wildcard conditioning" )
1578+ help_text .add_row ("a" , "Toggle colorblind-accessible mode" )
15621579 help_text .add_row ("r" , "Force refresh" )
15631580 help_text .add_row ("Ctrl+r" , "Hard refresh (reload historical data)" )
15641581 help_text .add_row ("" , "" )
@@ -1648,20 +1665,28 @@ def _make_progress_bar(self, progress: WorkflowProgress, width: int = 40) -> Tex
16481665 total = max (1 , progress .total_jobs )
16491666 succeeded = progress .completed_jobs
16501667 failed = progress .failed_jobs
1668+ config = self ._accessibility_config
16511669
16521670 # Calculate widths for each segment
16531671 succeeded_width = int ((succeeded / total ) * width )
16541672 failed_width = int ((failed / total ) * width )
1655- remaining_width = width - succeeded_width - failed_width
1673+ unfinished = max (0 , progress .total_jobs - progress .completed_jobs - progress .failed_jobs )
1674+ incomplete = (
1675+ min (len (progress .incomplete_jobs_list ), unfinished )
1676+ if progress .status == WorkflowStatus .INCOMPLETE
1677+ else 0
1678+ )
1679+ incomplete_width = int ((incomplete / total ) * width )
1680+ remaining_width = width - succeeded_width - failed_width - incomplete_width
16561681
16571682 # Build the bar with colored segments
16581683 bar = Text ()
1659- bar .append ("█" * succeeded_width , style = "green" )
1660- bar .append ("█" * failed_width , style = "red" )
1661- if progress . status == WorkflowStatus . INCOMPLETE :
1662- bar .append ("░" * remaining_width , style = "yellow" ) # Incomplete = yellow
1663- else :
1664- bar .append ("░" * remaining_width , style = "dim" )
1684+ bar .append (config . succeeded . char * succeeded_width , style = "green" )
1685+ bar .append (config . failed . char * failed_width , style = "red" )
1686+ if incomplete_width > 0 :
1687+ bar .append (config . incomplete . char * incomplete_width , style = "yellow" )
1688+ if remaining_width > 0 :
1689+ bar .append (config . remaining . char * remaining_width , style = "dim" )
16651690
16661691 return bar
16671692
@@ -1719,14 +1744,35 @@ def _make_progress_panel(
17191744
17201745 eta_text = Text .from_markup (" " .join (eta_parts )) if eta_parts else Text ("" )
17211746
1722- # Legend for the progress bar when there are failures
1747+ # Legend for the progress bar
1748+ config = self ._accessibility_config
17231749 legend = Text ()
1724- if progress .failed_jobs > 0 :
1750+ show_legend = progress .failed_jobs > 0 or config .show_legend
1751+ if show_legend :
17251752 legend .append (" (" , style = "dim" )
1726- legend .append ("█" , style = "green" )
1727- legend .append (f"={ progress .completed_jobs } succeeded " , style = "dim" )
1728- legend .append ("█" , style = "red" )
1729- legend .append (f"={ progress .failed_jobs } failed" , style = "dim" )
1753+ legend .append (config .succeeded .char , style = "green" )
1754+ legend .append (f"={ progress .completed_jobs } { config .succeeded .label } " , style = "dim" )
1755+ if progress .failed_jobs > 0 :
1756+ legend .append (" " , style = "dim" )
1757+ legend .append (config .failed .char , style = "red" )
1758+ legend .append (f"={ progress .failed_jobs } { config .failed .label } " , style = "dim" )
1759+ unfinished = max (
1760+ 0 , progress .total_jobs - progress .completed_jobs - progress .failed_jobs
1761+ )
1762+ incomplete = (
1763+ min (len (progress .incomplete_jobs_list ), unfinished )
1764+ if progress .status == WorkflowStatus .INCOMPLETE
1765+ else 0
1766+ )
1767+ remaining = unfinished - incomplete
1768+ if incomplete > 0 :
1769+ legend .append (" " , style = "dim" )
1770+ legend .append (config .incomplete .char , style = "yellow" )
1771+ legend .append (f"={ incomplete } { config .incomplete .label } " , style = "dim" )
1772+ if remaining > 0 :
1773+ legend .append (" " , style = "dim" )
1774+ legend .append (config .remaining .char , style = "dim" )
1775+ legend .append (f"={ remaining } { config .remaining .label } " , style = "dim" )
17301776 legend .append (")" , style = "dim" )
17311777
17321778 # Border color based on status (use FG colors for normal states)
@@ -1740,7 +1786,7 @@ def _make_progress_panel(
17401786 border_style = border_colors .get (progress .status , FG_BLUE )
17411787
17421788 # Combine progress line with legend if present
1743- if progress . failed_jobs > 0 :
1789+ if show_legend :
17441790 full_progress = Text ()
17451791 full_progress .append (progress_line )
17461792 full_progress .append (legend )
0 commit comments