Feature: TUI conversation module coverage Directly exercises the cleveragents.tui.conversation module to cover pruning hysteresis, settings clamping, persistence loading, and edge cases that the high-level TUI scenarios do not reach. @tdd_issue @tdd_issue_6350 Scenario: Default ConversationSettings derive trigger_line_count from low_mark and excess When I build default ConversationSettings Then conversation settings prune_low_mark should equal 1500 And conversation settings prune_excess should equal 1000 And conversation settings trigger_line_count should equal 2500 @tdd_issue @tdd_issue_6350 Scenario: ConversationSettings clamps prune_low_mark below the minimum When I build ConversationSettings with prune_low_mark 10 prune_excess 200 preserve_recent_lines 50 Then conversation settings prune_low_mark should equal 100 And conversation settings prune_excess should equal 200 And conversation settings preserve_recent_lines should equal 50 @tdd_issue @tdd_issue_6350 Scenario: ConversationSettings clamps prune_low_mark above the maximum When I build ConversationSettings with prune_low_mark 99999 prune_excess 200 preserve_recent_lines 50 Then conversation settings prune_low_mark should equal 10000 @tdd_issue @tdd_issue_6350 Scenario: ConversationSettings clamps prune_excess below the minimum When I build ConversationSettings with prune_low_mark 500 prune_excess 1 preserve_recent_lines 50 Then conversation settings prune_excess should equal 10 @tdd_issue @tdd_issue_6350 Scenario: ConversationSettings clamps prune_excess above the maximum When I build ConversationSettings with prune_low_mark 500 prune_excess 99999 preserve_recent_lines 50 Then conversation settings prune_excess should equal 5000 @tdd_issue @tdd_issue_6350 Scenario: ConversationSettings clamps preserve_recent_lines below zero When I build ConversationSettings with prune_low_mark 500 prune_excess 100 preserve_recent_lines -25 Then conversation settings preserve_recent_lines should equal 0 @tdd_issue @tdd_issue_6350 Scenario: ConversationBlock line_count counts trailing newlines and empty text Then a conversation block with text "alpha" should have line count 1 And a conversation block with text "alpha\nbeta" should have line count 2 And a conversation block with empty text should have line count 0 @tdd_issue @tdd_issue_6350 Scenario: ConversationStream bootstrap installs a protected welcome block Given a fresh ConversationStream with default settings When I bootstrap the stream with welcome text "Hello world" Then the stream should have exactly 1 block And the stream total_lines should be greater than zero And the stream rendered text should contain "Hello world" @tdd_issue @tdd_issue_6350 Scenario: ConversationStream clear empties blocks and resets total_lines Given a fresh ConversationStream with default settings When I bootstrap the stream with welcome text "Hello" And I clear the stream Then the stream should have exactly 0 blocks And the stream total_lines should be zero @tdd_issue @tdd_issue_6350 Scenario: ConversationStream extend appends multiple blocks Given a fresh ConversationStream with default settings When I extend the stream with 3 plain message blocks Then the stream should have exactly 3 blocks And the stream rendered text should contain "extend-block-2" @tdd_issue @tdd_issue_6350 Scenario: ConversationStream renders markup blocks verbatim and escapes plain blocks Given a fresh ConversationStream with default settings When I add a plain block "[red]plain[/]" to the stream And I add a markup block "[red]styled[/]" to the stream Then the stream rendered text should contain "[red]styled[/]" And the stream rendered plain segment should be backslash escaped @tdd_issue @tdd_issue_6350 Scenario: ConversationStream skips empty-text blocks during render Given a fresh ConversationStream with default settings When I add an empty plain block to the stream And I add a plain block "after-empty" to the stream Then the stream rendered text should equal "after-empty" @tdd_issue @tdd_issue_6350 Scenario: ConversationStream pruning preserves protected blocks Given a fresh ConversationStream with low_mark 200 excess 50 preserve_recent_lines 100 When I bootstrap the stream with welcome text "Protected welcome" And I deliver 4 plain blocks of 200 lines each through add_block Then the stream rendered text should contain "Protected welcome" And the stream total_lines should be less than or equal to 350 @tdd_issue @tdd_issue_6350 Scenario: ConversationStream pruning counts rendered separators between blocks Given a fresh ConversationStream with low_mark 100 excess 50 preserve_recent_lines 0 When I deliver 100 plain blocks of 1 lines each through add_block Then the stream should have exactly 1 note block And the stream rendered text should not contain "plain-1-line-1" And the stream total_lines should equal the rendered line count And the stream rendered line count should be less than or equal to 150 @tdd_issue @tdd_issue_6350 Scenario: ConversationStream pruning updates the note in place rather than re-inserting Given a fresh ConversationStream with low_mark 200 excess 50 preserve_recent_lines 100 When I deliver 8 plain blocks of 200 lines each through add_block Then the stream should have exactly 1 note block @tdd_issue @tdd_issue_6350 Scenario: ConversationStream pruning with preserve_recent_lines zero allows full prune Given a fresh ConversationStream with low_mark 200 excess 50 preserve_recent_lines 0 When I deliver 6 plain blocks of 200 lines each through add_block Then the stream total_lines should be less than or equal to 250 @tdd_issue @tdd_issue_6350 Scenario: load_conversation_settings returns defaults when the config file is absent When I load conversation settings from a missing config path Then loaded settings prune_low_mark should equal 1500 And loaded settings prune_excess should equal 1000 @tdd_issue @tdd_issue_6350 Scenario: load_conversation_settings returns defaults when the config file is malformed JSON When I load conversation settings from a malformed JSON config Then loaded settings prune_low_mark should equal 1500 And loaded settings prune_excess should equal 1000 @tdd_issue @tdd_issue_6350 Scenario: load_conversation_settings honours explicit ui values When I load conversation settings from a config with low_mark 800 excess 400 Then loaded settings prune_low_mark should equal 800 And loaded settings prune_excess should equal 400 @tdd_issue @tdd_issue_6350 Scenario: load_conversation_settings returns defaults when ui values are non-numeric When I load conversation settings from a config with non-numeric values Then loaded settings prune_low_mark should equal 1500 And loaded settings prune_excess should equal 1000 @tdd_issue @tdd_issue_6350 Scenario: load_conversation_settings returns defaults when payload is not a dict When I load conversation settings from a list-valued JSON config Then loaded settings prune_low_mark should equal 1500 And loaded settings prune_excess should equal 1000