Skip to content

refactor(tui): prompt deep-module PR 1 — frame snapshot + height budgets#218

Merged
elkaix merged 12 commits into
mainfrom
refactor/tui-prompt-deep-module-pr1
Jul 20, 2026
Merged

refactor(tui): prompt deep-module PR 1 — frame snapshot + height budgets#218
elkaix merged 12 commits into
mainfrom
refactor/tui-prompt-deep-module-pr1

Conversation

@elkaix

@elkaix elkaix commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

PR 1 of the TUI prompt deep-module refactor plan (docs/superpowers/plans/2026-07-19-tui-prompt-deep-module-refactor.md), covering Tasks 1–3.

What

  • Task 1 — compatibility contract (4a38abf2): freezes the shell prompt's public import surface and constructor semantics in tests/ui_and_conv/test_prompt_public_contract.py (22 exported symbols, keyword construction, 30 FPS render cap), plus characterization of delegate render sampling.
  • Task 2 — frame snapshot (fe2d4f42): new ui/shell/prompting/ package with PromptFrame / PromptFrameCollector; every render samples each running-prompt/modal delegate method exactly once per frame via before_render/after_render, eliminating redundant delegate calls. Live input decisions still use live delegate lookup.
  • Task 3 — height budgets (5bd40e23): new prompting/renderer.py with PromptSceneBudget (replaces the fixed 7-row subtraction) and a priority-ordered row allocator (modal → input → footer → pinned → separator → body → status → shortcuts). Scenes that fit render byte-identically; clipping engages only on overflow, pinned allocation is zero-safe, and the interactive preview no longer derives a competing body budget.
  • Resize e2e (2fd528f2): PTY test resizing a live turn through heights 12/8/6/4, asserting the redrawn frame fits and the turn survives.

Verification

  • make check-pythinker-code and make test-pythinker-code green at the PR boundary (8238 passed in the pre-push gate).
  • Exhaustive new height matrix: 12 heights × 4 widths × 7 scenes × both TUI styles.
  • PTY layout suite (4 tests, local-only by design) passing.
  • Existing behavior suites (test_prompt_tips, test_visualize_running_prompt, test_modal_lifecycle) pass unmodified.

Summary by CodeRabbit

  • Bug Fixes
    • Improved shell prompt layout during terminal resizing, including very small heights, ensuring the prompt scene redraws cleanly across shrink/redraw cycles.
    • Fixed input-card “fossilization”/stale borders on height-only redraws.
    • Corrected clipping and row-budgeting across pinned/status, modal, toolbars, shortcut/update footer, and live output.
    • Updated “update available” footer refresh so shell-mode/frame changes clear and re-render correctly.
  • Documentation
    • Documented and froze the public shell prompt compatibility contract, including constructor and rendering coverage.

elkaix added 9 commits July 19, 2026 16:05
Add prompting/renderer.py with PromptSceneBudget and a priority-ordered
row allocator (modal > input > footer > pinned > body > status >
shortcuts). Rendering is unchanged for scenes that fit; on overflow the
scene is clipped tail-first per priority, pinned allocation is
zero-safe, and the interactive preview no longer derives a competing
body budget.
Resize the live PTY through heights 12/8/6/4 during a running turn and
assert the redrawn frame stays within the new geometry, never
fossilizes the input card, and the turn still completes after the size
is restored.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The prompt renderer now captures delegate output once per frame, allocates terminal rows across prompt surfaces, clips preambles and toolbars consistently, and adds coverage for resizing, scene precedence, sampling, and public compatibility.

Changes

Prompt rendering

