Skip to content

feat(tui v2): selection/paste/scroll/reload polish - #424

Merged
lsdefine merged 1 commit into
lsdefine:mainfrom
nianyucatfish:feat/tui-batch-2
May 19, 2026
Merged

feat(tui v2): selection/paste/scroll/reload polish#424
lsdefine merged 1 commit into
lsdefine:mainfrom
nianyucatfish:feat/tui-batch-2

Conversation

@nianyucatfish

Copy link
Copy Markdown
Collaborator

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".

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
lsdefine merged commit e95bc5c into lsdefine:main May 19, 2026
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".
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.

2 participants