23 Commits

Author SHA1 Message Date
0afd9d402f docs: use david's most recent README from feature/vector_display branch 2026-03-18 23:24:15 -07:00
347ae68f43 refactor: Make EffectPlugin an abstract base class, update effects to inherit from it, and improve plugin discovery. 2026-03-18 23:24:15 -07:00
bd400c25be fix: use theme-aware msg_gradient for ntfy messages
Replace lr_gradient_opposite() with msg_gradient() in render_message_overlay().
Messages now render in complementary colors matching the active theme:
- Green theme → magenta messages
- Orange theme → blue messages
- Purple theme → yellow messages
2026-03-18 23:24:15 -07:00
65b8e2dfd0 feat(daemon): add display abstraction and daemon mode with C&C 2026-03-18 23:24:15 -07:00
16289f1f10 feat(cmdline): add command-line interface for mainline control 2026-03-18 23:24:14 -07:00
f8bfbc23ce feat(effects): add plugin architecture with performance monitoring 2026-03-18 23:24:14 -07:00
86e2c02f19 refactor: phase 4 - event-driven architecture foundation
- Add EventBus class with pub/sub messaging (thread-safe)
- Add emitter Protocol classes (EventEmitter, Startable, Stoppable)
- Add event emission to NtfyPoller (NtfyMessageEvent)
- Add event emission to MicMonitor (MicLevelEvent)
- Update StreamController to publish stream start/end events
- Add comprehensive tests for eventbus and emitters modules
2026-03-18 23:24:14 -07:00
56643ae04f refactor: phase 3 - API efficiency improvements
Add typed dataclasses for tuple returns:
- types.py: HeadlineItem, FetchResult, Block dataclasses with legacy tuple converters
- fetch.py: Add type hints and HeadlineTuple type alias

Add pyright for static type checking:
- Add pyright to dependencies
- Verify type coverage with pyright (0 errors in core modules)

This enables:
- Named types instead of raw tuples (better IDE support, self-documenting)
- Type-safe APIs across modules
- Backward compatibility via to_tuple/from_tuple methods

Note: Lazy imports skipped for render.py - startup impact is minimal.
2026-03-18 23:24:14 -07:00
234d5290bc refactor: phase 2 - modularization of scroll engine
Split monolithic scroll.py into focused modules:
- viewport.py: terminal size (tw/th), ANSI positioning helpers
- frame.py: FrameTimer class, scroll step calculation
- layers.py: message overlay, ticker zone, firehose rendering
- scroll.py: simplified orchestrator, imports from new modules

Add stream controller and event types for future event-driven architecture:
- controller.py: StreamController for source initialization and stream lifecycle
- events.py: EventType enum and event dataclasses (HeadlineEvent, FrameTickEvent, etc.)

Added tests for new modules:
- test_viewport.py: 8 tests for viewport utilities
- test_frame.py: 10 tests for frame timing
- test_layers.py: 13 tests for layer compositing
- test_events.py: 11 tests for event types
- test_controller.py: 6 tests for stream controller

This enables:
- Testable chunks with clear responsibilities
- Reusable viewport utilities across modules
- Better separation of concerns in render pipeline
- Foundation for future event-driven architecture

Also includes Phase 1 documentation updates in code comments.
2026-03-18 23:24:14 -07:00
b1280dc193 refactor: phase 1 - testability improvements
- Add Config dataclass with get_config()/set_config() for injection
- Add Config.from_args() for CLI argument parsing (testable)
- Add platform font path detection (Darwin/Linux)
- Bound translate cache with @lru_cache(maxsize=500)
- Add fixtures for external dependencies (network, feeds, config)
- Add 15 tests for Config class, from_args, and platform detection

This enables testability by:
- Allowing config injection instead of global mutable state
- Supporting custom argv in from_args() for testing
- Providing reusable fixtures for mocking network/filesystem
- Preventing unbounded memory growth in translation cache

Fixes: _arg_value/_arg_int not accepting custom argv
2026-03-18 23:23:24 -07:00
a27c09f58e refactor: Fix import ordering and code formatting with ruff
Organize imports in render.py and scroll.py to meet ruff style requirements.
Add blank lines for code formatting compliance.
2026-03-18 23:20:22 -07:00
21d90bd3e6 feat: add pick_color_theme() UI and integration 2026-03-18 23:20:22 -07:00
8f4bbc46dc feat: update scroll.py to use theme message gradient
Add msg_gradient() helper function to render.py that applies message
(ntfy) gradient using the active theme's message_gradient property.
This replaces hardcoded magenta gradient in scroll.py with a theme-aware
approach that uses complementary colors from the active theme.

- Add msg_gradient(rows, offset) helper in render.py with fallback to
  default magenta gradient when no theme is active
