Implement 6-stage render pipeline: Router, Formatter, Layout, Dirty Check, Ncurses, Composite #307

Closed
opened 2026-03-16 01:59:26 +00:00 by freemo · 1 comment
Owner

Metadata

Key Value
Branch feature/m2-render-pipeline
Commit Message feat: implement 6-stage render pipeline
Parent Epic #306 — Render Pipeline

Background and Context

The render pipeline is the heart of the Terminal UI's output system. It transforms abstract RenderOps into concrete terminal updates through 6 sequential stages. Each stage has a focused responsibility, making the pipeline modular and testable.

Expected Behavior

The 6-stage pipeline processes a batch of RenderOps each frame:

  1. Router — Routes each RenderOp to its target window based on the window field. Groups ops by window for efficient processing.
  2. Formatter — Formats content within each op: applies ANSI color codes, handles word wrapping to window width, processes markup/styling directives.
  3. Layout — Solves layout constraints: window positions, sizes, borders. Uses a constraint-satisfaction approach where windows declare minimum/preferred sizes and the layout engine allocates available terminal space.
  4. Dirty Check — Compares each window's new content against its previous frame. Windows with no changes are marked clean and skipped in subsequent stages.
  5. Ncurses — Writes dirty windows' content to their ncurses window buffers using wnoutrefresh (deferred refresh).
  6. Composite — Calls doupdate to flush all ncurses window changes to the terminal in a single operation, minimizing flicker.
pipeline = RenderPipeline.new(windows, layout)
# Each frame:
pipeline.process(render_ops)
# Internally: Route → Format → Layout → DirtyCheck → Ncurses → Composite

Acceptance Criteria

  • RenderPipeline processes RenderOps through all 6 stages in order.
  • Router correctly groups ops by target window.
  • Formatter applies ANSI colors and word wrapping.
  • Layout engine allocates terminal space to windows via constraint satisfaction.
  • Dirty check correctly identifies changed vs unchanged windows.
  • Ncurses stage writes to window buffers using wnoutrefresh.
  • Composite stage calls doupdate exactly once per frame.
  • Each stage is a separate class/module for testability.
  • Pipeline handles empty op batches gracefully (no-op frame).

Subtasks

Code

  • Create RenderPipeline orchestrator class with #process(ops).
  • Implement RouterStage grouping ops by target window.
  • Implement FormatterStage with ANSI color application and word wrapping.
  • Implement LayoutStage with constraint-satisfaction window sizing.
  • Implement DirtyCheckStage comparing window content hashes.
  • Implement NcursesStage writing to ncurses window buffers.
  • Implement CompositeStage calling doupdate.
  • Wire all stages together in RenderPipeline#process.

Quality

  • Docs: Update YARD comments on affected classes and methods. Update relevant Docusaurus documentation pages if applicable.
  • Tests (Cucumber): Add tests/unit/render_pipeline.feature covering ops routed to correct windows, ANSI formatting applied, layout constraint solving, dirty windows rendered while clean windows skipped, ncurses buffer writes, composite doupdate, empty batch no-op, multiple ops to same window.
  • Tests (Cucumber Integration): Add integration feature in tests/integration/ for the full 6-stage render pipeline processing RenderOps into terminal output.
  • Tests (Profiling): Run bundle exec rake unit_profile and verify no performance regressions.
  • Quality: Verify coverage >=97% via bundle exec rake unit. If coverage is <97% then review the current unit test coverage report at build/tests/unit/coverage/ and use it to write new Cucumber based unit tests to improve coverage. Specifically, write Cucumber/Gherkin style unit tests that are descriptively named and specifically improve coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun bundle exec rake unit to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%.
  • Quality: Run bundle exec rake (default task: unit tests with coverage) and bundle exec rake integration, fix any errors if needed ensuring both pass across entire code base, do not ignore any failure even if it seems unrelated to this commit, fix it.

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation.
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.
## Metadata | Key | Value | |-----|-------| | **Branch** | `feature/m2-render-pipeline` | | **Commit Message** | `feat: implement 6-stage render pipeline` | | **Parent Epic** | #306 — Render Pipeline | ## Background and Context The render pipeline is the heart of the Terminal UI's output system. It transforms abstract RenderOps into concrete terminal updates through 6 sequential stages. Each stage has a focused responsibility, making the pipeline modular and testable. ## Expected Behavior The 6-stage pipeline processes a batch of RenderOps each frame: 1. **Router** — Routes each RenderOp to its target window based on the `window` field. Groups ops by window for efficient processing. 2. **Formatter** — Formats content within each op: applies ANSI color codes, handles word wrapping to window width, processes markup/styling directives. 3. **Layout** — Solves layout constraints: window positions, sizes, borders. Uses a constraint-satisfaction approach where windows declare minimum/preferred sizes and the layout engine allocates available terminal space. 4. **Dirty Check** — Compares each window's new content against its previous frame. Windows with no changes are marked clean and skipped in subsequent stages. 5. **Ncurses** — Writes dirty windows' content to their ncurses window buffers using `wnoutrefresh` (deferred refresh). 6. **Composite** — Calls `doupdate` to flush all ncurses window changes to the terminal in a single operation, minimizing flicker. ```ruby pipeline = RenderPipeline.new(windows, layout) # Each frame: pipeline.process(render_ops) # Internally: Route → Format → Layout → DirtyCheck → Ncurses → Composite ``` ## Acceptance Criteria - [ ] `RenderPipeline` processes RenderOps through all 6 stages in order. - [ ] Router correctly groups ops by target window. - [ ] Formatter applies ANSI colors and word wrapping. - [ ] Layout engine allocates terminal space to windows via constraint satisfaction. - [ ] Dirty check correctly identifies changed vs unchanged windows. - [ ] Ncurses stage writes to window buffers using `wnoutrefresh`. - [ ] Composite stage calls `doupdate` exactly once per frame. - [ ] Each stage is a separate class/module for testability. - [ ] Pipeline handles empty op batches gracefully (no-op frame). ## Subtasks ### Code - [ ] Create `RenderPipeline` orchestrator class with `#process(ops)`. - [ ] Implement `RouterStage` grouping ops by target window. - [ ] Implement `FormatterStage` with ANSI color application and word wrapping. - [ ] Implement `LayoutStage` with constraint-satisfaction window sizing. - [ ] Implement `DirtyCheckStage` comparing window content hashes. - [ ] Implement `NcursesStage` writing to ncurses window buffers. - [ ] Implement `CompositeStage` calling `doupdate`. - [ ] Wire all stages together in `RenderPipeline#process`. ### Quality - [ ] Docs: Update YARD comments on affected classes and methods. Update relevant Docusaurus documentation pages if applicable. - [ ] Tests (Cucumber): Add `tests/unit/render_pipeline.feature` covering ops routed to correct windows, ANSI formatting applied, layout constraint solving, dirty windows rendered while clean windows skipped, ncurses buffer writes, composite doupdate, empty batch no-op, multiple ops to same window. - [ ] Tests (Cucumber Integration): Add integration feature in `tests/integration/` for the full 6-stage render pipeline processing RenderOps into terminal output. - [ ] Tests (Profiling): Run `bundle exec rake unit_profile` and verify no performance regressions. - [ ] Quality: Verify coverage >=97% via `bundle exec rake unit`. If coverage is <97% then review the current unit test coverage report at `build/tests/unit/coverage/` and use it to write new Cucumber based unit tests to improve coverage. Specifically, write Cucumber/Gherkin style unit tests that are descriptively named and specifically improve coverage on whichever file has the most uncovered lines by writing tests that will target the uncovered lines in the report. Once that is done rerun `bundle exec rake unit` to verify all tests pass and coverage is above >=97%. Only mark this as complete once coverage is >=97%, if not repeat this task as many times as is needed until coverage reaches >=97%. - [ ] Quality: Run `bundle exec rake` (default task: unit tests with coverage) and `bundle exec rake integration`, fix any errors if needed ensuring both pass across **entire** code base, do not ignore any failure even if it seems unrelated to this commit, fix it. ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details about the implementation. - The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done.
freemo added this to the v1.1.0 milestone 2026-03-16 01:59:26 +00:00
freemo self-assigned this 2026-03-16 01:59:27 +00:00
Author
Owner

Implementation Summary

Implemented the 6-stage render pipeline as specified:

Pipeline Stages

  1. RouterStage - Groups RenderOps by target window
  2. FormatterStage - Applies ANSI colors and word wrapping
  3. LayoutStage - Constraint-satisfaction layout solving for window positions/sizes
  4. DirtyCheckStage - Compares against previous frame, marks clean windows
  5. NcursesStage - Writes dirty windows to buffers using wnoutrefresh
  6. CompositeStage - Calls doupdate once per frame

Files Created

  • lib/aethyr/core/render/render_pipeline.rb - Main orchestrator
  • lib/aethyr/core/render/pipeline_stages/*.rb - 6 stage implementations
  • tests/unit/render_pipeline.feature - 30 unit test scenarios
  • tests/integration/render_pipeline_integration.feature - 10 integration scenarios

Test Results

  • All 40 new test scenarios pass
  • Rubocop passes with no offenses on all new files

PR

PR #317 submitted for review.

## Implementation Summary Implemented the 6-stage render pipeline as specified: ### Pipeline Stages 1. **RouterStage** - Groups RenderOps by target window 2. **FormatterStage** - Applies ANSI colors and word wrapping 3. **LayoutStage** - Constraint-satisfaction layout solving for window positions/sizes 4. **DirtyCheckStage** - Compares against previous frame, marks clean windows 5. **NcursesStage** - Writes dirty windows to buffers using wnoutrefresh 6. **CompositeStage** - Calls doupdate once per frame ### Files Created - `lib/aethyr/core/render/render_pipeline.rb` - Main orchestrator - `lib/aethyr/core/render/pipeline_stages/*.rb` - 6 stage implementations - `tests/unit/render_pipeline.feature` - 30 unit test scenarios - `tests/integration/render_pipeline_integration.feature` - 10 integration scenarios ### Test Results - All 40 new test scenarios pass - Rubocop passes with no offenses on all new files ### PR PR #317 submitted for review.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
Reference: aethyr/Aethyr#307
No description provided.