fix: pass border parameter to display and handle special sources properly

BUG FIXES:

1. Border parameter not being passed to display.show()
   - Display backends support border parameter but app.py wasn't passing it
   - Now app.py passes params.border to display.show(border=params.border)
   - Enables border-test preset to actually render borders

2. WebSocket and Multi displays didn't support border parameter
   - Updated WebSocket Protocol to include border parameter
   - Updated MultiDisplay.show() to accept and forward border parameter
   - Updated test to expect border parameter in mock calls

3. app.py didn't properly handle special sources (empty, pipeline-inspect)
   - Border-test preset with source='empty' was still fetching headlines
   - Pipeline-inspect source was never using the introspection data source
   - Now app.py detects special sources and uses appropriate data source stages:
     * 'empty' source → EmptyDataSource stage
     * 'pipeline-inspect' → PipelineIntrospectionSource stage
     * Other sources → traditional items-based approach
   - Uses SourceItemsToBufferStage for special sources instead of RenderStage
   - Sets pipeline on introspection source after build to avoid circular dependency

TESTING:

- All 463 tests pass
- Linting passes
- Manual test: `uv run mainline.py --preset border-test` now correctly shows empty source
- border-test preset now properly initializes without fetching unnecessary content

The issue was that the enhanced app.py code from the original diff didn't make it into
the refactor commit. This fix restores that functionality.
This commit is contained in:
2026-03-16 19:53:58 -07:00
parent e0bbfea26c
commit 637cbc5515
4 changed files with 71 additions and 29 deletions

View File

@@ -51,6 +51,7 @@ def run_pipeline_mode(preset_name: str = "demo"):
from engine.fetch import fetch_all, fetch_poetry, load_cache
from engine.pipeline.adapters import (
RenderStage,
SourceItemsToBufferStage,
create_items_stage,
create_stage_from_display,
create_stage_from_effect,
@@ -85,19 +86,29 @@ def run_pipeline_mode(preset_name: str = "demo"):
)
print(" \033[38;5;245mFetching content...\033[0m")
cached = load_cache()
if cached:
items = cached
elif preset.source == "poetry":
items, _, _ = fetch_poetry()
# Handle special sources that don't need traditional fetching
introspection_source = None
if preset.source == "pipeline-inspect":
items = []
print(" \033[38;5;245mUsing pipeline introspection source\033[0m")
elif preset.source == "empty":
items = []
print(" \033[38;5;245mUsing empty source (no content)\033[0m")
else:
items, _, _ = fetch_all()
cached = load_cache()
if cached:
items = cached
elif preset.source == "poetry":
items, _, _ = fetch_poetry()
else:
items, _, _ = fetch_all()
if not items:
print(" \033[38;5;196mNo content available\033[0m")
sys.exit(1)
if not items:
print(" \033[38;5;196mNo content available\033[0m")
sys.exit(1)
print(f" \033[38;5;82mLoaded {len(items)} items\033[0m")
print(f" \033[38;5;82mLoaded {len(items)} items\033[0m")
display = DisplayRegistry.create(preset.display)
if not display:
@@ -108,18 +119,45 @@ def run_pipeline_mode(preset_name: str = "demo"):
effect_registry = get_registry()
pipeline.add_stage("source", create_items_stage(items, preset.source))
pipeline.add_stage(
"render",
RenderStage(
items,
width=80,
height=24,
camera_speed=params.camera_speed,
camera_mode=preset.camera,
firehose_enabled=params.firehose_enabled,
),
)
# Create source stage based on preset source type
if preset.source == "pipeline-inspect":
from engine.data_sources.pipeline_introspection import (
PipelineIntrospectionSource,
)
from engine.pipeline.adapters import DataSourceStage
introspection_source = PipelineIntrospectionSource(
pipeline=None, # Will be set after pipeline.build()
viewport_width=80,
viewport_height=24,
)
pipeline.add_stage(
"source", DataSourceStage(introspection_source, name="pipeline-inspect")
)
elif preset.source == "empty":
from engine.data_sources.sources import EmptyDataSource
from engine.pipeline.adapters import DataSourceStage
empty_source = EmptyDataSource(width=80, height=24)
pipeline.add_stage("source", DataSourceStage(empty_source, name="empty"))
else:
pipeline.add_stage("source", create_items_stage(items, preset.source))
# Add appropriate render stage
if preset.source in ("pipeline-inspect", "empty"):
pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer"))
else:
pipeline.add_stage(
"render",
RenderStage(
items,
width=80,
height=24,
camera_speed=params.camera_speed,
camera_mode=preset.camera,
firehose_enabled=params.firehose_enabled,
),
)
for effect_name in preset.effects:
effect = effect_registry.get(effect_name)
@@ -132,6 +170,10 @@ def run_pipeline_mode(preset_name: str = "demo"):
pipeline.build()
# For pipeline-inspect, set the pipeline after build to avoid circular dependency
if introspection_source is not None:
introspection_source.set_pipeline(pipeline)
if not pipeline.initialize():
print(" \033[38;5;196mFailed to initialize pipeline\033[0m")
sys.exit(1)
@@ -162,7 +204,7 @@ def run_pipeline_mode(preset_name: str = "demo"):
result = pipeline.execute(items)
if result.success:
display.show(result.data)
display.show(result.data, border=params.border)
if hasattr(display, "is_quit_requested") and display.is_quit_requested():
if hasattr(display, "clear_quit_request"):

View File

@@ -30,9 +30,9 @@ class MultiDisplay:
for d in self.displays:
d.init(width, height, reuse=reuse)
def show(self, buffer: list[str]) -> None:
def show(self, buffer: list[str], border: bool = False) -> None:
for d in self.displays:
d.show(buffer)
d.show(buffer, border=border)
def clear(self) -> None:
for d in self.displays:

View File

@@ -24,7 +24,7 @@ class Display(Protocol):
"""Initialize display with dimensions."""
...
def show(self, buffer: list[str]) -> None:
def show(self, buffer: list[str], border: bool = False) -> None:
"""Show buffer on display."""
...