Two critical fixes:
1. ListDataSource Cache Bug
- Previously, ListDataSource.__init__ cached raw tuples directly
- get_items() would return cached raw tuples without converting to SourceItem
- This caused SourceItemsToBufferStage to receive tuples and stringify them
- Results: ugly tuple representations in terminal/pygame instead of formatted text
- Fix: Store raw items in _raw_items, let fetch() convert to SourceItem
- Cache now contains proper SourceItem objects
2. Camera Dependency Resolution
- CameraStage declared dependency on 'source.items' exactly
- DataSourceStage provides 'source.headlines' (or 'source.poetry', etc.)
- Capability matching didn't trigger prefix match for exact dependency
- Fix: Change CameraStage dependency to 'source' for prefix matching
3. Added app.py Camera Stage Support
- Pipeline now adds camera stage from preset.camera config
- Supports vertical, horizontal, omni, floating, bounce modes
- Tests now passing with proper data flow through all stages
Tests: All 502 tests passing, 16 skipped
- Add PartialUpdate dataclass and supports_partial_updates to EffectPlugin
- Add dirty region tracking to Canvas (mark_dirty, get_dirty_rows, etc.)
- Canvas auto-marks dirty on put_region, put_text, fill
- CanvasStage exposes dirty rows via pipeline context
- EffectChain creates PartialUpdate and calls process_partial() for optimized effects
- HudEffect implements process_partial() to skip processing when rows 0-2 not dirty
- This enables effects to skip work when canvas regions haven't changed
- Add Camera class with modes: vertical, horizontal, omni, floating
- Refactor scroll.py and demo to use Camera abstraction
- Add vis_offset for horizontal scrolling support
- Add camera_x to EffectContext for effects
- Add pygame window resize handling
- Add HUD effect plugin for demo mode
- Add --demo flag to run demo mode
- Add tests for Camera and vis_offset