Explorar el Código

Standardize DisplayManager and IconManager responsibilities

Refactored display system to establish clear separation of concerns:

**DisplayManager Changes:**
- Added comprehensive class documentation explaining design principles
- New standardized methods for common display patterns:
  - display_status_table() - Status tables with success/error indicators
  - display_summary_table() - Simple key-value tables
  - display_file_operation_table() - File operations with sizes
  - display_heading() - Headings with optional icons
  - display_warning_with_confirmation() - Interactive warnings
  - display_skipped() - Skipped/disabled messages
  - get_lock_icon() - Lock icon accessor for prompts
- All methods now consistently use IconManager internally

**IconManager Changes:**
- Clarified that IconManager is for INTERNAL use only
- External code should never import or call IconManager directly
- All icon display must go through DisplayManager methods

**Module Updates:**
- module.py: Replaced direct IconManager calls and console.print
  with DisplayManager methods (65+ replacements)
- repo.py: Updated to use display_status_table()
- prompt.py: Removed IconManager import, uses display methods

**Documentation:**
- Updated AGENTS.md with comprehensive DisplayManager guide
- Added architecture diagram showing proper layer separation
- Documented all new DisplayManager methods with examples
- Clear best practices: what NOT to do vs what to do

**Impact:**
- Consistent display patterns across entire CLI
- Easier to maintain and extend display functionality
- Clear separation: Module code → DisplayManager → IconManager
- All existing commands tested and working correctly

Fixes #1297
xcad hace 9 meses
padre
commit
08bc6f19e8
Se han modificado 5 ficheros con 304 adiciones y 119 borrados
  1. 82 33
      AGENTS.md
  2. 150 1
      cli/core/display.py
  3. 62 68
      cli/core/module.py
  4. 3 5
      cli/core/prompt.py
  5. 7 12
      cli/core/repo.py

+ 82 - 33
AGENTS.md

@@ -157,57 +157,106 @@ git sparse-checkout set library/*
 
 ### Display Manager
 
-`DisplayManager` (`cli/core/display.py`) provides consistent output rendering:
-
-**Key Methods:**
-- `display_message(level, message, context)` - Unified message display
-- `display_success(message, context)` - Success messages
-- `display_error(message, context)` - Error messages  
-- `display_warning(message, context)` - Warning messages
-- `display_info(message, context)` - Info messages
-- `display_templates_table(templates, module, title)` - Template listings
-- `display_template_details(template, id)` - Detailed template view
+`DisplayManager` (`cli/core/display.py`) is the **centralized interface** for ALL CLI output rendering.
+
+**Design Principles:**
+- **Single Responsibility**: All display logic goes through DisplayManager
+- **Encapsulation**: IconManager is ONLY used internally by DisplayManager
+- **Consistency**: External code should NEVER directly call `IconManager` or `console.print`
+- **Standardization**: Provides unified methods for all display types
+
+**Categories of Methods:**
+
+1. **Status Messages:**
+   - `display_success(message, context)` - Success messages with check icon
+   - `display_error(message, context)` - Error messages with error icon
+   - `display_warning(message, context)` - Warning messages with warning icon
+   - `display_info(message, context)` - Info messages with info icon
+   - `display_skipped(message, reason)` - Skipped/disabled messages
+
+2. **Tables:**
+   - `display_templates_table(templates, module, title)` - Template listings
+   - `display_status_table(title, rows, columns)` - Status tables with success/error indicators
+   - `display_summary_table(title, items)` - Simple key-value summary tables
+   - `display_file_operation_table(files)` - File operations with sizes and statuses
+
+3. **Structured Display:**
+   - `display_template_details(template, id)` - Detailed template view with file tree and variables
+   - `display_config_tree(spec, module_name, show_all)` - Configuration as tree view
+   - `display_file_generation_confirmation(output_dir, files, existing)` - File generation preview
+
+4. **Interactive:**
+   - `display_warning_with_confirmation(message, details, default)` - Warning with user confirmation
+   - `display_section_header(title, description)` - Section headers for prompts
+
+5. **Utility:**
+   - `display_heading(text, icon_type, style)` - Headings with optional icons
+   - `display_next_steps(next_steps, variable_values)` - Post-generation instructions
+   - `get_lock_icon()` - Get lock icon for sensitive variables (for prompts)
 
 **Usage:**
 ```python
 from cli.core.display import DisplayManager
 
 display = DisplayManager()
+
+# Status messages
 display.display_success("Operation completed")
 display.display_error("Failed to process", context="module_name")
+
+# Tables
+status_rows = [("lib1", "Updated", True), ("lib2", "Failed", False)]
+display.display_status_table("Update Summary", status_rows)
+
+# Warnings with confirmation
+if display.display_warning_with_confirmation(
+    "Directory is not empty",
+    ["5 files will be overwritten"],
+    default=False
+):
+    # User confirmed
+    pass
 ```
 
 ### Icon Manager
 
-`IconManager` provides **Nerd Font icons** for consistent CLI display:
+`IconManager` provides **Nerd Font icons** internally for DisplayManager.
 
-**Categories:**
-- **File Types**: `FILE_YAML`, `FILE_JSON`, `FILE_MARKDOWN`, `FILE_JINJA2`, `FILE_DOCKER`, etc.
-- **Status**: `STATUS_SUCCESS` (✓), `STATUS_ERROR` (✗), `STATUS_WARNING` (⚠), `STATUS_INFO` (ℹ)
-- **UI Elements**: `UI_CONFIG`, `UI_LOCK`, `UI_SETTINGS`, `UI_ARROW_RIGHT`
-
-**Important:** Icons use Nerd Font glyphs (Unicode characters). The terminal must have a Nerd Font installed.
+**IMPORTANT:** 
+- ❌ **DO NOT import or use IconManager directly in your code**
+- ✅ **All icon display should go through DisplayManager methods**
+- The only exception is DisplayManager itself, which uses IconManager internally
 
-**Usage:**
-```python
-from cli.core.display import IconManager
-
-# Get status icon
-icon = IconManager.get_status_icon("success")  # Returns \uf00c (✓)
-
-# Get file icon
-icon = IconManager.get_file_icon("config.yaml")  # Returns \uf15c
+**Icon Categories (Internal):**
+- **File Types**: `FILE_YAML`, `FILE_JSON`, `FILE_MARKDOWN`, `FILE_JINJA2`, `FILE_DOCKER`, etc.
+- **Status**: `STATUS_SUCCESS`, `STATUS_ERROR`, `STATUS_WARNING`, `STATUS_INFO`, `STATUS_SKIPPED`
+- **UI Elements**: `UI_CONFIG`, `UI_LOCK`, `UI_SETTINGS`, `UI_ARROW_RIGHT`, `FILE_FOLDER`
 
-# Direct access
-folder = IconManager.folder()  # \uf07b
-lock = IconManager.lock()  # \uf084
+**Architecture:**
+```
+┌─────────────────┐
+│  Module Code    │
+│  (module.py,    │
+│   repo.py, etc) │
+└────────┬────────┘
+         │
+         ▼
+┌─────────────────┐
+│ DisplayManager  │  ◄── Use this for ALL output
+└────────┬────────┘
+         │
+         ▼
+┌─────────────────┐
+│  IconManager    │  ◄── Internal use only
+└─────────────────┘
 ```
 
 **Best Practices:**
-- ❌ **Don't use emojis** (✓, ✗, ⚠) directly in output
-- ✅ **Do use IconManager** for all icons and symbols
-- ✅ **Do use DisplayManager** for consistent formatting
-- Example: `display.display_success(f"Added {name}")` not `console.print(f"✓ Added {name}")`
+- ❌ `from cli.core.display import IconManager` - NEVER do this
+- ❌ `console.print(f"✓ {message}")` - Don't use emojis or direct console access
+- ❌ `IconManager.get_status_icon("success")` - Don't call IconManager directly
+- ✅ `display.display_success(message)` - Always use DisplayManager methods
+- ✅ `display.get_lock_icon()` - Exception: prompts can get lock icon for text formatting
 
 ## Architecture Notes
 

+ 150 - 1
cli/core/display.py

@@ -145,7 +145,21 @@ class IconManager:
 
 
 class DisplayManager:
-    """Handles all rich rendering for the CLI."""
+    """Handles all rich rendering for the CLI.
+    
+    This class is responsible for ALL display output in the CLI, including:
+    - Status messages (success, error, warning, info)
+    - Tables (templates, summaries, results)
+    - Trees (file structures, configurations)
+    - Confirmation dialogs and prompts
+    - Headers and sections
+    
+    Design Principles:
+    - All display logic should go through DisplayManager methods
+    - IconManager is ONLY used internally by DisplayManager
+    - External code should never directly call IconManager or console.print
+    - Consistent formatting across all display types
+    """
 
     def display_templates_table(
         self, templates: list, module_name: str, title: str
@@ -517,3 +531,138 @@ class DisplayManager:
             logger.warning(f"Failed to render next_steps as template: {e}")
             # Fallback to plain text if rendering fails
             console.print(next_steps)
+    
+    def display_status_table(self, title: str, rows: list[tuple[str, str, bool]], 
+                            columns: tuple[str, str] = ("Item", "Status")) -> None:
+        """Display a status table with success/error indicators.
+        
+        Args:
+            title: Table title
+            rows: List of tuples (name, message, success_bool)
+            columns: Column headers (name_header, status_header)
+        """
+        table = Table(title=title, show_header=True)
+        table.add_column(columns[0], style="cyan", no_wrap=True)
+        table.add_column(columns[1])
+        
+        for name, message, success in rows:
+            status_style = "green" if success else "red"
+            status_icon = IconManager.get_status_icon("success" if success else "error")
+            table.add_row(name, f"[{status_style}]{status_icon} {message}[/{status_style}]")
+        
+        console.print(table)
+    
+    def display_summary_table(self, title: str, items: dict[str, str]) -> None:
+        """Display a simple two-column summary table.
+        
+        Args:
+            title: Table title
+            items: Dictionary of key-value pairs to display
+        """
+        table = Table(title=title, show_header=False, box=None, padding=(0, 2))
+        table.add_column(style="bold")
+        table.add_column()
+        
+        for key, value in items.items():
+            table.add_row(key, value)
+        
+        console.print(table)
+    
+    def display_file_operation_table(self, files: list[tuple[str, int, str]]) -> None:
+        """Display a table of file operations with sizes and statuses.
+        
+        Args:
+            files: List of tuples (file_path, size_bytes, status)
+        """
+        table = Table(show_header=True, header_style="bold cyan", box=None, padding=(0, 1))
+        table.add_column("File", style="white", no_wrap=False)
+        table.add_column("Size", justify="right", style="dim")
+        table.add_column("Status", style="yellow")
+        
+        for file_path, size_bytes, status in files:
+            # Format size
+            if size_bytes < 1024:
+                size_str = f"{size_bytes}B"
+            elif size_bytes < 1024 * 1024:
+                size_str = f"{size_bytes / 1024:.1f}KB"
+            else:
+                size_str = f"{size_bytes / (1024 * 1024):.1f}MB"
+            
+            table.add_row(str(file_path), size_str, status)
+        
+        console.print(table)
+    
+    def display_heading(self, text: str, icon_type: str | None = None, style: str = "bold") -> None:
+        """Display a heading with optional icon.
+        
+        Args:
+            text: Heading text
+            icon_type: Type of icon to display (e.g., 'folder', 'file', 'config')
+            style: Rich style to apply
+        """
+        if icon_type:
+            icon = self._get_icon_by_type(icon_type)
+            console.print(f"[{style}]{icon} {text}[/{style}]")
+        else:
+            console.print(f"[{style}]{text}[/{style}]")
+    
+    def display_warning_with_confirmation(self, message: str, details: list[str] | None = None, 
+                                         default: bool = False) -> bool:
+        """Display a warning message with optional details and get confirmation.
+        
+        Args:
+            message: Warning message to display
+            details: Optional list of detail lines to show
+            default: Default value for confirmation
+            
+        Returns:
+            True if user confirms, False otherwise
+        """
+        icon = IconManager.get_status_icon('warning')
+        console.print(f"\n[yellow]{icon} {message}[/yellow]")
+        
+        if details:
+            for detail in details:
+                console.print(f"[yellow]  {detail}[/yellow]")
+        
+        from rich.prompt import Confirm
+        return Confirm.ask("Continue?", default=default)
+    
+    def display_skipped(self, message: str, reason: str | None = None) -> None:
+        """Display a skipped/disabled message.
+        
+        Args:
+            message: The main message to display
+            reason: Optional reason why it was skipped
+        """
+        icon = IconManager.get_status_icon('skipped')
+        if reason:
+            console.print(f"\n[dim]{icon} {message} (skipped - {reason})[/dim]")
+        else:
+            console.print(f"\n[dim]{icon} {message} (skipped)[/dim]")
+    
+    def get_lock_icon(self) -> str:
+        """Get the lock icon for sensitive variables.
+        
+        Returns:
+            Lock icon unicode character
+        """
+        return IconManager.lock()
+    
+    def _get_icon_by_type(self, icon_type: str) -> str:
+        """Get icon by semantic type name.
+        
+        Args:
+            icon_type: Type of icon (e.g., 'folder', 'file', 'config', 'lock')
+            
+        Returns:
+            Icon unicode character
+        """
+        icon_map = {
+            'folder': IconManager.folder(),
+            'file': IconManager.FILE_DEFAULT,
+            'config': IconManager.config(),
+            'lock': IconManager.lock(),
+            'arrow': IconManager.arrow_right(),
+        }
+        return icon_map.get(icon_type, '')

+ 62 - 68
cli/core/module.py

@@ -11,7 +11,7 @@ from rich.panel import Panel
 from rich.prompt import Confirm
 from typer import Argument, Context, Option, Typer, Exit
 
-from .display import DisplayManager, IconManager
+from .display import DisplayManager
 from .library import LibraryManager
 from .prompt import PromptHandler
 from .template import Template
@@ -136,7 +136,7 @@ class Module(ABC):
       )
     else:
       logger.info(f"No templates found matching '{query}' for module '{self.name}'")
-      console.print(f"[yellow]No templates found matching '{query}' for module '{self.name}'[/yellow]")
+      self.display.display_warning(f"No templates found matching '{query}'", context=f"module '{self.name}'")
 
     return filtered_templates
 
@@ -262,12 +262,16 @@ class Module(ABC):
     # Warn if directory is not empty
     if dir_not_empty:
       if interactive:
-        console.print(f"\n[yellow]{IconManager.get_status_icon('warning')} Warning: Directory '{output_dir}' is not empty.[/yellow]")
+        details = []
         if existing_files:
-          console.print(f"[yellow]  {len(existing_files)} file(s) will be overwritten.[/yellow]")
+          details.append(f"{len(existing_files)} file(s) will be overwritten.")
         
-        if not Confirm.ask(f"Continue and potentially overwrite files in '{output_dir}'?", default=False):
-          console.print("[yellow]Generation cancelled.[/yellow]")
+        if not self.display.display_warning_with_confirmation(
+          f"Directory '{output_dir}' is not empty.",
+          details if details else None,
+          default=False
+        ):
+          self.display.display_info("Generation cancelled")
           return None
       else:
         # Non-interactive mode: show warning but continue
@@ -305,7 +309,7 @@ class Module(ABC):
     # Final confirmation (only if we didn't already ask about overwriting)
     if not dir_not_empty and not dry_run:
       if not Confirm.ask("Generate these files?", default=True):
-        console.print("[yellow]Generation cancelled.[/yellow]")
+        self.display.display_info("Generation cancelled")
         return False
     
     return True
@@ -323,31 +327,30 @@ class Module(ABC):
         show_files: Whether to display file contents
     """
     import os
-    from rich.table import Table
     
     console.print()
     console.print("[bold cyan]Dry Run Mode - Simulating File Generation[/bold cyan]")
     console.print()
     
     # Simulate directory creation
-    console.print(f"[bold]{IconManager.folder()} Directory Operations:[/bold]")
+    self.display.display_heading("Directory Operations", icon_type="folder")
     
     # Check if output directory exists
     if output_dir.exists():
-      console.print(f"  [green]{IconManager.get_status_icon('success')}[/green] Output directory exists: [cyan]{output_dir}[/cyan]")
+      self.display.display_success(f"Output directory exists: [cyan]{output_dir}[/cyan]")
       # Check if we have write permissions
       if os.access(output_dir, os.W_OK):
-        console.print(f"  [green]{IconManager.get_status_icon('success')}[/green] Write permission verified")
+        self.display.display_success("Write permission verified")
       else:
-        console.print(f"  [yellow]{IconManager.get_status_icon('warning')}[/yellow] Write permission may be denied")
+        self.display.display_warning("Write permission may be denied")
     else:
-      console.print(f"  [dim]{IconManager.arrow_right()}[/dim] Would create output directory: [cyan]{output_dir}[/cyan]")
+      console.print(f"  [dim][/dim] Would create output directory: [cyan]{output_dir}[/cyan]")
       # Check if parent directory exists and is writable
       parent = output_dir.parent
       if parent.exists() and os.access(parent, os.W_OK):
-        console.print(f"  [green]{IconManager.get_status_icon('success')}[/green] Parent directory writable")
+        self.display.display_success("Parent directory writable")
       else:
-        console.print(f"  [yellow]{IconManager.get_status_icon('warning')}[/yellow] Parent directory may not be writable")
+        self.display.display_warning("Parent directory may not be writable")
     
     # Collect unique subdirectories that would be created
     subdirs = set()
@@ -357,23 +360,19 @@ class Module(ABC):
         subdirs.add(Path(*parts[:i]))
     
     if subdirs:
-      console.print(f"  [dim]{IconManager.arrow_right()}[/dim] Would create {len(subdirs)} subdirectory(ies)")
+      console.print(f"  [dim][/dim] Would create {len(subdirs)} subdirectory(ies)")
       for subdir in sorted(subdirs):
-        console.print(f"    [dim]{IconManager.folder()}[/dim] {subdir}/")
+        console.print(f"    [dim]📁[/dim] {subdir}/")
     
     console.print()
     
     # Display file operations in a table
-    console.print(f"[bold]{IconManager.get_file_icon('file.txt')} File Operations:[/bold]")
-    
-    table = Table(show_header=True, header_style="bold cyan", box=None, padding=(0, 1))
-    table.add_column("File", style="white", no_wrap=False)
-    table.add_column("Size", justify="right", style="dim")
-    table.add_column("Status", style="yellow")
+    self.display.display_heading("File Operations", icon_type="file")
     
     total_size = 0
     new_files = 0
     overwrite_files = 0
+    file_operations = []
     
     for file_path, content in sorted(rendered_files.items()):
       full_path = output_dir / file_path
@@ -388,32 +387,26 @@ class Module(ABC):
         status = "Create"
         new_files += 1
       
-      # Format size
-      if file_size < 1024:
-        size_str = f"{file_size}B"
-      elif file_size < 1024 * 1024:
-        size_str = f"{file_size / 1024:.1f}KB"
-      else:
-        size_str = f"{file_size / (1024 * 1024):.1f}MB"
-      
-      table.add_row(str(file_path), size_str, status)
+      file_operations.append((file_path, file_size, status))
     
-    console.print(table)
+    self.display.display_file_operation_table(file_operations)
     console.print()
     
     # Summary statistics
-    console.print(f"[bold]{IconManager.get_status_icon('info')} Summary:[/bold]")
-    console.print(f"  Total files: {len(rendered_files)}")
-    console.print(f"  New files: {new_files}")
-    console.print(f"  Files to overwrite: {overwrite_files}")
-    
     if total_size < 1024:
       size_str = f"{total_size}B"
     elif total_size < 1024 * 1024:
       size_str = f"{total_size / 1024:.1f}KB"
     else:
       size_str = f"{total_size / (1024 * 1024):.1f}MB"
-    console.print(f"  Total size: {size_str}")
+    
+    summary_items = {
+      "Total files:": str(len(rendered_files)),
+      "New files:": str(new_files),
+      "Files to overwrite:": str(overwrite_files),
+      "Total size:": size_str
+    }
+    self.display.display_summary_table("Summary", summary_items)
     console.print()
     
     # Show file contents if requested
@@ -427,7 +420,7 @@ class Module(ABC):
         print()  # Add blank line after content
       console.print()
     
-    console.print(f"[yellow]{IconManager.get_status_icon('success')} Dry run complete - no files were written[/yellow]")
+    self.display.display_success("Dry run complete - no files were written")
     console.print(f"[dim]Files would have been generated in '{output_dir}'[/dim]")
     logger.info(f"Dry run completed for template '{id}' - {len(rendered_files)} files, {total_size} bytes")
 
@@ -445,9 +438,9 @@ class Module(ABC):
       full_path.parent.mkdir(parents=True, exist_ok=True)
       with open(full_path, 'w', encoding='utf-8') as f:
         f.write(content)
-      console.print(f"[green]Generated file: {file_path}[/green]")
+      console.print(f"[green]Generated file: {file_path}[/green]")  # Keep simple per-file output
     
-    console.print(f"\n[green]{IconManager.get_status_icon('success')} Template generated successfully in '{output_dir}'[/green]")
+    self.display.display_success(f"Template generated successfully in '{output_dir}'")
     logger.info(f"Template written to directory: {output_dir}")
 
   def generate(
@@ -619,7 +612,7 @@ class Module(ABC):
     
     # Set the default value
     config.set_default_value(self.name, actual_var_name, actual_value)
-    console.print(f"[green]{IconManager.get_status_icon('success')} Set default:[/green] [cyan]{actual_var_name}[/cyan] = [yellow]{actual_value}[/yellow]")
+    self.display.display_success(f"Set default: [cyan]{actual_var_name}[/cyan] = [yellow]{actual_value}[/yellow]")
     console.print(f"\n[dim]This will be used as the default value when generating templates with this module.[/dim]")
 
   def config_remove(
@@ -643,9 +636,9 @@ class Module(ABC):
     if var_name in defaults:
       del defaults[var_name]
       config.set_defaults(self.name, defaults)
-      console.print(f"[green]{IconManager.get_status_icon('success')} Removed default for '{var_name}'[/green]")
+      self.display.display_success(f"Removed default for '{var_name}'")
     else:
-      console.print(f"[red]No default found for variable '{var_name}'[/red]")
+      self.display.display_error(f"No default found for variable '{var_name}'")
 
   def config_clear(
     self,
@@ -674,24 +667,27 @@ class Module(ABC):
       if var_name in defaults:
         del defaults[var_name]
         config.set_defaults(self.name, defaults)
-        console.print(f"[green]{IconManager.get_status_icon('success')} Cleared default for '{var_name}'[/green]")
+        self.display.display_success(f"Cleared default for '{var_name}'")
       else:
-        console.print(f"[red]No default found for variable '{var_name}'[/red]")
+        self.display.display_error(f"No default found for variable '{var_name}'")
     else:
       # Clear all defaults
       if not force:
-        console.print(f"[bold yellow]{IconManager.get_status_icon('warning')} Warning:[/bold yellow] This will clear ALL defaults for module '[cyan]{self.name}[/cyan]'")
-        console.print()
-        # Show what will be cleared
+        detail_lines = [f"This will clear ALL defaults for module '{self.name}':", ""]
         for var_name, var_value in defaults.items():
-          console.print(f"  [green]{var_name}[/green] = [yellow]{var_value}[/yellow]")
+          detail_lines.append(f"  [green]{var_name}[/green] = [yellow]{var_value}[/yellow]")
+        
+        self.display.display_warning("Warning: This will clear ALL defaults")
+        console.print()
+        for line in detail_lines:
+          console.print(line)
         console.print()
         if not Confirm.ask(f"[bold red]Are you sure?[/bold red]", default=False):
           console.print("[green]Operation cancelled.[/green]")
           return
       
       config.clear_defaults(self.name)
-      console.print(f"[green]{IconManager.get_status_icon('success')} Cleared all defaults for module '{self.name}'[/green]")
+      self.display.display_success(f"Cleared all defaults for module '{self.name}'")
 
   def config_list(self) -> None:
     """Display the defaults for this specific module in YAML format.
@@ -770,7 +766,7 @@ class Module(ABC):
           _ = template.used_variables
           # Trigger variable definition validation by accessing variables
           _ = template.variables
-          console.print(f"[green]{IconManager.get_status_icon('success')} Jinja2 validation passed[/green]")
+          self.display.display_success("Jinja2 validation passed")
           
           # Semantic validation
           if semantic:
@@ -792,9 +788,9 @@ class Module(ABC):
                   has_semantic_errors = True
             
             if not has_semantic_errors:
-              console.print(f"\n[green]{IconManager.get_status_icon('success')} Semantic validation passed[/green]")
+              self.display.display_success("Semantic validation passed")
             else:
-              console.print(f"\n[red]{IconManager.get_status_icon('error')} Semantic validation found errors[/red]")
+              self.display.display_error("Semantic validation found errors")
               raise Exit(code=1)
           
           if verbose:
@@ -802,7 +798,7 @@ class Module(ABC):
             console.print(f"[dim]Found {len(template.used_variables)} variables[/dim]")
             console.print(f"[dim]Generated {len(rendered_files)} files[/dim]")
         except ValueError as e:
-          console.print(f"[red]{IconManager.get_status_icon('error')} Validation failed for '{template_id}':[/red]")
+          self.display.display_error(f"Validation failed for '{template_id}':")
           console.print(f"\n{e}")
           raise Exit(code=1)
           
@@ -828,27 +824,25 @@ class Module(ABC):
           _ = template.variables
           valid_count += 1
           if verbose:
-            console.print(f"[green]{IconManager.get_status_icon('success')}[/green] {template_id}")
+            self.display.display_success(template_id)
         except ValueError as e:
           invalid_count += 1
           errors.append((template_id, str(e)))
           if verbose:
-            console.print(f"[red]{IconManager.get_status_icon('error')}[/red] {template_id}")
+            self.display.display_error(template_id)
         except Exception as e:
           invalid_count += 1
           errors.append((template_id, f"Load error: {e}"))
           if verbose:
-            console.print(f"[yellow]{IconManager.get_status_icon('warning')}[/yellow] {template_id}")
+            self.display.display_warning(template_id)
       
       # Summary
-      console.print(f"\n[bold]Validation Summary:[/bold]")
-      summary_table = Table(show_header=False, box=None, padding=(0, 2))
-      summary_table.add_column(style="bold")
-      summary_table.add_column()
-      summary_table.add_row("Total templates:", str(total))
-      summary_table.add_row("[green]Valid:[/green]", str(valid_count))
-      summary_table.add_row("[red]Invalid:[/red]", str(invalid_count))
-      console.print(summary_table)
+      summary_items = {
+        "Total templates:": str(total),
+        "[green]Valid:[/green]": str(valid_count),
+        "[red]Invalid:[/red]": str(invalid_count)
+      }
+      self.display.display_summary_table("Validation Summary", summary_items)
       
       # Show errors if any
       if errors:
@@ -858,7 +852,7 @@ class Module(ABC):
           console.print(f"[dim]{error_msg}[/dim]")
         raise Exit(code=1)
       else:
-        console.print(f"\n[green]{IconManager.get_status_icon('success')} All templates are valid![/green]")
+        self.display.display_success("All templates are valid!")
 
   @classmethod
   def register_cli(cls, app: Typer) -> None:

+ 3 - 5
cli/core/prompt.py

@@ -6,7 +6,7 @@ from rich.console import Console
 from rich.prompt import Prompt, Confirm, IntPrompt
 from rich.table import Table
 
-from .display import DisplayManager, IconManager
+from .display import DisplayManager
 from .variable import Variable
 from .collection import VariableCollection
 
@@ -53,9 +53,7 @@ class PromptHandler:
           else:
             unsatisfied_titles.append(dep_key)
         dep_names = ", ".join(unsatisfied_titles) if unsatisfied_titles else "unknown"
-        self.console.print(
-          f"\n[dim]{IconManager.get_status_icon('skipped')} {section.title} (skipped - requires {dep_names} to be enabled)[/dim]"
-        )
+        self.display.display_skipped(section.title, f"requires {dep_names} to be enabled")
         logger.debug(f"Skipping section '{section_key}' - dependencies not satisfied: {dep_names}")
         continue
 
@@ -123,7 +121,7 @@ class PromptHandler:
     if variable.sensitive or variable.autogenerated:
       # Format: "Prompt text 🔒 (default)"
       # The lock icon goes between the text and the default value in parentheses
-      prompt_text = f"{prompt_text} {IconManager.lock()}"
+      prompt_text = f"{prompt_text} {self.display.get_lock_icon()}"
 
     # Check if this specific variable is required (has no default and not autogenerated)
     var_is_required = variable.is_required()

+ 7 - 12
cli/core/repo.py

@@ -13,7 +13,7 @@ from rich.table import Table
 from typer import Argument, Option, Typer
 
 from ..core.config import ConfigManager
-from ..core.display import DisplayManager, IconManager
+from ..core.display import DisplayManager
 from ..core.exceptions import ConfigError
 
 logger = logging.getLogger(__name__)
@@ -191,7 +191,7 @@ def update(
     libraries = config.get_libraries()
     
     if not libraries:
-        console.print("[yellow]No libraries configured.[/yellow]")
+        display.display_warning("No libraries configured")
         console.print("Libraries are auto-configured on first run with a default library.")
         return
     
@@ -244,16 +244,11 @@ def update(
     
     # Display summary table
     if not verbose:
-        table = Table(title="Library Update Summary", show_header=True)
-        table.add_column("Library", style="cyan", no_wrap=True)
-        table.add_column("Status")
-        
-        for name, message, success in results:
-            status_style = "green" if success else "red"
-            status_icon = IconManager.get_status_icon("success" if success else "error")
-            table.add_row(name, f"[{status_style}]{status_icon} {message}[/{status_style}]")
-        
-        console.print(table)
+        display.display_status_table(
+            "Library Update Summary",
+            results,
+            columns=("Library", "Status")
+        )
     
     # Summary
     total = len(results)