fix: redraw the whole tree on every render pass [minor] - #113
Merged
Merged
Conversation
UIApplication.Render() clears the entire console on every pass, but UIElementBase.Render() only drew while IsDirty was still set, and IsDirty was cleared after an element's first draw. The two mechanisms cancelled out: the screen was wiped each frame while only the elements the last input had invalidated were redrawn, so any static element vanished on the very next pass — a label beside a focused input disappeared on the first keystroke. Commit to one rendering model. A visible element now draws unconditionally, so every clear is followed by a full redraw. IsDirty and Invalidate() stay as the "changed since last draw" signal rather than a drawing gate, which is what a future dirty-region renderer would build on once the clear is removed and real region tracking exists. Adds UIApplicationTests covering the issue's scenario (invalidate one child, assert its static sibling and the border title are still drawn), that clears and full redraws stay in step across passes, and that an unsized root still takes its size from the console. Fixes #109 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017nBCLgjFXPejQfEDEWyZkp
|
matt-edmondson
pushed a commit
that referenced
this pull request
Sep 16, 2026
#113 (for #109) and #118 (for #114) landed on main while this was open. UIApplication.cs merged cleanly: #113 rewrote Render's dirty-tracking model, which this branch does not touch, and this branch's changes are confined to RunAsync's interrupt registration and ProcessInputAsync's read. UIApplicationTests.cs conflicted because both branches filled what had been an empty file, with two unrelated sets of tests. Neither side is redundant, so main's render-loop tests stay in UIApplicationTests.cs unchanged and this branch's run-lifecycle tests move to UIApplicationLifecycleTests.cs. That matches how the suite already splits BorderElementTests from BorderElementRenderTests, and keeps either class readable on its own. 129/129 tests pass and the solution builds clean across net10.0;net9.0;net8.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01671tcpA4zkfbgPcJm8cTsB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #109
The problem
UIApplication.Render()cleared the entire console on every pass, butUIElementBase.Render()only drew whileIsDirtywas still set — andIsDirtywas cleared right after an element's first draw. The two mechanisms cancelled each other out: the screen was wiped every frame while only the elements the last input had invalidated were redrawn, so any static element vanished on the very next pass. A label beside a focused input disappeared on the first keystroke, which is the most ordinary layout the library has.The fix
The issue asked for a deliberate choice between the two rendering models rather than a patch. This commits to full-clear-plus-full-redraw, which the triage flagged as the simpler and more obviously correct starting point:
UIElementBase.Render()draws unconditionally for a visible element. Skipping a clean element would not preserve its previous output — the clear had already erased it.IsDirtyandInvalidate()stay as the "changed since last draw" signal and keep raisingInvalidated; they no longer gate drawing. That is the plumbing a dirty-region renderer would build on later, once the per-pass clear is removed and real region tracking exists.Render/Invalidateremarks, theIUIElement.Invalidate()contract, and the rendering-flow section ofCLAUDE.md, which still described the old dirty-only model.Deliberately out of scope: dirty-region rendering (the larger end-state noted in the issue), and #111's resize handling, which the triage sequenced behind this decision and which is now a small addition on top of it.
Tests
New
TUI.Test/UIApplicationTests.cs(the file existed but was empty), using the repo's existingRecordingConsoleProviderdouble:RenderAfterOnlyOneChildInvalidatesStillDrawsTheStaticSibling— the issue's exact scenario: a titled border around a static label and an interactive sibling; invalidate only the interactive child and assert the static label, the sibling, and the border title are all drawn in the next pass.RenderCleanElementTwiceDrawsItTwice— a clean element redraws instead of being skipped.RenderRedrawsTheWholeTreeOnEveryClear— clears and full-tree redraws stay in step across three passes.RenderAssignsConsoleDimensionsToAnUnsizedRoot— guards the existing first-pass sizing behaviour (passes before and after the change).Verification:
main'sUIElementBase.Render()(confirmed by temporarily restoring theIsDirtygate: 3 of 4 failed) and pass with the change.dotnet build TUI.sln— clean across net8.0/net9.0/net10.0, 0 warnings.dotnet test— 117/117 pass, up from 113 baseline, no regressions.🤖 Generated with Claude Code
https://claude.ai/code/session_017nBCLgjFXPejQfEDEWyZkp
Generated by Claude Code