- Update scroll.py imports to use msg_gradient instead of
  lr_gradient_opposite
- Replace lr_gradient_opposite() call with msg_gradient() in message
  overlay rendering
- Add 6 comprehensive tests for msg_gradient covering theme usage,
  fallback behavior, and edge cases

All tests pass (121 passed), no regressions detected.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-18 23:20:22 -07:00
2d917de0b6 feat: update lr_gradient to use config.ACTIVE_THEME
- Remove hardcoded GRAD_COLS and MSG_GRAD_COLS module constants
- Add _default_green_gradient() and _default_magenta_gradient() fallback functions
- Add _color_codes_to_ansi() to convert integer color codes from themes to ANSI escape strings
- Update lr_gradient() signature: cols parameter (was grad_cols)
- lr_gradient() now pulls colors from config.ACTIVE_THEME when available
- Falls back to default green gradient when no theme is active
- Existing calls with explicit cols parameter continue to work
- Add comprehensive tests for new functionality

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-18 23:20:22 -07:00
7b1481e7b0 feat: add ACTIVE_THEME global and set_active_theme() to config 2026-03-18 23:20:22 -07:00
9acd7b6fd0 feat: create Theme class and registry with finalized color gradients 2026-03-18 23:20:22 -07:00
a6f2eedae5 docs: add color scheme implementation plan
Comprehensive plan with 6 chunks, each containing bite-sized TDD tasks:
- Chunk 1: Theme class and registry
- Chunk 2: Config integration
- Chunk 3: Render pipeline
- Chunk 4: Message gradient integration
- Chunk 5: Color picker UI
- Chunk 6: Integration and validation

Each step includes exact code, test commands, and expected output.
2026-03-18 23:20:22 -07:00
dc2d59db5d docs: add color scheme feature documentation to README
- Update opening description to mention selectable color gradients
- Add new 'Color Schemes' section with picker usage instructions
- Document three available themes (Green, Orange, Purple)
- Clarify that boot UI uses hardcoded green, not theme colors
2026-03-18 23:20:22 -07:00
ced83ce247 docs: add terminal resize handling clarification 2026-03-18 23:20:22 -07:00
aa2f0f9a8b docs: revise color scheme design spec to address review feedback
- Clarify boot messages use hardcoded green, not theme gradients
- Finalize all gradient ANSI color codes (no TBD)
- Add initialization guarantee for ACTIVE_THEME
- Resolve circular import risk with data-only themes.py
- Update theme ID naming to match menu labels
- Expand test strategy and mock approach
2026-03-18 23:20:22 -07:00
03cdd70ad0 docs: add color scheme switcher design spec 2026-03-18 23:20:22 -07:00
d0e18518a2 feat: introduce a 'code' mode to display source code lines, add new font assets, and include dedicated tests for code fetching. 2026-03-18 23:20:22 -07:00
c36342061f docs: add code-scroll mode design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 23:20:22 -07:00

View File

@@ -164,6 +164,58 @@ class TestSetFontSelection:
assert original_index == config.FONT_INDEX
class TestActiveTheme:
"""Tests for ACTIVE_THEME global and set_active_theme function."""
def test_active_theme_initially_none(self):
"""ACTIVE_THEME should be None at module start."""
# Reset to None to test initial state
original = config.ACTIVE_THEME
config.ACTIVE_THEME = None
try:
assert config.ACTIVE_THEME is None
finally:
config.ACTIVE_THEME = original
def test_set_active_theme_green(self):
"""Setting green theme works correctly."""
config.set_active_theme("green")
assert config.ACTIVE_THEME is not None
assert config.ACTIVE_THEME.name == "green"
assert len(config.ACTIVE_THEME.main_gradient) == 12
assert len(config.ACTIVE_THEME.message_gradient) == 12
def test_set_active_theme_default(self):
"""Default theme is green when not specified."""
config.set_active_theme()
assert config.ACTIVE_THEME is not None
assert config.ACTIVE_THEME.name == "green"
def test_set_active_theme_invalid(self):
"""Invalid theme_id raises KeyError."""
with pytest.raises(KeyError):
config.set_active_theme("nonexistent")
def test_set_active_theme_all_themes(self):
"""Verify orange and purple themes work."""
# Test orange
config.set_active_theme("orange")
assert config.ACTIVE_THEME.name == "orange"
# Test purple
config.set_active_theme("purple")
assert config.ACTIVE_THEME.name == "purple"
def test_set_active_theme_idempotent(self):
"""Calling set_active_theme multiple times works."""
config.set_active_theme("green")
first_theme = config.ACTIVE_THEME
config.set_active_theme("green")
second_theme = config.ACTIVE_THEME
assert first_theme.name == second_theme.name
assert first_theme.name == "green"
class TestConfigDataclass:
"""Tests for Config dataclass."""