feat(tui v2): selection/paste/scroll/reload polish - #424
Merged
Conversation
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py: 1. Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do. 2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly. 3. Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`. SGR codes from the model/tool output would render as literal `[31m` glyphs and only resolve after `m.done` flipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live. 4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)". 5. Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the agent still receives the full content via expand_placeholders. 6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions). 7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading. 8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no `overflow` to Table.add_column, so the default "ellipsis" hides long cell content with `…` on narrow widths. Patch TableElement.__rich_console__ on import to pass overflow="fold".
wellsoren
pushed a commit
to wellsoren/GenericAgent
that referenced
this pull request
May 19, 2026
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py: 1. Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do. 2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly. 3. Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`. SGR codes from the model/tool output would render as literal `[31m` glyphs and only resolve after `m.done` flipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live. 4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)". 5. Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the agent still receives the full content via expand_placeholders. 6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions). 7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading. 8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no `overflow` to Table.add_column, so the default "ellipsis" hides long cell content with `…` on narrow widths. Patch TableElement.__rich_console__ on import to pass overflow="fold".
lsdefine
pushed a commit
that referenced
this pull request
Jul 23, 2026
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py: 1. Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do. 2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly. 3. Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`. SGR codes from the model/tool output would render as literal `[31m` glyphs and only resolve after `m.done` flipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live. 4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)". 5. Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the agent still receives the full content via expand_placeholders. 6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions). 7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading. 8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no `overflow` to Table.add_column, so the default "ellipsis" hides long cell content with `…` on narrow widths. Patch TableElement.__rich_console__ on import to pass overflow="fold".
Imzl-zl
pushed a commit
to Imzl-zl/GenericAgent
that referenced
this pull request
Aug 5, 2026
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py: 1. Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do. 2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly. 3. Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`. SGR codes from the model/tool output would render as literal `[31m` glyphs and only resolve after `m.done` flipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live. 4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)". 5. Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the agent still receives the full content via expand_placeholders. 6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions). 7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading. 8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no `overflow` to Table.add_column, so the default "ellipsis" hides long cell content with `…` on narrow widths. Patch TableElement.__rich_console__ on import to pass overflow="fold".
Imzl-zl
pushed a commit
to Imzl-zl/GenericAgent
that referenced
this pull request
Aug 5, 2026
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py: 1. Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do. 2. Blockquote-continuation selection offset: Rich re-emits `▌ ` on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly. 3. Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with `Text(content, style=C_FG)` instead of `Text.from_ansi`. SGR codes from the model/tool output would render as literal `[31m` glyphs and only resolve after `m.done` flipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live. 4. continue picker LIMIT removed: tuiapp's own `LIMIT = 20` truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)". 5. Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 / `⋯ 省略 N 行 ⋯`); the agent still receives the full content via expand_placeholders. 6. mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.__init__ / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions). 7. Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading. 8. Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no `overflow` to Table.add_column, so the default "ellipsis" hides long cell content with `…` on narrow widths. Patch TableElement.__rich_console__ on import to pass overflow="fold".
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.
Eight independent fixes accumulated since the last batch, all landing in tuiapp_v2.py:
Code-block selection offset: _align_md_renders's is_centered heuristic tripped on any code line indented >=4 spaces (Python's default), stripping the indent from the source string so the visible selection and the copied text disagreed. Add wide_lead >= 2*nt0_lead guard — only Rich's H1 centering (wide_lead ~ width/2) crosses it, code lines never do.
Blockquote-continuation selection offset: Rich re-emits
▌on every wrapped visual line of a quote, but the wide-render carries the marker only once. Strip the marker as a decoration prefix (counts as indent, not content) and switch the wrap-accumulator to account for the per-break eaten space — both pre-existing code-block wraps and blockquotes now map correctly.Streaming ANSI garbage: while a turn is streaming, the tail text segment was rendered with
Text(content, style=C_FG)instead ofText.from_ansi. SGR codes from the model/tool output would render as literal[31mglyphs and only resolve afterm.doneflipped or a resize forced a remount via the Markdown path. Switch the two streaming fast paths to Text.from_ansi — O(n) scan, no Markdown parse, colors honored live.continue picker LIMIT removed: tuiapp's own
LIMIT = 20truncated the /continue picker even though list_sessions() always builds the full list. Show everything; header now reads "选择要恢复的会话 (N 条)".Long-paste rescue + user echo elision: Windows Terminal yanks window focus when its >=5KiB paste-warning dialog pops, leaving focused=None after the user confirms; Textual then routes the Paste event to the App bubble instead of InputArea, and the paste appears to do nothing. Add a GenericAgentTUI._on_paste forwarder that refocuses #input and replays the event into InputArea._on_paste. Separately, on submit, elide user-side display of very long pastes (head 10 / tail 5 /
⋯ 省略 N 行 ⋯); the agent still receives the full content via expand_placeholders.mykey.py hot reload: load_llm_sessions() already had mtime-gated reload plumbing, but it only ran on Agent.init / next_llm / list_llms — so editing mykey.py mid-session required restart. Two triggers added: on_input_area_submitted now calls load_llm_sessions() before sending (cheap mtime check; rebuilds on change), and a new /reload-keys command forces a sweep across every session (resetting llmcore._mykey_mtime per-iteration so the cache doesn't short-circuit later sessions).
Resize / sidebar toggle no longer jumps to bottom: _remount_current_session used to unconditionally scroll_end after rebuilding. Capture scroll_y + "was at bottom?" first; stick to bottom only if the user was already there, otherwise restore the prior scroll_y so resizes don't yank the user away from what they were reading.
Markdown table cells wrap instead of ellipsizing: Rich's Markdown TableElement passes no
overflowto Table.add_column, so the default "ellipsis" hides long cell content with…on narrow widths. Patch TableElement.rich_console on import to pass overflow="fold".