Layer / File(s) Summary
Scene contracts and allocation primitives
src/pythinker_code/ui/shell/prompting/*
Adds immutable prompt frames, frame collection, scene budgets, deterministic surface allocation, and explicit exports.
Captured prompt rendering integration
src/pythinker_code/ui/shell/prompt.py, src/pythinker_code/ui/shell/visualize/_interactive.py
Refactors rendering to use captured fragments and shared row budgets for modal content, pinned tails, live bodies, preambles, update notices, and toolbars.
Prompt compatibility and regression coverage
tests/ui_and_conv/*, tests/e2e/test_shell_pty_prompt_layout_e2e.py, CHANGELOG.md
Adds scene-budget, terminal-resize, delegate-sampling, rendering-order, public-contract, and update-notice coverage.
Immediate-fix guidance
AGENTS.md
Adds a rule requiring applicable real issues to be fixed immediately unless expanded scope is requested.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CustomPromptSession
  participant PromptFrameCollector
  participant allocate_prompt_scene_rows
  participant Terminal
  CustomPromptSession->>PromptFrameCollector: Capture delegate fragments and UI state
  PromptFrameCollector->>allocate_prompt_scene_rows: Request bounded surface allocations
  allocate_prompt_scene_rows-->>CustomPromptSession: Return PromptSceneAllocation
  CustomPromptSession->>Terminal: Render clipped prompt and toolbar
Loading

Possibly related PRs

Suggested labels: bug

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description has useful details, but it omits the required issue link, template sections, and checklist items. Add the Related Issue/Resolve # section, a Description section, and the checklist items from the template, including tests and changelog/docs confirmation.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses valid conventional-commit format and matches the refactor scope of prompt snapshotting and height budgets.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/tui-prompt-deep-module-pr1

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.03987% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/ui/shell/prompt.py 85.07% 16 Missing and 14 partials ⚠️
src/pythinker_code/ui/shell/prompting/frame.py 90.90% 3 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 44-48: Update the “Never propose deferring an applicable issue or
fix” guidance in AGENTS.md to align with the surgical-change rule: require
immediate fixes only for release-blocking correctness, security, or
data-integrity issues, and require explicit tracking for other findings instead
of forcing unrelated redesigns or silently ignoring them.

In `@src/pythinker_code/ui/shell/prompt.py`:
- Around line 3149-3160: Update _render_shell_prompt_message to refresh
_prompt_footer_row_budget before or alongside the _fit_preamble_with_pinned_tail
flow, using the current terminal dimensions and shell-mode footer requirements.
Ensure the refreshed budget is applied before _fit_toolbar_to_terminal runs,
including after mode switches and terminal resizes.

In `@tests/e2e/test_shell_pty_prompt_layout_e2e.py`:
- Around line 272-274: Remove the trivially true len(rows) == height assertion
in the resized-frame test around _render_sized. Replace it with an assertion
that validates observable post-resize behavior, such as preservation of the
running-prompt content or absence of a fossil border on the final line, while
retaining the existing _has_fossil_border_above_content check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a531ca57-7c2b-4377-ba87-f7ab7e3371c1

📥 Commits

Reviewing files that changed from the base of the PR and between b5cfe09 and 9637154.

⛔ Files ignored due to path filters (3)
  • docs/superpowers/plans/2026-07-19-tui-content-rendering-standardization.md is excluded by !docs/**
  • docs/superpowers/specs/2026-07-19-tui-content-rendering-standardization-design.md is excluded by !docs/**
  • tasks/lessons.md is excluded by !tasks/**
📒 Files selected for processing (12)
  • AGENTS.md
  • CHANGELOG.md
  • src/pythinker_code/ui/shell/prompt.py
  • src/pythinker_code/ui/shell/prompting/__init__.py
  • src/pythinker_code/ui/shell/prompting/frame.py
  • src/pythinker_code/ui/shell/prompting/renderer.py
  • src/pythinker_code/ui/shell/visualize/_interactive.py
  • tests/e2e/test_shell_pty_prompt_layout_e2e.py
  • tests/ui_and_conv/test_prompt_height_budget.py
  • tests/ui_and_conv/test_prompt_public_contract.py
  • tests/ui_and_conv/test_prompt_tips.py
  • tests/ui_and_conv/test_visualize_running_prompt.py

Comment thread AGENTS.md
Comment thread src/pythinker_code/ui/shell/prompt.py
Comment thread tests/e2e/test_shell_pty_prompt_layout_e2e.py
…rtion

Shell-mode rendering never set _prompt_footer_row_budget, so
_fit_toolbar_to_terminal clipped the bottom toolbar against a stale
agent-mode value (or the initial 0, hiding the footer entirely in a
pure shell session). Refresh it every shell render. Replace a
trivially-true pyte row-count assertion in the resize e2e with a
width-overflow check.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pythinker_code/ui/shell/prompt.py (1)

2845-2846: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Refresh the update notice for every captured frame.

_append_update_notice trusts _prompt_frame_update_notice whenever _current_prompt_frame is set, but the cache is populated only by agent rendering and is never cleared with the frame. Shell mode can therefore suppress a current notice on its first frame or replay stale agent-mode text after a mode switch.

Refresh the notice in shell rendering (or capture it in _capture_prompt_frame) and clear it in _clear_prompt_frame.

Suggested fix
 def _clear_prompt_frame(_app: Application[str]) -> None:
     self._current_prompt_frame = None
+    self._prompt_frame_update_notice = None

 def _render_shell_prompt_message(self) -> FormattedText:
     frame = self._prompt_frame_for_render()
+    provider = self._update_notice_provider
+    self._prompt_frame_update_notice = provider() if provider is not None else None

Also applies to: 3127-3134, 4355-4364

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pythinker_code/ui/shell/prompt.py` around lines 2845 - 2846, Update
prompt-frame lifecycle handling so _append_update_notice always uses a notice
captured for the current frame: refresh _prompt_frame_update_notice during shell
rendering or within _capture_prompt_frame, and reset it alongside
_current_prompt_frame in _clear_prompt_frame. Apply the same behavior to the
corresponding frame-rendering paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/pythinker_code/ui/shell/prompt.py`:
- Around line 2845-2846: Update prompt-frame lifecycle handling so
_append_update_notice always uses a notice captured for the current frame:
refresh _prompt_frame_update_notice during shell rendering or within
_capture_prompt_frame, and reset it alongside _current_prompt_frame in
_clear_prompt_frame. Apply the same behavior to the corresponding
frame-rendering paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fca03a6d-c2fc-4558-a14f-4f6ea314c86c

📥 Commits

Reviewing files that changed from the base of the PR and between 9637154 and d15d807.

📒 Files selected for processing (2)
  • src/pythinker_code/ui/shell/prompt.py
  • tests/e2e/test_shell_pty_prompt_layout_e2e.py

The per-frame _prompt_frame_update_notice snapshot was refreshed only by
the agent render path and never cleared with the frame, so shell frames
suppressed a live update notice (initial None) or replayed a stale
agent-mode notice after a mode switch. Refresh the snapshot in the shell
render like the agent path and reset it in _clear_prompt_frame.
@elkaix

elkaix commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Addressed the outside-diff finding (_append_update_notice reading a stale per-frame update-notice snapshot) in 7078692.

_render_shell_prompt_message now refreshes _prompt_frame_update_notice from the provider every render (mirroring the agent path), and _clear_prompt_frame resets it with the frame — so a shell frame no longer suppresses a live notice (initial None) or replays a stale agent-mode notice after a mode switch. Added test_shell_render_refreshes_update_notice_snapshot, which exercises both the snapshot refresh and the _append_update_notice cache-read branch end-to-end (fails without the fix). The AGENTS.md:48 governance suggestion remains intentionally out of scope for this rendering PR.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Relevant symbols in prompt.py =='
rg -n -C 5 \
  'def _render_shell_prompt_message\b|def _clear_prompt_frame\b|_prompt_frame_update_notice|def _append_update_notice\b' \
  src/pythinker_code/ui/shell/prompt.py

echo
echo '== Outline and test location =='
ast-grep outline tests/ui_and_conv/test_prompt_height_budget.py --items all --type function --match 'test_shell_render_refreshes_update_notice_snapshot|test_update_notice_is_sampled_once_for_prompt_and_footer' || true

rg -n -C 8 \
  'def test_shell_render_refreshes_update_notice_snapshot\b|def test_update_notice_is_sampled_once_for_prompt_and_footer\b' \
  tests/ui_and_conv/test_prompt_height_budget.py

echo
echo '== Commit summary =='
git show --stat --oneline --no-renames 70786925

Length of output: 7476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Shell update-notice regression test =='
sed -n '148,190p' tests/ui_and_conv/test_prompt_height_budget.py

echo
echo '== Update-notice cache-read branch =='
sed -n '4359,4395p' src/pythinker_code/ui/shell/prompt.py

echo
echo '== Shell renderer refresh context =='
sed -n '3130,3165p' src/pythinker_code/ui/shell/prompt.py

Length of output: 6227


@elkaix Confirmed. The shell render now refreshes the per-frame update-notice snapshot, frame cleanup clears it, and the regression test covers both stale-value replacement and stale-value clearing through _append_update_notice’s captured-frame cache path.

The rendering finding is resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/ui_and_conv/test_prompt_height_budget.py`:
- Around line 148-180: The test
test_shell_render_refreshes_update_notice_snapshot should also exercise the
registered after_render cleanup callback. Complete a rendered frame through the
existing session/render lifecycle, then assert that
session._prompt_frame_update_notice is None after the frame finishes, covering
cleanup independently of the provider refresh and None-clearing assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2a57efce-f2c8-40f6-972c-97946b3cecf8

📥 Commits

Reviewing files that changed from the base of the PR and between d15d807 and 7078692.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/pythinker_code/ui/shell/prompt.py
  • tests/ui_and_conv/test_prompt_height_budget.py

Comment thread tests/ui_and_conv/test_prompt_height_budget.py
Extract the after_render frame-clear closure into a named
_clear_prompt_frame_snapshot method so the lifecycle cleanup is directly
testable, and assert a completed frame leaves _prompt_frame_update_notice
as None.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/ui_and_conv/test_prompt_height_budget.py (1)

109-126: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise the real footer render in the height assertion.

This test fabricates a 3/4-row footer and mocks get_app_or_none, so it can pass even if _render_bottom_toolbar or the actual prompt-toolkit layout adds rows. Assert the observable rendered layout using the real toolbar/render lifecycle (or the existing PTY path).

As per path instructions, tests under tests/**/*.py should avoid mocking internal implementation details in place of observable behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ui_and_conv/test_prompt_height_budget.py` around lines 109 - 126,
Update the height-budget test around _fit_toolbar_to_terminal to exercise the
real footer rendering lifecycle, including _render_bottom_toolbar and
prompt-toolkit layout behavior, instead of fabricating footer rows and mocking
shell_prompt.get_app_or_none. Assert the observable rendered layout through the
existing PTY or equivalent real render path, while preserving the
message-plus-footer height constraint.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/ui_and_conv/test_prompt_height_budget.py`:
- Around line 109-126: Update the height-budget test around
_fit_toolbar_to_terminal to exercise the real footer rendering lifecycle,
including _render_bottom_toolbar and prompt-toolkit layout behavior, instead of
fabricating footer rows and mocking shell_prompt.get_app_or_none. Assert the
observable rendered layout through the existing PTY or equivalent real render
path, while preserving the message-plus-footer height constraint.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbf19eba-b690-450a-9a5c-5c932be45a0a

📥 Commits

Reviewing files that changed from the base of the PR and between 7078692 and ae8669f.

📒 Files selected for processing (2)
  • src/pythinker_code/ui/shell/prompt.py
  • tests/ui_and_conv/test_prompt_height_budget.py

@elkaix
elkaix merged commit 2c7a94c into main Jul 20, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant