Skip to content

Draw changes made outside a keypress by redrawing when the tree invalidates [patch] - #203

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/154-redraw-on-invalidate
Oct 8, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/154-redraw-on-invalidate

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #154

What was wrong

UIApplication rendered only at startup, after a key was handled, and when the poll tick saw a resize. Invalidate() bubbled up to the root, but nothing subscribed to the root's Invalidated. A clock, progress value, or status line set by a background task therefore stayed frozen until the user pressed a key.

Change

  • The RootElement setter subscribes to the root's Invalidated, and unsubscribes from the old root when it changes. The handler only sets an Interlocked redraw flag. It never draws, because it can fire on any thread.
  • The poll branch of ProcessInputAsync now renders when _redrawRequested is set or the console has resized. All drawing stays on the loop thread, and latency is bounded by ResizePollInterval (100 ms).
  • Render() clears the flag when it starts. It also records which thread it is running on, so invalidations from its own arrange step, such as size assignments, are ignored. Without that, every pass would request the next one and the app would redraw at 10 Hz forever. That is the same flicker WaitingForInputWithoutAResizeDoesNotRedraw guards against.
  • UIApplication.RequestRender() lets hosts that change state without going through Invalidate set the same flag. It is on the class only, not on IUIApplication, so this is not a breaking interface change.
  • CLAUDE.md now describes how invalidation drives render passes.

Tests (new UIApplicationInvalidationTests)

  • TextChangedOffTheLoopThreadIsDrawnWithoutAKeypress: the issue's repro. Changing TextElement.Text from another thread produces a render pass while the read stays pending.
  • RequestRenderDrawsWithoutAKeypress
  • ARenderPassDoesNotRequestTheNextOne: with a nested tree and nothing changing, the app does not keep redrawing.

With the fix reverted (keeping a no-op RequestRender so the tests compile), the first two fail by timeout. With the fix, the full suite passes (256/256) on 3 consecutive runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GJ23oLAT8FnLaMo3MfBviR


Generated by Claude Code

…invalidates [patch]

UIApplication only rendered at startup, after a key and after a resize, and
nothing listened to Invalidated, so a clock, progress value or status set by a
background task stayed frozen until a key was pressed.

The application now subscribes to the root's Invalidated (re-subscribing when
RootElement changes) and sets an Interlocked redraw flag, which the input
loop's poll tick draws on the loop thread. Invalidations a render pass raises
on its own thread while arranging are ignored, so a pass never requests the
next one. RequestRender() sets the same flag for hosts.

Fixes #154

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJ23oLAT8FnLaMo3MfBviR
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit f5c73d8 into main Oct 8, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/154-redraw-on-invalidate branch October 8, 2026 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants