diff --git a/plugins/platforms/slack/block_kit.py b/plugins/platforms/slack/block_kit.py index ac1055200..f3bcf1b9f 100644 --- a/plugins/platforms/slack/block_kit.py +++ b/plugins/platforms/slack/block_kit.py @@ -451,10 +451,22 @@ def render_blocks( indent, ordered, txt = items[-1] items[-1] = (indent, ordered, txt + " " + lines[i].strip()) i += 1 - elif not lines[i].strip(): - # blank line — soft separator within a list run; - # skip so that ordered items stay in one rich_text_list. - i += 1 + elif not lines[i].strip() and items: + # Blank line inside a list run. LLM-authored ordered + # lists commonly separate items with a blank line; if + # the next non-blank line is another list item, treat + # the blank(s) as a soft separator and keep the run + # going so the items stay in one rich_text_list (Slack + # numbers each list independently, so splitting would + # restart every item at "1."). Otherwise the blank + # ends the list. + j = i + 1 + while j < n and not lines[j].strip(): + j += 1 + if j < n and (_BULLET_RE.match(lines[j]) or _ORDERED_RE.match(lines[j])): + i = j + else: + break else: break blocks.append(_list_block(items)) diff --git a/tests/gateway/test_slack_block_kit.py b/tests/gateway/test_slack_block_kit.py index 6d1bfad4c..268560666 100644 --- a/tests/gateway/test_slack_block_kit.py +++ b/tests/gateway/test_slack_block_kit.py @@ -106,6 +106,31 @@ class TestInlineFormatting: items = lists[0]["elements"] assert len(items) == 3 + def test_blank_separated_mixed_list_matches_contiguous_layout(self): + """A blank line between different list kinds must render like the + contiguous form: one rich_text block whose sub-lists split only on + (indent, ordered) changes — not a separate block per item. + """ + rich = [b for b in render_blocks("1. a\n\n- b") if b["type"] == "rich_text"] + # Single rich_text block (matches contiguous "1. a\n- b"), two sub-lists + assert len(rich) == 1 + styles = [e["style"] for e in rich[0]["elements"] if e["type"] == "rich_text_list"] + assert styles == ["ordered", "bullet"] + + def test_blank_line_before_paragraph_ends_the_list(self): + """A blank line followed by non-list content must still end the run, + so a list → paragraph → list sequence stays three separate blocks. + """ + blocks = render_blocks("1. a\n\nsome paragraph text\n\n1. b") + lists = [ + e + for b in blocks + for e in b.get("elements", []) + if e.get("type") == "rich_text_list" + ] + # Two independent single-item lists, not one merged three-item list + assert [len(e["elements"]) for e in lists] == [1, 1] + class TestTables: def test_pipe_table_renders_native_table_block(self):