You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Code-organization gap — drift risk across 8 adapters.
Current state
src/chat_sdk/shared/adapter_utils.py exposes only extract_card() and extract_files(). Every other cross-adapter helper lives duplicated inside each adapter's cards.py / format_converter.py:
Emoji placeholder resolution ({{emoji:thumbs_up}} → platform-native code). Each adapter imports DEFAULT_EMOJI_MAP from shared/ and walks children independently. Slack, Discord, Telegram, Teams, Google Chat, Linear, GitHub all do this; WhatsApp doesn't (because it doesn't translate emoji placeholders at all — consistent gap).
Button style mapping (style: "primary" | "secondary" | "danger" → Block Kit "primary" / AdaptiveCard "positive" / Discord 1..4 / Linear mention syntax). Scattered across each adapter.
Card fallback text (render a Card to plain markdown for platforms with limited card support, or for notification previews). Reinvented in Telegram and WhatsApp; partially in Discord.
GFM table rendering (render Table to an ASCII-aligned markdown table for adapters without native table blocks). Duplicated in Telegram, WhatsApp, Linear; Discord has its own; Slack has a separate Block-Kit-first path.
Table cell escaping (|, newlines, backticks inside cells). Each adapter that renders tables has its own escape routine; subtle bugs have bitten us in Slack tables specifically.
Why it matters
Drift: eight adapters solving the same problem independently means eight different bug surfaces for the same logical operation. We've already shipped one fix to Slack empty-header cells (0.4.26) that didn't propagate to other adapters because there was no shared routine to fix.
Maintenance: new platform emoji added to DEFAULT_EMOJI_MAP requires audit of every adapter to confirm it's picked up. A shared converter removes the audit.
Consistency: "render this Card as fallback text" should produce the same output regardless of which adapter is asking. Today it doesn't.
Proposed surface
In src/chat_sdk/shared/adapter_utils.py:
defcreate_emoji_converter(
platform_map: Mapping[str, str] |None=None,
) ->Callable[[str], str]:
"""Return a function that replaces {{emoji:name}} placeholders with platform-native code."""defmap_button_style(
style: ButtonStyle,
target: Literal["slack", "teams", "discord", "telegram", "whatsapp", "gchat", "linear", "github"],
) ->str|int:
"""Translate SDK button style to the target platform's native representation."""defcard_to_fallback_text(card: Card, *, emoji_converter: Callable[[str], str] |None=None) ->str:
"""Render a Card to plain markdown text, honoring emoji placeholders."""defrender_gfm_table(table: Table, *, aligned: bool=True) ->str:
"""Render a Table to a GFM-flavored markdown table, optionally padding for visual alignment."""defescape_table_cell(text: str) ->str:
"""Escape `|`, newlines, and backticks for safe inclusion inside a markdown table cell."""
Each helper replaces the per-adapter copy. Per-adapter code stays responsible for (a) choosing which helpers to call, (b) mapping to platform-specific payload shapes (Block Kit, AdaptiveCard, Discord components, etc.) — that's legitimately platform-specific and shouldn't move.
create_emoji_converter (largest blast radius; save for last when the pattern is settled)
Each PR: promote the helper, update all adapters to use it, delete the duplicated copies, add regression tests in shared/ for the helper itself + keep existing adapter tests passing.
Acceptance
All five helpers live in shared/adapter_utils.py with direct unit tests
Each of the 8 adapters calls the shared helper instead of a local copy
Each migration PR shows net LOC deletion
No behavior change (captured by existing adapter tests; add a parity test that runs the same Card through every adapter's fallback path and snapshots the output)
Code-organization gap — drift risk across 8 adapters.
Current state
src/chat_sdk/shared/adapter_utils.pyexposes onlyextract_card()andextract_files(). Every other cross-adapter helper lives duplicated inside each adapter'scards.py/format_converter.py:{{emoji:thumbs_up}}→ platform-native code). Each adapter importsDEFAULT_EMOJI_MAPfromshared/and walks children independently. Slack, Discord, Telegram, Teams, Google Chat, Linear, GitHub all do this; WhatsApp doesn't (because it doesn't translate emoji placeholders at all — consistent gap).style: "primary" | "secondary" | "danger"→ Block Kit"primary"/ AdaptiveCard"positive"/ Discord1..4/ Linear mention syntax). Scattered across each adapter.Cardto plain markdown for platforms with limited card support, or for notification previews). Reinvented in Telegram and WhatsApp; partially in Discord.Tableto an ASCII-aligned markdown table for adapters without native table blocks). Duplicated in Telegram, WhatsApp, Linear; Discord has its own; Slack has a separate Block-Kit-first path.|, newlines, backticks inside cells). Each adapter that renders tables has its own escape routine; subtle bugs have bitten us in Slack tables specifically.Why it matters
DEFAULT_EMOJI_MAPrequires audit of every adapter to confirm it's picked up. A shared converter removes the audit.Cardas fallback text" should produce the same output regardless of which adapter is asking. Today it doesn't.Proposed surface
In
src/chat_sdk/shared/adapter_utils.py:Each helper replaces the per-adapter copy. Per-adapter code stays responsible for (a) choosing which helpers to call, (b) mapping to platform-specific payload shapes (Block Kit, AdaptiveCard, Discord components, etc.) — that's legitimately platform-specific and shouldn't move.
Migration
One PR per helper, in this order:
escape_table_cell(most localized; low risk)render_gfm_table(builds on security: Fix all critical and high findings from security audit #1)card_to_fallback_text(uses security: Fix all critical and high findings from security audit #1 + fix: correct GitHub Actions commit SHAs #2)map_button_style(independent)create_emoji_converter(largest blast radius; save for last when the pattern is settled)Each PR: promote the helper, update all adapters to use it, delete the duplicated copies, add regression tests in
shared/for the helper itself + keep existing adapter tests passing.Acceptance
shared/adapter_utils.pywith direct unit testsCardthrough every adapter's fallback path and snapshots the output)