MAJOR REFACTORING: Consolidate duplicated pipeline code and standardize on capability-based dependency resolution. This is a significant but backwards-compatible restructuring that improves maintainability and extensibility. ## ARCHITECTURE CHANGES ### Data Sources Consolidation - Move engine/sources_v2.py → engine/data_sources/sources.py - Move engine/pipeline_sources/ → engine/data_sources/ - Create unified DataSource ABC with common interface: * fetch() - idempotent data retrieval * get_items() - cached access with automatic refresh * refresh() - force cache invalidation * is_dynamic - indicate streaming vs static sources - Support for SourceItem dataclass (content, source, timestamp, metadata) ### Display Backend Improvements - Update all 7 display backends to use new import paths - Terminal: Improve dimension detection and handling - WebSocket: Better error handling and client lifecycle - Sixel: Refactor graphics rendering - Pygame: Modernize event handling - Kitty: Add protocol support for inline images - Multi: Ensure proper forwarding to all backends - Null: Maintain testing backend functionality ### Pipeline Adapter Consolidation - Refactor adapter stages for clarity and flexibility - RenderStage now handles both item-based and buffer-based rendering - Add SourceItemsToBufferStage for converting data source items - Improve DataSourceStage to work with all source types - Add DisplayStage wrapper for display backends ### Camera & Viewport Refinements - Update Camera class for new architecture - Improve viewport dimension detection - Better handling of resize events across backends ### New Effect Plugins - border.py: Frame rendering effect with configurable style - crop.py: Viewport clipping effect for selective display - tint.py: Color filtering effect for atmosphere ### Tests & Quality - Add test_border_effect.py with comprehensive border tests - Add test_crop_effect.py with viewport clipping tests - Add test_tint_effect.py with color filtering tests - Update test_pipeline.py for new architecture - Update test_pipeline_introspection.py for new data source location - All 463 tests pass with 56% coverage - Linting: All checks pass with ruff ### Removals (Code Cleanup) - Delete engine/benchmark.py (deprecated performance testing) - Delete engine/pipeline_sources/__init__.py (moved to data_sources) - Delete engine/sources_v2.py (replaced by data_sources/sources.py) - Update AGENTS.md to reflect new structure ### Import Path Updates - Update engine/pipeline/controller.py::create_default_pipeline() * Old: from engine.sources_v2 import HeadlinesDataSource * New: from engine.data_sources.sources import HeadlinesDataSource - All display backends import from new locations - All tests import from new locations ## BACKWARDS COMPATIBILITY This refactoring is intended to be backwards compatible: - Pipeline execution unchanged (DAG-based with capability matching) - Effect plugins unchanged (EffectPlugin interface same) - Display protocol unchanged (Display duck-typing works as before) - Config system unchanged (presets.toml format same) ## TESTING - 463 tests pass (0 failures, 19 skipped) - Full linting check passes - Manual testing on demo, poetry, websocket modes - All new effect plugins tested ## FILES CHANGED - 24 files modified/added/deleted - 723 insertions, 1,461 deletions (net -738 LOC - cleanup!) - No breaking changes to public APIs - All transitive imports updated correctly
10 KiB
Agent Development Guide
Development Environment
This project uses:
- mise (mise.jdx.dev) - tool version manager and task runner
- hk (hk.jdx.dev) - git hook manager
- uv - fast Python package installer
- ruff - linter and formatter
- pytest - test runner
Setup
# Install dependencies
mise run install
# Or equivalently:
uv sync --all-extras # includes mic, websocket, sixel support
Available Commands
mise run test # Run tests
mise run test-v # Run tests verbose
mise run test-cov # Run tests with coverage report
mise run test-browser # Run e2e browser tests (requires playwright)
mise run lint # Run ruff linter
mise run lint-fix # Run ruff with auto-fix
mise run format # Run ruff formatter
mise run ci # Full CI pipeline (topics-init + lint + test-cov)
Runtime Commands
mise run run # Run mainline (terminal)
mise run run-poetry # Run with poetry feed
mise run run-firehose # Run in firehose mode
mise run run-websocket # Run with WebSocket display only
mise run run-sixel # Run with Sixel graphics display
mise run run-both # Run with both terminal and WebSocket
mise run run-client # Run both + open browser
mise run cmd # Run C&C command interface
Git Hooks
At the start of every agent session, verify hooks are installed:
ls -la .git/hooks/pre-commit
If hooks are not installed, install them with:
hk init --mise
mise run pre-commit
IMPORTANT: Always review the hk documentation before modifying hk.pkl:
The project uses hk configured in hk.pkl:
- pre-commit: runs ruff-format and ruff (with auto-fix)
- pre-push: runs ruff check + benchmark hook
Benchmark Runner
Benchmark tests are in tests/test_benchmark.py with @pytest.mark.benchmark.
Hook Mode (via pytest)
Run benchmarks in hook mode to catch performance regressions:
mise run test-cov # Run with coverage
The benchmark tests will fail if performance degrades beyond the threshold.
The pre-push hook runs benchmark in hook mode to catch performance regressions before pushing.
Workflow Rules
Before Committing
-
Always run the test suite - never commit code that fails tests:
mise run test -
Always run the linter:
mise run lint -
Fix any lint errors before committing (or let the pre-commit hook handle it).
-
Review your changes using
git diffto understand what will be committed.
On Failing Tests
When tests fail, determine whether it's an out-of-date test or a correctly failing test:
-
Out-of-date test: The test was written for old behavior that has legitimately changed. Update the test to match the new expected behavior.
-
Correctly failing test: The test correctly identifies a broken contract. Fix the implementation, not the test.
Never modify a test to make it pass without understanding why it failed.
Code Review
Before committing significant changes:
- Run
git diffto review all changes - Ensure new code follows existing patterns in the codebase
- Check that type hints are added for new functions
- Verify that tests exist for new functionality
Testing
Tests live in tests/ and follow the pattern test_*.py.
Run all tests:
mise run test
Run with coverage:
mise run test-cov
The project uses pytest with strict marker enforcement. Test configuration is in pyproject.toml under [tool.pytest.ini_options].
Test Coverage Strategy
Current coverage: 56% (463 tests)
Key areas with lower coverage (acceptable for now):
- app.py (8%): Main entry point - integration heavy, requires terminal
- scroll.py (10%): Terminal-dependent rendering logic (unused)
Key areas with good coverage:
- display/backends/null.py (95%): Easy to test headlessly
- display/backends/terminal.py (96%): Uses mocking
- display/backends/multi.py (100%): Simple forwarding logic
- effects/performance.py (99%): Pure Python logic
- eventbus.py (96%): Simple event system
- effects/controller.py (95%): Effects command handling
Areas needing more tests:
- websocket.py (48%): Network I/O, hard to test in CI
- ntfy.py (50%): Network I/O, hard to test in CI
- mic.py (61%): Audio I/O, hard to test in CI
Note: Terminal-dependent modules (scroll, layers render) are harder to test in CI.
Performance regression tests are in tests/test_benchmark.py with @pytest.mark.benchmark.
Architecture Notes
- ntfy.py - standalone notification poller with zero internal dependencies
- sensors/ - Sensor framework (MicSensor, OscillatorSensor) for real-time input
- eventbus.py provides thread-safe event publishing for decoupled communication
- effects/ - plugin architecture with performance monitoring
- The new pipeline architecture: source → render → effects → display
Canvas & Camera
- Canvas (
engine/canvas.py): 2D rendering surface with dirty region tracking - Camera (
engine/camera.py): Viewport controller for scrolling content
The Canvas tracks dirty regions automatically when content is written (via put_region, put_text, fill), enabling partial buffer updates for optimized effect processing.
Pipeline Architecture
The new Stage-based pipeline architecture provides capability-based dependency resolution:
- Stage (
engine/pipeline/core.py): Base class for pipeline stages - Pipeline (
engine/pipeline/controller.py): Executes stages with capability-based dependency resolution - StageRegistry (
engine/pipeline/registry.py): Discovers and registers stages - Stage Adapters (
engine/pipeline/adapters.py): Wraps existing components as stages
Capability-Based Dependencies
Stages declare capabilities (what they provide) and dependencies (what they need). The Pipeline resolves dependencies using prefix matching:
"source"matches"source.headlines","source.poetry", etc.- This allows flexible composition without hardcoding specific stage names
Sensor Framework
- Sensor (
engine/sensors/__init__.py): Base class for real-time input sensors - SensorRegistry: Discovers available sensors
- SensorStage: Pipeline adapter that provides sensor values to effects
- MicSensor (
engine/sensors/mic.py): Self-contained microphone input - OscillatorSensor (
engine/sensors/oscillator.py): Test sensor for development - PipelineMetricsSensor (
engine/sensors/pipeline_metrics.py): Exposes pipeline metrics as sensor values
Sensors support param bindings to drive effect parameters in real-time.
Pipeline Introspection
- PipelineIntrospectionSource (
engine/data_sources/pipeline_introspection.py): Renders live ASCII visualization of pipeline DAG with metrics - PipelineIntrospectionDemo (
engine/pipeline/pipeline_introspection_demo.py): 3-phase demo controller for effect animation
Preset: pipeline-inspect - Live pipeline introspection with DAG and performance metrics
Partial Update Support
Effect plugins can opt-in to partial buffer updates for performance optimization:
- Set
supports_partial_updates = Trueon the effect class - Implement
process_partial(buf, ctx, partial)method - The
PartialUpdatedataclass indicates which regions changed
Preset System
Presets use TOML format (no external dependencies):
-
Built-in:
engine/presets.toml -
User config:
~/.config/mainline/presets.toml -
Local override:
./presets.toml -
Preset loader (
engine/pipeline/preset_loader.py): Loads and validates presets -
PipelinePreset (
engine/pipeline/presets.py): Dataclass for preset configuration
Functions:
validate_preset()- Validate preset structurevalidate_signal_path()- Detect circular dependenciesgenerate_preset_toml()- Generate skeleton preset
Display System
-
Display abstraction (
engine/display/): swap display backends via the Display protocoldisplay/backends/terminal.py- ANSI terminal outputdisplay/backends/websocket.py- broadcasts to web clients via WebSocketdisplay/backends/sixel.py- renders to Sixel graphics (pure Python, no C dependency)display/backends/null.py- headless display for testingdisplay/backends/multi.py- forwards to multiple displays simultaneouslydisplay/__init__.py- DisplayRegistry for backend discovery
-
WebSocket display (
engine/display/backends/websocket.py): real-time frame broadcasting to web browsers- WebSocket server on port 8765
- HTTP server on port 8766 (serves HTML client)
- Client at
client/index.htmlwith ANSI color parsing and fullscreen support
-
Display modes (
--displayflag):terminal- Default ANSI terminal outputwebsocket- Web browser display (requires websockets package)sixel- Sixel graphics in supported terminals (iTerm2, mintty, etc.)both- Terminal + WebSocket simultaneously
Effect Plugin System
-
EffectPlugin ABC (
engine/effects/types.py): abstract base class for effects- All effects must inherit from EffectPlugin and implement
process()andconfigure() - Runtime discovery via
effects_plugins/__init__.pyusingissubclass()checks
- All effects must inherit from EffectPlugin and implement
-
EffectRegistry (
engine/effects/registry.py): manages registered effects -
EffectChain (
engine/effects/chain.py): chains effects in pipeline order
Command & Control
- C&C uses separate ntfy topics for commands and responses
NTFY_CC_CMD_TOPIC- commands from cmdline.pyNTFY_CC_RESP_TOPIC- responses back to cmdline.py- Effects controller handles
/effectscommands (list, on/off, intensity, reorder, stats)
Pipeline Documentation
The rendering pipeline is documented in docs/PIPELINE.md using Mermaid diagrams.
IMPORTANT: When making significant architectural changes to the rendering pipeline (new layers, effects, display backends), update docs/PIPELINE.md to reflect the changes:
- Edit
docs/PIPELINE.mdwith the new architecture - If adding new SVG diagrams, render them manually using an external tool (e.g., Mermaid Live Editor)
- Commit both the markdown and any new diagram files