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
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); Tablecaption/pageSize.
84219537 feat(teams): render tables as the Adaptive Card 1.5 Table element (#906) — chat@4.41.0 — core slice: TableVerticalAlignment, TableGridStyle, and Tablewidths/verticalAlign/gridLines/gridStyle (documented as Teams-only).
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.
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.
JSX conversions (fromReactElement Chart/DateInput/NumberInput/tooltip/dispatchAction): no JSX runtime in Python.
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 → "".
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.
Summary
Port the platform-neutral card/modal builder additions from 4.34–4.41: a
Chartcard element (pie/bar/area/line) with ASCII fallback text;Tableoptions (caption, page_size, widths, vertical_align, grid_lines, grid_style);tooltiponButton/LinkButtonand awidthhint onCard;DateInput/NumberInputmodal children; opt-indispatch_actiononSelect/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
4717a384feat(slack): support data table and data visualization blocks (#696) — chat@4.34.0 — core slice:Chart*types,Chart()builder,ChartElementinCardChild;chartElementToFallbackTextinmarkdown.ts(title + ASCII table; pie →Label/Value; series →xLabel ?? ""+ one column per series, blank for missing points);Tablecaption/pageSize.84219537feat(teams): render tables as the Adaptive Card 1.5 Table element (#906) — chat@4.41.0 — core slice:TableVerticalAlignment,TableGridStyle, andTablewidths/verticalAlign/gridLines/gridStyle(documented as Teams-only).4a0b5c0cfeat(cards): add button tooltips and a card width hint (#895) — chat@4.40.0 —CardWidth = "default" | "full",Card.width, andtooltiponButton/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.0153a39ffeat(modals): add DateInput and NumberInput modal children (#757) — chat@4.36.0 —date_input(id, label, placeholder?, initialValue?: string, optional?) andnumber_input(… initialValue?: number, min?, max?, decimal?) elements and builders, both added to the valid modal-child list.ad904325feat(slack): support modal selection change events (#952) — chat@4.41.0 — core slice:dispatchAction?: booleanonSelectandRadioSelectelements and builders.929878b5fix(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 indocs/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 nochartcase;shared/card_utils.py:64-91_child_to_fallback_textandadapters/discord/cards.py:294-321fall through to it.TableElement(cards.py:107-113) has onlyheaders/rows/align.Table()(cards.py:338-356) takes only those.ButtonElement(cards.py:25-33)/LinkButtonElement(:44-49) lacktooltip;CardElement(:143-150)/Card()(:177-197) lackwidth.modals.py:101hasVALID_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) andRadioSelect()(modals.py:263-286) have nodispatch_action.modals.py:291-296, and the card aliases exported from__init__.py, e.g."table").Scope
cards.py: TypedDictsChartSegment,ChartDataPoint,ChartSeries,PieChartDefinition,SeriesChartDefinition(x_label/y_labeloptional),ChartDefinitionunion,ChartElement;Chart(*, title, chart)+chartalias;ChartElementinCardChild.chart_element_to_fallback_text(element)(incards.py, next totable_element_to_ascii). Wire it intocard_child_to_fallback_textandshared/card_utils.py::_child_to_fallback_text.TableElementandTable():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 theTableVerticalAlignment/TableGridStylealiases.tooltip: stronButtonElement/LinkButtonElementand their builders.CardWidth = Literal["default","full"]andwidthonCardElement/Card().modals.py:DateInputElement/NumberInputElementTypedDicts;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_inputaliases; add both toVALID_MODAL_CHILD_TYPESand theModalChildunion (modals.py:83).dispatch_action: bool | NoneonSelectElement/RadioSelectElementand their builders.chat_sdk/__init__.py: export and add to__all__the new builders, aliases, types andchart_element_to_fallback_text.shared/adapter_utils.py(emoji converter, button styles, GFM table, fallback text) #70): if duplication betweencards.pyandshared/card_utils.pygrows, factor the chart case once.Out of scope
data_table/data_visualizationblocks,datepicker/number_inputblocks, thedispatch_actionwire field and selection change events: [4.41/SL7] Slack cards & modals: data_table / data_visualization blocks, datepicker, number_input, selection change events #212.Table, tooltip/width rendering andInput.Date/Input.Number: [4.41/T5] Teams cards & dialogs: Adaptive Card 1.5 Table, tooltips, width, Input.Date/Number #220.tooltipwhen callback-URL tokens rewrite a button (test "keeps every other button field when replacing the callback URL"): [4.41/CB] Callback tokens: consume once, bind to conversation, shorter TTL, preserve button fields #194.fromReactElementChart/DateInput/NumberInput/tooltip/dispatchAction): no JSX runtime in Python.Porting notes
None. Upstream assignsundefinedoptions (dropped byJSON.stringify). Python builders omit keys whose argument isNone, likeCard()/Table()today; tests assert absence ("caption" not in el).grid_lines=False,decimal=False,dispatch_action=FalseandNumberInput(initial_value=0, min=0)must be kept. Useis not None, never truthiness (upstream test "should keep a zero initial value", plusit.each"preserves dispatchAction=%s" overtrue/false/undefined).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.String(45)is"45"; Pythonstr(45.0)is"45.0". Recommended default:str(int(v))whenfloat(v).is_integer(), elsestr(v); excludebool. Upstream expects"Kit Kat | 45"and"Mon | 100 | 50".label == category(aligned by category, not point order); missing →"".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.chart(they do today viareturn None).Tests
packages/chat/src/cards.test.tsandpackages/chat/src/modals.test.tsare not inscripts/verify_test_fidelity.pyMAPPING today. #185 intends to map them (totests/test_cards.py/tests/test_modals.py). If P0 has landed, these names are strict.describe.each([Select, RadioSelect])("%s action dispatch")→it.each([true, false, undefined])("preserves dispatchAction=%s")(expand per P0's naming; skip thefromReactModalElementcopy, since it is JSX)filter_modal_childrenkeepsdate_input/number_input;1.5renders"1.5"and45.0renders"45";card_to_fallback_textof a card with a Chart includes the title.Acceptance criteria
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.mdupdated: the JSX row notes 929878b5 and the new JSX props as N/A, and the fallback-number formatting is recorded.Table()/Card()output is byte-identical when the new options are omitted.Dependencies
Blocked by #185. Blocks #212, #220, #203.
Metadata
Part of #184.