Conversation
At an idle prompt, prompt_async() always has a live _pending_input future, so the Ctrl+C binding took the cancel-pending branch: it cancelled the future (which broke the input loop via KeyboardInterrupt) but never called app.exit(). The app kept running with a dead input loop — typed input was echoed and silently dropped, and only a second Ctrl+C exited. Pending input no longer counts as in-flight work when deciding whether to exit: Ctrl+C with no running handler and no active boxes now exits on the first press, matching the documented behavior. The future is still cancelled first so direct prompt_async() callers continue to observe KeyboardInterrupt. The idle-exit unit test previously set _pending_input = None — a state that never occurs at a real idle prompt — which is how this slipped past. It now models the live future, and a new end-to-end regression test drives run_async() with a stubbed Application to assert the session actually terminates on the first Ctrl+C.
DialogManager.show() now raises RuntimeError if a dialog is already open. Previously a second show() overwrote _current_dialog, orphaning the first dialog's result future — its awaiter hung forever with no way to resolve. Failing loudly makes the single-dialog contract explicit. SettingsDialog with can_cancel=False now disables Escape (_UNSET) instead of setting escape_result to the string "close", which leaked through DialogManager.show() as the return value of show_settings_dialog() — violating its documented dict-or-None contract (callers iterating result.items() would crash on a str). With no cancel concept, the Done button is the only way out.
ThinkingBoxControl was always created with its default expand_key
("c-t"), so a custom AppInfo.expand_key changed the actual binding
but the truncation hint still read "ctrl-t to expand". The manager
now takes expand_key and forwards it to every box it creates.
StreamingContent.set_line(-5, ...) on shorter content fell through normalization still negative, skipped the extend loop, and failed on the list assignment with an error naming the normalized index. It now raises immediately with the index the caller passed, and leaves the content untouched.
finish_all() now reports each box's max_collapsed_lines, and finish_thinking() uses it instead of the session-wide max_thinking_height — matching the per-box finish path used by ThinkingContext.finish(). Boxes created with a custom max_lines were previously truncated to the wrong limit on the deprecated path.
Display.clear() raw-printed \033[2J\033[H to stdout, bypassing prompt_toolkit's renderer. With the app running, the renderer's idea of what is on screen goes stale and the next repaint draws against the wrong baseline. Screen clearing now lives in ThinkingPromptSession.clear(): app.renderer.clear() when running, escape-code fallback when not. Display.clear() handles only its own state (history buffer + pending output).
get_line_count measured raw len(line), so SGR escape sequences in ansi-format content inflated the wrapping estimate and the collapsed box was sized taller than its visible content. Escape sequences are now stripped before width math.
The header line was rendered at a hardcoded 80 columns, so it never spanned wider terminals and overflowed narrower ones. The width now comes from the running app's output size, falling back to 80 outside a running app.
…obally Move the Rich/Pygments helpers (_is_rich_renderable, _rich_to_ansi, _renderable_to_ansi, _markdown_to_ansi, _highlight_code) out of display.py into rich_utils.py. types.py previously had to import these from display at function level to dodge a cycle — a low-level module reaching up into the display layer. display.py re-exports the names for backward compatibility. The left-aligned heading style was applied by replacing Markdown.elements['heading_open'] at import time, which changed heading rendering for the host application's own Rich usage. It is now a scoped Markdown subclass used only by our markdown rendering; rich.markdown.Markdown is left untouched.
…_output Neither method has a production caller: the session-level binding (via ThinkingBoxManager) owns expand/collapse, and Display.thinking() owns console truncation. get_key_bindings also shadowed the UIControl.get_key_bindings() API with an incompatible signature and built a fresh KeyBindings object per call. Tests that existed only to exercise the dead methods are removed with them.
The same frame tuple was duplicated in layout.py and app_info.py. It now lives in types.py (which imports nothing from the package); layout re-exports it for backward compatibility and AppInfo uses it as the field default.
Document that synchronous input handlers run on the event loop and freeze the UI (including Ctrl+C handling) until they return — in the README quick start and the on_input/run_async docstrings. Add Unreleased changelog entries covering the Ctrl+C idle-exit fix, dialog lifecycle fixes, thinking-box polish, and the rich_utils refactor.
fix: first Ctrl+C at idle prompt exits instead of zombifying the session
fix: dialog reentrancy guard and settings-dialog Escape contract
fix: thinking-box polish — six small correctness fixes
refactor: extract rich_utils; stop monkey-patching Rich's Markdown globally
docs: sync-handler event-loop warning; changelog for pending fixes
The showcase hardcoded version="0.3.0" while the package was already at 0.3.2, so the welcome banner showed a stale version in recordings. Sourcing it from thinking_prompt.__version__ keeps it correct across future bumps.
chore(demo): source showcase version from package __version__
Minor bump from 0.3.2. Highlights (see CHANGELOG for full list): - Fix: first Ctrl+C at an idle prompt exits instead of zombifying the session - Fix: DialogManager.show() guards against concurrent dialogs - Fix: settings dialog (can_cancel=False) no longer leaks "close" on Escape - Fix: expand_key reflected in the truncation hint; renderer-safe clear(); ANSI-aware line counting; terminal-width header; per-box finish truncation - Refactor: rich_utils module; no global Rich Markdown monkey-patch Minor (not patch) because ThinkingBoxManager.finish_all() gained a fifth tuple element and idle Ctrl+C now also calls app.exit() for direct prompt_async() consumers.
shoom1
added a commit
that referenced
this pull request
Jun 13, 2026
Owner
Author
|
Re-released as v0.3.3 (patch) instead of 0.4.0 per maintainer request. The v0.4.0 tag and GitHub release were removed; |
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.
Release v0.4.0
Minor release rolling up PRs #17–#22 from
develop. Bumps0.3.2 → 0.4.0.Why minor (not patch)
ThinkingBoxManager.finish_all()return tuples gained a fifth element (max_collapsed_lines) — a public signature change.app.exit()in addition to raisingKeyboardInterrupt, a visible behavior change for code drivingprompt_async()directly.Fixed
DialogManager.show()raisesRuntimeErroron a concurrent dialog instead of orphaning the first dialog's future.show_settings_dialog(can_cancel=False)no longer leaks the string"close"on Escape (now disabled in that mode).AppInfo.expand_keyreflected in the truncation hint.StreamingContent.set_line()raises a clearIndexErrorfor out-of-range negative indices.finish_thinking()truncates each box to its ownmax_lines.clear()goes through the renderer while the app is running.Changed
rich.markdown.Markdownmonkey-patch on import (scoped subclass instead).rich_utils(backward-compat re-exports kept).ThinkingBoxControlmethods removed;DEFAULT_SPINNER_FRAMESdeduped intotypes.__version__.Full detail in CHANGELOG.md.