Comprehensive visual design improvements for TUI

This commit implements a complete visual redesign and refactoring to improve
consistency, accessibility, and maintainability of the TUI interface.

## New Visual Constants Module
- Created trace/tui/visual_constants.py with centralized constants:
  - Layout: Screen positioning and structure (header, content, footer)
  - Spacing: Padding and margins (dialogs, horizontal padding)
  - ColumnWidths: Fixed column widths for lists (tags, IOCs, content)
  - DialogSize: Standard dialog dimensions (small, medium, large)
  - Icons: Unicode symbols used throughout UI
  - Timing: Animation and feedback timing constants

## Color System Improvements
- Fixed duplicate color definitions (removed from tui_app.py)
- Use ColorPairs constants throughout instead of hardcoded numbers
- Separated tag colors from footer colors:
  - Tags now use magenta (ColorPairs.TAG) instead of yellow
  - Footers keep yellow (ColorPairs.WARNING) for consistency
  - Updated TAG_SELECTED to magenta on cyan
- All 129+ color_pair() calls now use semantic ColorPairs constants

## Accessibility Enhancements
- Added warning icons (⚠) to all IOC displays for visual + color cues
- Tag highlighting now uses distinct magenta color
- Improved color semantics reduce reliance on color alone
- Smart text truncation at word boundaries for better readability

## Layout & Spacing Standardization
- Replaced magic numbers with Layout/Spacing constants:
  - Footer positioning: height - Layout.FOOTER_OFFSET_FROM_BOTTOM
  - Content area: Layout.CONTENT_START_Y
  - Truncation: width - Spacing.HORIZONTAL_PADDING
  - Dialog margins: Spacing.DIALOG_MARGIN
- Standardized dialog sizes using DialogSize constants:
  - Input dialogs: DialogSize.MEDIUM
  - Multiline dialogs: DialogSize.LARGE
  - Confirm dialogs: DialogSize.SMALL (with dynamic width)
  - Settings dialog: DialogSize.MEDIUM

## User Experience Improvements
- Enhanced footer command organization with visual grouping:
  - Used Icons.SEPARATOR_GROUP (│) to group related commands
  - Example: "[n] Add Note │ [t] Tags [i] IOCs │ [v] View [e] Export"
- Smart content truncation (_safe_truncate):
  - Added word_break parameter (default True)
  - Breaks at word boundaries when >60% text retained
  - Maintains Unicode safety while improving readability
- Improved empty state messages:
  - New _draw_empty_state() helper for consistent visual structure
  - Centered boxes with proper spacing
  - Clear call-to-action hints
  - Applied to "No cases found" and "No cases match filter"

## Code Quality & Maintainability
- Eliminated hardcoded spacing values throughout 3,468-line file
- Used Icons constants for all Unicode symbols (─│┌└◆●○▸⚠⌗◈✓✗?)
- Fixed circular import issues with delayed global imports in TUI.__init__
- Updated comments to reflect new ColorPairs constants
- Consistent use of f-strings for footer construction

## Visual Consistency
- Replaced all "─" literals with Icons.SEPARATOR_H
- Standardized truncation widths (width - Spacing.HORIZONTAL_PADDING)
- Consistent use of ColumnWidths for tag (30) and IOC (50+2) displays
- All dialogs now use standard sizes from visual_constants

## Testing
- Verified no syntax errors in all modified files
- Confirmed successful module imports
- Tested CLI functionality (--help, --list)
- Backward compatibility maintained

This establishes a strong foundation for future UI enhancements while
significantly improving code maintainability and visual consistency.
This commit is contained in:
Claude
2025-12-15 10:08:11 +00:00
parent 15bc00a195
commit a2e7798a2d
4 changed files with 298 additions and 201 deletions

View File

@@ -39,5 +39,5 @@ def init_colors():
curses.init_pair(ColorPairs.TAG, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
# IOCs on selected background (red on cyan)
curses.init_pair(ColorPairs.IOC_SELECTED, curses.COLOR_RED, curses.COLOR_CYAN)
# Tags on selected background (yellow on cyan)
curses.init_pair(ColorPairs.TAG_SELECTED, curses.COLOR_YELLOW, curses.COLOR_CYAN)
# Tags on selected background (magenta on cyan)
curses.init_pair(ColorPairs.TAG_SELECTED, curses.COLOR_MAGENTA, curses.COLOR_CYAN)

View File

@@ -113,15 +113,15 @@ class TextRenderer:
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.ERROR) | curses.A_BOLD)
else: # tag
# Tag highlighting: yellow on cyan if selected, yellow on black otherwise
# Tag highlighting: magenta on cyan if selected, magenta on black otherwise
if is_selected:
screen.attron(curses.color_pair(ColorPairs.TAG_SELECTED))
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.TAG_SELECTED))
else:
screen.attron(curses.color_pair(ColorPairs.WARNING))
screen.attron(curses.color_pair(ColorPairs.TAG))
screen.addstr(y, x_pos, text)
screen.attroff(curses.color_pair(ColorPairs.WARNING))
screen.attroff(curses.color_pair(ColorPairs.TAG))
x_pos += len(text)
last_pos = end

View File

@@ -0,0 +1,68 @@
"""Visual constants for consistent TUI layout and styling"""
class Layout:
"""Screen layout constants"""
HEADER_Y = 0
HEADER_X = 2
CONTENT_START_Y = 2
CONTENT_INDENT = 4
FOOTER_OFFSET_FROM_BOTTOM = 3
BORDER_OFFSET_FROM_BOTTOM = 2
class Spacing:
"""Spacing and padding constants"""
SECTION_VERTICAL_GAP = 2
ITEM_VERTICAL_GAP = 1
DIALOG_MARGIN = 4
HORIZONTAL_PADDING = 6 # width - 6 for truncation
HASH_DISPLAY_PADDING = 20 # width - 20
class ColumnWidths:
"""Fixed column widths for list displays"""
TAG_COLUMN = 30
IOC_COLUMN = 50
CONTENT_PREVIEW = 50
NOTE_PREVIEW = 60
class DialogSize:
"""Standard dialog dimensions (width, height)"""
SMALL = (40, 8) # Confirm dialogs
MEDIUM = (60, 15) # Settings, single input
LARGE = (70, 20) # Multiline, help
class Icons:
"""Unicode symbols used throughout UI"""
ACTIVE = ""
INACTIVE = ""
DIAMOND = ""
SQUARE = ""
SMALL_SQUARE = ""
ARROW_RIGHT = ""
WARNING = ""
HASH = ""
FILTER = ""
VERIFIED = ""
FAILED = ""
UNSIGNED = "?"
SEPARATOR_H = ""
SEPARATOR_V = ""
SEPARATOR_GROUP = "" # For grouping footer commands
BOX_TL = ""
BOX_BL = ""
# Box drawing for improved empty states
BOX_DOUBLE_TL = ""
BOX_DOUBLE_TR = ""
BOX_DOUBLE_BL = ""
BOX_DOUBLE_BR = ""
BOX_DOUBLE_H = ""
BOX_DOUBLE_V = ""
class Timing:
"""Timing constants"""
FLASH_MESSAGE_DURATION = 3 # seconds

File diff suppressed because it is too large Load Diff