Skip to content

[4.41/C10] Cards & modals core: Chart, Table options, Button tooltip, Card width, DateInput/NumberInput, select change events #202

Description

@patrick-chinchill

Summary

Port the platform-neutral card/modal builder additions from 4.34–4.41: a Chart card element (pie/bar/area/line) with ASCII fallback text; Table options (caption, page_size, widths, vertical_align, grid_lines, grid_style); tooltip on Button/LinkButton and a width hint on Card; DateInput/NumberInput modal children; opt-in dispatch_action on Select/RadioSelect. This PR covers only the builders, TypedDicts, fallback text and exports. Adapters render them in #212 (Slack) and #220 (Teams); every other adapter keeps ignoring unknown fields.

Upstream changes

  • 4717a384 feat(slack): support data table and data visualization blocks (#696) — chat@4.34.0 — core slice: Chart* types, Chart() builder, ChartElement in CardChild; chartElementToFallbackText in markdown.ts (title + ASCII table; pie → Label/Value; series → xLabel ?? "" + one column per series, blank for missing points); Table caption/pageSize.
  • 84219537 feat(teams): render tables as the Adaptive Card 1.5 Table element (#906) — chat@4.41.0 — core slice: TableVerticalAlignment, TableGridStyle, and Table widths/verticalAlign/gridLines/gridStyle (documented as Teams-only).
  • 4a0b5c0c feat(cards): add button tooltips and a card width hint (#895) — chat@4.40.0 — CardWidth = "default" | "full", Card.width, and tooltip on Button/LinkButton. The callback-URL button-copy half of this commit belongs to [4.41/CB] Callback tokens: consume once, bind to conversation, shorter TTL, preserve button fields #194.
  • 0153a39f feat(modals): add DateInput and NumberInput modal children (#757) — chat@4.36.0 — date_input (id, label, placeholder?, initialValue?: string, optional?) and number_input (… initialValue?: number, min?, max?, decimal?) elements and builders, both added to the valid modal-child list.
  • ad904325 feat(slack): support modal selection change events (#952) — chat@4.41.0 — core slice: dispatchAction?: boolean on Select and RadioSelect elements and builders.
  • 929878b5 fix(chat): allow link button IDs in JSX (#838) — chat@4.39.0 — JSX-only. N/A: LinkButton(id=...) already exists (src/chat_sdk/cards.py:288-312). It gets a row in docs/UPSTREAM_SYNC.md.

Current Python behavior

  • grep -rn -i 'chart' src/chat_sdk --include='*.py' finds only emoji names. CardChild (cards.py:131-140) has no chart member; card_child_to_fallback_text (cards.py:422-450) has no chart case; shared/card_utils.py:64-91 _child_to_fallback_text and adapters/discord/cards.py:294-321 fall through to it.
  • TableElement (cards.py:107-113) has only headers/rows/align. Table() (cards.py:338-356) takes only those.
  • ButtonElement (cards.py:25-33)/LinkButtonElement (:44-49) lack tooltip; CardElement (:143-150)/Card() (:177-197) lack width.
  • modals.py:101 has VALID_MODAL_CHILD_TYPES = {"text_input", "select", "external_select", "radio_select", "text", "fields"}. grep -rn 'date_input\|number_input\|dispatch_action' src → nothing.
  • Select() (modals.py:188-212) and RadioSelect() (modals.py:263-286) have no dispatch_action.
  • snake_case aliases exist for every builder (modals.py:291-296, and the card aliases exported from __init__.py, e.g. "table").

Scope

  • cards.py: TypedDicts ChartSegment, ChartDataPoint, ChartSeries, PieChartDefinition, SeriesChartDefinition (x_label/y_label optional), ChartDefinition union, ChartElement; Chart(*, title, chart) + chart alias; ChartElement in CardChild.
  • chart_element_to_fallback_text(element) (in cards.py, next to table_element_to_ascii). Wire it into card_child_to_fallback_text and shared/card_utils.py::_child_to_fallback_text.
  • TableElement and Table(): caption, page_size, widths: list[int], vertical_align: Literal["top","center","bottom"], grid_lines: bool, grid_style: Literal["default","emphasis","accent","good","attention","warning"]. Add the TableVerticalAlignment/TableGridStyle aliases.
  • tooltip: str on ButtonElement/LinkButtonElement and their builders. CardWidth = Literal["default","full"] and width on CardElement/Card().
  • modals.py: DateInputElement/NumberInputElement TypedDicts; DateInput(*, id, label, placeholder=None, initial_value=None, optional=None); NumberInput(*, id, label, placeholder=None, initial_value=None, optional=None, min=None, max=None, decimal=None); date_input/number_input aliases; add both to VALID_MODAL_CHILD_TYPES and the ModalChild union (modals.py:83).
  • dispatch_action: bool | None on SelectElement/RadioSelectElement and their builders.
  • chat_sdk/__init__.py: export and add to __all__ the new builders, aliases, types and chart_element_to_fallback_text.
  • Optional ride-along (Promote per-adapter card helpers into shared/adapter_utils.py (emoji converter, button styles, GFM table, fallback text) #70): if duplication between cards.py and shared/card_utils.py grows, factor the chart case once.

Out of scope

Porting notes

  • Omitted keys vs None. Upstream assigns undefined options (dropped by JSON.stringify). Python builders omit keys whose argument is None, like Card()/Table() today; tests assert absence ("caption" not in el).
  • Falsy-but-valid values. grid_lines=False, decimal=False, dispatch_action=False and NumberInput(initial_value=0, min=0) must be kept. Use is not None, never truthiness (upstream test "should keep a zero initial value", plus it.each "preserves dispatchAction=%s" over true/false/undefined).
  • Key casing. snake_case keys inside the SDK (page_size, vertical_align, grid_lines, grid_style, initial_value, dispatch_action, x_label, y_label), as existing TypedDicts do; camelCase only at the adapters' wire boundary.
  • Number formatting in fallback text. JS String(45) is "45"; Python str(45.0) is "45.0". Recommended default: str(int(v)) when float(v).is_integer(), else str(v); exclude bool. Upstream expects "Kit Kat | 45" and "Mon | 100 | 50".
  • Series lookup. Per series, find the point whose label == category (aligned by category, not point order); missing → "".
  • Builder validation. Upstream Chart() does no validation (Slack enforces its limits in [4.41/SL7] Slack cards & modals: data_table / data_visualization blocks, datepicker, number_input, selection change events #212). Do not add Python-only validation here.
  • Adapter child-type chains must fall through safely for chart (they do today via return None).

Tests

packages/chat/src/cards.test.ts and packages/chat/src/modals.test.ts are not in scripts/verify_test_fidelity.py MAPPING today. #185 intends to map them (to tests/test_cards.py / tests/test_modals.py). If P0 has landed, these names are strict.

  • cards.test.ts: [Card] "creates a card with a width hint"; [Button] "creates a button with a tooltip"; [LinkButton] "creates a link button with a tooltip"; [Table] "creates a table with caption and pageSize", "leaves caption and pageSize undefined when omitted", "carries the Teams-only rendering options", "leaves the rendering options undefined when omitted"; [Chart] "creates a pie chart", "creates a line chart with series and categories"; [chart fallback text] "renders pie chart data as a labelled ASCII table", "renders series chart data with one column per series"
  • modals.test.ts: [DateInput] "should create with required fields", "should include optional fields"; [NumberInput] "should create with required fields", "should include optional fields", "should keep a zero initial value"
    • describe.each([Select, RadioSelect])("%s action dispatch") → it.each([true, false, undefined])("preserves dispatchAction=%s") (expand per P0's naming; skip the fromReactModalElement copy, since it is JSX)
  • Skip the JSX-only tests ("should convert a DateInput react element", "should convert a NumberInput react element", jsx-runtime "converts tooltip on Button and LinkButton", "converts Card width") and document them under the existing JSX row.
  • Python-specific (no duplicates of upstream-name tests): filter_modal_children keeps date_input/number_input; 1.5 renders "1.5" and 45.0 renders "45"; card_to_fallback_text of a card with a Chart includes the title.

Acceptance criteria

  • Full validation command from CLAUDE.md passes.
  • All the upstream test names above exist in tests/test_cards.py / tests/test_modals.py. If [4.41/P0] Fidelity tooling for the 4.41 wave: single pin constant, SHA pin, it.each expansion, map new core test files #185 mapped these files, non-strict fidelity reports them matched.
  • docs/UPSTREAM_SYNC.md updated: the JSX row notes 929878b5 and the new JSX props as N/A, and the fallback-number formatting is recorded.
  • CHANGELOG entry under "Unreleased (4.41 wave)".
  • Consumer-visible behavior called out: additive only. Existing Table()/Card() output is byte-identical when the new options are omitted.

Dependencies

Blocked by #185. Blocks #212, #220, #203.

Metadata

Part of #184.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions