Skip to content

fix(tui): keep the indent of a paragraph nested in a list - #7146

Merged
Hmbown merged 2 commits into
codewhale-hq:mainfrom
Lstarsky0:fix/indent-list-paragraphs
Oct 31, 2026
Merged

Hmbown merged 2 commits into
codewhale-hq:mainfrom
Lstarsky0:fix/indent-list-paragraphs

Conversation

@Lstarsky0

Copy link
Copy Markdown
Contributor

What and why

A paragraph indented under a list item renders flush left: the word wrapper drops spaces at the start of a row, so the indent never reaches the screen. Models write these often, as a bullet's second paragraph or a note after a numbered step's code block. At 60 columns, before:

● Setup:
▏
▏ 1. Install the dependencies:
▏
       npm install --save-dev typescript
▏
▏ This also writes a lockfile, which you should commit
▏ together with the change.
▏
▏ 2. Build:
       npm run build
▏ 3. Check the config:
▏
       {
         "strict": true
       }
▏
▏ - A bullet with a second paragraph.
▏
▏ The second paragraph of that bullet, indented two spaces
▏ and long enough to wrap.
▏ - Last bullet

The code under step 1 keeps its indent but the note after it doesn't, so step 1 looks finished and the note reads as top-level text. After:

● Setup:
▏
▏ 1. Install the dependencies:
▏
       npm install --save-dev typescript
▏
▏    This also writes a lockfile, which you should commit
▏    together with the change.
▏
▏ 2. Build:
       npm run build
▏ 3. Check the config:
▏
       {
         "strict": true
       }
▏
▏ - A bullet with a second paragraph.
▏
▏   The second paragraph of that bullet, indented two
▏   spaces and long enough to wrap.
▏ - Last bullet

The parser now carries whether a list is open, next to its code-fence state: a list item opens it, blank lines, code and indented paragraphs keep it open, and anything else closes it. While a list is open, a paragraph keeps its leading spaces, and render_paragraph_tagged wraps the text in the remaining width and draws the indent on every row, capped at half the width like a list item's indent (#7030); links shift with it. The decision is made when the line is parsed rather than in the renderer, because streaming renders the text between code blocks separately and a look-back there would not match the final render. Copy follows list items: the first row copies with its indent, as written, and wrapped rows strip it.

Command notes go through the same renderer, and some have indented rows that aren't under a list item, such as /status's key/value rows and part of /cost. Those render as before: I ran /status, /cost, /context, /hooks, /mcp, /skills, /tools, /memory, /doctor, /version, /usage, /agents, /workspace, /permissions, /sandbox, /todo, /goal, /diff, /stats and /profile in one session on both builds, and the screens match once the session id is masked. A first version that kept every indent did shift /status and /cost, which is why the decision follows the list.

Issue

No-Issue: a paragraph indented under a list item renders flush left; I searched and found no issue for it

How I tested it

  • Ran a debug build of this change and one of main at 588426d in a pseudo-terminal at 60 columns with an empty HOME and a local mock OpenAI-compatible server replying with the steps and bullets above; the rows above come from those runs.
  • Selection copy: with a throwaway test (not part of this PR) that selects 1. Install followed by an indented paragraph, and - item followed by an indented line, at 24 columns and calls selection_to_text, this change copies both as written ("1. Install\n\n alpha beta … kappa"); main copies them without the indent ("1. Install\n\nalpha beta … kappa").
  • cargo nextest run -p codewhale-tui -p codewhale-tui-kit --lib: 13880 of 13886 passed, with the 427 tests whose names match markdown, paragraph, selection or transcript among them. The 6 failures (status_public_output_and_read_only_state_match_baseline, golden_turn_events_match, model_visible_prefix_bytes_match_goldens, interrupted_session_deletion_keeps_recovery_incomplete_and_can_finish, worker_compacts_past_its_context_window_and_keeps_working, reasoning_effort_uses_one_strict_alias_table_and_legacy_fallback) also fail on main at 588426d without this change.
  • cargo fmt --all -- --check, cargo clippy -p codewhale-tui -p codewhale-tui-kit with CI's flags (also on 1.99), cargo check -p codewhale-tui -p codewhale-tui-kit --all-targets and cargo +1.89.0 check -p codewhale-tui -p codewhale-tui-kit: clean.

Checklist

  • One focused change, on 588426d; it merges cleanly into current main
  • cargo fmt --all passes, and tests cover new or changed behavior — cargo fmt passes; no new test, per AGENTS.md, so the runs above stand in for one
  • If this adds or changes a user-facing feature, I updated its docs and its row in docs/features.toml (cargo test -p codewhale-tui --test feature_registry checks it) — n/a

A paragraph indented under a list item, such as a bullet's second
paragraph or the note after a numbered step's code block, rendered
flush left: the word wrapper drops spaces at the start of a row. The
item then looks finished and the paragraph reads as top-level text.

The parser now carries whether a list is open: a list item opens it,
blank lines, code and indented paragraphs keep it open, and anything
else closes it. A paragraph keeps its indent only while a list is open,
so indented rows of command notes such as /status and /cost render as
before. The decision is made at parse time because streaming renders
the text between code blocks separately, so a look-back in the renderer
would not match the final render.

The kept indent is drawn on every row, capped at half the width like a
list item's. As with list items, the first row copies with its indent
and wrapped rows strip it.

Signed-off-by: Lstarsky0 <59827030+Lstarsky0@users.noreply.github.com>
@Lstarsky0
Lstarsky0 requested a review from Hmbown as a code owner October 29, 2026 19:33

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions github-actions Bot added the contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm label Oct 29, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-29T19:33:38.513505Z 9dc506b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@carloslindhstrom-code carloslindhstrom-code left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

``

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thank you!! this is great

@Hmbown
Hmbown merged commit 70190ac into codewhale-hq:main Oct 31, 2026
26 of 27 checks passed
Hmbown pushed a commit that referenced this pull request Oct 31, 2026
fix(tui): render ~~~ fences as code blocks

Conflict with #7146 resolved in the ParseState doc comment only: the carry is
now `open_fence`, `code_language`, `code_block_id`, `in_list`. All code hunks
merged cleanly.
Hmbown pushed a commit that referenced this pull request Oct 31, 2026
…urfaces

Changelog lines for #7147, #7146, #7156, #7157, #7158, #7152 and #7150, and
credit for the contributors across CHANGELOG `### Contributors`,
docs/CONTRIBUTORS.md and UNRELEASED_CONTRIBUTORS (adds @luochen211, whose #7150
and #7149 landed). #7149 is a dead-code chore and has no changelog line.

Verified: `python3 scripts/check-contributor-credit.py` reports every contributor
in the window credited on all three surfaces; `scripts/preflight.sh --check`
passes; `sh scripts/sync-changelog.sh` regenerated crates/tui/CHANGELOG.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants